Android 跨進程通訊 深刻淺出AIDL(一)

 

前言

本文主要講述AIDL做用以及如何快速上手AIDL項目java

簡介

A [android] 
I [Interface] 
D [Definition] 
L [Language] 
Android接口定義語言。 
做用:方便系統爲咱們生成代碼從而實現跨進程通信,僅此而已。(玉剛老師如是說也),也就是說這個AIDL就只是一個快速跨進程通信的工具。android

快速上手

本篇文章意在快速實現AIDL項目,更多詳細內容下篇繼續闡述。程序員

  • 在服務端建立AIDL文件,用來聲明java Bean以及傳輸調用的接口。【聲明文件】
  • 在服務端建立Service而且實現上面的接口。【建立服務】
  • 客戶端綁定Service。【綁定服務】
  • 客戶端調用服務端接口。【跨進程調用】

服務端

建立服務端項目 
首先咱們在app/src/main 目錄下建立AIDL文件夾。app

 


這裏寫圖片描述

 

 

建立載體MessageBeanide

首先咱們在這個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];
        }
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

 

建立AIDL文件MessageBean.AIDL學習

由於AIDL這個語言的規範就是aidl文件,因此咱們必須將MessageBean轉爲aidl文件,供其它aidl的調用與交互。就這麼簡單,一個文件裏面兩行代碼。(這個文件要與java Bean放置同一包下)gradle

package qdx.aidlserver;//手動導包
parcelable MessageBean;//parcelable是小寫
  • 1
  • 2



AIDL文件與java文件的區別ui

  • 文件類型:用AIDL書寫的文件的後綴是 .aidl,而不是 .java。
  • 數據類型: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)

  • in : 客戶端數據對象流向服務端,而且服務端對該數據對象的修改不會影響到客戶端。
  • out : 數據對象由服務端流向客戶端,(客戶端傳遞的數據對象此時服務端收到的對象內容爲空,服務端能夠對該數據對象修改,並傳給客戶端)
  • inout : 以上兩種數據流向的結合體。(可是不建議用此tag,會增長開銷)
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);//客戶端<->服務端
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

 

埋坑與完善

該篇文章內容很少,可是到處皆是精華,尤爲是如下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']
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

 

建立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);
        }
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

最後咱們在清單文件中註冊服務 
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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

 

客戶端

建立客戶端項目 
 

拷貝AIDL文件夾

將服務端建立的aidl文件夾拷貝至客戶端app/src/main 目錄下,而且在gradle.build中關聯aidl路徑。

sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5

 

開啓服務

Intent intent = new Intent();
        intent.setAction("com.tengxun.aidl");//service的action
        intent.setPackage("qdx.aidlserver");//aidl文件夾裏面aidl文件的包名
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
  • 1
  • 2
  • 3
  • 4

 

關聯對象,調用方法

服務綁定成功以後,咱們將服務端的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) {
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

至此咱們就能夠經過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);//客戶端->服務端

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9



同時咱們在IDemandManager.aidl文件中,加上綁定/解綁監聽方法

void registerListener(IDemandListener listener);

     void unregisterListener(IDemandListener listener);
  • 1
  • 2
  • 3



最後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 {

        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15



那麼,關鍵的步驟來了,咱們一般使用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);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11



最後,咱們經過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推一次消息
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

 

客戶端項目(接收定時推送)

在咱們建立的客戶端項目就不用過多的處理了,建立IDemandListener.Stub(此時咱們這個客戶端項目相對推送過程而言是」Server」),而且將它加入至demandManager便可。

IDemandListener.Stub listener = new IDemandListener.Stub() {
        @Override
        public void onDemandReceiver(final MessageBean msg) throws RemoteException {
            //該方法運行在Binder線程池中,是非ui線程

        }
    };


    demandManager.registerListener(listener);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

 

結束語

至此一個簡單通用的AIDL項目建立完成。 
按照我我的習慣而言,學習和領悟一個項目知識以前,若是這個項目沒有RUN成功,那麼學習的過程變會略枯燥乏味,因此咱們先把這個流程走一遍,而後帶着種種問題去學習,應該會更加深入去理解和看待這個知識。固然也有點急功求成的感受… 
Android 深刻淺出AIDL(二)

項目下載

http://download.csdn.net/download/qian520ao/9992461

相關文章
相關標籤/搜索