一步一步搭建 AIDL 跨进程的回调

关于有许多关于 AIDL 的文章,高质量的也很多,就不打算描述了,主要是想说说实际操作,如何在客户端与服务端搭建一个回调(观察者模式)。


搭建同进程中的通信

第一步先搭建一个服务端以及它的 Service ,新建一个 Project。

先别着急写代码,思考下,假设我们新建一个 AIDL 实现两个简单的功能:

  • 算出两个数字的和并返回
  • 对某个字符串取大写并返回。

那是不是应该在 app(默认 module)中直接添加 AIDL 呢,当然是可以的。

不过我们还有更好的选择,先说说 AIDL 的定义,Application Interface Defination Language(应用程序接口定义语言) 从本质上还是一个接口吧,服务端实现了该接口的方法,由客户端调用该方法并传入需要使用的参数。

那既然是个接口,说明是双方都可以共用的吧,那么我们是不是可以使用一个公共的 Library ,让客户端和服务端都来依赖这个 Library 呢 ?

那我们来导入一个 Library 。

在这里插入图片描述

然后在 main 目录上右键,添加 AIDL 文件。

添加后,发现 main 目录下多了一个 AIDL 目录,并且里面生成了我们刚输入名字的 AIDL 文件,里面已经有一个方法,和我们需要的不太一样,我们把它删除,然后写入需要的方法。

interface IMyAidl {
    
    int onNumberPlus(int number1,int number2);

    String onUpperString(String strins);

}

并且点击 Sync Project With Gradle Files。Android Studio 会为我们自动生成对应的文件。

不过现在好像没法知道是不是文件生成好了呢,先不着急,让我们先让服务端,即 app(Module) 依赖 commonlib (Library),在 app/build.gradle 文件中添加代码。

implementation project (':commonlib')

然后在 app Module 中的 Acitivity 中写一个 IMyAidl;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //测试 aidl 文件是否自动生成好。
        IMyAidl iMyAidl;
    }
}

点击 IMyAidl 发现是自动生成好的文件,并且上面的提示也不建议我们进行编辑,咱们先暂时不看这个自动生成的,能看见这个文件,说明前面的操作没有问题,咱们就开始动手编写服务端的代码。(如果想认真看看的话,建议使用代码对齐后查看)

新建一个 Service 类来完成服务端的逻辑。

并且添加之前我们要实现的功能,对两个数字求和返回与求一个字符串取大写字符返回。

这里我们是做的两个方法的实现,解耦了调用与实现。

public class WorkService extends Service {
    
    private int addNumber(int a, int b){
        return a + b;
    }

    private String toUpperString(String s){
        if(s == null || s.equals("")){
            return null;
        }else{
            return s.toUpperCase();
        }
    }
}

我们的目的是让客户端能调用服务端的代码,现在客户端需要拿到服务端的一个 Service 实例,那么肯定是需要在 Service 的 onBind() 中返回一个 IBinder 对象。

在 Service 中新建一个类,并且继承 IMyAidl.Stub 。

public class WorkBinder extends IMyAidl.Stub{
    @Override
    public int onNumberPlus(int number1, int number2) throws RemoteException {
        //调用前面写的实现
        return addNumber(number1,number2);
    }

    @Override
    public String onUpperString(String strins) throws RemoteException {
        //调用前面写的实现
        return toUpperString(strins);
    }
}

这里也需要实现两个代码的逻辑,不过我们在前面已经写了,那么直接把前面的代码加进来, WorkBinder 只负责调用,真正的逻辑实现是在外部。

当然还需要在 WorkService 中增加一个 WorkBinder 的对象,不然我们返回哪个对象是吧,所以在 Service 的 onCreate 中,对WorkBinder 进行初始化,然后在连接成功时返回该 WorkBinder 实例。

然后整理好 WorkService 就是这样的。

public class WorkService extends Service {
    
