Android IPC淺析之Binder

在認識IPC(Inter-Process Communication,進程間通信)之前,肯定要先理解什么是線程,什么是進程?在多數(shù)操作系統(tǒng)中,線程是最小的可操作調(diào)度單元。而進程一般則是一個具體的應(yīng)用(比如QQ、微信),其可以包含有多個線程。要在不同的進程進行數(shù)據(jù)的傳輸與通信,就需要用到IPC機制了。

在Android中跨進程通信的方式有很多,如intent傳遞、讀寫文件、socket通信、SharedPreferences(不可靠)、ContentProvider以及Binder等。本文主要講解Binder機制的用法及其簡單原理,這次的demo是客戶端跨進程請求服務(wù)端返回一個我們需要的int值,效果如下:


簡單來說Binder是Android系統(tǒng)為我們提供的一個用于進程通信的類,我們一般通過AIDL語言自動讓ide工具為我們生成(如果你不嫌累不嫌頭疼完全可以自己寫),其結(jié)構(gòu)類似這樣:

package com.example.lucky.aidl;
// Declare any non-default types here with import statements

public interface IMyAidlInterface extends android.os.IInterface
{
    public int plusResult(int a, int b) throws android.os.RemoteException;
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.example.lucky.aidl.IMyAidlInterface
    {
        private static final java.lang.String DESCRIPTOR = "com.example.lucky.aidl.IMyAidlInterface";
        /** Construct the stub at attach it to the interface. */
        public Stub()
        {
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
         * Cast an IBinder object into an com.example.lucky.aidl.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.example.lucky.aidl.IMyAidlInterface asInterface(android.os.IBinder obj)
        {
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.example.lucky.aidl.IMyAidlInterface))) {
            return ((com.example.lucky.aidl.IMyAidlInterface)iin);
        }
            return new com.example.lucky.aidl.IMyAidlInterface.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
        {
            return super.onTransact(code, data, reply, flags);
        }
        private static class Proxy implements com.example.lucky.aidl.IMyAidlInterface
        {
        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;
        }
        @Override public int plusResult(int a, int b) throws android.os.RemoteException
        {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeInt(a);
        _data.writeInt(b);
        mRemote.transact(Stub.TRANSACTION_plusResult, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readInt();
        }
        finally {
        _reply.recycle();
        _data.recycle();
        }
        return _result;
        }
        }
        static final int TRANSACTION_plusResult = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

}

乍一看代碼很雜亂,邏輯也很復雜,其實對于我們這樣的萌新只需要理解清這個接口的內(nèi)部類Stub、內(nèi)部類中的asInterface(android.os.IBinder obj)onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)、以及Proxy就可以簡單使用Binder了。下面我會逐一分析這些。

Stub

這個Stub就是我們需要的Binder類了,當我們在客戶端bindService時會通過遠程服務(wù)端的onBinder方法中會return 給我們.

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }

通過這個Binder類,我們可以就在客戶端取得 他的內(nèi)部類Proxy,從而在客戶端完成相應(yīng)操作。

asInterface(android.os.IBinder obj)

通過此方法我們可以將服務(wù)端生成的Binder對象(Stub)轉(zhuǎn)化成AIDL接口型的對象。需要注意的是此方法是區(qū)分進程的,如果客戶端和服務(wù)端處于同一進程,則不會進行轉(zhuǎn)換,直接把Stub返回給客戶端,當處于不同進程時,會返回Stub.Proxy對象。

public static com.example.lucky.aidl.IMyAidlInterface asInterface(android.os.IBinder obj){
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.example.lucky.aidl.IMyAidlInterface))) {
            return ((com.example.lucky.aidl.IMyAidlInterface)iin);
        }
            return new com.example.lucky.aidl.IMyAidlInterface.Stub.Proxy(obj);
}
onTransact

此方法運行在服務(wù)端的Binder線程池中,當我們在客戶端遠程請求服務(wù)端的方法時,就會執(zhí)行該方法。我們可以發(fā)現(xiàn)它就收的參數(shù)類型int code, android.os.Parcel data, android.os.Parcel reply, int flags都是可序列化的Parcel 型,這點后面會講。基本原理就是服務(wù)端通過data取出客戶端傳入的參數(shù),然后寫入reply返回給客戶端。

Proxy

Proxy可以直譯為代理人,從名字上我們就能看出這是服務(wù)端返回給我們的Binder的一個代理人(為了與時俱進,我們可以把它稱作經(jīng)紀人~),通過它我們可以在客戶端遠程調(diào)用服務(wù)端的方法。注意此時會掛起客戶端的當前線程,直到服務(wù)器返回數(shù)據(jù)成功,所以如果我們直接在此處更新UI是很容易引起ANR讓我們的應(yīng)用掛掉(因為等待服務(wù)器一般都比較耗時)!為了避免ANR,常用的方法就是新建線程通過判斷返回的Proxy是否為null來請求服務(wù)端。

