首页 > 系统相关 >Android AIDL 跨进程通信超详版

Android AIDL 跨进程通信超详版

时间:2023-06-22 14:07:25浏览次数:37  
标签:java AIDL void 超详 public Override Android com android


来了新公司,公司项目里用了很多的独立进程的服务与他们之间存在了很多跨进程的通信。之前有很长一段时间没有实际去做跨进程通信 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" />

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

Android AIDL 跨进程通信超详版_客户端

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

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

Android AIDL 跨进程通信超详版_客户端_02

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

Android AIDL 跨进程通信超详版_客户端_03

很简单就一个暂停和播放

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 目录下生成新的产物,我们查看一下:

Android AIDL 跨进程通信超详版_客户端_04

我们注意到产物已经生成,并且还是一个 .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 文件拷贝到客户端,注意包名变化。

Android AIDL 跨进程通信超详版_服务端_05

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

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

}

Android AIDL 跨进程通信超详版_ide_06

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

Android AIDL 跨进程通信超详版_服务端_07


Android AIDL 跨进程通信超详版_客户端_08

启动日志:

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

Android AIDL 跨进程通信超详版_ide_09

历经了一些小的挫折之后,成功的把客户端的数据通过 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 效果:

Android AIDL 跨进程通信超详版_ide_10


Android AIDL 跨进程通信超详版_android_11

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

in out inout

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

Android AIDL 跨进程通信超详版_服务端_12

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

Android AIDL 跨进程通信超详版_ide_13

out 直接报错

Android AIDL 跨进程通信超详版_ide_14

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…

标签:java,AIDL,void,超详,public,Override,Android,com,android
From: https://blog.51cto.com/u_16163453/6534753

相关文章

  • Android13(T) 的Target适配问题总结
    最近在做Android13(T)的Target适配,整理了适配过程中遇到的问题分以下三部分:影响所有应用的变更(包含target33),只影响TargetSdkVersion=33的变更,其他更改(新增或者改善的功能).1.影响所有应用的变更1.1必须要适配此项1.1.1通知的运行时权限Android13中引入了一种新的......
  • Android app的启动优化总结
    工欲善其事必先利其器,最近在启动优化上踩了不少坑,写篇文章记录下,也给大伙避避坑,节省些时间。启动优化是什么,完全可以顾名思义,本文就不赘述了。至于为什么要做性能优化–QAQ,大家dddd问题场景主要分为如下两种场景,笔者主要在第一种场景下进行实操哈1、项目中已有性能启动相关埋点以及......
  • 95后Android开发:“我现在是真想躺平...“
    我是真想躺平…说实话,我现在每天上班都很难受,我也不知道为啥反正就很丧,很想当一条咸鱼,就想躺着。最近疫情、裁员…坏消息很多,大环境不好,我本就打算今年换工作的,现在这环境就有点烦…其他行业可能不知道,程序员跳槽最佳的时间就是3-4月,或者9-10月,被称为金三银四和金九银十,但是今年这......
  • 【工程化】Android开发电脑中都装了哪些软件
    写在前面工欲善其事,必先利其器。作为一名Android开发者,在开始正式开发之前,给电脑安装各种开发相关软件是必不可少的。今天来罗列下我电脑中装的那些开发相关的软件,一来换新电脑时,可以方便根据应用清单安装软件,二来如果你是刚从事Android开发,也可以参考着安装这些软件,希望可以帮助到......
  • Framework有多重要?Android framework 深层解读
    前言时间已经到了六月份了,这段时间整理了一下自打当公司Android面试官以来的奇葩事情,这才发现这奇葩事可真是多,跟另外一个HR朋友聊天,他说前段时间面一个Android高级架构,最后和他差点干起来了…我问他为什么,面个试还这么惊险刺激,差点挨上一顿打,真被打了那算工伤,公司指定得给你报销药......
  • AndroidUI进阶-为什么不能在子线程更新UI
    为什么不能在子线程更新UIandroid.view.ViewRootImpl$CalledFromWrongThreadException:Onlytheoriginalthreadthatcreatedaviewhierarchycantouchitsviews.atandroid.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)atandroid.view.ViewR......
  • 这应该是堪称教科书级别的“Android Framework学习笔记”了,字节九位大佬联合打造,首次
    相信大家在找工作的时候,肯定或多或少都被面试官问到过安卓的八股文。ActivityManagerService(简称AMS),或者WindowManagerService(WMS)怎么实现的啊,有些什么细节需要注意啊,View被加入到ViewRoot的流程啊等等。在我看来,对于应用开发来说,面试考这些纯粹就是扯淡,很有可能面试官自己也......
  • 为什么90%的Android开发都成不了年薪百万的架构师?
    身为技术人,相信你也思考过这个问题:工作了几年,代码写得非常熟练,上线的程序也少有bug,时不时还能搞个技术分享,但接下来要往哪个方向发展呢?想来无非是3种选择:专精技术、转型管理、晋升架构师。包括我自己在内的很多朋友,都选择了第三种,或正朝这个方向努力。但我发现,有些人做了7、8......
  • Android NDK 开发基础:C 语言的内存管理
    简介C语言的内存管理,分成两部分。一部分是系统管理的,另一部分是用户手动管理的。系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。这些变量存放的区域称为”栈“(stack),”栈“所在的内存是系统自动管理的。用户手动管理......
  • 对未来感到迷茫?Android资深架构师教你如何打破这个局面!
    随着“5G”(第五代移动通信技术)商用进程越来越快,各个芯片和终端厂商们都已经开始布局准备,想必智能手机会是消费者最先能够接触到5G的重要终端,而和其相辅相生的移动互联网也势必会有新的发展。但是和行业本身的发展不相称的是,Android开发者的市场需求仿佛处于寒冬。最直观的现象就......