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

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

我们公司是做一些定制系统的开发,开发的各种应用。
经常是由一个 App 运行另一个 App ,然后由那个 App 处理完数据再回传。
这样能很好地把一些核心算法逻辑单独封装成一个 Service,由一个固定的 App 运行 。
这样跨进程的调用实际上就是 AIDL ,不过关于有许多关于 AIDL 的文章,高质量的也很多,就不打算描述了。
主要是想说说实际操作,以及如何在客户端与服务端搭建一个回调(观察者模式)。

  • 第一次 2018年12月22日
  • 第一次 2019年03月09日
    • 调整代码块
    • 变量、类名、方法 标记统一

    • 一步一步搭建 AIDL 跨进程的回调
      • 搭建同进程中的通信
      • 搭建跨进程的通信
      • 搭建一个回调
      • 传递非基本类型数据
      • 更标准的使用
      • 更简单的使用
      • 过程中的思考


搭建同进程中的通信

先统一下说法,负责处理数据的部分称为服务端,负责将数据传入服务端,再将处理结果接收的部分称为客户端。

现在我们先尝试在一个 App 中将处理数据的部分做成一个 Service ,在 Activity 中进行绑定,然后调用 Service 的接口处理数据。

此时这个例子,Acitivty 就是客户端, Service 就是服务端。

假设我们需要实现两个简单的功能:

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

打算新建一个 AIDL 文件,因为 AIDL 既然能跨进程调用,那么肯定也能在同一个进程中使用。

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

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

那我们来导入一个 Library ,比如就叫 commonlib ,然后在 commonlib 中新建 AIDL 文件。

(本来这里是可以直接用普通接口类写在 App 里,不用 AIDL 文件;不过后面会逐渐改成跨进程调用,所以这里就直接使用 AIDL 文件)

一步一步搭建 AIDL 跨进程的回调_第1张图片

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

一步一步搭建 AIDL 跨进程的回调_第2张图片

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


    interface IMyAidl {
        //取两个整数和返回
        int onNumberPlus(int number1,int number2);
		//取字符串大写返回
        String onUpperString(String strins);
    }

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

一步一步搭建 AIDL 跨进程的回调_第3张图片

不过现在好像没法知道文件是不是生成好了,先不着急。

让我们先让 app 依赖 commonlib,在 app/build.gradle 文件中添加代码。

    implementation project (':commonlib')

然后在 app 中的 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 发现是自动生成好的文件,并且上面的提示也不建议我们进行编辑,咱们先暂时不看这个自动生成的。

能看见这个文件,说明前面的操作没有问题,咱们就开始动手编写服务端的代码。(如果想认真看看的话,建议使用代码对齐后查看)

一步一步搭建 AIDL 跨进程的回调_第4张图片

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

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

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

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


 	public class WorkService extends 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);
	        }
	    }
    
        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();
            }
        }
    }
   

这里也需要实现两个代码的逻辑,不过我们在前面已经写了,那么直接把前面的代码加进来。

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) {
        	return mWorkBinder == null ? null : 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,同样让 client 依赖 commonlib
一步一步搭建 AIDL 跨进程的回调_第5张图片
现在要把 client 作为客户端, app 作为服务端,因为之前处理逻辑的 Serviceapp 当中。

我们先写 client 的代码。再修改 app 中的代码。

client 也类似 app,唯一不同的就是开启 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);
        }
    }

然后我们去服务端 app 那边 ,把隐式 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 ,然后运行客户端 client

嗯,然后一打开发现崩溃了,咱们来瞅瞅日志。


    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);
    }

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

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);
    }

一步一步搭建 AIDL 跨进程的回调_第6张图片

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


    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 跨进程的回调_第7张图片
既然修改了 AIDL 文件,那么服务端 app 的实现逻辑肯定会变。

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


    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;
            }
        }
        
        //反注册回调实现 因为这里是一对一注册,所以传入参数没有使用到。
        //如果是有多个客户端注册,那么需要根据传入的 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;
            }
        }
    }

服务端 app 实现修改好了,我们继续回到客户端 client

先增加两个按钮,方便我们点击注册与反注册。


    <?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.CountListener S t u b Stub StubProxy@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;

一步一步搭建 AIDL 跨进程的回调_第8张图片
然后在相同的包下,java 目录下,新建一个类,也叫 Person。并且实现 Parcelable
一步一步搭建 AIDL 跨进程的回调_第9张图片

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

实现 Parcelable 的目的,就是能将对象序列化为序列化对象,也能将序列化对象转化为对象。

可以从 Person(Parcel in) 与 writeToParcel(Parcel dest, int flags) 看出。


    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类。

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


    import com.rambopan.commonlib.Person;
    interface CountListener {
    
        //计数的接口
        void onGetNumber(int number);
    
        //获取每个人的信息
        void onGetPersonInfo(in Person person);
    }
    

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

服务端 appWorkService 中修改 startCount() ,增加构造 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;
    }

客户端 clientMainAcitivity 中修改 CountClientStub 类,增加打印的消息。


    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.CountListener S t u b Stub StubProxy@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 下工程,再 build ,在这个目录下可以看见生成的 jar 文件。(不同 Android Studio 版本可能有差异)

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

一步一步搭建 AIDL 跨进程的回调_第10张图片

然后把 jar 文件分别放进客户端 client 与 服务端 app,再分别修改 build.gradle

注释掉之前依赖的 commonlib。并确保最后一行代码存在 :对 libs 目录所有 jar 文件依赖。

(注意此处的 libs 目录是和 src 目录同级的)

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

一步一步搭建 AIDL 跨进程的回调_第11张图片

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

一步一步搭建 AIDL 跨进程的回调_第12张图片

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


更简单的使用

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

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

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

为了简单,就用获取 AIDL 好了。
一步一步搭建 AIDL 跨进程的回调_第13张图片


    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 包后同步下工程,简单调整客户 client 绑定代码。

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


    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

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


	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 类当中的各种方法,就是将数据变成序列化之后,对每个方法加上唯一的描述符。

然后调用 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);
	        ……
	}
    

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

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

技术浅薄,如存在问题或错误,欢迎指出

你可能感兴趣的:(Android)