Android AIDL 跨进程通信超详版

Posted 嘴巴吃糖了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android AIDL 跨进程通信超详版相关的知识,希望对你有一定的参考价值。

来了新公司,公司项目里用了很多的独立进程的服务与他们之间存在了很多跨进程的通信。之前有很长一段时间没有实际去做跨进程通信 AIDL了,查阅了一些资料和文章看了些 Demo 把温习的心路历程介绍一下。

来模拟一个 ktv 播控系统(client)控制大屏上的歌曲的 播放、暂停动作

  • KtvAIDLClient
  • KtvAIDLService
  • 客户端 : 发起绑定的为客户端
  • 服务端 :被绑定的服务为服务端

服务创建

1 先把 service 创建出来

public class KtvService extends Service 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

manifest.xml

<service
    android:name=".KtvService"
    android:exported="true" />

先把几个关键的标签释义说明一下:

了解了这几个关键字以后我们重点需要注意一点:
如果需要在两个进程间通讯,我们这里准备了两个工程 1 KtvAIDLClient 2 KtvAIDLService 实际上两个工程运行起来就已经是各自独立的进程了,所以不需要声明 process。如果我们只有一个工程例如客户端和服务端都在一个 APP 下则需要把服务 process 单独命名设置,这样就是在一个 APP 下除了主进程以外的单独进程。

2 我们开始定义 AIDL 文件 先在与 java 同级的目录

创建 AIDL 文件,遵循规则我们以 I 起头起名

很简单就一个暂停和播放

KtvAIDLService/app/src/main/aidl/com/thunder/ktvaidlservice/IKtvController.aidl

interface IKtvController 

    void setPause(String pause);

    void setPlay(String play);


AIDL 文件定义好了。我们找到工具栏 build 选项里面选中 Make Project 或者是 rebuild。build结束后会在 build 目录下生成新的产物,我们查看一下:

我们注意到产物已经生成,并且还是一个 .java 文件结尾的接口文件。我们试着去代码中引用它。看下全文件

public class KtvService extends Service 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        Log.e("zxm", "onBind");
        return new KtvBinder();
    
    
    private static class KtvBinder extends IKtvController.Stub 

        @Override
        public void setPause(String pause) throws RemoteException 
            Log.e("zxm", pause);
        

        @Override
        public void setPlay(String play) throws RemoteException 
            Log.e("zxm", play);
        
    

    @Override
    public boolean onUnbind(Intent intent) 
        Log.e("zxm", "onUnbind");
        return super.onUnbind(intent);
    

    @Override
    public void onDestroy() 
        Log.e("zxm", "onDestroy");
        super.onDestroy();
    

    @Override
    public void onCreate() 
        Log.e("zxm", "onCreate");
        super.onCreate();
    

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        Log.e("zxm", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    

    @Override
    public void onRebind(Intent intent) 
        Log.e("zxm", "onRebind");
        super.onRebind(intent);
    

客户端创建

服务的代码告一段落,进行客户端 AIDL 相关编写。和刚刚一样在 java 同级目录创建 AIDL 文件夹并且把刚刚服务的的 aidl 文件拷贝到客户端,注意包名变化。

接下来我们进行服务的绑定:

private void bindKtvService() 
    Intent intent = new Intent();
    intent.setClassName("com.thunder.ktvaidlservice", "com.thunder.ktvaidlservice.KtvService");
    getApplicationContext().bindService(intent, new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            Log.e("zxm", "onServiceConnected: ");
            
            
            
        

        @Override
        public void onServiceDisconnected(ComponentName name) 
            Log.e("zxm", "onServiceDisconnected: ");

        
    , Context.BIND_AUTO_CREATE);

然后我们获得 stub 的实例,记住此处千万不能 new 实例需要用

.Stub.asInterface(service);

方法。 随后我们在界面上添加两个按钮作为控制,client 完整代码如下:

public class MainActivity extends AppCompatActivity 

    private IKtvController ktvController;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                try 
                    ktvController.setPause("sorry ~ pause");
                 catch (RemoteException e) 
                    e.printStackTrace();
                
            
        );
        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                try 
                    ktvController.setPause("hi ~ play");
                 catch (RemoteException e) 
                    e.printStackTrace();
                
            
        );
        bindKtvService();
    

    private void bindKtvService() 
        Intent intent = new Intent();
        intent.setClassName("com.thunder.ktvaidlservice", "com.thunder.ktvaidlservice.KtvService");
        getApplicationContext().bindService(intent, new ServiceConnection() 
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) 
                Log.e("zxm", "onServiceConnected: ");
                try 
                    ktvController = IKtvController.Stub.asInterface(service);
                 catch (Exception e) 

                
            

            @Override
            public void onServiceDisconnected(ComponentName name) 
                Log.e("zxm", "onServiceDisconnected: ");
            
        , Context.BIND_AUTO_CREATE);
    


