Android IPC之AIDL看这一篇还不够

一、AIDL是什么?

如果你想深入学习基于Binder通信的知识,请看我之前的文章:

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

AIDL(Android Interface Define Language) 是IPC进程间通信方式的一种.用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。
AIDL规定了两个进程之间如何使用Binder来通信,AIDL文件就是接口和数据声明。Android Studio利用我们写好的AIDL文件生成Java文件,真正在打包成apk文件时会把aidl文件生成的java代码打包进去,不会打包AIDL文件,那么很好理解,Android Studio利用我们写好的AIDL文件生成基于Binder进程通信java代码。binder进程通信是C/S和代理模式实现的。ADIL文件生成的java文件中可以看出来。
一句话总结:

Android Studio利用我们写好的AIDL文件生成基于Binder进程通信java代码。生成的java代码会被打包到apk文件,AIDL文件只是一个模具而已。

基于Binder的AIDL数据通信流程图:

这里写图片描述
这里写图片描述

二、AIDL通信支持的数据传送类型

2.1 Android Binder支持传送数据类型

进程通信就是交换数据,AIDL通信传送支持的类型其实就是Binder支持的,AIDL并不是支持所有的数据类型,java中有很多数据类型,java基本的数据类型,集合数据类型,还有我们用户自己定义的数据类型。那么AIDL支持哪些列?

共 4 种:

  1. Java 的基本数据类型
  2. List 和 Map
    元素必须是 AIDL 支持的数据类型
    Server 端具体的类里则必须是 ArrayList 或者 HashMap
  3. 其他 AIDL 生成的接口
  4. 实现 Parcelable 的实体

2.2 Android序列化方式解析

其中List和Map集合装的实体也是要实现Parcelable 接口的,或者直接传递一个实现了Parcelable 接口的实体类。Parcelable 是Android自带的序列化接口,那么相比Java自带序列化接口有什么异同列?

1. Serializable(Java自带):
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。实现方式很简单,只需要实现Serializable就可以了。

2. Parcelable(android 专用):
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。实现方式复杂,需要复写两个方法,分别是describeContents和writeToParcel,实例化静态内部对象CREATOR,实现接口Parcelable.Creator 。这两个Android Studio可以帮我智能实现,另外左右的变量都需要实现set和get方法,Android Studio的快捷方式也是很方便实现的额。

选择序列化方法的原则:

1) 在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。

2) Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。

3) Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

注意:
需要在多个部件(Activity或Service)之间通过Intent传递一些数据,简单类型(如:数字、字符串)的可以直接放入Intent。复杂类型必须实现Parcelable接口。需要传输的实现了Parcelable接口的实体类,在Client端和Server端都需要用一个.aidl文件上声明一下。

可以先看一下我们需要传输的已经实现了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传递实体类数据必须实现Parcelable接口,也可以更高效地在内存中传输
 */
public class ServiceData implements Parcelable {
    public String id;
    public String name;
    public String price;
    public String type;
    public ServiceData( ) {
    }
    /**
     * 读数据恢复
     * 给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中构造一个实现了Parcelable的类的实例处理。
     *  因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板
     *  参数传入。
     *  为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数
     *  分别返回单个和多个继承类实例。
     */
    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];
        }
    };

    /**
     * 内容描述接口,基本不用管,IDE自动生成
     */
    @Override
    public int describeContents() {
        return 0;
    }
    /**
     * 写入接口函数,打包,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文本文件,没有智能提示的,所以很很关键的是里面的package 包名和import导包都要十分小心,不能写错了,Android Studio里面会有一个aidl文件夹,新建的aidl文件都在这个文件夹里面,这个文件夹里面还可以建立包名。包名跟java实体类的包名也要完全一致,不然也会报错。总结需要注意的两点:

3.1.1 Android Studio aidl文件夹里的包名要跟对应java实体类包名一致

如果不一致会报错,无法找到对应的java类

这里写图片描述

3.1.2 aidl文件里面package 包名和import导包都要跟java实体类包名一致

不一致也是会报错的,导包不对直接无法识别类

这里写图片描述

3.1.3 注意细节

1.接口名和aidl文件名相同。
2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。
3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),
使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。
如果使用自定义类型作为参数或返回值,自定义类型必须实现Parcelable接口。
4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。
5.在aidl文件中所有非Java基本类型(需要实现Parcelable接口)参数必须加上in、out、inout标记,
以指明参数是输入参数、输出参数还是输入输出参数。
6.Java原始类型默认的标记为in,不能为其它标记。

注意:
我们想一下aidl文件最后都是要变成java文件的,文件的命名和导包的包名都要跟实际java类一模一样。不然生成的java类里面也是有错误的,那么有可能导致直接无法生成R.java类,那么我们项目中在使用id的时候,R类都是找不到的。在我们的apk打包aidl工具在处理aidl文件时也是无法通过的。Android apk打包请看我之前的文章:Android apk打包流程

3.2编写AIDL文件

主要的aidl文件,会根据此aidl文件生成基于binder夸进程通信的java代码:

// MyAIDLService.aidl
//生产的MyAIDLService.java类会使用这个包名
package com.server.service.aidl;
//导入数据javabean类,注意包名不要错
import com.server.service.data.ServiceData;
// Declare any non-default types here with import statements
interface MyAIDLService {
    /**
     * 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
     */
    void addData(in ServiceData data);

    List<ServiceData> getDataList();
}

