本文主要講述AIDL做用以及如何快速上手AIDL項目java
A [android]
I [Interface]
D [Definition]
L [Language]
Android接口定義語言。
做用:方便系統爲咱們生成代碼從而實現跨進程通信,僅此而已。(玉剛老師如是說也),也就是說這個AIDL就只是一個快速跨進程通信的工具。android
本篇文章意在快速實現AIDL項目,更多詳細內容下篇繼續闡述。程序員
建立服務端項目
首先咱們在app/src/main 目錄下建立AIDL文件夾。app
建立載體MessageBean
ide
首先咱們在這個AIDL文件夾裏建立用來傳輸的java Bean對象(包名並不重要),而且實現Parcelable
接口(建議使用Parcelable插件),由於進程間通信須要將該對象轉化爲字節序列,用於傳輸或者存儲。(傳遞的載體)工具
public class MessageBean implements Parcelable { private String content;//需求內容 private int level;//重要等級 `````` get set方法 `````` @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.content); dest.writeInt(this.level); } //若是須要支持定向tag爲out,inout,就要重寫該方法 public void readFromParcel(Parcel dest) { //注意,此處的讀值順序應當是和writeToParcel()方法中一致的 this.content = dest.readString(); this.level = dest.readInt(); } protected MessageBean(Parcel in) { this.content = in.readString(); this.level = in.readInt(); } public MessageBean() { public static final Creator<MessageBean> CREATOR = new Creator<MessageBean>() { @Override public MessageBean createFromParcel(Parcel source) { return new MessageBean(source); } @Override public MessageBean[] newArray(int size) { return new MessageBean[size]; } }; }
建立AIDL文件MessageBean.AIDL
學習
由於AIDL這個語言的規範就是aidl文件,因此咱們必須將MessageBean
轉爲aidl文件,供其它aidl的調用與交互。就這麼簡單,一個文件裏面兩行代碼。(這個文件要與java Bean放置同一包下)gradle
package qdx.aidlserver;//手動導包 parcelable MessageBean;//parcelable是小寫
數據類型:AIDL默認支持一些數據類型,在使用這些數據類型的時候是不須要導包的,可是除了這些類型以外的數據類型,在使用以前必須導包,就算目標文件與當前正在編寫的 .aidl 文件在同一個包下——在 Java 中,這種狀況是不須要導包的。好比,如今咱們編寫了兩個文件,一個叫作 IDemandManager.java
,另外一個叫作 IDemandManager.aidl
,它們都在 qdx.aidlserver
包下 ,如今咱們須要在 .aidl 文件裏使用 MessageBean
對象,那麼咱們就必須在 .aidl 文件裏面寫上 import qdx.aidlserver.MessageBean; 哪怕 .java 文件和 .aidl 文件就在一個包下。this
默認支持的數據類型
Java中的八種基本數據類型( byte,short(不支持short,編譯不經過),int,long,float,double,boolean,char)
String 和 CharSequence類型
List : List中的全部元素必須是AIDL支持的類型之一,裏面的每一個元素都必須可以被AIDL支持
Map : Map中的全部元素必須是AIDL支持的類型之一,包括key和value
Parcelabel : 全部實現了Parcelabel 接口的對象
AILD : 全部的AIDL接口自己也能夠在AIDL文件中使用
建立AIDL文件IDemandManager.AIDL
咱們建立IDemandManager
接口用來實現傳遞方法。
另外方法內若是有傳輸載體,就必須指明定向tag(in,out,inout)
package qdx.aidlserver; import qdx.aidlserver.MessageBean; import qdx.aidlserver.IDemandListener; interface IDemandManager { MessageBean getDemand(); void setDemandIn(in MessageBean msg);//客戶端->服務端 //out和inout都須要重寫MessageBean的readFromParcel方法 void setDemandOut(out MessageBean msg);//服務端->客戶端 void setDemandInOut(inout MessageBean msg);//客戶端<->服務端 }
埋坑與完善
該篇文章內容很少,可是到處皆是精華,尤爲是如下3條建議,以防引發慘案。
1. xxx.aidl 中不能存在同方法名不一樣參數的方法。
2. xxx.aidl 中實體類必需要有指定的tag。
3. 在Android Studio裏寫完aidl文件還須要在build.gradle文件中android{}方法內添加aidl路徑。
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
建立Service
最後咱們還須要建立一個服務,用來處理客戶端發來的請求,或者是定時推信息到客戶端。
public class AIDLService extends Service { @Override public IBinder onBind(Intent intent) { return demandManager; } //Stub內部繼承Binder,具備跨進程傳輸能力 IDemandManager.Stub demandManager = new IDemandManager.Stub() { @Override public MessageBean getDemand() throws RemoteException { MessageBean demand = new MessageBean("首先,看到我要敬禮", 1); return demand; } @Override public void setDemandIn(MessageBean msg) throws RemoteException {//客戶端數據流向服務端 Log.i(TAG, "程序員:" + msg.toString()); } @Override public void setDemandOut(MessageBean msg) throws RemoteException {//服務端數據流向客戶端 Log.i(TAG, "程序員:" + msg.toString());//msg內容必定爲空 msg.setContent("我不想聽解釋,下班前把全部工做都搞好!"); msg.setLevel(5); } @Override public void setDemandInOut(MessageBean msg) throws RemoteException {//數據互通 Log.i(TAG, "程序員:" + msg.toString()); msg.setContent("把用戶交互顏色都改爲粉色"); msg.setLevel(3); } }; }
最後咱們在清單文件中註冊服務
action 爲服務名稱,客戶端能夠經過此(com.tengxun.aidl)隱式啓動該服務。
在魅族的手機上,系統禁止了隱式方法啓動服務的權限,因此務必在手機管家/權限管理/ 中,開啓該項目的自啓權限。
<service android:name=".AIDLService" android:exported="true"> <intent-filter> <action android:name="com.tengxun.aidl" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
建立客戶端項目
拷貝AIDL文件夾
將服務端建立的aidl文件夾拷貝至客戶端app/src/main 目錄下,而且在gradle.build中關聯aidl路徑。
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
開啓服務
Intent intent = new Intent(); intent.setAction("com.tengxun.aidl");//service的action intent.setPackage("qdx.aidlserver");//aidl文件夾裏面aidl文件的包名 bindService(intent, connection, Context.BIND_AUTO_CREATE);
關聯對象,調用方法
服務綁定成功以後,咱們將服務端的IDemandManager.Stub
對象,經過asInterface
轉化成爲咱們客戶端須要的AIDL接口類型對象,而且這種轉化過程是區分進程的(下篇詳細敘述)。
private IDemandManager demandManager; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { demandManager = IDemandManager.Stub.asInterface(service);//獲得該對象以後,咱們就能夠用來進行進程間的方法調用和傳輸啦。 } @Override public void onServiceDisconnected(ComponentName name) { } };
至此咱們就能夠經過AIDL來進行跨進程通信了。
上面的步驟已經能夠實現正常的跨進程通信了,這時候若是咱們想服務端項目想要定時推送消息到客戶端項目,那麼跨進程中如何完成呢?
——————使用觀察者模式(多個回調方法的集合)——————
與一般的回調方法差很少,首先咱們在AIDL文件夾裏建立回調接口IDemandListener.aidl
可是這裏的定向tag要稍加註意,可能有些童鞋以爲咱們在服務端裏給客戶端發消息,照理不該該是設置out標記嗎?
其實所謂的「服務端」和」客戶端」在Binder通信中是相對的,由於咱們在客戶端項目中拷貝了aidl文件,因此咱們客戶端項目其實不只能夠發送消息,充當」Client」
,同時咱們也能夠收到服務端項目推送的消息,從而升級爲」Server」,這個功能依賴於AIDL的Stub和Proxy……(更多見下篇)
因此服務端onDemandReceiver
推送消息的過程實際上至關於」Client」
package qdx.aidlserver; import qdx.aidlserver.MessageBean; interface IDemandListener { void onDemandReceiver(in MessageBean msg);//客戶端->服務端 }
同時咱們在IDemandManager.aidl
文件中,加上綁定/解綁監聽方法
void registerListener(IDemandListener listener); void unregisterListener(IDemandListener listener);
最後Clean Project,在IDemandManager.Stub
中多處綁定/解綁兩個方法。
IDemandManager.Stub demandManager = new IDemandManager.Stub() { `````` setDemandIn/out等方法 `````` @Override public void registerListener(IDemandListener listener) throws RemoteException { } @Override public void unregisterListener(IDemandListener listener) throws RemoteException { } };
那麼,關鍵的步驟來了,咱們一般使用List<IDemandListener>
來存放監聽接口集合
可是!
因爲跨進程傳輸客戶端的同一對象會在服務端生成不一樣的對象!
上面這句話說明跨進程通信的過程當中,這個傳遞的對象載體並非像寄快遞同樣,從客戶端傳給服務端。而是通過中間人狸貓換太子的一些手段傳遞的。
不過傳遞過程當中間人(binder)對象都是同一個,因此Android經過這個特性提供RemoteCallbackList,讓咱們用來存儲監聽接口集合。
這個RemoteCallbackList內部自動實現了線程同步的功能,並且它的本質是一個ArrayMap
,因此咱們用它來綁定/解綁時,不須要作額外的線程同步操做。
private RemoteCallbackList<IDemandListener> demandList = new RemoteCallbackList<>(); @Override public void registerListener(IDemandListener listener) throws RemoteException { demandList.register(listener);// } @Override public void unregisterListener(IDemandListener listener) throws RemoteException { demandList.unregister(listener); }
最後,咱們經過Handler來進行定時發送消息。(handler並不能精準的作定時任務,由於handler在發送和接收的過程當中會有時間損耗)
另外咱們須要經過beginBroadcast()來獲取RemoteCallbackList中元素的個數,同時beginBroadcast()和finishBroadcast()必需要配對使用,哪怕僅僅只是獲取一下這個集合的元素個數。
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (demandList != null) { int nums = demandList.beginBroadcast(); for (int i = 0; i < nums; i++) { MessageBean messageBean = new MessageBean("我丟", count); count++; try { demandList.getBroadcastItem(i).onDemandReceiver(messageBean); } catch (RemoteException e) { e.printStackTrace(); } } demandList.finishBroadcast(); } mHandler.sendEmptyMessageDelayed(WHAT_MSG, 3000);//每3s推一次消息 } };
在咱們建立的客戶端項目就不用過多的處理了,建立IDemandListener.Stub
(此時咱們這個客戶端項目相對推送過程而言是」Server」),而且將它加入至demandManager
便可。
IDemandListener.Stub listener = new IDemandListener.Stub() { @Override public void onDemandReceiver(final MessageBean msg) throws RemoteException { //該方法運行在Binder線程池中,是非ui線程 } }; demandManager.registerListener(listener);
至此一個簡單通用的AIDL項目建立完成。
按照我我的習慣而言,學習和領悟一個項目知識以前,若是這個項目沒有RUN成功,那麼學習的過程變會略枯燥乏味,因此咱們先把這個流程走一遍,而後帶着種種問題去學習,應該會更加深入去理解和看待這個知識。固然也有點急功求成的感受…
Android 深刻淺出AIDL(二)