    private WorkBinder mWorkBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化实例
        if(mWorkBinder == null){
            mWorkBinder = new WorkBinder();
        }
    }

    //加法实现
    private int addNumber(int a, int b){
        return a + b;
    }

    //大写实现
    private String toUpperString(String s){
        if(s == null || s.equals("")){
            return null;
        }else{
            return s.toUpperCase();
        }
    }

    public class WorkBinder extends IMyAidl.Stub{
        @Override
        public int onNumberPlus(int number1, int number2) throws RemoteException {
            return addNumber(number1,number2);
        }

        @Override
        public String onUpperString(String strins) throws RemoteException {
            return toUpperString(strins);
        }
    }

    //返回该 Service 实例时重要的方法。
    //如果是通过 startService 方法时,可以忽略此方法。
    @Override
    public IBinder onBind(Intent intent) {
        if(mWorkBinder == null)
            return null;
        }else{
            return mWorkBinder;
        }
    }
}

好了,我们在 Activity 当中,直接开启 Service ,并且在连接建立后,尝试调用AIDL定义的方法。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Server.MainActivity";
    //定义为全局引用,方便后面调用
    IMyAidl myAidl;
    
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
            int sum;
            String string = null;
            try {
                sum = myAidl.onNumberPlus(10,20);
                string = myAidl.onUpperString("abcde");
            } catch (RemoteException e) {
                e.printStackTrace();
                sum = -1;
                string = "Error";
            }
            //试着打印下结果
            Log.i(TAG, "onServiceConnected: sum " + sum );
            Log.i(TAG, "onServiceConnected: String " + string );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService();
    }
    
    //建立连接
    private boolean bindService(){
        Intent intent = new Intent(this,WorkService.class);
        return bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //结束时解除连接
        if(mServiceConnection != null){
            unbindService(mServiceConnection);
        }
    }
}

最后不要忘了,在 Manifest 文件中添加 Service。

<application
    ……
    <activity android:name=".MainActivity">
        ……
    </activity>
    <service android:name=".WorkService"/>
</application>

好了,我们启动来,看看 Log.

com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected:

com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: sum 30

com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: String ABCDE

看来没什么问题,当然这个 AIDL 此时的作用只是在同一个进程当中。


搭建跨进程的通信

接下来改成跨进程的通讯,搭建客户端,新建一个 Module 。比如叫 Client。同样让客户端依赖 commonlib (Library)。

在这里插入图片描述

客户端也类似服务端,唯一不同的就是开启 Service 时需要隐式打开,我们先写客户端代码。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client.MainActivity";
    IMyAidl myAidl;
    
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
            int sum;
            String string = null;
            try {
                sum = myAidl.onNumberPlus(40,47);
                string = myAidl.onUpperString("client");
            } catch (RemoteException e) {
                e.printStackTrace();
                sum = -1;
                string = "Error";
            }
            Log.i(TAG, "onServiceConnected: sum " + sum );
            Log.i(TAG, "onServiceConnected: String " + string );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //结束时解除连接
        if(mServiceConnection != null){
            unbindService(mServiceConnection);
        }
    }

    private void bindService() {
        Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
}

然后我们去服务端那边,把隐式 Intent 所需的 Action 添加进 Manifest 文件。

<application
    ……
    <activity android:name=".MainActivity">
        ……
    </activity>
    
    <service android:name=".WorkService" >
        <intent-filter>
            <action android:name="com.rambopan.commonlib.IMyAidl"/>
        </intent-filter>
    </service>
    
</application>

这次我们先把之前服务端的 app 重新装一次,因为添加了隐式的 action ,然后运行客户端。
嗯,然后一打开发现崩溃了,咱们来瞅瞅日志。

12-14 23:52:41.530 12552-12552/com.rambopan.client E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rambopan.client, PID: 12552
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.rambopan.client/com.rambopan.client.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2315)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2375)
    at android.app.ActivityThread.access$900(ActivityThread.java:147)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1283)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5254)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:910)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:705)
 Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
    at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1686)
    at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1785)
    at android.app.ContextImpl.bindService(ContextImpl.java:1763)
    at android.content.ContextWrapper.bindService(ContextWrapper.java:548)
    at com.rambopan.client.MainActivity.bindService(MainActivity.java:52)
    at com.rambopan.client.MainActivity.onCreate(MainActivity.java:47)
    at android.app.Activity.performCreate(Activity.java:5993)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)