主要里面使用到的java数据类,这个累必须实现Parcelable接口,首先看一下用aidl文件声明一下这个java类:

// 实体类包名,要跟实际的ServiceData.java包名一致
package com.server.service.data;

//实现了Parcelable序列化接口的实体类
parcelable ServiceData;

实现Parcelable接口的java数据类:

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( ) {

    }
    /**
     * 读数据恢复
     * 系统自动添加,给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中构造一个实现了Parcelable的类的实例处理。
     *  因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板
     *  参数传入。
     * 为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数
     * 分别返回单个和多个继承类实例。
     */
    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];
        }
    };

    /**
     * 内容描述接口,基本不用管,IDE自动生成
     */
    @Override
    public int describeContents() {
        return 0;
    }
    /**
     * 写入接口函数,打包,IDE自动生成
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        dest.writeString(name);
        dest.writeString(price);
        dest.writeString(type);
    }
}

相关的解释已经在代码中了,我这里就不解释了。
上面的两个aidl文件和一个java数据类是Server端和Client端共有的 ,上面写好的aidl文件和用到的java数据类都复制一份在Server端和Client端,这些是进程通信的链接器。
接下来就是便携Server端的Service和Client的服务连接和请求代码了:

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规定了写法
     */
    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端连接我们的服务。

3.2.2 Client端代码编写

Client需要注意就是远程的Server端的Service进程是有可能被杀死的,我们需要在Client端监听远程的Binder是不是被杀死了,使用DeathRecipient,远程的Binder被杀死会被回调:

