Android IPC之AIDL看這一篇還不夠

一瑞筐、AIDL是什么块蚌?

如果你想深入學習基于Binder通信的知識纱控,請看我之前的文章:

Android IPC之代理模式
Android IPC之Binder機制分析

AIDL(Android Interface Define Language) 是IPC進程間通信方式的一種.用于生成可以在Android設(shè)備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼唾那。
AIDL規(guī)定了兩個進程之間如何使用Binder來通信,AIDL文件就是接口和數(shù)據(jù)聲明。Android Studio利用我們寫好的AIDL文件生成Java文件,真正在打包成apk文件時會把aidl文件生成的java代碼打包進去,不會打包AIDL文件,那么很好理解鹊汛,Android Studio利用我們寫好的AIDL文件生成基于Binder進程通信java代碼至耻。binder進程通信是C/S和代理模式實現(xiàn)的。ADIL文件生成的java文件中可以看出來。
一句話總結(jié):

Android Studio利用我們寫好的AIDL文件生成基于Binder進程通信java代碼。生成的java代碼會被打包到apk文件逸吵,AIDL文件只是一個模具而已韩脑。

基于Binder的AIDL數(shù)據(jù)通信流程圖:

這里寫圖片描述
這里寫圖片描述

二进苍、AIDL通信支持的數(shù)據(jù)傳送類型

2.1 Android Binder支持傳送數(shù)據(jù)類型

進程通信就是交換數(shù)據(jù)沈贝,AIDL通信傳送支持的類型其實就是Binder支持的市俊,AIDL并不是支持所有的數(shù)據(jù)類型,java中有很多數(shù)據(jù)類型昭躺,java基本的數(shù)據(jù)類型帝洪,集合數(shù)據(jù)類型龙助,還有我們用戶自己定義的數(shù)據(jù)類型。那么AIDL支持哪些列?

共 4 種:

  1. Java 的基本數(shù)據(jù)類型
  2. List 和 Map
    元素必須是 AIDL 支持的數(shù)據(jù)類型
    Server 端具體的類里則必須是 ArrayList 或者 HashMap
  3. 其他 AIDL 生成的接口
  4. 實現(xiàn) Parcelable 的實體

2.2 Android序列化方式解析

其中List和Map集合裝的實體也是要實現(xiàn)Parcelable 接口的蝗蛙,或者直接傳遞一個實現(xiàn)了Parcelable 接口的實體類。Parcelable 是Android自帶的序列化接口,那么相比Java自帶序列化接口有什么異同列?

1. Serializable(Java自帶):
Serializable是序列化的意思惊畏,表示將一個對象轉(zhuǎn)換成可存儲或可傳輸?shù)臓顟B(tài)浪讳。序列化后的對象可以在網(wǎng)絡(luò)上進行傳輸,也可以存儲到本地。實現(xiàn)方式很簡單,只需要實現(xiàn)Serializable就可以了。

2. Parcelable(android 專用):
Serializable是序列化的意思,表示將一個對象轉(zhuǎn)換成可存儲或可傳輸?shù)臓顟B(tài)。序列化后的對象可以在網(wǎng)絡(luò)上進行傳輸,也可以存儲到本地。實現(xiàn)方式復雜寂玲,需要復寫兩個方法彰檬,分別是describeContents和writeToParcel景图,實例化靜態(tài)內(nèi)部對象CREATOR,實現(xiàn)接口Parcelable.Creator 。這兩個Android Studio可以幫我智能實現(xiàn),另外左右的變量都需要實現(xiàn)set和get方法,Android Studio的快捷方式也是很方便實現(xiàn)的額。

選擇序列化方法的原則:

1) 在使用內(nèi)存的時候,Parcelable比Serializable性能高,所以推薦使用Parcelable。

2) Serializable在序列化的時候會產(chǎn)生大量的臨時變量,從而引起頻繁的GC。

3) Parcelable不能使用在要將數(shù)據(jù)存儲在磁盤上的情況乍恐,因為Parcelable不能很好的保證數(shù)據(jù)的持續(xù)性在外界有變化的情況下呜投。盡管Serializable效率低點纵东,但此時還是建議使用Serializable 甜橱。

