IPC-進程間通信(二)AIDL

前言

本文主要是用兩個app進行演示,一個作爲Server進程,一個作爲Client進程。
手把手教會你擼會AIDL。

準備工作

  • 準備兩個app,作爲Server和Client
  • 請先搞清楚AIDL是幹什麼的
  • 對AIDL基本概念至少有一些簡單的瞭解

AIDL介紹

AIDL 能夠實現進程間通信,其內部是通過 Binder 機制來實現的

Binder不瞭解的推薦看下:Android跨進程通信:圖文詳解 Binder機制 原理,講的通俗易懂。我是隻看了這一篇關於Binder機制講解的,我就搞懂了。

組成部分

  1. 客戶端,調用遠程服務
  2. 服務端,提供服務
  3. AIDL接口,用來傳遞的參數,提供進程間通信。

Server

1).創建一個AIDL文件
在這裏插入圖片描述
2).SAIDLInterface.aidl

// SAIDLInterface.aidl
package c.s.sever;
interface SAIDLInterface {
// 定義通信函數)
    void noReturnValue(String desc);
}

3).需要進行編譯下才可進行後面的操作,編譯後會產生一個文件:有興趣的可以去自行研究下里面這麼實現的原理。
在這裏插入圖片描述
4).創建一個service,重寫onBind,把Binder (SAIDLInterface.Stub()) 返回。


public class SeverService extends Service {

    public static String tag = "AIDL Sever_";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(tag, " onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(tag, " onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return saidlInterface;
    }

    private Binder saidlInterface = new SAIDLInterface.Stub() {

        @Override
        public void noReturnValue(String params) throws RemoteException {
            Log.e(tag, "noReturnValue = " + params);
        }
    };

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(tag, " onUnbind");
        return super.onUnbind(intent);
    }

}


5).在清單文件註冊SeverService

        <service
            android:name=".SeverService"
            android:exported="true"
            android:process=":remote"
         >
            <!--android:process="c.s.remote.newprocess" 自己定義新的進程名也可以-->
            <intent-filter>
             <!--開啓服務的Action標識-->
                <action android:name="c.s.sever.SAIDLInterface" />
            </intent-filter>
        </service>

完成以上5步,最基礎的Server進程就完成了。

Client

1).需要把Sever下創建的aidl目錄拷貝到Client相對應的目錄,不要做任何改動。同樣需要編譯下
在這裏插入圖片描述
2).開啓Server端的SeverService進程

    // Sever端,清單文件中設置的Action標識
    private static String SEVER_ACTION = "c.s.sever.SAIDLInterface";
    // sever端包名
    private static String SEVER_PACKAGE = "c.s.sever";
    private SAIDLInterface asInterface;
    public static String tag="AIDL Client_";
    /**
    *調用toBind,去開啓Server端的SAIDLInterface進行建立連接
    */
    public void toBind(View view) {
        Intent intent = new Intent();
        intent.setAction(SEVER_ACTION);
        intent.setPackage(SEVER_PACKAGE);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    
  ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 從服務端獲取binder對象
            //注:一定要用asInterface這個函數獲取SAIDLInterface
            asInterface = SAIDLInterface.Stub.asInterface(service);
            Log.e(tag, "AIDL 連接成功");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            asInterface = null;
        }
    };