说需要指定为显示的,我们明明要通过其他应用来开启 Service ,肯定是需要隐式的。google 一下,发现是缺少一些信息,在 5.0 之后会抛出异常,找到一个解决方案,增加自定义类的包名。

具体分析可查看这篇文章:https://www.jianshu.com/p/08902fab84d4

然后修改 bindService 代码,添加包名。

private void bindService() {
    Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
    //添加目标Service的包名
    intent.setPackage("com.rambopan.aidlcallback");
    bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}

修改之后我们先把之前服务端开启的应用从后台清除,再运行客户端,观察下日志。

com.rambopan.client I/Client.MainActivity: onServiceConnected:

com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87

com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT

没有启动服务端,只启动客户端,也成功调起了服务端的服务。


搭建一个回调

现在已经实现了基本的调用操作,那么 …… 如果我们想实现这样一个功能:

开启服务端,让服务端不断的执行数据处理,然后回调给客户端,假设从 1 开始 ,每两秒发送 +1 的结果给客户端。

按照类似 setOnClickListener 的方式,客户端给服务端设置一个监听,由服务端把数据回调给客户端,我们先来试一下。

然后新加入一个 AIDL 文件 CountListener 作为回调的接口。

interface CountListener {

    //计数的接口
    void onGetNumber(int number);
}

先在 Library 中的 AIDL 加入四个新的方法:开始计数、是停止计数、注册回调、反注册回调。

interface IMyAidl {

    int onNumberPlus(int number1,int number2);

    String onUpperString(String strins);

    //开始计数
    boolean onStartCount();

    //停止计数
    boolean onStopCount();

    //注册回调
    void registerListener(CountListener listener);

    //反注册回调
    void unregisterListener(CountListener listener);
}

因为对 CountListener 的引用,在 IMyAidl 文件中,对 CountListener 进行导入。注意观察注释,注意必须手动导入。

既然修改了 AIDL 文件,那么服务端的实现逻辑肯定会变。

主要是增加了自动生成要传递的数字,以及注册接口。

public class WorkService extends Service {
    private static final String TAG = "Server.WorkService";

    private WorkBinder mWorkBinder;
    //开启线程来递增数字
    private Thread mThread;
    //回调接口
    private CountListener mCountListener;
    //递增数字
    private int mNumber = 0;
    //开始与结束线程
    private boolean isStart = false;

    @Override
    public void onCreate() {
        super.onCreate();
        if(mWorkBinder == null){
            mWorkBinder = new WorkBinder();
        }
    }

    private int addNumber(int a, int b){
        return a + b;
    }

    private String toUpperString(String s){
        if(s == null || s.equals("")){
            return null;
        }else{
            return s.toUpperCase();
        }
    }

    //开始计数的实现 开启线程,2秒+1
    private boolean startCount(){
        if(!isStart && mThread == null){
            mThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(isStart){
                        mNumber++;
                        if(mCountListener != null){
                            try {
                                mCountListener.onGetNumber(mNumber);
                            } catch (RemoteException e) {
                                Log.w(TAG, "deliver number Error  " );
                                e.printStackTrace();
                            }
                        }
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            isStart = true;
            mThread.start();
            return true;
        }
        return false;
    }

    //停止计数的实现
    private boolean stopCount(){
        if(isStart && mThread != null){
            isStart = false;
            return true;
        }
        return false;
    }

    //注册回调实现
    private void registerListenerImp(CountListener listener){
        Log.i(TAG, "registerListener: listener : " + listener);
        if(listener != null){
            mCountListener = listener;
        }
    }
    
    //反注册回调实现
    private void unregisterListenerImp(CountListener listener){
        Log.i(TAG, "unregisterListenerImp: listener : " + listener);
        mCountListener = null;
    }

    //包装一层,调用 Service 中的实现。
    public class WorkBinder extends IMyAidl.Stub{
        @Override
        public int onNumberPlus(int number1, int number2) throws RemoteException {
            return addNumber(number1,number2);
        }

        @Override
        public String onUpperString(String strins) throws RemoteException {
            return toUpperString(strins);
        }

        @Override
        public boolean onStartCount() throws RemoteException {
            return startCount();
        }

        @Override
        public boolean onStopCount() throws RemoteException {
            return stopCount();
        }

        @Override
        public void registerListener(CountListener listener) throws RemoteException {
           registerListenerImp(listener);
        }

        @Override
        public void unregisterListener(CountListener listener) throws RemoteException {
            unregisterListenerImp(listener);
        }
    }

    //返回该 Service 实例时重要的方法。
    //如果是通过 startService 方法时,可以忽略此方法。
    @Override
    public IBinder onBind(Intent intent) {
        if(mWorkBinder == null){
            return null;
        } else{
            return mWorkBinder;
        }
    }
}

服务端实现修改好了,我们继续回到客户端。
客户端先增加两个按钮,方便我们点击注册与反注册。

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <Button
        android:id="@+id/btn_start_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start_Count"
        />