此时两端代码编写完毕,先后把服务和客户端的代码重新 run 一下,可以看到 pid 3919/3842 的两个进程

启动日志:
绑定服务的时候服务端调用了 onCreate 和 onBind 方法。客户端则成功的获取到了 ktv 的控制器的 bind 实例。我们用控制器实例才试着处理下夸进程的数据传输一个为暂停、一个为播放:

历经了一些小的挫折之后,成功的把客户端的数据通过 aidl 的跨进程传输的方式输出到了服务端,见上方日志。

双向通信

上方我们完成了两个独立进程的跨进程通信,但是不知道大家有没有发现只有一个客户端向服务端的单向通信。但是实际在跨进程通信中双向通信的使用场景特别多。下面我们就接着刚刚的例子,当在客户端点击暂停和播放以后服务端给客户端回复一个成功或者失败的状态。

1 修改 AIDL 文件,我们新增了一个状态的 aidl 文件我看看 AIDL 这块的变化

interface IConrtollerStatusListener 
    void onPauseSuccess();
    void onPauseFailed(int errorCode);
    void onPlaySuccess();
    void onPlayFailed(int errorCode);

import com.thunder.ktvaidlservice.IConrtollerStatusListener;

interface IKtvController 

    void setOnControllerStatusListener(in IConrtollerStatusListener i);

    void setPause(String pause);

    void setPlay(String play);

分别重新添加到服务端以及客户端的工程中,然后 make 各自的项目。

2 客户端的工作很简单,把监听设置上即可代码如下,注意回来后操作UI需要自己切线程:

private void bindKtvService() 
    Intent intent = new Intent();
    intent.setClassName("com.thunder.ktvaidlservice", "com.thunder.ktvaidlservice.KtvService");
    getApplicationContext().bindService(intent, new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            Log.e("zxm", "onServiceConnected: ");
            try 
                ktvController = IKtvController.Stub.asInterface(service);
                ktvController.setOnControllerStatusListener(new IConrtollerStatusListener.Stub() 
                    @Override
                    public void onPauseSuccess() 
                        new Handler(Looper.getMainLooper()).post(() -> 
                            Toast.makeText(MainActivity.this, "onPauseSuccess", Toast.LENGTH_SHORT).show();
                        );

                    

                    @Override
                    public void onPauseFailed(int errorCode) 
                        new Handler(Looper.getMainLooper()).post(() -> 
                            Toast.makeText(MainActivity.this, "onPauseFailed" + errorCode, Toast.LENGTH_SHORT).show();
                        );
                    

                    @Override
                    public void onPlaySuccess() 
                        Toast.makeText(MainActivity.this, "onPlaySuccess", Toast.LENGTH_SHORT).show();
                    

                    @Override
                    public void onPlayFailed(int errorCode) throws RemoteException 
                        Toast.makeText(MainActivity.this, "onPlayFailed" + errorCode, Toast.LENGTH_SHORT).show();
                    
                );
             catch (Exception e) 

            
        

        @Override
        public void onServiceDisconnected(ComponentName name) 
            Log.e("zxm", "onServiceDisconnected: ");
        
    , Context.BIND_AUTO_CREATE);

3 服务端代码,我们来回调暂停和播放。各自模拟一个 1 秒的耗时操作,代码如下:

