類型 | 描述 | 用時 |
---|---|---|
選題 | silencezwm | 0.5小時 |
寫做時間 | 2017年10月25日 | 5.5小時 |
審稿 | silencezwm、Mya婷婷 | 2小時 |
校對上線 | silencezwm | 1小時 |
Tips:4個環節,共計約9小時的精心打磨完成上線,同時也很是感謝參與審稿的同窗。java
看漫畫,漲薪資(¥) >>>【小豬的傳奇一輩子】:
該漫畫描述了小豬仔出生後,就生活在豬圈中,快樂的成長着。有一天,小豬長大了,屠宰場的老闆就會經過船將肥豬運輸過來進行屠宰,而後將豬肉銷往世界各地。
看完該漫畫後,是否是以爲小豬仔的一輩子有點小悲涼,要怪就怪可惡的人類,無肉不歡,哈哈。android
精彩的漫畫背後,總隱藏着一絲絲技術的小知識。本文將爲你介紹「Android跨進程通訊」的相關知識,經過本文的學習,你能夠了解到:服務器
1、單進程通訊與多進程通訊之間的區別2、跨進程通訊常見的五種實現方式app
3、跨進程通訊的注意事項ide
概念普及:IPC(Inter-Process Communication)機制,即進程間通訊或者跨進程通訊機制,是指兩個進程之間進行數據交換的過程。
在好久之前,小豬仔從生到死都是在豬圈中生活的,沒有外來者的入侵。一樣的,在Android開發中,默認狀況下,程序運行後,都只是運行在一個以項目包名建立的主進程中(就比如豬圈),例如學習
項目包名爲:com.silencezwm.ipcdemo 默認進程名:com.silencezwm.ipcdemo
單進程通訊就如:不一樣品類的豬仔(Android中不一樣的組件)在相同的豬圈(在同一進程中)中生活(運行)。ui
忽然有一天,河流的右邊來了一個商人,他發現河對岸有很多肥豬在遊蕩。因而,他發現了一個發家致富的機會,他在這裏建了一個屠宰場。而後常常經過船將對岸的肥豬運送過來,進行屠宰,賺的盆滿鉢滿。this
這個就相似Android程序,原本只有一個進程在運行,可是由於產品提了個奇葩的需求,使得咱們程序猿們不得很少開一個進程來實現該需求。spa
程序猿們一頓牢騷後,最後仍是動手幹活了。.net
他們在AndroidManifest.xml文件中相應的組件添加了android:process屬性,並指定進程名。而後打開了兩個Activity,可是BActivity被指定運行在新的進程,當程序跑起來後,此時能夠看到有兩個進程正在運行,如圖:
咱們知道,在單進程中通訊,組件間是能夠隨意進行通訊,由於它們都處於同一個內存空間。那多進程之間是怎樣通訊的呢?
漫畫中,屠宰場的老闆經過船將河對岸的肥豬運送過來,就由於船的存在,該老闆就能夠跨越河流到達對岸。那麼,Android跨進程通訊中,咱們也須要擁有一樣功能的船,它就是Binder,經過Binder的中轉,進程之間就能順利的進行數據交換了。
五種常見的實現方式可分爲兩大類:四大組件的跨進程通訊和AIDL。
Activity、Service、BroadcastReceiver、Content Provider四大組件只須要在AndroidManifest.xml文件相應的組件中添加android:process屬性,並指定進程名。程序運行起來後,它們就會運行在不一樣的進程中,它們之間的通訊,官方已經給咱們作了很是好的封裝,因此使用起來也很是方便,這裏就很少作解釋了。
概念普及:AIDL(Android interface definition Language),即Android接口定義語言。
要想應用AIDL技術,就至少須要有兩個進程存在,A進程經過定義的AIDL接口文件與B進程進行通訊,具體的實現步驟以下:
一、準備兩個進程:新建一個項目 IPCDemo,而後新建一個Module IPCClient,這樣咱們就準備好了兩個進程,完成後的項目結構如圖:
二、建立AIDL文件:在服務端IPCDemo中新建一個AIDL接口文件:IMyAidlInterface.aidl,其中會默認實現basicTypes方法,而後咱們再定義一個login方法,IMyAidlInterface.aidl完整代碼以下;
package com.silencezwm.ipcdemo; /** * 定義的AIDL接口文件 */ interface IMyAidlInterface { /** * AIDL默認實現的方法 */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString); /** * 定義了一個登陸方法,包含用戶名、密碼兩個參數 */ void login(String username, String password); }
三、build項目:此時,Android Studio會自動爲你生成一個繼承自IInterface的java文件,IMyAidlInterface.java完整代碼以下;
package com.silencezwm.ipcdemo; /** * 定義的AIDL接口文件 */ public interface IMyAidlInterface extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.silencezwm.ipcdemo.IMyAidlInterface { private static final java.lang.String DESCRIPTOR = "com.silencezwm.ipcdemo.IMyAidlInterface"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.silencezwm.ipcdemo.IMyAidlInterface interface, * generating a proxy if needed. */ public static com.silencezwm.ipcdemo.IMyAidlInterface asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.silencezwm.ipcdemo.IMyAidlInterface))) { return ((com.silencezwm.ipcdemo.IMyAidlInterface) iin); } return new com.silencezwm.ipcdemo.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 { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_basicTypes: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); long _arg1; _arg1 = data.readLong(); boolean _arg2; _arg2 = (0 != data.readInt()); float _arg3; _arg3 = data.readFloat(); double _arg4; _arg4 = data.readDouble(); java.lang.String _arg5; _arg5 = data.readString(); this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); reply.writeNoException(); return true; } case TRANSACTION_login: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); java.lang.String _arg1; _arg1 = data.readString(); this.login(_arg0, _arg1); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.silencezwm.ipcdemo.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; } /** * AIDL默認實現的方法 */ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt); _data.writeLong(aLong); _data.writeInt(((aBoolean) ? (1) : (0))); _data.writeFloat(aFloat); _data.writeDouble(aDouble); _data.writeString(aString); mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } /** * 定義了一個登陸方法,包含用戶名、密碼兩個參數 */ @Override public void login(java.lang.String username, java.lang.String password) 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(username); _data.writeString(password); mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } /** * AIDL默認實現的方法 */ public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException; /** * 定義了一個登陸方法,包含用戶名、密碼兩個參數 */ public void login(java.lang.String username, java.lang.String password) throws android.os.RemoteException; }
四、新建Service:繼續在服務器端src/main/java/包名目錄下新建一個Service,並在配置文件中註冊,而後設置其action值爲com.silencezwm.ipcdemo,以供客戶端進行調用,並在Service內部建立一個內部類繼承靜態抽象類IMyAidlInterface.Stub,AidlService.java完整代碼以下:
package com.silencezwm.ipcdemo; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class AidlService extends Service { private static final String TAG = AidlService.class.getName(); public AidlService() { } @Override public IBinder onBind(Intent intent) { return new MyBinder(); } class MyBinder extends IMyAidlInterface.Stub { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { Log.d(TAG, "=====:basicTypes"); } @Override public void login(String username, String password) throws RemoteException { Log.d(TAG, "=====:login" + username + "==" + password); } } }
五、拷貝aidl目錄文件:至此,服務端的代碼編寫完成,接下來咱們只須要完成客戶端的調用代碼便可。至關簡單,首先把服務端aidl整個文件夾拷貝到客戶端src/main目錄下(至於拷貝的緣由,稍後會進行闡述),而後build項目。此時,客戶端、服務端就同時擁有AIDL相同的代碼;
六、綁定服務端Service:在客戶端你想要的地方經過服務端Service所在地的包名以及action來進行綁定,而後將Service鏈接成功後返回的IBinder對象,經過IMyAidlInterface.Stub.asInterface方法轉換爲咱們定義的aidl對象,而後根據該對象調用咱們所定義的方法便可完成整個通訊過程,客戶端MainActivity.java調用代碼以下:
package com.silencezwm.ipcclient; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import com.silencezwm.ipcdemo.IMyAidlInterface; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button mBtnLogin; private IMyAidlInterface mIMyAidlInterface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnLogin = (Button) findViewById(R.id.btn_login); mBtnLogin.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_login: Intent intent = new Intent(); // 服務端AndroidManifest.xml文件該Service所配置的action intent.setAction("com.silencezwm.ipcdemo"); // Service所在的包名 intent.setPackage("com.silencezwm.ipcdemo"); bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE); break; } } class ConnectCallBack implements ServiceConnection{ // 服務鏈接成功回調 @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder); login(); } // 服務斷開鏈接回調 @Override public void onServiceDisconnected(ComponentName componentName) { mIMyAidlInterface = null; } } private void login() { try { mIMyAidlInterface.login("silencezwm", "123456"); } catch (RemoteException e) { e.printStackTrace(); } } }
七、結果驗證:收穫的季節來了,先將服務端程序跑起來,以後將客戶端程序跑起來,點擊登陸按鈕,不出意外的話,咱們已經綁定了服務端的Service,並調用了login方法,將數據傳遞到服務端了,來看看Log的打印信息:
回顧AIDL整個實現過程,其實並不複雜。此時,聰明的人每每都會有一個疑問:
客戶端的數據究竟是如何傳遞到服務端的呢?
接下來,咱們就來一探究竟,如下的代碼主要涉及到自動生成的IMyAidlInterface.java文件。
在客戶端調用代碼中,咱們知道,一旦綁定Service成功後,會返回一個IBinder對象,調用IMyAidlInterface.Stub.asInterface(iBinder)方法將該對象轉換爲了咱們所定義的AIDL接口對象,該方法具體作了什麼呢?來看看:
private static final java.lang.String DESCRIPTOR = "com.silencezwm.ipcdemo.IMyAidlInterface"; public Stub() { this.attachInterface(this, DESCRIPTOR); } public static com.silencezwm.ipcdemo.IMyAidlInterface asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.silencezwm.ipcdemo.IMyAidlInterface))) { return ((com.silencezwm.ipcdemo.IMyAidlInterface) iin); } return new com.silencezwm.ipcdemo.IMyAidlInterface.Stub.Proxy(obj); }
上面這段代碼中,首先Stub構造方法被調用,跟着attachInterface方法被調用:
private IInterface mOwner; private String mDescriptor; ... public void attachInterface(IInterface owner, String descriptor) { mOwner = owner; mDescriptor = descriptor; }
至關好理解,就是進行接口對象和字符串標識符的賦值。接下來在asInterface方法中,會根據標識符去IBinder的本地去查找是否有該對象,也就是調用obj.queryLocalInterface(DESCRIPTOR)方法,繼續源碼中Binder.java
public IInterface queryLocalInterface(String descriptor) { if (mDescriptor.equals(descriptor)) { return mOwner; } return null; }
意思就是若是本地存在這個標識符的IInterface對象,那就直接返回以前構造方法中初始化的mOwner對象,不然返回null,由於咱們這裏涉及到了跨進程通訊,因此這裏會直接返回null。代碼繼續往下走,很顯然,如下這段代碼會調用:
return new com.silencezwm.ipcdemo.IMyAidlInterface.Stub.Proxy(obj);
代碼字面意思就是返回IBinder的代碼對象,以下:
private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; }
到這裏,咱們就拿到了一個IBinder的代理對象,經過代理對象,咱們就能夠調用以前所定義的login方法啦,代碼:
@Override public void login(java.lang.String username, java.lang.String password) 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(username); _data.writeString(password); mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }
這裏就涉及到了一個重要的類Parcel,Parcel天生具有跨進程傳輸數據能力。在文章開頭的漫畫中,可不是直接把豬仔遇上船就行的,萬一豬仔亂跑掉河裏去了怎麼辦,因此屠宰場老闆就準備了些豬籠。首先將豬仔趕進豬籠中,待船靠岸後,打開豬籠,將豬仔放出來便可。咱們這裏的Parcel就比如豬籠,咱們把須要傳遞的數據寫入Parcel中,而後到達目標進程後,將Parcel中的數據讀出便可,因此能夠將Parcel稱爲數據傳輸載體。Parcel支持的數據類型很是之多,足以知足咱們平常開發所需。
如今你知道客戶端的數據是如何傳遞到服務端了嗎?
3.一、客戶端與服務端aidl文件以及包名必須一致,不然沒法正常通訊。3.二、在綁定服務端Service的時候,intent最好設置目標Service所在的包名,如intent.setPackage("com.silencezwm.ipcdemo"),當SDK版本大於14的時候,你會碰到這個錯誤 java.lang.IllegalArgumentException: Service Intent must be explicit:。
3.三、跨進程傳遞實體類必須進行序列化,不信你試試看。
3.四、Parcel所佔用的內存,會隨着你傳遞的數據量大小而相應變化。
好啦,本篇「Android跨進程通訊」的相關介紹就到這裏了,感謝你的到來!