    <Button
        android:id="@+id/btn_stop_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop_Count"/>

</LinearLayout>

MainActivity 当中主要是增加 CountListener.Stub 类,关于回调的传递、注册、实现。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client.MainActivity";
    IMyAidl myAidl;

    private CountClientStub mCountClientStub;
    //新建一个继承 CountListener.Stub的类作为传入的对象。
    private class CountClientStub extends CountListener.Stub{
        @Override
        public void onGetNumber(int number) throws RemoteException {
            Log.i(TAG, "onGetNumber: ---> " + number);
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
            int sum;
            String string = null;
            try {
                sum = myAidl.onNumberPlus(40,47);
                string = myAidl.onUpperString("client");
            } catch (RemoteException e) {
                e.printStackTrace();
                sum = -1;
                string = "Error";
            }
            Log.i(TAG, "onServiceConnected: sum " + sum );
            Log.i(TAG, "onServiceConnected: String " + string );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindService();

        //传入一个 Listener
        if(mCountClientStub == null) {
            mCountClientStub = new CountClientStub();
        }
        Button btnStartCount = findViewById(R.id.btn_start_count);
        Button btnStopCount = findViewById(R.id.btn_stop_count);
        btnStartCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(myAidl != null){
                    try {
                        myAidl.registerListener(mCountClientStub);
                        myAidl.onStartCount();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: registerListener RemoteException" );
                    }
                }
            }
        });
        btnStopCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(myAidl != null){
                    try {
                        myAidl.onStopCount();
                        myAidl.unregisterListener(mCountClientStub);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: unregisterListener RemoteException" );
                    }
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //结束时解除连接
        if(mServiceConnection != null){
            unbindService(mServiceConnection);
        }
    }

    private void bindService() {
        Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
        intent.setPackage("com.rambopan.aidlcallback");
        bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
}

现在把服务端客户端重新装一次,再试一次。

12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected:

12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87

12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT

12-15 16:10:11.276 25606-25623/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListenerStubStubProxy@cd84546

12-15 16:10:11.306 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1

12-15 16:10:13.308 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2

12-15 16:10:15.310 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3

12-15 16:10:17.312 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4

12-15 16:10:19.314 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5

12-15 16:10:21.316 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 6


传递非基本类型数据

成功了,那么我们如果回调,不只是传基本类型,要是传一些类的对象呢,那肯定是需要对该类实现序列化的接口。才能在一个进程中序列化写入,另一个进程序列化读出。

在 aidl 目录下,新建一个新的文件,Person。只需要写一句话。

parcelable Person;

然后在相同的包下,java 目录下,新建一个类,也叫 Person。并且实现 Parcelable。

我们增加一个名字和一个年龄属性,然后自动生成代码。

实现 Parcelable 的目的,就是通过 Parcelable 能过将对象序列化和反序列化,可以从 protected Person(Parcel in) 看出。

public class Person implements Parcelable {

    private String mName;
    private int mAge;

    protected Person(Parcel in) {
        mName = in.readString();
        mAge = in.readInt();
    }