注意觀察Server 和Client的Log控制檯,Server別忘切換到定義的進程下去查看Log
在這裏插入圖片描述
到這裏我們可以用得到的 asInterface 進行調用我們定義的通信函數 noReturnValue
3).調用noReturnValue,進行測試通信

 private void isReturn() {
        if (null == asInterface) {
            return;
        }
    }
 /**
     * client向Server發送數據,無返回值
     * */
    public void clientToSever_NoReturn(View view) {
        isReturn();
        try {
            asInterface.noReturnValue("Client調用noReturnValue,傳數據給Sever進程");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

Server端日誌:
在這裏插入圖片描述
到這裏就已經利用AIDL完成跨進程通信了。

ADIL傳輸:有返回值通信、List、Map

在定義的AIDL SAIDLInterface文件中定義:Server和Client端都要定義,定義後別忘編譯,有關定義AIDl的相關函數的改動,Server和Client兩端都要保持一致的,切記!切記!切記!)

  • 有返回值的函數
  • List傳輸
  • Map傳輸
//有返回值的函數
String haveReturnValue( String desc);
//List傳輸
List<String> sendListReturnList(in List<String> desc);
//Map傳輸
Map sendMapReturnMap( in Map map);

// Map傳輸,不能是Map<String,String>這種Map<Key,value> 形式
// list正常使用

注:AIDL傳遞List和Map都需要指定一個定向Tag:in**

定向TAG介紹請移至下方 定向Tag介紹
AIDL(SAIDLInterface)定義的通信函數,在server端不要忘記重寫定義的通信函數:

  private Binder saidlInterface = new SAIDLInterface.Stub() {
  @Override
        public void noReturnValue(String params) throws RemoteException {
            Log.e(tag, "noReturnValue = " + params);
        }

        @Override
        public String haveReturnValue(String desc) throws RemoteException {
            Log.e(tag, "haveReturnValue =  " + desc);
            return "Sever接收並處理 =  " + desc;
        }

        @Override
        public List<String> sendListReturnList(List<String> descList) throws RemoteException {
            for (String desc : descList) {
                Log.e(tag, "Sever接收Client傳遞List :" + desc);
            }
            descList.add("C_");
            return descList;
        }

        @Override
        public Map sendMapReturnMap(Map map) throws RemoteException {
            for (Map.Entry<String, String> entry : ((Map<String, String>) map).entrySet()) {
                Log.e(tag, "Sever接收Client傳遞的Map:Key = " + entry.getKey() + ", Value = " + entry.getValue());
            }
            map.put("Sever_C", "CC");
            return map;
        }
    };
  • 有返回值調用:Client 調用 haveReturnValue
  /**
     * client向Server發送基本數據類型,有返回值
     * */
    public void clientToSever_HaveReturn(View view) {
        isReturn();
        try {
            String returnData = asInterface.haveReturnValue("Client調用haveReturnValue,傳數據給Sever進程,sever處理並返回");
            Log.e(tag, " 返回數據:" + returnData);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

左邊:server端 右邊:Client在這裏插入圖片描述

  • Map傳輸:Client 調用 sendMapReturnMap
/**
     * client向Sever發送Map<String>數據,sever接收並處理client傳遞的數據 再返回
     * */
    public void clientToSever_SendMap(View view) {
        isReturn();
        Map<String, String> map = new HashMap<>();
        map.put("Client_A", "AA");
        map.put("Client_B", "BB");
        try {
            Map<String, String> returnMap = asInterface.sendMapReturnMap(map);
            for (Map.Entry<String, String> entry : returnMap.entrySet()) {
                Log.e(tag,"Sever返回處理過後的Map:Key = " + entry.getKey() + ", Value = " + entry.getValue());
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

左邊:server端 右邊:Client
在這裏插入圖片描述

  • List傳輸:用法同上差不多,不再做描述。

定向Tag介紹

  1. 所有的非基本數據類型的參數都需要一個定向tag來指出數據的流向 :in , out ,或者 inout
  2. 基本數據類型參數的定向tag默認是,並且只能是 in
  3. 數據流向定向Tag:(默認Tag=in)
  4. List和Map傳輸都需要指定in(需要定義出來)例如:Map sendMapReturnMap( in Map map);

Tag三種模式

  1. in
    服務端將會接收到一個那個對象的完整數據,但是客戶端的那個對象不會因爲服務端對傳參的修改而發生變動
  2. out
    服務端將會接收到那個對象的參數爲空的對象,但是在服務端對接收到的空對象有任何修改之後客戶端將會同步變動
  3. inout
    將會接收到客戶端傳來對象的完整信息,並且客戶端將會同步服務端對該對象的任何變動

傳輸複雜數據(這裏會講解下定向Tag的數據流向)

1. 需要準備一個實體類:

a).需要實現Parcelable,進行序列化。Parcelable重寫的方法都是根據快捷鍵自動生成

// 定義了4個變量:年紀、姓名、性別、是否結婚

public class STestBean implements Parcelable {

    private String name;
    private String sex;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "STestBean{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", married=" + married +
                '}';
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isMarried() {
        return married;
    }

    public void setMarried(boolean married) {
        this.married = married;
    }

    private boolean married;

    public STestBean() {
    }

    public STestBean(String name, String sex, int age, boolean married) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.married = married;
    }

    protected STestBean(Parcel in) {
        name = in.readString();
        sex = in.readString();
        age = in.readInt();
        married = in.readByte() != 0;
    }
    public void readFromParcel(Parcel in){
        name = in.readString();
        sex = in.readString();
        age = in.readInt();
        married = in.readByte() != 0;
    }
    public static final Creator<STestBean> CREATOR = new Creator<STestBean>() {
        @Override
        public STestBean createFromParcel(Parcel in) {
            return new STestBean(in);
        }

        @Override
        public STestBean[] newArray(int size) {
            return new STestBean[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(sex);
        dest.writeInt(age);
        dest.writeByte((byte) (married ? 1 : 0));
    }
}

b).需要創建一個和STestBean對應的STestBean.aidl
在這裏插入圖片描述
在這裏插入圖片描述
注:STestBean要和STestBean.aidl放到同一個目錄下

// Server 和client兩端都要在app的build文件裏配置  防止運行時找不到STestBean這個類
sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }

    }

2. AIDL (SAIDLInterface )文件中定義函數:

Server和Client端都要定義,定義後別忘編譯,有關定義AIDl的相關函數的改動,Server和Client兩端都要保持一致的,切記!切記!切記!)

  /**
             * AIDL 定向Tag三種模式
             * **/
             STestBean tag_in(in STestBean bean);
             STestBean tag_out(out STestBean bean);
             STestBean tag_inout(inout STestBean bean);

3.server端重寫這三個方法:

    private Binder saidlInterface = new SAIDLInterface.Stub() {
         // ........ 省略N行代碼
       @Override
        public STestBean tag_in(STestBean bean) throws RemoteException {
            Log.e(tag, "定向Tag_in: Server接收數據 :" + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
            bean.setName("Sever修改——in");
            bean.setSex("女");
            bean.setMarried(true);
            bean.setAge(100);
            Log.e(tag, "定向Tag_in: Server接收後修改,並返回給Client數據 :  " + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
            return bean;
        }

        @Override
        public STestBean tag_out(STestBean bean) throws RemoteException {
            Log.e(tag, "定向Tag_out: Server接收數據 :" + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
            bean.setName("Sever修改——out");
            bean.setSex("女");
            bean.setMarried(true);
            bean.setAge(100);
            Log.e(tag, "定向Tag_out: Server接收後修改,並返回給Client數據 :  " + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
            return bean;
        }

        @Override
        public STestBean tag_inout(STestBean bean) throws RemoteException {
            Log.e(tag, "定向Tag_inout: Server接收數據 :" + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
            bean.setName("Sever修改");
            bean.setSex("女");
            bean.setMarried(true);
            bean.setAge(100);
            Log.e(tag, "定向Tag_inout: Server接收後修改,並返回給Client數據 :  " + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
            return bean;
        }
    };

4.(定向Tag: in 模式 )client調用 tag_in

public void tag_in(View view) {
        STestBean sTestBean=new STestBean();
        sTestBean.setAge(19);
        sTestBean.setName("SIX_in");
        sTestBean.setSex("男");
        sTestBean.setMarried(false);
        isReturn();
        try {
            STestBean bean = asInterface.tag_in(sTestBean);//發送數據到server
            Log.e(tag, "定向Tag_in: client接收server修改後返回的數據: " + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        Log.e(tag, "定向Tag_in傳遞前原數據:"+"\n年齡 = " + sTestBean.getAge() + " 姓名 = " + sTestBean.getName() + "  結婚 = " + sTestBean.isMarried() + "  性別= " + sTestBean.getSex());

    }

下圖是Client 調用tag_in(View view) 對應打印的Log:
這個函數裏打印的Log的時機是已經和Server端通信過的數據:
第一條:是Server端返回的數據,server做了修改。
第二條:是打印的是調用asInterface.tag_in()前設置的原始數據。發現還是之前原始數據
在這裏插入圖片描述
下圖是Server接收數據後,並做了修改打印的Log
第一條:是Client調用asInterface.tag_in()後得到得數據
第二條:是Server接收到數據後進行了修改,在返回給Client的
在這裏插入圖片描述
對比可以發現:定向Tag in模式,Client的原始數據,並不會因爲把數據傳給Server,Server做出數據的修改,而導致Client的原始數據發生改變

out模式、inout模式 需要在序列化的實體類增加一個方法供數據流向由Server流向Client時進行的寫入操作。

在這裏插入圖片描述

//在實體類STestBean中增加此方法: 如果不增加,就無法指定out  inout模式
public void readFromParcel(Parcel in){
        name = in.readString();
        sex = in.readString();
        age = in.readInt();
        married = in.readByte() != 0;
    }

5.(定向Tag: out 模式 )client調用 tag_out

public void tag_out(View view) {
        STestBean sTestBean=new STestBean();
        sTestBean.setAge(19);
        sTestBean.setName("SIX_out");
        sTestBean.setSex("男");
        sTestBean.setMarried(false);
        isReturn();
        try {
            STestBean bean = asInterface.tag_out(sTestBean);
            Log.e(tag, "定向Tag_out: client接收server修改後返回的數據: " + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        Log.e(tag, "定向Tag_out傳遞前原數據:"+"\n年齡 = " + sTestBean.getAge() + " 姓名 = " + sTestBean.getName() + "  結婚 = " + sTestBean.isMarried() + "  性別= " + sTestBean.getSex());

    }

Client日誌:
Client調用asInterface.tag_out(sTestBean)通信的打印結果:發現接收Client調用asInterface.tag_out(sTestBean)之前設置的原始數據也發生了改變:
在這裏插入圖片描述
Server日誌
第一條日誌:發現打印的數據全部爲null,說明Client調用asInterface.tag_out(sTestBean)並沒有把數據傳遞過來。
在這裏插入圖片描述
對比可以發現:定向Tag out模式,Server並不能接收Client傳遞數據,相反Server對數據的修改反而能導致Client的原始數據發生改變

6.(定向Tag: inout 模式 )client調用 tag_inout

public void tag_inout(View view) {
        STestBean sTestBean=new STestBean();
        sTestBean.setAge(19);
        sTestBean.setName("SIX_inout");
        sTestBean.setSex("男");
        sTestBean.setMarried(false);
        isReturn();
        try {
            STestBean bean = asInterface.tag_inout(sTestBean);
            Log.e(tag, "定向Tag_inout: client接收server修改後返回的數據: " + "\n年齡 = " + bean.getAge() + "  姓名 = " + bean.getName() + "  結婚 = " + bean.isMarried() + "  性別= " + bean.getSex());
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        Log.e(tag, "定向Tag_inout傳遞前原數據:"+"\n年齡 = " + sTestBean.getAge() + " 姓名 = " + sTestBean.getName() + "  結婚 = " + sTestBean.isMarried() + "  性別= " + sTestBean.getSex());
    }

Client:

在這裏插入圖片描述

Server
在這裏插入圖片描述
對比可以發現:定向Tag inout模式,Server既能接收Client傳遞數據,Server對數據的修改也能導致Client的原始數據發生改變

到這AIDL相關使用就結束了,寫的不好的不對的請客官指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章