注意:
需要在多個部件(Activity或Service)之間通過Intent傳遞一些數(shù)據(jù),簡單類型(如:數(shù)字、字符串)的可以直接放入Intent。復雜類型必須實現(xiàn)Parcelable接口。需要傳輸?shù)膶崿F(xiàn)了Parcelable接口的實體類,在Client端和Server端都需要用一個.aidl文件上聲明一下。

可以先看一下我們需要傳輸?shù)囊呀?jīng)實現(xiàn)了Parcelable接口的實體類:

package com.server.data;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
 * Created by dawish on 2017/8/22.
 * Binder傳遞實體類數(shù)據(jù)必須實現(xiàn)Parcelable接口,也可以更高效地在內(nèi)存中傳輸
 */
public class ServiceData implements Parcelable {
    public String id;
    public String name;
    public String price;
    public String type;
    public ServiceData( ) {
    }
    /**
     * 讀數(shù)據(jù)恢復
     * 給createFromParcel里面用,IDE自動生成
     * @param in
     */
    protected ServiceData(Parcel in) {
        id = in.readString();
        name = in.readString();
        price = in.readString();
        type = in.readString();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }

    /**
     *  IDE自動生成
     *  讀取接口憨攒,目的是要從Parcel中構(gòu)造一個實現(xiàn)了Parcelable的類的實例處理杏瞻。
     *  因為實現(xiàn)類在這里還是不可知的忧吟,所以需要用到模板的方式煌抒,繼承類名通過模板
     *  參數(shù)傳入。
     *  為了能夠?qū)崿F(xiàn)模板參數(shù)的傳入,這里定義Creator嵌入接口,內(nèi)含兩個接口函數(shù)
     *  分別返回單個和多個繼承類實例。
     */
    public static final Creator<ServiceData> CREATOR = new Creator<ServiceData>() {
        @Override
        public ServiceData createFromParcel(Parcel in) {
            return new ServiceData(in);
        }

        @Override
        public ServiceData[] newArray(int size) {
            Log.i("danxx", "newArray size--->"+size);
            return new ServiceData[size];
        }
    };

    /**
     * 內(nèi)容描述接口,基本不用管,IDE自動生成
     */
    @Override
    public int describeContents() {
        return 0;
    }
    /**
     * 寫入接口函數(shù),打包,IDE自動生成
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        dest.writeString(name);
        dest.writeString(price);
        dest.writeString(type);
    }
}

三、aidl文件編寫

3.1 aidl文件編寫注意事項

Android Studio寫aidl文件還不是很智能的,寫aidl文件就像是寫txt文本文件,沒有智能提示的赊瞬,所以很很關(guān)鍵的是里面的package 包名和import導包都要十分小心谤绳,不能寫錯了,Android Studio里面會有一個aidl文件夾,新建的aidl文件都在這個文件夾里面,這個文件夾里面還可以建立包名。包名跟java實體類的包名也要完全一致,不然也會報錯∩嘞猓總結(jié)需要注意的兩點:

3.1.1 Android Studio aidl文件夾里的包名要跟對應java實體類包名一致

如果不一致會報錯,無法找到對應的java類

這里寫圖片描述

3.1.2 aidl文件里面package 包名和import導包都要跟java實體類包名一致

不一致也是會報錯的卖擅,導包不對直接無法識別類

這里寫圖片描述

3.1.3 注意細節(jié)

1.接口名和aidl文件名相同。
2.接口和方法前不用加訪問權(quán)限修飾符public,private,protected等,也不能用final,static。
3.Aidl默認支持的類型包話java基本類型(int、long、boolean等)和(String、List、Map、CharSequence),
使用這些類型時不需要import聲明。對于List和Map中的元素類型必須是Aidl支持的類型。
如果使用自定義類型作為參數(shù)或返回值彻秆,自定義類型必須實現(xiàn)Parcelable接口扎附。
4.自定義類型和AIDL生成的其它接口類型在aidl描述文件中香伴,應該顯式import,即便在該類和定義的包在同一個包中。
5.在aidl文件中所有非Java基本類型(需要實現(xiàn)Parcelable接口)參數(shù)必須加上in、out改橘、inout標記碌识,
以指明參數(shù)是輸入?yún)?shù)胖烛、輸出參數(shù)還是輸入輸出參數(shù)。
6.Java原始類型默認的標記為in,不能為其它標記。

注意:
我們想一下aidl文件最后都是要變成java文件的,文件的命名和導包的包名都要跟實際java類一模一樣。不然生成的java類里面也是有錯誤的,那么有可能導致直接無法生成R.java類,那么我們項目中在使用id的時候,R類都是找不到的。在我們的apk打包aidl工具在處理aidl文件時也是無法通過的疯兼。Android apk打包請看我之前的文章:Android apk打包流程

3.2編寫AIDL文件

主要的aidl文件姨裸,會根據(jù)此aidl文件生成基于binder夸進程通信的java代碼:

// MyAIDLService.aidl
//生產(chǎn)的MyAIDLService.java類會使用這個包名
package com.server.service.aidl;
//導入數(shù)據(jù)javabean類售淡,注意包名不要錯
import com.server.service.data.ServiceData;
// Declare any non-default types here with import statements
interface MyAIDLService {
    /**
     * 除了基本數(shù)據(jù)類型汤纸,其他類型的參數(shù)都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
     */
    void addData(in ServiceData data);