    //新增构造函数。
    public Person(String name,int age){
        mName = name;
        mAge = age;
    }
    
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeInt(mAge);
    }
    
    @Override
    public String toString() {
        return " mName : " + mName +
                " mAge : " + mAge;
    }
}

我们增加了一个两个参数的构造函数。并且复写了 toString 方法,方便一会打印参数。

Person 类准备好了,我们去 CountListener 当中新增加一个方法,并且加入需要导入的 Person类。

import com.rambopan.commonlib.Person;

interface CountListener {

    //计数的接口
    void onGetNumber(int number);

    //获取每个人的信息
    void onGetPersonInfo(in Person person);
}

注意:Person 前面需要增加 in 关键字。

我们分别对服务端客户端代码进行修改。

服务端:增加构造 Person 类的方法。

Random mRandom = new Random();

//开始计数的实现 开启线程,2秒+1
private boolean startCount(){
    if(!isStart && mThread == null){
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(isStart){
                    mNumber++;
                    if(mCountListener != null){
                        try {
                            mCountListener.onGetNumber(mNumber);

                            //随机生成 A - Z 的字母作为名字
                            String name = Character.toString((char)(mRandom.nextInt(26) + 65));
                            //随机生成 0 - 20 的数字作为年龄
                            int age = mRandom.nextInt(20);
                            Person person = new Person(name,age);

                            mCountListener.onGetPersonInfo(person);
                        } catch (RemoteException e) {
                            Log.w(TAG, "deliver number Error  " );
                            e.printStackTrace();
                        }
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        isStart = true;
        mThread.start();
        return true;
    }
    return false;
}

客户端增加打印的消息。

private class CountClientStub extends CountListener.Stub{
    @Override
    public void onGetNumber(int number) throws RemoteException {
        Log.i(TAG, "onGetNumber: ---> " + number);
    }

    @Override
    public void onGetPersonInfo(Person person) throws RemoteException {
        Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
    }
}

好,现在来运行一下。

12-15 16:49:46.764 26999-27017/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListenerStubStubProxy@cd84546

12-15 16:49:46.764 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1

12-15 16:49:46.774 26963-26988/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : I mAge : 15

12-15 16:49:48.776 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2

12-15 16:49:48.786 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : F mAge : 8

12-15 16:49:50.788 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3

12-15 16:49:50.788 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : P mAge : 7

12-15 16:49:52.789 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4

12-15 16:49:52.789 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : M mAge : 11

12-15 16:49:54.801 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5

12-15 16:49:54.801 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : K mAge : 8

嗯,成功了。


更方便的使用

现在可以把 commonlib 共享出去,给需要使用的其他工程依赖。那么依赖很麻烦,有没有更简单的方法,比如打个 jar 包,然后让其他应用直接丢进 lib 文件夹依赖。

当然是可以的,我们可以先 clean 下工程,再 rebuid ,在这个目录下可以看见生成的 jar 文件。(不同 Android Studio 版本可能有差异)

/commonlib/build/intermediates/packaged-classes/debug/

在这里插入图片描述

然后把 jar 文件分别放进客户端与服务端,再分别修改 build.gradle :注释掉之前依赖的 commonlib。并确保下一行代码存在。

//implementation project (':commonlib')
//依赖 libs 目录下所有 jar 类型文件。
implementation fileTree(dir: 'libs', include: ['*.jar'])

在这里插入图片描述

替换了文件,然后重新同步下工程,再点击 IMyAidl ,已经提示是通过 .class 文件反编译的,无法修改了,说明是 jar 包生效了。

在这里插入图片描述

这样保证了工程在共享过程中不会被随意改动。


那还有没有什么可以优化的地方呢,要是每个客户端用的时候,都要写一次绑定服务的代码,是不是也可以把这重复的部分抽出来呢 ? 当然是可以的啦。

来创建一个辅助单例类 ConnectAssist,把绑定,解绑定等做成公共的部分(当然确保客户端不会添加新的功能)。

  • 把连接相关的逻辑放进来。
  • 把 AIDL 的方法包装一层,或者做一个获取 AIDL 的公共方法。为了简单,就用获取 AIDL 好了。

在这里插入图片描述

public class ConnectAssist {
    private static final String TAG = "ConnectAssist";
    private static ConnectAssist connectAssist;
    private IMyAidl myAidl;
    private Context mContext;

    private ConnectAssist(Context context){
        mContext = context;
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            myAidl = IMyAidl.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: " );
        }
    };

    public static synchronized ConnectAssist getInstance(Context context){
        if(connectAssist == null){
            synchronized (ConnectAssist.class){
                if(connectAssist == null){
                    connectAssist = new ConnectAssist(context.getApplicationContext());
                }
            }
        }
        return connectAssist;
    }

    public boolean bindService() {
        Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
        intent.setPackage("com.rambopan.aidlcallback");
        return mContext.bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
    }
    
    public boolean unbindService(){
        if(mServiceConnection != null){
            mContext.unbindService(mServiceConnection);
            return true;
        }else{
            return false;
        }
    }

    //获取 AIDL 接口
    public IMyAidl getMyAidl(){
        return myAidl;
    }
}

重新生成 jar 文件,然后替换客户端 jar 包,替换 jar 包后同步下工程,简单调整客户点绑定代码。

调整后如图,其实如果不是两个 OnClickListener 和 CountClientStub 类 ,还是挺简洁的 ……

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client.MainActivity";
    IMyAidl myAidl;

    private CountClientStub mCountClientStub;
    //新建一个继承 CountListener.Stub的类作为传入的对象。
    private class CountClientStub extends CountListener.Stub{
        @Override
        public void onGetNumber(int number) throws RemoteException {
            Log.i(TAG, "onGetNumber: ---> " + number);
        }

        @Override
        public void onGetPersonInfo(Person person) throws RemoteException {
            Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
        }
    }

    ConnectAssist connectAssist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //bindService();

        connectAssist = ConnectAssist.getInstance(this);
        connectAssist.bindService();

        //传入一个 Listener
        if(mCountClientStub == null) {
            mCountClientStub = new CountClientStub();
        }
        Button btnStartCount = findViewById(R.id.btn_start_count);
        Button btnStopCount = findViewById(R.id.btn_stop_count);
        btnStartCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //尝试在使用之前判断一次,如果为 null 就取一次
                if(myAidl == null){
                    myAidl = connectAssist.getMyAidl();
                }
                if(myAidl != null){
                    try {
                        myAidl.registerListener(mCountClientStub);
                        myAidl.onStartCount();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: registerListener RemoteException" );
                    }
                }
            }
        });
        btnStopCount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //尝试在使用之前判断一次,如果为 null 就取一次
                if(myAidl == null){
                    myAidl = connectAssist.getMyAidl();
                }
                if(myAidl != null){
                    try {
                        myAidl.onStopCount();
                        myAidl.unregisterListener(mCountClientStub);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        Log.w(TAG, "onClick: unregisterListener RemoteException" );
                    }
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //结束时解除连接
        connectAssist.unbindService();
    }
}

