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