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

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

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

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


搭建同进程中的通信

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

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

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

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

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

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

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

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

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

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

在这里插入图片描述

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

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


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

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

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

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

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

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

我们的目的是让客户端能调用服务端的代码,现在客户端需要拿到服务端的一个 Service 实例,那么肯定是需要在 ServiceonBind() 中返回一个 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 的对象,不然我们返回哪个对象是吧。

所以在 ServiceonCreate() 中,对 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
在这里插入图片描述
现在要把 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);
    }

先在 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 文件,那么服务端 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;


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

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

实现 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/

在这里插入图片描述

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

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

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

    //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 包后同步下工程,简单调整客户 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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值