终于圆满了,当然在操作过程中也碰到一些需要思考的地方,比如。

  • WorkBinder 为什么要继承 IMyAidl.Stub ?

那我们先把 commonlib 中的 IMyAidl 点开,看看是什么结构。

public interface IMyAidl extends android.os.IInterface {
    
    ……
    
    public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
        
        ……
        
        private static class Proxy implements com.rambopan.commonlib.IMyAidl {
            
            ……
        }
    }
}


public class Binder implements IBinder {
    ……
}

可以看到 IMyAidl.Stub 继承了 Binder 类,Binder 类对 IBinder 进行了实现。所以 Stub 可以作为 IBinder 类型返回。

看看 服务端 ServiceConnection 代码。

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected: ");
        myAidl = IMyAidl.Stub.asInterface(service);
        ……
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected: " );
    }
};

从名字上可以看出,是把 IBinder 类型作为定义的接口类型返回,而 Stub 类,就是不光继承了 IBinder 类,同时也实现了定义的 AIDL 接口类 IMyAidl 。

    public static com.rambopan.commonlib.IMyAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.rambopan.commonlib.IMyAidl))) {
            return ((com.rambopan.commonlib.IMyAidl) iin);
        }
        return new com.rambopan.commonlib.IMyAidl.Stub.Proxy(obj);
    }