private static class KtvBinder extends IKtvController.Stub 

    private IConrtollerStatusListener listener;

    @Override
    public void setOnControllerStatusListener(IConrtollerStatusListener i) throws RemoteException 
        listener = i;
    

    @Override
    public void setPause(String pause) throws RemoteException 
        Log.e("zxm", pause);
        //模拟暂停耗时 1000 毫秒
        if (listener != null) 
            new Thread(() -> 
                try 
                    Thread.sleep(1000);
                    if (System.currentTimeMillis() % 2 == 0) 
                        listener.onPauseSuccess();
                     else 
                        listener.onPauseFailed(1002);
                    
                 catch (InterruptedException e) 
                    e.printStackTrace();
                 catch (RemoteException remoteException) 
                    remoteException.printStackTrace();
                
            ).start();
        
    

    @Override
    public void setPlay(String play) throws RemoteException 
        Log.e("zxm", play);
        //模拟播放耗时 1000 毫秒
        if (listener != null) 
            new Thread(() -> 
                try 
                    Thread.sleep(1000);
                    if (System.currentTimeMillis() % 2 == 0) 
                        listener.onPlaySuccess();
                     else 
                        listener.onPlayFailed(1001);
                    
                 catch (InterruptedException e) 
                    e.printStackTrace();
                 catch (RemoteException remoteException) 
                    remoteException.printStackTrace();
                
            ).start();
        
    

这样既完成了一个两个进程之间的双向通信,我们来看下 UI 效果:

失败和成功都是从另外一个进程传回来,包括一次错误码的数据输出。

in out inout

注意到我们上面修改的 AIDL 文件代码有的 in ,这和我们平时看到的 Java 代码不一样。

不妨我们把 AIDL 的这个 in 修改为 out 试试?看看是否还能进行服务端返回给客户端的通信。

out 直接报错

inout 从字面理解是支持双流向,但是编译也报错
我们来看下这篇文章的解释
(https://maimai.cn/article/detail?fid=1609989144&efid=hY89uIb6wFrJ4mChypxecQ)

注意事项

1 点击设置错误

2021-10-16 14:33:07.061 3919-3919/com.thunder.ktvaidlclient E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.thunder.ktvaidlclient, PID: 3919
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:414)
        at android.view.View.performClick(View.java:6256)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992)
        at android.view.View$PerformClick.run(View.java:24697)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409)
        at android.view.View.performClick(View.java:6256) 
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) 
        at android.view.View$PerformClick.run(View.java:24697) 
        at android.os.Handler.handleCallback(Handler.java:789) 
        at android.os.Handler.dispatchMessage(Handler.java:98) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6541) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 
     Caused by: java.lang.SecurityException: Binder invocation to an incorrect interface
        at android.os.Parcel.readException(Parcel.java:1942)
        at android.os.Parcel.readException(Parcel.java:1888)
        at com.thunder.ktvaidlclient.IKtvController$Stub$Proxy.setPause(IKtvController.java:110)

2 切记客户端要与服务端的 aidl 的包名一致否则报如下错误

    Process: com.thunder.ktvaidlclient, PID: 4087
    java.lang.SecurityException: Binder invocation to an incorrect interface
        at android.os.Parcel.readException(Parcel.java:1942)
        at android.os.Parcel.readException(Parcel.java:1888)
        at com.thunder.ktvaidlclient.IKtvController$Stub$Proxy.setPause(IKtvController.java:110)
        at com.thunder.ktvaidlclient.MainActivity$2.onClick(MainActivity.java:37)
        at android.view.View.performClick(View.java:6256)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992)
        at android.view.View$PerformClick.run(View.java:24697)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)

3 在 aidl 方法中如果想要操作 UI 需要自己处理线程切换。否则报错如下:

    java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:390)
        at android.widget.Toast.<init>(Toast.java:114)
        at android.widget.Toast.makeText(Toast.java:277)
        at android.widget.Toast.makeText(Toast.java:267)
        at com.thunder.ktvaidlclient.MainActivity$3$1.onPauseFailed(MainActivity.java:67)
        at com.thunder.ktvaidlservice.IConrtollerStatusListener$Stub.onTransact(IConrtollerStatusListener.java:77)
        at android.os.Binder.execTransact(Binder.java:674)

Other

服务其实还有很多种,我们上文介绍到的是跨进程的 bind 服务,其实还有普通服务、以及前台服务,他们也都有各种的用途和场景。详见 Google 文档服务概览
参考资料
developer.android.google.cn/guide/topic…
www.jianshu.com/p/d1fac6cce…
github.com/FelixLee052…
www.bilibili.com/video/BV1oD…
www.bilibili.com/video/BV185…

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

以上是关于Android AIDL 跨进程通信超详版的主要内容,如果未能解决你的问题,请参考以下文章

Android跨进程通信Binder机制与AIDL实例

Android跨进程通信Binder机制与AIDL实例

Android跨进程通信——AIDL原理解析

Android跨进程通信AIDL服务

Android 跨进程通信-AIDL中的代理模式之源码分析

Android 跨进程通信Aidl的使用及注意事项