摘要:單進程通信在很久以前,小豬仔從生到死都是在豬圈中生活的,沒有外來者的入侵。那么,跨進程通信中,我們也需要擁有同樣功能的船,它就是,通過的中轉(zhuǎn),進程之間就能順利的進行數(shù)據(jù)交換了。
【免費】全網(wǎng)獨家:這是一份非常值得珍藏的Android知識體系!!!
類型 | 描述 | 用時 |
---|---|---|
選題 | silencezwm | 0.5小時 |
寫作時間 | 2017年10月25日 | 5.5小時 |
審稿 | silencezwm、Mya婷婷 | 2小時 |
校對上線 | silencezwm | 1小時 |
Tips:4個環(huán)節(jié),共計約9小時的精心打磨完成上線,同時也非常感謝參與審稿的同學。
看漫畫,漲薪資(¥) >>>【小豬的傳奇一生】:
該漫畫描述了小豬仔出生后,就生活在豬圈中,快樂的成長著。有一天,小豬長大了,屠宰場的老板就會通過船將肥豬運輸過來進行屠宰,然后將豬肉銷往世界各地。
看完該漫畫后,是不是覺得小豬仔的一生有點小悲涼,要怪就怪可惡的人類,無肉不歡,哈哈。
精彩的漫畫背后,總隱藏著一絲絲技術(shù)的小知識。本文將為你介紹“Android跨進程通信”的相關(guān)知識,通過本文的學習,你可以了解到:
一、單進程通信與多進程通信之間的區(qū)別一、單進程通信與多進程通信之間的區(qū)別二、跨進程通信常見的五種實現(xiàn)方式
三、跨進程通信的注意事項
概念普及:IPC(Inter-Process Communication)機制,即進程間通信或者跨進程通信機制,是指兩個進程之間進行數(shù)據(jù)交換的過程。
在很久以前,小豬仔從生到死都是在豬圈中生活的,沒有外來者的入侵。同樣的,在Android開發(fā)中,默認情況下,程序運行后,都只是運行在一個以項目包名創(chuàng)建的主進程中(就好比豬圈),例如
項目包名為:com.silencezwm.ipcdemo 默認進程名:com.silencezwm.ipcdemo
單進程通信就如:不同品類的豬仔(Android中不同的組件)在相同的豬圈(在同一進程中)中生活(運行)。
突然有一天,河流的右邊來了一個商人,他發(fā)現(xiàn)河對岸有不少肥豬在游蕩。于是,他發(fā)現(xiàn)了一個發(fā)家致富的機會,他在這里建了一個屠宰場。然后經(jīng)常通過船將對岸的肥豬運送過來,進行屠宰,賺的盆滿缽滿。
這個就類似Android程序,本來只有一個進程在運行,但是因為產(chǎn)品提了個奇葩的需求,使得我們程序猿們不得不多開一個進程來實現(xiàn)該需求。
程序猿們一頓牢騷后,最后還是動手干活了。
他們在AndroidManifest.xml文件中相應(yīng)的組件添加了android:process屬性,并指定進程名。然后打開了兩個Activity,但是BActivity被指定運行在新的進程,當程序跑起來后,此時可以看到有兩個進程正在運行,如圖:
我們知道,在單進程中通信,組件間是可以隨意進行通信,因為它們都處于同一個內(nèi)存空間。那多進程之間是怎樣通信的呢?
漫畫中,屠宰場的老板通過船將河對岸的肥豬運送過來,就因為船的存在,該老板就可以跨越河流到達對岸。那么,Android跨進程通信中,我們也需要擁有同樣功能的船,它就是Binder,通過Binder的中轉(zhuǎn),進程之間就能順利的進行數(shù)據(jù)交換了。
五種常見的實現(xiàn)方式可分為兩大類:四大組件的跨進程通信和AIDL。
Activity、Service、BroadcastReceiver、Content Provider四大組件只需要在AndroidManifest.xml文件相應(yīng)的組件中添加android:process屬性,并指定進程名。程序運行起來后,它們就會運行在不同的進程中,它們之間的通信,官方已經(jīng)給我們做了非常好的封裝,所以使用起來也非常方便,這里就不多做解釋了。
概念普及:AIDL(Android interface definition Language),即Android接口定義語言。
要想應(yīng)用AIDL技術(shù),就至少需要有兩個進程存在,A進程通過定義的AIDL接口文件與B進程進行通信,具體的實現(xiàn)步驟如下:
1、準備兩個進程:新建一個項目 IPCDemo,然后新建一個Module IPCClient,這樣我們就準備好了兩個進程,完成后的項目結(jié)構(gòu)如圖:
2、創(chuàng)建AIDL文件:在服務(wù)端IPCDemo中新建一個AIDL接口文件:IMyAidlInterface.aidl,其中會默認實現(xiàn)basicTypes方法,然后我們再定義一個login方法,IMyAidlInterface.aidl完整代碼如下;
package com.silencezwm.ipcdemo; /** * 定義的AIDL接口文件 */ interface IMyAidlInterface { /** * AIDL默認實現(xiàn)的方法 */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString); /** * 定義了一個登錄方法,包含用戶名、密碼兩個參數(shù) */ void login(String username, String password); }
3、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默認實現(xiàn)的方法 */ @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(); } } /** * 定義了一個登錄方法,包含用戶名、密碼兩個參數(shù) */ @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默認實現(xiàn)的方法 */ public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException; /** * 定義了一個登錄方法,包含用戶名、密碼兩個參數(shù) */ public void login(java.lang.String username, java.lang.String password) throws android.os.RemoteException; }
4、新建Service:繼續(xù)在服務(wù)器端src/main/java/包名目錄下新建一個Service,并在配置文件中注冊,然后設(shè)置其action值為com.silencezwm.ipcdemo,以供客戶端進行調(diào)用,并在Service內(nèi)部創(chuàng)建一個內(nèi)部類繼承靜態(tài)抽象類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); } } }
5、拷貝aidl目錄文件:至此,服務(wù)端的代碼編寫完成,接下來我們只需要完成客戶端的調(diào)用代碼即可。相當簡單,首先把服務(wù)端aidl整個文件夾拷貝到客戶端src/main目錄下(至于拷貝的原因,稍后會進行闡述),然后build項目。此時,客戶端、服務(wù)端就同時擁有AIDL相同的代碼;
6、綁定服務(wù)端Service:在客戶端你想要的地方通過服務(wù)端Service所在地的包名以及action來進行綁定,然后將Service連接成功后返回的IBinder對象,通過IMyAidlInterface.Stub.asInterface方法轉(zhuǎn)換為我們定義的aidl對象,然后根據(jù)該對象調(diào)用我們所定義的方法即可完成整個通信過程,客戶端MainActivity.java調(diào)用代碼如下:
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(); // 服務(wù)端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{ // 服務(wù)連接成功回調(diào) @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder); login(); } // 服務(wù)斷開連接回調(diào) @Override public void onServiceDisconnected(ComponentName componentName) { mIMyAidlInterface = null; } } private void login() { try { mIMyAidlInterface.login("silencezwm", "123456"); } catch (RemoteException e) { e.printStackTrace(); } } }
7、結(jié)果驗證:收獲的季節(jié)來了,先將服務(wù)端程序跑起來,之后將客戶端程序跑起來,點擊登錄按鈕,不出意外的話,我們已經(jīng)綁定了服務(wù)端的Service,并調(diào)用了login方法,將數(shù)據(jù)傳遞到服務(wù)端了,來看看Log的打印信息:
回顧AIDL整個實現(xiàn)過程,其實并不復雜。此時,聰明的人往往都會有一個疑問:
客戶端的數(shù)據(jù)到底是如何傳遞到服務(wù)端的呢?
接下來,我們就來一探究竟,以下的代碼主要涉及到自動生成的IMyAidlInterface.java文件。
在客戶端調(diào)用代碼中,我們知道,一旦綁定Service成功后,會返回一個IBinder對象,調(diào)用IMyAidlInterface.Stub.asInterface(iBinder)方法將該對象轉(zhuǎn)換為了我們所定義的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構(gòu)造方法被調(diào)用,跟著attachInterface方法被調(diào)用:
private IInterface mOwner; private String mDescriptor; ... public void attachInterface(IInterface owner, String descriptor) { mOwner = owner; mDescriptor = descriptor; }
相當好理解,就是進行接口對象和字符串標識符的賦值。接下來在asInterface方法中,會根據(jù)標識符去IBinder的本地去查找是否有該對象,也就是調(diào)用obj.queryLocalInterface(DESCRIPTOR)方法,繼續(xù)源碼中Binder.java
public IInterface queryLocalInterface(String descriptor) { if (mDescriptor.equals(descriptor)) { return mOwner; } return null; }
意思就是如果本地存在這個標識符的IInterface對象,那就直接返回之前構(gòu)造方法中初始化的mOwner對象,否則返回null,因為我們這里涉及到了跨進程通信,所以這里會直接返回null。代碼繼續(xù)往下走,很顯然,以下這段代碼會調(diào)用:
return new com.silencezwm.ipcdemo.IMyAidlInterface.Stub.Proxy(obj);
代碼字面意思就是返回IBinder的代碼對象,如下:
private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; }
到這里,我們就拿到了一個IBinder的代理對象,通過代理對象,我們就可以調(diào)用之前所定義的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天生具備跨進程傳輸數(shù)據(jù)能力。在文章開頭的漫畫中,可不是直接把豬仔趕上船就行的,萬一豬仔亂跑掉河里去了怎么辦,所以屠宰場老板就準備了些豬籠。首先將豬仔趕進豬籠中,待船靠岸后,打開豬籠,將豬仔放出來即可。我們這里的Parcel就好比豬籠,我們把需要傳遞的數(shù)據(jù)寫入Parcel中,然后到達目標進程后,將Parcel中的數(shù)據(jù)讀出即可,所以可以將Parcel稱為數(shù)據(jù)傳輸載體。Parcel支持的數(shù)據(jù)類型非常之多,足以滿足我們?nèi)粘i_發(fā)所需。
現(xiàn)在你知道客戶端的數(shù)據(jù)是如何傳遞到服務(wù)端了嗎?
三、跨進程通信的注意事項3.1、客戶端與服務(wù)端aidl文件以及包名必須一致,否則無法正常通信。3.2、在綁定服務(wù)端Service的時候,intent最好設(shè)置目標Service所在的包名,如intent.setPackage("com.silencezwm.ipcdemo"),當SDK版本大于14的時候,你會碰到這個錯誤 java.lang.IllegalArgumentException: Service Intent must be explicit:。
3.3、跨進程傳遞實體類必須進行序列化,不信你試試看。
3.4、Parcel所占用的內(nèi)存,會隨著你傳遞的數(shù)據(jù)量大小而相應(yīng)變化。
好啦,本篇“Android跨進程通信”的相關(guān)介紹就到這里了,感謝你的到來!
【免費】全網(wǎng)獨家:這是一份非常值得珍藏的Android知識體系!!!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/67946.html
摘要:中為何新增來作為主要的方式運行機制是怎樣的機制有什么優(yōu)勢運行機制是怎樣的基于通信模式,除了端和端,還有兩角色一起合作完成進程間通信功能。 目錄介紹 2.0.0.1 什么是Binder?為什么要使用Binder?Binder中是如何進行線程管理的?總結(jié)binder講的是什么? 2.0.0.2 Android中進程和線程的關(guān)系?什么是IPC?為何需要進行IPC?多進程通信可能會出現(xiàn)什么問...
摘要:前言進程間通信簡稱就是指進程與進程之間進行通信一般來說一個只有一個進程但是可能會有多個線程所以我們用得比較多的是多線程通信比如但是在一些特殊的情況下我們會需要多個進程或者是我們在遠程服務(wù)調(diào)用時就需要跨進程通信了設(shè)置多進程設(shè)置多進程的步驟很 前言: 進程間通信(Inter-Process Communication),簡稱IPC,就是指進程與進程之間進行通信.一般來說,一個app只有一個...
閱讀 2391·2023-04-26 02:54
閱讀 2315·2021-10-14 09:43
閱讀 3355·2021-09-22 15:19
閱讀 2843·2019-08-30 15:44
閱讀 2699·2019-08-30 12:54
閱讀 983·2019-08-29 18:43
閱讀 1936·2019-08-29 17:12
閱讀 1329·2019-08-29 16:40