//远程服务连接成功后监听远程的Binder死亡回调
new ServiceConnection() {
        /**
         * service连接成功后的回调
         * @param name
         * @param service  通讯接口
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            showMessage("-----远程服务链接成功-----", R.color.praised_num);
            isBound = true;
            myAIDLService = MyAIDLService.Stub.asInterface(service); //远程service写法
            try {
                //设置死亡代理
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
           }
      }
  }
    /**
     * 监听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后重链远程服务--", 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;

    /**服务是否连接*/
    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的回调接口
     */
    ServiceConnection serviceConnection = new ServiceConnection() {
        /**
         * service连接成功后的回调
         * @param name
         * @param service  通讯接口
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            showMessage("-----远程服务链接成功-----", R.color.praised_num);
            isBound = true;
            myAIDLService = MyAIDLService.Stub.asInterface(service); //远程service写法
            try {
                //设置死亡代理
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
            myAIDLService = null;
            showMessage("-----远程服务已断开连接-----", R.color.red_deep);
        }
    };

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

    /**
     * Service 的两种启动方法和区别
     Service的生命周期方法onCreate, onStart, onDestroy
     有两种方式启动一个Service,他们对Service生命周期的影响是不一样的。
     1 通过startService
       Service会经历 onCreate -> onStart
      stopService的时候直接onDestroy
     如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。下次调用者再起来可以stopService。
     2 通过bindService
       Service只会运行onCreate, 这个时候服务的调用者和服务绑定在一起
     调用者退出了,Srevice就会调用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,"服务还未连接!", 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,"服务还未连接!", 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
    //服务端使用死亡回调回收数据
    //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]/
    /**
     * 监听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后重链远程服务--", R.color.red_wine);
        }
    };

    private void doBindService(){
        showMessage("-----开始链接远程服务-----", 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)
用于将服务端的Binder对象转换为客户端需要的AIDL接口类型的对象,转换区分进程,客户端服务端位于同一进程,返回服务端的 //Stub对象本身;否则返回的是系统的封装后的Stub.proxy对象。
3、asBInder
返回Binder对象
4、onTransact
此方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。
5、Proxy#add
此 方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象 _reple和返回值对象_result,然后将该方法的参数信息写入_data中;接着调用transact方法来发RPC请求,同时当前线程挂起;然 后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程返回的结果,写入 _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.
     * 并且这个接口里 有一个静态的抽象类Stub(注意这个名字是固定的 永远都是Stub 不会是其他)
     * 并且这个Stub是Binder的子类,并且实现了 MyAIDLService 这个接口
     * 这是Server服务端真是对象,具备真正的功能。
     */
    public static abstract class Stub extends android.os.Binder implements com.server.service.aidl.MyAIDLService {
        /**
         * 这个东西就是唯一的binder标示 可以看到就是IPersonManager的全路径名
         * SM是DNS Binder驱动是路由器,这个就是类似一个ip地址。
         */
        private static final java.lang.String DESCRIPTOR = "com.server.service.aidl.MyAIDLService";

        /**
         * Construct the stub at attach it to the interface.
         * 这个就是Stub的构造方法,供服务端调用,创建一个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个进程,也就是说进程内通信的话 我们就返回括号内里的对象
            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);
        }

        //返回当前对象 服务端调用
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        //只有在多进程通信的时候 才会调用这个方法 ,同一个进程是不会调用的。

        //首先 我们要明白 这个方法 一般情况下 都是返回true的,也只有返回true的时候才有意义,如果返回false了 就代表这个方法执行失败,
        //所以我们通常是用这个方法来做权限认证的,其实也很好理解,既然是多进程通信,那么我们服务端的进程当然不希望谁都能过来调用
        //所以权限认证是必须的,关于权限认证的代码 以后我再讲 先略过。

        //除此之外 ,onTransact 这个方法 就是运行在Binder线程池中的,一般就是客户端发起请求,然后android底层代码把这个客户端发起的
        //请求 封装成3个参数 来调用这个onTransact方法,第一个参数code 就代表客户端想要调用服务端 方法的 标志位。
        //其实也很好理解 服务端可能有n个方法 每个方法 都有一个对应的int值来代表,这个code就是这个int值,用来标示客户端想调用的服务端的方法
        //data就是方法参数,reply就是方法返回值。都很好理解

        //其实隐藏了很重要的一点,这个方法既然是运行在binder线程池中的,所以在这个方法里面调用的服务器方法也是运行在Binder线程池中的,
        //所以我们要记得 如果你的服务端程序 有可能和多个客户端相联的话,你方法里使用的那些参数 必须要是支持异步的,否则的话
        //值就会错乱了!这点一定要记住!结论就是Binder方法 一定要是同步方法!!!!!!

        /**
         *@param code  唯一标识,客户端传递标识执行服务端代码
         *@param data  客户端传递过来的参数
         *@param reply 服务器返回回去的值
         *@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: { //客服端请求区分1
                    data.enforceInterface(DESCRIPTOR);
                    com.server.service.data.ServiceData _arg0;
                    if ((0 != data.readInt())) {
                        //读取客户端传递过来的参数
                        _arg0 = com.server.service.data.ServiceData.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addData(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getDataList: { //客服端请求区分2
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.server.service.data.ServiceData> _result = this.getDataList();
                    reply.writeNoException();
                    //想客户端写入参数
                    reply.writeTypedList(_result);
                    return true;
                }
            }
            //利用Binder驱动发送数据
            return super.onTransact(code, data, reply, flags);
        }
        //注意这里的Proxy 这个类名也是不变的,从前文我们知道 只有在多进程通信的情况下  才会返回这个代理的对象
        //这个代理并不是Binder的子类,只是实现了MyAIDLService接口的一个影子,客户端通过操作这个影子来实现对服务端的通信
        //这个代理端只是告诉客户端 服务端拥有什么样的功能可以提供。正真完整通信实现数据交换的还是服务端的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;
            }

            /**
             * 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
             * 客户端调用
             */
            @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);
                        //写入请求参数
                        data.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //想服务端发送请求
                    mRemote.transact(Stub.TRANSACTION_addData, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            /**
             * 客户端调用
             * @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;
            }
        }
        //客服端请求区分1
        static final int TRANSACTION_addData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        //客服端请求区分2
        static final int TRANSACTION_getDataList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    /**
     * 除了基本数据类型,其他类型的参数都需要标上方向类型: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操作数据后,打开ADM:

这里写图片描述

选中远程的server进程,点击红色的进程停止stop按钮,看看我们的死亡远程Binder死亡监听重连是否有效:

这里写图片描述
这里写图片描述

详细代码请参考:
https://github.com/Dawish/AndReservoir
看这一篇真的还不够:
后面我还会写手动Client在Server端注册监听回调接口,这样在服务端支持多线程的情况下每一个客户端都可以知道数据在变化以及变化后的结果。
手动首先Binder机制通信,不依靠AIDL文件来实现,虽然这里我已经把AIDL实现IPC说得很清楚了,手动实现Binder无非就是吧aidl文件生产的java代码文件手动实现一遍,按照C/S和代理模式来操作就可以了。也是难事!

参考:

  1. Android 进阶7:进程通信之 AIDL 的使用
  2. Android 多进程编程 15问15答!
  3. AIDL使用解析
  4. 序列化Serializable和Parcelable的理解和区别
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容