通過上面的分析我們應(yīng)該對Binder的工作流程有了一定的了解,接下來就通過demo代碼,更直觀地學會如何使用Binder進行進程間通信。

Demo創(chuàng)建流程

  • 1 創(chuàng)建AIDL接口
// IMyAidlInterface.aidl
package com.example.lucky.aidl;

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

interface IMyAidlInterface {

    int plusResult(in int a, in int b);
}

這兒只聲明了一個方法plusResult,往后看就會發(fā)現(xiàn),通過AIDL暴露出來的方法會在Service端實現(xiàn),從而讓客戶端調(diào)用。這兒我們只關(guān)心AIDL文件中支持的數(shù)據(jù)類型:java基本數(shù)據(jù)類型、ArrayList(里面元素也必須支持AIDL)、HashMap(里面元素也必須支持AIDL)、所有可序列化的自定義對象(實現(xiàn)了Parcelable接口)。這兒我們的plusResult方法中接收的參數(shù)是最簡單的int型,如果是自定義的Parcel對象,我們還需要額外創(chuàng)建AIDL文件聲明該對象。例:

 int plusResult(in int a, in User use);

我們必須在同目錄下聲明該User對象:

// IMyAidlInterface.aidl
package com.example.lucky.aidl;

parcelable User;

參數(shù)名前面的in、out分別表示輸入、輸出型參數(shù)。

  • 2 創(chuàng)建遠程服務(wù)端
    此例的Service為了額外創(chuàng)建新的工程直接通過android:process=".anotherProcess"屬性將其進程定義為了其他線程。
public class PlusService extends Service {

    private Binder mBinder;
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new IMyAidlInterface.Stub() {
            @Override
            public int plusResult(int a, int b) throws RemoteException {
                /*try {
                    new Thread().sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                return a+b;
            }
        };
    }
}

代碼十分簡單,主要在onCreate時實例化 了IMyAidlInterface.Stub,在其中實現(xiàn)了客戶端請求的plusResult方法,然后通過onBind傳遞出去。

  • 3 客戶端bindService進行遠程請求
public class MainActivity extends AppCompatActivity {

    private ServiceConnection mServiceConnection;
    private int plusResult;
    @InjectView(R.id.firstEditText)
    EditText firstEditText;
    @InjectView(R.id.secondEditText)
    EditText secondEditText;
    @InjectView(R.id.result)
    TextView result;
    @InjectView(R.id.mButton)
    Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if ((firstEditText.getText().length()==0) || (secondEditText.getText().length()==0)) {
                    Toast.makeText(MainActivity.this,"請輸入數(shù)字",Toast.LENGTH_LONG).show();
                    return;
                } else {
                    final int a = Integer.valueOf(firstEditText.getText().toString().trim());
                    final int b = Integer.valueOf(secondEditText.getText().toString().trim());
                    mServiceConnection = new ServiceConnection() {

                        //onServiceConnected onServiceDisconnected都是在UI線程中進行的,所以當需要耗時操作時,可以通過判斷bookManager(proxy)對象是否為空,在子線程中進行
                        @Override
                        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                            IMyAidlInterface myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
                            try {
                                plusResult = myAidlInterface.plusResult(a,b);
                                result.setText(" "+plusResult);
                                Toast.makeText(MainActivity.this,"服務(wù)器響應(yīng)拉,親~",Toast.LENGTH_LONG).show();
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onServiceDisconnected(ComponentName componentName) {

                        }
                    };
                    Intent intent = new Intent(MainActivity.this,PlusService.class);
                    if (mServiceConnection != null) {
                        bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
                    }
                }

            }
        });
    }

    @Override
    protected void onDestroy() {
        if (mServiceConnection != null) {
            unbindService(mServiceConnection);
        }
        super.onDestroy();
    }
}

在ServiceConnection 的onServiceConnected回調(diào)方法中我們獲得了我們想要的經(jīng)紀人IMyAidlInterface myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);,通過它就可以遠程請求服務(wù)端的方法了,這兒為了代碼簡潔沒有新建線程處理,其實只要在服務(wù)端sleep一下,客戶的程序就很容易ANR掛掉了,有興趣的同學可以自己嘗試一下。最后千萬別忘記在客戶端unbindService,這樣一個簡單的跨進程通信demo就創(chuàng)建好了~

總結(jié)下使用AIDL時的那些坑:
  • 客戶端、服務(wù)端所有AIDL文件的packagename必須手動修改為相同的
  • 遠程服務(wù)端需要通過android:exported="true"屬性暴露出來
  • 有時候找不到系統(tǒng)自動生成的Binder類,我們需要remake一下這個工程
  • 自定義創(chuàng)建的對象一定要實現(xiàn)Parcelable且額外聲明
  • AIDL即使在相同package內(nèi),也要手動導包
    。。。。。。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容