    List<ServiceData> getDataList();
}

主要里面使用到的java數(shù)據(jù)類抖部,這個累必須實現(xiàn)Parcelable接口俯萎,首先看一下用aidl文件聲明一下這個java類:

// 實體類包名撇眯,要跟實際的ServiceData.java包名一致
package com.server.service.data;

//實現(xiàn)了Parcelable序列化接口的實體類
parcelable ServiceData;

實現(xiàn)Parcelable接口的java數(shù)據(jù)類:

package com.server.service.data;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

/**
 * Created by dawish on 2017/8/22.
 */

public class ServiceData implements Parcelable {
    public String id;
    public String name;
    public String price;
    public String type;
    public ServiceData( ) {

    }
    /**
     * 讀數(shù)據(jù)恢復
     * 系統(tǒng)自動添加,給createFromParcel里面用
     * @param in
     */
    protected ServiceData(Parcel in) {
        id = in.readString();
        name = in.readString();
        price = in.readString();
        type = in.readString();
    }
    public String getName() {
        return name;
    }

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

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    /**
     *  IDE自動生成
     *  讀取接口梆砸,目的是要從Parcel中構(gòu)造一個實現(xiàn)了Parcelable的類的實例處理。
     *  因為實現(xiàn)類在這里還是不可知的,所以需要用到模板的方式杨耙,繼承類名通過模板
     *  參數(shù)傳入。
     * 為了能夠?qū)崿F(xiàn)模板參數(shù)的傳入,這里定義Creator嵌入接口,內(nèi)含兩個接口函數(shù)
     * 分別返回單個和多個繼承類實例。
     */
    public static final Creator<ServiceData> CREATOR = new Creator<ServiceData>() {
        @Override
        public ServiceData createFromParcel(Parcel in) {
            return new ServiceData(in);
        }

        @Override
        public ServiceData[] newArray(int size) {
            Log.i("danxx", "newArray size--->"+size);
            return new ServiceData[size];
        }
    };

    /**
     * 內(nèi)容描述接口彭则,基本不用管,IDE自動生成
     */
    @Override
    public int describeContents() {
        return 0;
    }
    /**
     * 寫入接口函數(shù)芬萍,打包,IDE自動生成
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        dest.writeString(name);
        dest.writeString(price);
        dest.writeString(type);
    }
}

相關(guān)的解釋已經(jīng)在代碼中了漫蛔,我這里就不解釋了毯盈。
上面的兩個aidl文件和一個java數(shù)據(jù)類是Server端和Client端共有的 脑奠,上面寫好的aidl文件和用到的java數(shù)據(jù)類都復制一份在Server端和Client端秒咨,這些是進程通信的鏈接器陡厘。
接下來就是便攜Server端的Service和Client的服務(wù)連接和請求代碼了:

3.2.1 Server端代碼編寫

public class AidlService extends Service {

    public static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate() executed");
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy() executed");
    }

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

    /**
     * 本地service用法 揉抵;
     */
    /***/
    private List<ServiceData> mData = new ArrayList<>();
    /**
     * 遠程service需要使用AIDL來通訊戏罢,其實也是基于Binder所意,只是Google規(guī)定了寫法
     */
    MyAIDLService.Stub remoteBinder = new MyAIDLService.Stub() {

        @Override
        public void addData(ServiceData data) throws RemoteException {
            mData.add(data);
        }

        @Override
        public List<ServiceData> getDataList() throws RemoteException {
            return mData;
        }
    };

}

