Binder服务端执行服务代码一定在Binder线程吗?


前言

在我们的印象中,服务端Binder收到请求后调用onTransact处理消息,而运行的线程处于Binder管理的线程池汇中(Binder线程的创建和销毁是在用户空间,但是管理是由Binder驱动代为管理的)。这样说在大部分情况下正确,但也不是所以的情况均是如此。比如一种“远程回调”的情况,客户端代理在调用服务端方法后,也通过参数传递的方式传递一个IBinder给服务端,服务端获客户端取代理后回调。这种模式在源码中其实也常见。下面新建一个工程演示这种情况,顺便回顾一下AIDL的用法。

AIDL

创建AIDL文件

为了演示双向调用的情况,所以我们需要建立两个AIDL文件。这儿建了一个IHelloService和IHiService。

// IHelloService.aidl
package com.android.aidl;

// Declare any non-default types here with import statements

interface IHelloService {
    void Hello(String words,IBinder client);
}

// IHiService.aidl
package com.android.aidl;

// Declare any non-default types here with import statements

interface IHiService {

    void Hi(String words);
}

分别定义了两个接口,Hello和Hi,注意Hello接口的参数还要接收一个IBinder对象。这里多嘴说一下,虽然IBinder本质上是一个interface,没有真正意义上的IBinder对象。但是实际传递的是一个实现了IBinder接口的Binder对象,所以就这样简称了。这个对象就是让服务端收到请求后调用asInterface将其转为proxy后调用客户端的Hi方法。build后分别生成对应的java文件。以IHelloService.java为例:

build生成java文件

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.android.aidl;
// Declare any non-default types here with import statements

public interface IHelloService extends android.os.IInterface
{
  /** Default implementation for IHelloService. */
  public static class Default implements com.android.aidl.IHelloService
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.android.aidl.IHelloService
  {
    private static final java.lang.String DESCRIPTOR = "com.android.aidl.IHelloService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.android.aidl.IHelloService interface,
     * generating a proxy if needed.
     */
    public static com.android.aidl.IHelloService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.android.aidl.IHelloService))) {
        return ((com.android.aidl.IHelloService)iin);
      }
      return new com.android.aidl.IHelloService.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_Hello:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          android.os.IBinder _arg1;
          _arg1 = data.readStrongBinder();
          this.Hello(_arg0, _arg1);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.android.aidl.IHelloService
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(words);
          _data.writeStrongBinder(client);
          boolean _status = mRemote.transact(Stub.TRANSACTION_Hello, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().Hello(words, client);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.android.aidl.IHelloService sDefaultImpl;
    }
    static final int TRANSACTION_Hello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.android.aidl.IHelloService impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.android.aidl.IHelloService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException;
}

IHelloService.Stub是服务端需要继承的类,通过调用asInterface可以将IBinder转为生成IHello.Stub.Proxy客户端对象。AIDL文件build后生成的java文件有一些标志是默认的,为了方便之后修改观察现象,我们将AIDL删除,然后把build后的java文件加入工程。

创建Service

为了使用Binder,我们还需要创建一个Service组件来完成IBinder对象的传递。

public class MyService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new HelloService();
    }

    class HelloService extends IHelloService.Stub{
        @Override
        public void Hello(String words, IBinder client) throws RemoteException {
            Log.d("HelloService",words +" thread:" + Thread.currentThread());
            (IHiService.Stub.asInterface(client)).Hi("Hi");
            
        }
    }
}

如上所示,service的创建非常简单,定义HelloService服务类并实现IHelloService.Stub的Hello方法,只需要在onBind方法中new 一个HelloService实体然后返回即可。我们在服务端中将收到的IBinder对象client通过IHiService.Stub.asInterface转化为IHiService的proxy然后就可以调用客户端提供的服务接口Hi了。

创建MainActivity

Activity的创建也非常简单,创建HiService类,然后调用bindService去请求服务,同时把一个HiService的实例传递过去即可,如下所示。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService(new Intent(this, MyService.class), new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                try {
                    (IHelloService.Stub.asInterface(service)).Hello("hello",new HiService());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        },BIND_AUTO_CREATE);
    }

    class HiService extends IHiService.Stub{
        @Override
        public void Hi(String words) throws RemoteException {
            Log.d("HiService",words +" thread:" + Thread.currentThread());
        }
    }
}

结果分析

客户端和服务端同一进程

现在运行该程序看一下打印。

com.android.aidl D: hello , thread:Thread[main,5,main]
com.android.aidl D: Hi thread:Thread[main,5,main]

看来相互调用是成功了,但是都是在主线程执行的?这也对,他们在同一进程是不需要真正binder驱动参与的。那么将Service改到单独进程执行看看。

不同进程,主线程请求

要让service执行在不同的进程,只需要指定process属性即可。

        <service android:name=".MyService"
            android:process=":remote"/>

下面运行看看结果:

u0_a36    27264 326   1579504 70660          0 0000000000 S com.android.aidl:remote

从ps的结果来看service的确执行在了新进程。log打印的结果如下

27264-27279/com.android.aidl D: hello , thread:Thread[Binder:27264_3,5,main]
27247-27247/com.android.aidl D: Hi thread:Thread[main,5,main]

从打印结果来看service端的比较符合预期,执行在了binder线程中,但是为什么MainActivity端还是在主线程?这就是在本文一开头所说的特殊情况,这是Binder帮我们做的小优化,因为在MainActivity远程调用service的过程中本来就是阻塞等调用完成,没事儿干,那么发起远端请求的线程就会被用来执行这次服务。因为就算Binder开一个线程去执行这次服务,由于service端在等待返回,主线程仍是阻塞的,所以根本不需要重新开个线程执行。我们可以大胆猜测如果在MainActivity的子线程发起这样的请求,服务也应该在子线程处理,下面改一下代码。

不同进程,子线程发起请求

修改代码,子线程发起请求。

            @Override
            public void onServiceConnected(ComponentName name, final IBinder service) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            (IHelloService.Stub.asInterface(service)).Hello("hello",new HiService());
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }).start();

            }

下面看一下执行结果。

29014-29030/com.android.aidl D: hello , thread:Thread[Binder:29014_3,5,main]
28995-29057/com.android.aidl D: Hi thread:Thread[Thread-3,5,main]

果然,这次Hiservice服务的执行就在创建的子线程中了,所以结论与上面分析的一致。

不同进程,修改transact发送标志

刚才说到因为阻塞所以导致HiService就在向HelloService发送请求的线程中执行,那么如果不阻塞的情况呢?因为transact是有flag参数的,其中有个flag是FLAG_ONEWAY,表示单向通信,不在乎结果。记得最开始我们删掉AIDL文件,也是为了这一步骤,可以修改stub里面的东西。我们试试将HelloService的transact改为

            @Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(words);
                    _data.writeStrongBinder(client);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_Hello, _data, _reply, FLAG_ONEWAY);

运行查看log

29309-29321/com.android.aidl D: hello , thread:Thread[Binder:29309_1,5,main]
29293-29306/com.android.aidl D: Hi thread:Thread[Binder:29293_2,5,main]

这次两边都在Binder线程中执行了。同理,只修改HiService端transact的标志结果也是在Binder线程中执行。

总结

总的来说,Binder服务的执行会尽量放在Binder线程池的线程中去执行,减少对用户本来的线程的影响,当然会有一些特殊情况,那么Binder也会灵活地找到相应线程去执行,减少不必要的开销。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM