深入解讀Android的內(nèi)部進程通信接口AIDL
意義:
由于每個應用進程都有自己的獨立進程空間,在android平臺上,一個進程通常不能訪問另一個進程的內(nèi)存空間,而我們經(jīng)常需要夸進程傳遞對象,就需要把對象分解成操作對象可以理解的基本單元,并且有序的通過進程邊界。
定義:
AIDL(Android Interface Definition Language)是一種IDL語言,用于生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數(shù)。
說明以及實現(xiàn)流程:
AIDL接口和普通的java接口沒有什么區(qū)別,只是擴展名為.aidl,保存在src目錄下,如果其他應用程序需要IPC,則也需要在src目錄下創(chuàng)建同樣的AIDL文件,創(chuàng)建完畢之后,通過ADT工具,會在工程的gen目錄下生成相對應的.java文件。
一般實現(xiàn)兩個進程之間的通信需要實現(xiàn)下面幾個步驟
(1)在Eclipse的android工程目錄下面創(chuàng)建一個.aidl擴展名的文件,語法和java定義接口的語法差不多,不過需要自己手動import對應的包名。(比如需要用到list集合,則需要import java.util.List;)
(2)如果aidl文件符合規(guī)范,ADT工具會幫助編譯器在gen目錄下生成相對應的.java文件。
(3)需要繼承實現(xiàn)一個服務類,跨進程調用的基礎。
(4)在service端實現(xiàn)AIDL接口,如果有回調則在client端實現(xiàn)callback的AIDL接口。
(5)在AndroidManifest.xml注冊service。
注意:
實現(xiàn)AIDL,我們需要注意以下五點
(1)AIDL只支持接口方法,不能公開static變量。
(2)AIDL接口方法如果有參數(shù),則需要注意in、out、inout的使用規(guī)則,對于基本數(shù)據(jù)類型,默認是in類型,可以不需要添加聲明,非基本可變對象需要在變量名之前添加方法類型
in表示輸入?yún)?shù),調用者把值傳遞給使用者使用。
out表示輸出參數(shù),調用者把容器傳遞給使用者填充,然后自己使用處理。
inout標書輸入輸出參數(shù),傳送相應的值并接收返回。
列舉一個out的使用例子:
服務端傳參數(shù)給客戶端,客戶端填充,服務端調用完之后,可以讀取到客戶端填寫的內(nèi)容,具體的例子后面將給出。
(3)AIDL定義的接口名必須和文件名一致。
(4)oneway表示用戶請求相應功能時不需要等待響應可直接調用返回,非阻塞效果,該關鍵字可以用來聲明接口或者聲明方法,如果接口聲明中用到了oneway關鍵字,則該接口聲明的所有方法都采用oneway方式。
(5)AIDL傳遞非基本可變長度變量(非final對象),需要實現(xiàn)parcelable接口。
parcel一般都用在Binder通信,通過read和write方法進行客戶端與服務端的數(shù)據(jù)傳遞(通信)。
比如:frameworks層服務端與hardware客戶端的Binder通信
reply->writeInt32(getCardReaderSize()); int mid = data.readInt32();
用來存放parcel數(shù)據(jù)的是內(nèi)存(RAM),而不是永遠介質(Nand等)。
parcelable定義了把數(shù)據(jù)寫入parcel和從parcel讀出數(shù)據(jù)的接口,一個類的實例,如果需要封裝到消息中去,就必須實現(xiàn)這一接口,如果實現(xiàn)了這個接口,該類的實例就是可以“被打包”。
Parcelabel 的實現(xiàn),需要在類中添加一個靜態(tài)成員變量 CREATOR,這個變量需要繼承 Parcelable.Creator 接口。
package com.zlc.provider; import android.os.Parcel; import android.os.Parcelable; public class Students implements Parcelable{ private int stu_id; private String stu_name; public Students(Parcel source){ stu_id = source.readInt(); stu_name = source.readString(); } public int getStu_id() { return stu_id; } public void setStu_id(int stu_id) { this.stu_id = stu_id; } public String getStu_name() { return stu_name; } public void setStu_name(String stu_name) { this.stu_name = stu_name; } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // TODO Auto-generated method stub dest.writeInt(stu_id); dest.writeString(stu_name); } //Interface that must be implemented and provided as a public CREATOR field that generates instances of your Parcelable class from a Parcel. public final static Parcelable.Creator<Students> CREATOR = new Parcelable.Creator<Students>() { @Override public Students createFromParcel(Parcel source) { // TODO Auto-generated method stub return new Students(source); } @Override public Students[] newArray(int size) { // TODO Auto-generated method stub return new Students[size]; } }; }
實例:
下面列舉一個例子,主要實現(xiàn)客戶端調用服務端然后回調回來,具體實現(xiàn)功能改變客戶端的文字和圖片顯示,這個例子暫時效果是圖片的更改直接使用客戶端已經(jīng)準備好的圖片,接下來幾篇博客會基于這個功能完善,到達服務端可以發(fā)送文字、圖片、文件句柄(I/O流),并且直接由服務端通過方法名稱直接調用客戶端方法,客戶端只需要注冊對應的view并且提供相應的方法給服務端使用,后面的兩部的完善主要用到反射和重寫MemoryFile(達到parcelable序列化效果)來實現(xiàn)。
(1)首先按照我們上面的步驟需要創(chuàng)建aidl文件,分別創(chuàng)建調用和回調的aidl文件,為了闡述更詳細一些,小編把parcelable對象也添加進去,僅僅作為測試。
IMyAidlService.aidl主要由服務端實現(xiàn)客戶端調用
package com.zlc.aidl; import com.zlc.aidl.DemoParcelable; import com.zlc.aidl.AIDLCallback; interface IMyAidlService{ void registerClient(AIDLCallback cb);//注冊回調 void saveDemoInfo(in DemoParcelable demo);//實際調用方法 }
AIDLCallback.aidl主要由客戶端實現(xiàn),服務端調用
package com.zlc.aidl; import com.zlc.aidl.DemoParcelable; import java.util.List; interface AIDLCallback { int returnResult(out List<DemoParcelable> list,int a);//回調給客戶端 void testMethod(out Bundle params);//用來測試參數(shù)in/out的使用 }
DemoParcelable.aidl聲明傳遞對象:
package com.zlc.aidl; parcelable DemoParcelable;
補充一點:out和in參數(shù)區(qū)別其實很明顯我們直接查看adt生成在gen目錄下對應的java文件就可以看出區(qū)別:當是out參數(shù)的時候是執(zhí)行完之后從parcel對象讀取值,而in參數(shù)時是寫到parcel對象里面?zhèn)鬟^去。
我們看下當testMethod分別是out和in修飾時生成的文件
當時out的時候是從parcel對象里面讀數(shù)據(jù)
mRemote.transact(Stub.TRANSACTION_testMethod, _data, _reply, 0); _reply.readException(); if ((0!=_reply.readInt())) { params.readFromParcel(_reply); }
當時in的時候是從parcel對象里面取數(shù)據(jù)
if ((params!=null)) { _data.writeInt(1); params.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_testMethod, _data, _reply, 0); _reply.readException();
(2)實現(xiàn)一個服務類用來實現(xiàn)進程之間通信MyAidlService.java,貼出部分代碼,詳細代碼會在后面上傳。
@Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub Log.d(TAG, "MyAidlService onBind"); return mBinder; } private final IMyAidlService.Stub mBinder = new IMyAidlService.Stub() { private AIDLCallback cb; @Override public void saveDemoInfo(DemoParcelable demo) throws RemoteException { if (demo != null) { if ("meinv1".equals(demo.getDemo_name())) { demo.setDemo_name("meinv2"); } list.add(demo); Log.d(TAG, "saveDemoInfo list.size = " + list.size() + " list = " + list); cb.returnResult(list, 5); Bundle params = new Bundle(); cb.testMethod(params); int width = params.getInt("width", 0); int height = params.getInt("height", 0); Log.d(TAG, "width = " + width + " height = "+height); } } @Override public void registerClient(AIDLCallback cb) throws RemoteException { cb.asBinder().linkToDeath(new DeathRecipient() { @Override public void binderDied() { try { Log.i(TAG, "[ServiceAIDLImpl]binderDied."); } catch (Throwable e) { } } }, 0); } };
(3)實現(xiàn)客戶端連接并且實現(xiàn)callback方法
private ServiceConnection mRemoteConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub Log.d(TAG, "onServiceDisconnected"); } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub Log.d(TAG, "onServiceConnected"); mRemoteService = (IMyAidlService) IMyAidlService.Stub .asInterface(service); if(mRemoteService != null) Log.d(TAG, "onServiceConnected success"); } }; …… btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String actionName = "com.zlc.aidl.server.MyAidlService"; Intent intent = new Intent(actionName); boolean ret = bindService(intent, mRemoteConnection, Context.BIND_AUTO_CREATE); Log.d(TAG, " ret ?=" + ret); if (ret) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { DemoParcelable demo = new DemoParcelable(); List<String> list = new ArrayList<String>(); list.add("like dance"); demo.setDemo_id((Integer) img.getTag()); demo.setDemo_name("meinv1"); demo.setDemo_list(list); mRemoteService.registerClient(callback); mRemoteService.saveDemoInfo(demo); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); } } }); } …… private final AIDLCallback callback = new AIDLCallback.Stub() { @Override public int returnResult(List<DemoParcelable> list, int a) throws RemoteException { if (list != null) Log.d(TAG, "list.size = " + list.size()+" a="+a); for (DemoParcelable demoParcelable : list) { doFresh(demoParcelable); } return 0; } @Override public void testMethod(Bundle outParams) throws RemoteException { // TODO Auto-generated method stub if (outParams != null) { outParams.putInt("width", 11); outParams.putInt("height", 12); } } };
(4)在androidManifest.xml里面注冊service服務。
注意一點:android:process=":remote",代表在應用程序里,當需要該service時,會自動創(chuàng)建新的進程。而如果是android:process="remote",沒有“:”分號的,則創(chuàng)建全局進程,不同的應用程序共享該進程。
通過ps直接看pid進程號就可以看出。
讓應用的組件在一個單獨的進程中運行,如果帶冒號: ,則創(chuàng)建一個專屬于當前進程的進程,如果不帶冒號,需要使用標準的命名規(guī)范命名進程名,例如com.xxx.xxx.xxx,而且該進程是全局共享的進程,即不同應用的組件都可以運行于該進程。
這可以突破應用程序的24M(或16M)內(nèi)存限制。
總之,使用帶:remote的屬性的進程id pid不同,父進程ID PPID是一樣的。而使用不帶冒號的remote則會創(chuàng)建兩個完全獨立的進程。
相關文章
Android EditText默認不彈出輸入法的實現(xiàn)方法
下面小編就為大家分享一篇Android EditText默認不彈出輸入法的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01Android對EditTex的圖片實現(xiàn)監(jiān)聽
這篇文章主要為大家詳細介紹了Android如何對EditTex的圖片實現(xiàn)監(jiān)聽,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10解決Android TabLayout 在寬屏幕上tab不能平均分配的問題
這篇文章主要介紹了解決Android TabLayout 在寬屏幕上tab不能平均分配的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08詳解Android studio實現(xiàn)語音轉文字功能
這篇文章主要介紹了如何通過Android studio調用科大訊飛的語音轉文字功能,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2022-03-03學習使用Material Design控件(二)使用DrawerLayout實現(xiàn)側滑菜單欄效果
這篇文章主要為大家介紹了學習使用Material Design控件的詳細教程,使用DrawerLayout和NavigationView實現(xiàn)側滑菜單欄效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07