Server的service需要在當前項目中的AndroidManifest.xml文件中聲明一下:

        <service android:name=".service.binder.BinderService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.danxx.binderService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

需要提供intent-filter舶治,這樣方便遠程的Client端連接我們的服務(wù)。

3.2.2 Client端代碼編寫

Client需要注意就是遠程的Server端的Service進程是有可能被殺死的承绸,我們需要在Client端監(jiān)聽遠程的Binder是不是被殺死了界轩,使用DeathRecipient,遠程的Binder被殺死會被回調(diào):

//遠程服務(wù)連接成功后監(jiān)聽遠程的Binder死亡回調(diào)
new ServiceConnection() {
        /**
         * service連接成功后的回調(diào)
         * @param name
         * @param service  通訊接口
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            showMessage("-----遠程服務(wù)鏈接成功-----", R.color.praised_num);
            isBound = true;
            myAIDLService = MyAIDLService.Stub.asInterface(service); //遠程service寫法
            try {
                //設(shè)置死亡代理
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
           }
      }
  }
    /**
     * 監(jiān)聽Binder是否死亡
     */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (myAIDLService == null) {
                return;
            }
            //死亡后解除綁定
            myAIDLService.asBinder().unlinkToDeath(mDeathRecipient, 0);
            myAIDLService = null;
            //重新綁定
            doBindService();
            showMessage("--DeathRecipient后重鏈遠程服務(wù)--", R.color.red_wine);
        }
    };

Client的完整代碼:

public class ActivityAidlClient extends AppCompatActivity {

    @BindView(R.id.bind_service)
    public Button bindService;

    @BindView(R.id.unbind_service)
    public Button unbindService;

    @BindView(R.id.addData)
    public Button addData;

    @BindView(R.id.getData)
    public Button getData;

    @BindView(R.id.txt)
    TextView tvInfo;

    @BindView(R.id.scrollView)
    ScrollView scrollView;