点开 asInterface() 方法,可以看出,是对 IBinder 类进行判断。

  • 如果在本地能查询到该类型 IInterface ,则向上转型。

  • 如果查询不到,则使用代理类,对 IBinder 类进行代理,返回一个该类型 IInterface。

      private static class Proxy implements com.rambopan.commonlib.IMyAidl {
          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;
          }
      
          @Override
          public int onNumberPlus(int number1, int number2) throws android.os.RemoteException {
              android.os.Parcel _data = android.os.Parcel.obtain();
              android.os.Parcel _reply = android.os.Parcel.obtain();
              int _result;
              try {
                  _data.writeInterfaceToken(DESCRIPTOR);
                  _data.writeInt(number1);
                  _data.writeInt(number2);
                  mRemote.transact(Stub.TRANSACTION_onNumberPlus, _data, _reply, 0);
                  _reply.readException();
                  _result = _reply.readInt();
              } finally {
                  _reply.recycle();
                  _data.recycle();
              }
              return _result;
          }
      
          ……
      
      }
    

点击 Proxy 类,能看到持有一个 IBInder 类型引用,几个方法都基本类似,我们就只分析一个 onNumberPlus。

Proxy 类当中的各种方法,就是将数据变成序列化之后,对每个方法加上唯一的描述符,然后调用 mRemote 引用中的 transact() 方法,把需要调用该方法的数据传入,等待 mRemote 调用结束之后,再从回复当中读取数据,就完成了一次跨进程的调用。

此处的 mRemote ,就是外层的 Stub 类,来看看 Stub 类中对应的方法,发现并没有找到 transact 方法,那去它的父类 IBinder 类中看看。

public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
        int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

观察 transact 执行的逻辑,主要还是在 onTransact 方法中,虽然 IBinder 当中是存在 onTransact 的,不过 Stub 类中已经重写了该类。在我们定义的这几种操作的情况下,会执行我们写的逻辑,其他情况下调用 super 。

public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
    private static final java.lang.String DESCRIPTOR = "com.rambopan.commonlib.IMyAidl";

    ……

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(descriptor);
                return true;
            }
            case TRANSACTION_onNumberPlus: {
                data.enforceInterface(descriptor);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.onNumberPlus(_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            
            ……
            
            default: {
                return super.onTransact(code, data, reply, flags);
            }
        }
    }
    ……
}

可以看到执行的逻辑,先判断对应的 transact 当时写入的每个操作对应的唯一描述符,先从序列化中读出数据,再调用 this.onNumberPlus() ,再把结果写入。

而 this 此时就是指每个 Stub 对象,而我们在 WorkBinder 定义当中已经复写这几个操作的逻辑,所以现在绕了一圈,所有线索都拼接上了。也算是把 AIDL 流程简单熟悉了下。


  • 为什么要在 myAidl 使用前进行一次 (myAidl == null) 的判断?在 onCreate 当中加入获取不行么?

当然是可以的,不过如果直接在 bindService 之后去获取的话,可能是 null 。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        connectAssist = ConnectAssist.getInstance(this);
        connectAssist.bindService();
        //测试
        myAidl = connectAssist.getMyAidl();
        Log.i(TAG, "ConnectAssist: ---> myAidl : " + myAidl);
        ……
    }

我们加入 log 测试,查看一下。

11:32:13.418 12674-12674/com.rambopan.client I/Client.MainActivity: ConnectAssist: —> myAidl : null

11:32:13.448 12674-12674/com.rambopan.client I/ConnectAssist: onServiceConnected:

从结果来看,Service 连接成功还需要一点时间,所以 bindService 是异步执行的,所以等没有等待连接成功或失败之后再执行剩下的代码。

Demo 地址:https://github.com/RamboPan/demo_aidlcallback

  • 技术浅薄,如果存在任何问题或错误,欢迎指出,会予以确认与改正。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章