    /**服務(wù)是否連接*/
    public boolean isBound = false;
    /**遠程service使用*/
    public MyAIDLService myAIDLService;
    private SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
    /**
     *  ServiceConnection is Interface for monitoring the state of an application service
     *  ServiceConnection是一個觀察程序service的回調(diào)接口
     */
    ServiceConnection serviceConnection = new ServiceConnection() {
        /**
         * service連接成功后的回調(diào)
         * @param name
         * @param service  通訊接口
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            showMessage("-----遠程服務(wù)鏈接成功-----", R.color.praised_num);
            isBound = true;
            myAIDLService = MyAIDLService.Stub.asInterface(service); //遠程service寫法
            try {
                //設(shè)置死亡代理
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
            myAIDLService = null;
            showMessage("-----遠程服務(wù)已斷開連接-----", R.color.red_deep);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl_client);
        ButterKnife.bind(ActivityAidlClient.this);
        showMessage("-----遠程服務(wù)等待連接-----", R.color.green_pure);
    }

    /**
     * Service 的兩種啟動方法和區(qū)別
     Service的生命周期方法onCreate, onStart, onDestroy
     有兩種方式啟動一個Service,他們對Service生命周期的影響是不一樣的祖搓。
     1 通過startService
       Service會經(jīng)歷 onCreate -> onStart
      stopService的時候直接onDestroy
     如果是調(diào)用者自己直接退出而沒有調(diào)用stopService的話,Service會一直在后臺運行摘仅。下次調(diào)用者再起來可以stopService掏击。
     2 通過bindService
       Service只會運行onCreate, 這個時候服務(wù)的調(diào)用者和服務(wù)綁定在一起
     調(diào)用者退出了,Srevice就會調(diào)用onUnbind->onDestroyed所謂綁定在一起就共存亡了亚侠。并且這種方式還可以使得
     *
     */

    @OnClick({R.id.bind_service, R.id.unbind_service,R.id.addData,R.id.getData})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bind_service:
                if (!isBound) {
                    //用Intent匹配的方式綁定service
                    doBindService();
                }
                break;
            case R.id.unbind_service:
                if(isBound && myAIDLService != null && serviceConnection!=null) {
                    unbindService(serviceConnection);
                    isBound = false;
                    showMessage("-----主動斷開遠程連接-----", R.color.red_deep);
                }
                break;
            case R.id.addData:
                addData();
                break;
            case R.id.getData:
                getData();
                break;
            default:
                break;
        }
    }

    private void addData(){
        if(isBound){
            ServiceData serviceData = new ServiceData();
            try {
                int size = myAIDLService.getDataList().size();
                serviceData.setName("no-"+size);
                serviceData.setId(String.valueOf(size));
                serviceData.setPrice(String.valueOf(size+2));
                serviceData.setType(String.valueOf(size%8));

                StringBuffer stringBuffer = new StringBuffer();
                stringBuffer.append("Name:"+serviceData.getName());
                stringBuffer.append("  Id:"+serviceData.getId());
                stringBuffer.append("  Type:"+serviceData.getType());
                stringBuffer.append("  Price:"+serviceData.getPrice());
                showMessage(stringBuffer.toString(), R.color.blue_color);
                myAIDLService.addData(serviceData);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }else {
            Toast.makeText(this,"服務(wù)還未連接!", Toast.LENGTH_SHORT).show();
        }

    }

    private void getData(){
        if(isBound){
            try {
                int size = myAIDLService.getDataList().size();
//                tvInfo.setText("Data size: "+ size);
                showMessage("Data size: "+ size, R.color.blue_color);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }else {
            Toast.makeText(this,"服務(wù)還未連接颗管!", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 顯示文字
     *
     * @param info 提示信息
     */
    private void showMessage(final String info, final int color) {
        tvInfo.post(new Runnable() {
            @Override
            public void run() {
                int startPos = stringBuilder.length();
                stringBuilder.append("\n"+info);
                tvInfo.setText(DisplayUtil.changeTextColor(ActivityAidlClient.this, stringBuilder, color, startPos));
            }
        });
        tvInfo.postDelayed(new Runnable() {
            @Override
            public void run() {
                if(null != scrollView) scrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        },100);
    }

    protected void showToast(String msg){
        Toast.makeText(ActivityAidlClient.this, msg,Toast.LENGTH_SHORT).show();
    }

    //客戶端使用死亡代理姨涡,可以重啟service
    //http://blog.csdn.net/liuyi1207164339/article/details/51706585
    //服務(wù)端使用死亡回調(diào)回收數(shù)據(jù)
    //http://www.cnblogs.com/punkisnotdead/p/5158016.html
    //死亡通知原理分析
    //http://light3moon.com/2015/01/28/Android%20Binder%20%E5%88%86%E6%9E%90%E2%80%94%E2%80%94%E6%AD%BB%E4%BA%A1%E9%80%9A%E7%9F%A5[DeathRecipient]/
    /**
     * 監(jiān)聽Binder是否死亡
     */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (myAIDLService == null) {
                return;
            }
            //死亡后解除綁定
            myAIDLService.asBinder().unlinkToDeath(mDeathRecipient, 0);
            myAIDLService = null;
            //重新綁定
            doBindService();
            showMessage("--DeathRecipient后重鏈遠程服務(wù)--", R.color.red_wine);
        }
    };

    private void doBindService(){
        showMessage("-----開始鏈接遠程服務(wù)-----", R.color.light_yellow);
        Intent intent = new Intent();
        intent.setAction("com.danxx.aidlService");
        intent.setPackage("com.server");
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }
}

對應的XML文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="#f2f2f2"
        android:layout_margin="8dp">
        <TextView
            android:id="@+id/txt"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="22sp"
            android:gravity="left"
            android:padding="8dp"/>
    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp">
        <Button
            android:id="@+id/bind_service"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:background="@drawable/btn_selector_done"
            android:layout_marginRight="4dp"
            android:text="Bind Service" />

        <Button
            android:id="@+id/unbind_service"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:background="@drawable/btn_selector_done"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:layout_marginLeft="4dp"
            android:text="Unbind Service" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp">
        <Button
            android:id="@+id/addData"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:background="@drawable/btn_selector_done"
            android:layout_marginRight="4dp"
            android:text="add data" />

        <Button
            android:id="@+id/getData"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:background="@drawable/btn_selector_done"
            android:textAllCaps="false"
            android:layout_marginLeft="4dp"
            android:text="get data" />
    </LinearLayout>

</LinearLayout>

四距潘、AIDL生成的Java代碼分析

代碼中的幾個方法:
1稽犁、DESCRIPTION
Binderd的唯一標識震鹉,一般用當前的類名表示。
2、asInterface(android.os.IBinder obj)
用于將服務(wù)端的Binder對象轉(zhuǎn)換為客戶端需要的AIDL接口類型的對象忽妒,轉(zhuǎn)換區(qū)分進程决侈,客戶端服務(wù)端位于同一進程,返回服務(wù)端的 //Stub對象本身;否則返回的是系統(tǒng)的封裝后的Stub.proxy對象。
3痰娱、asBInder
返回Binder對象
4、onTransact
此方法運行在服務(wù)端中的Binder線程池中晴股,當客戶端發(fā)起跨進程請求時怎诫,遠程請求會通過系統(tǒng)底層封裝后交由此方法處理。
5狗唉、Proxy#add
此 方法運行在客戶端东亦,當客戶端遠程調(diào)用此方法時,它的內(nèi)部實現(xiàn)是這樣的:首先創(chuàng)建該方法所需要的輸入型Parcel對象_data座菠、輸出型Parcel對象 _reple和返回值對象_result,然后將該方法的參數(shù)信息寫入_data中;接著調(diào)用transact方法來發(fā)RPC請求,同時當前線程掛起额嘿;然 后服務(wù)端的onTransact方法會被調(diào)用帐我,直到RPC過程返回后,當前線程繼續(xù)執(zhí)行日戈,并從_reply中取出RPC過程返回的結(jié)果,寫入 _result中酌住。
AIDL文件生成java文件詳細解釋:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: F:\\GitHubCode\\AndReservoir\\Server\\src\\main\\aidl\\com\\server\\service\\aidl\\MyAIDLService.aidl
 */
package com.server.service.aidl;
// Declare any non-default types here with import statements
//為了讓大家看的更清楚 我把生成的binder代碼 給拷貝到另外一個工程下面了逞带,并且用ide 給他format
public interface MyAIDLService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     * 并且這個接口里 有一個靜態(tài)的抽象類Stub(注意這個名字是固定的 永遠都是Stub 不會是其他)
     * 并且這個Stub是Binder的子類簿废,并且實現(xiàn)了 MyAIDLService 這個接口
     * 這是Server服務(wù)端真是對象迂烁,具備真正的功能。
     */
    public static abstract class Stub extends android.os.Binder implements com.server.service.aidl.MyAIDLService {
        /**
         * 這個東西就是唯一的binder標示 可以看到就是IPersonManager的全路徑名
         * SM是DNS Binder驅(qū)動是路由器塞关,這個就是類似一個ip地址。
         */
        private static final java.lang.String DESCRIPTOR = "com.server.service.aidl.MyAIDLService";

        /**
         * Construct the stub at attach it to the interface.
         * 這個就是Stub的構(gòu)造方法,供服務(wù)端調(diào)用憋他,創(chuàng)建一個Binder類
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.server.service.aidl.MyAIDLService interface,
         * generating a proxy if needed.
         *這個方法 其實就做了一件事,如果是同一個進程,那么就返回Stub對象本身
         * 如果不是同一個進程瓤鼻,就返回Stub.Proxy這個代理對象了
         */
        public static com.server.service.aidl.MyAIDLService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //如果是同1個進程沃粗,也就是說進程內(nèi)通信的話 我們就返回括號內(nèi)里的對象
            if (((iin != null) && (iin instanceof com.server.service.aidl.MyAIDLService))) {
                return ((com.server.service.aidl.MyAIDLService) iin);
            }
            //如果不是同一進程疤祭,是2個進程之間相互通信悲柱,那我們就得返回這個Stub.Proxy 看上去叫Stub 代理的對象了
            return new com.server.service.aidl.MyAIDLService.Stub.Proxy(obj);
        }

        //返回當前對象 服務(wù)端調(diào)用
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        //只有在多進程通信的時候 才會調(diào)用這個方法 ,同一個進程是不會調(diào)用的砸逊。

        //首先 我們要明白 這個方法 一般情況下 都是返回true的崖叫,也只有返回true的時候才有意義,如果返回false了 就代表這個方法執(zhí)行失敗淆攻,
        //所以我們通常是用這個方法來做權(quán)限認證的蝉娜,其實也很好理解狐榔,既然是多進程通信楣颠,那么我們服務(wù)端的進程當然不希望誰都能過來調(diào)用
        //所以權(quán)限認證是必須的侧馅,關(guān)于權(quán)限認證的代碼 以后我再講 先略過攀例。

        //除此之外 ,onTransact 這個方法 就是運行在Binder線程池中的蔚舀,一般就是客戶端發(fā)起請求缅叠,然后android底層代碼把這個客戶端發(fā)起的
        //請求 封裝成3個參數(shù) 來調(diào)用這個onTransact方法悯森,第一個參數(shù)code 就代表客戶端想要調(diào)用服務(wù)端 方法的 標志位。
        //其實也很好理解 服務(wù)端可能有n個方法 每個方法 都有一個對應的int值來代表朋贬,這個code就是這個int值削解,用來標示客戶端想調(diào)用的服務(wù)端的方法
        //data就是方法參數(shù)唉铜,reply就是方法返回值讼撒。都很好理解

        //其實隱藏了很重要的一點丽惭,這個方法既然是運行在binder線程池中的担映,所以在這個方法里面調(diào)用的服務(wù)器方法也是運行在Binder線程池中的朋魔,
        //所以我們要記得 如果你的服務(wù)端程序 有可能和多個客戶端相聯(lián)的話,你方法里使用的那些參數(shù) 必須要是支持異步的移国,否則的話
        //值就會錯亂了!這點一定要記孜镅印!結(jié)論就是Binder方法 一定要是同步方法<叶帷A渚洹!!H瞎臁杆烁!

        /**
         *@param code  唯一標識,客戶端傳遞標識執(zhí)行服務(wù)端代碼
         *@param data  客戶端傳遞過來的參數(shù)
         *@param reply 服務(wù)器返回回去的值
         *@param flags 是否有返回值 0:有 1:沒有
         *@return
         *@throws RemoteException 異常
         */
        @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_addData: { //客服端請求區(qū)分1
                    data.enforceInterface(DESCRIPTOR);
                    com.server.service.data.ServiceData _arg0;
                    if ((0 != data.readInt())) {
                        //讀取客戶端傳遞過來的參數(shù)
                        _arg0 = com.server.service.data.ServiceData.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addData(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getDataList: { //客服端請求區(qū)分2
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.server.service.data.ServiceData> _result = this.getDataList();
                    reply.writeNoException();
                    //想客戶端寫入?yún)?shù)
                    reply.writeTypedList(_result);
                    return true;
                }
            }
            //利用Binder驅(qū)動發(fā)送數(shù)據(jù)
            return super.onTransact(code, data, reply, flags);
        }
        //注意這里的Proxy 這個類名也是不變的沪饺,從前文我們知道 只有在多進程通信的情況下  才會返回這個代理的對象
        //這個代理并不是Binder的子類蛾扇,只是實現(xiàn)了MyAIDLService接口的一個影子更哄,客戶端通過操作這個影子來實現(xiàn)對服務(wù)端的通信
        //這個代理端只是告訴客戶端 服務(wù)端擁有什么樣的功能可以提供麻敌。正真完整通信實現(xiàn)數(shù)據(jù)交換的還是服務(wù)端的Binder子類
        private static class Proxy implements com.server.service.aidl.MyAIDLService {
            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;
            }

            /**
             * 除了基本數(shù)據(jù)類型玩讳,其他類型的參數(shù)都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
             * 客戶端調(diào)用
             */
            @Override
            public void addData(com.server.service.data.ServiceData data) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((data != null)) {
                        _data.writeInt(1);
                        //寫入請求參數(shù)
                        data.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //想服務(wù)端發(fā)送請求
                    mRemote.transact(Stub.TRANSACTION_addData, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            /**
             * 客戶端調(diào)用
             * @return
             * @throws android.os.RemoteException
             */
            @Override
            public java.util.List<com.server.service.data.ServiceData> getDataList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.server.service.data.ServiceData> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getDataList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.server.service.data.ServiceData.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
        //客服端請求區(qū)分1
        static final int TRANSACTION_addData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        //客服端請求區(qū)分2
        static final int TRANSACTION_getDataList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    /**
     * 除了基本數(shù)據(jù)類型蝌戒,其他類型的參數(shù)都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
     */
    public void addData(com.server.service.data.ServiceData data) throws android.os.RemoteException;

    public java.util.List<com.server.service.data.ServiceData> getDataList() throws android.os.RemoteException;
}

五、測試

我們在把Server端app和Client端app都安裝啟動并在Client操作數(shù)據(jù)后沼琉,打開ADM:

這里寫圖片描述

選中遠程的server進程北苟,點擊紅色的進程停止stop按鈕,看看我們的死亡遠程Binder死亡監(jiān)聽重連是否有效:

這里寫圖片描述
這里寫圖片描述

詳細代碼請參考:
https://github.com/Dawish/AndReservoir
看這一篇真的還不夠:
后面我還會寫手動Client在Server端注冊監(jiān)聽回調(diào)接口打瘪,這樣在服務(wù)端支持多線程的情況下每一個客戶端都可以知道數(shù)據(jù)在變化以及變化后的結(jié)果友鼻。
手動首先Binder機制通信,不依靠AIDL文件來實現(xiàn)闺骚,雖然這里我已經(jīng)把AIDL實現(xiàn)IPC說得很清楚了彩扔,手動實現(xiàn)Binder無非就是吧aidl文件生產(chǎn)的java代碼文件手動實現(xiàn)一遍,按照C/S和代理模式來操作就可以了僻爽。也是難事虫碉!

參考:

  1. Android 進階7:進程通信之 AIDL 的使用
  2. Android 多進程編程 15問15答!
  3. AIDL使用解析
  4. 序列化Serializable和Parcelable的理解和區(qū)別
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胸梆,一起剝皮案震驚了整個濱河市蔗衡,隨后出現(xiàn)的幾起案子纤虽,更是在濱河造成了極大的恐慌,老刑警劉巖绞惦,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逼纸,死亡現(xiàn)場離奇詭異,居然都是意外死亡济蝉,警方通過查閱死者的電腦和手機杰刽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來王滤,“玉大人贺嫂,你說我怎么就攤上這事⊙阆纾” “怎么了第喳?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長踱稍。 經(jīng)常有香客問我曲饱,道長,這世上最難降的妖魔是什么珠月? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任扩淀,我火速辦了婚禮,結(jié)果婚禮上啤挎,老公的妹妹穿的比我還像新娘驻谆。我一直安慰自己,他們只是感情好庆聘,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布胜臊。 她就那樣靜靜地躺著,像睡著了一般伙判。 火紅的嫁衣襯著肌膚如雪区端。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天澳腹,我揣著相機與錄音织盼,去河邊找鬼。 笑死酱塔,一個胖子當著我的面吹牛沥邻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羊娃,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唐全,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邮利,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弥雹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后延届,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剪勿,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年方庭,在試婚紗的時候發(fā)現(xiàn)自己被綠了厕吉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡械念,死狀恐怖头朱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情龄减,我是刑警寧澤项钮,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站希停,受9級特大地震影響烁巫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脖苏,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望定踱。 院中可真熱鬧棍潘,春花似錦、人聲如沸崖媚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畅哑。三九已至肴楷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荠呐,已是汗流浹背赛蔫。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泥张,地道東北人呵恢。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像媚创,于是被迫代替她去往敵國和親渗钉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容