AIDL的理解

    xiaoxiao2023-03-24  4

    文章参考: http://www.open-open.com/lib/view/open1469493649028.html

    因为今天写的是关于AIDL的一些收获,AIDL作为Android接口定义语言,其实最终的目的是为了跨进程通讯,其中需要一些Service的基础.所以我们也趁机来学一下.

    Service

    Service对于很多入门级文章都有写到,我之前在四大组件也写了大概的一些.这次我打算就比较结构性地介绍一下,一般我对它的认知是从哪几个方面进行的.

    Service的创建

    1) 创建一个类继承Service,当然子类也可以例如IntentService.重写方法:

    onCreate(): 在每个service的生命周期中这个方法会且仅会调用一次,并且它的调用在onStartCommand()以及onBind()之前,我们可以在这个方法中进行一些一次性的初始化工作。

    onStartCommand(): 其他组件通过startService()启动service,就会调用这个方法,service的主要操作就在这里.

    onBind(): 其他组件通过bindService()进行绑定Service,就会调用这个方法.这个方法返回一个IBinder,这意味重写的时候必须返回一个IBinder对象,这个IBinder是用来支撑其他组件和Service通信.假如不希望被绑定,返回一个Null就可以了.

    onDestory(): 这是service一生中调用的最后一个方法,当这个方法被调用之后,service就会被销毁。所以我们应当在这个方法里面进行一些资源的清理,比如注册的一些监听器什么的。

    2)注册

    一般我们用名字在Mainfest文件进行声明就好了. 属性介绍:

    android:enabled : 如果为true,则这个service可以被系统实例化,如果为false,则不行。默认为true

    android:exported : 如果为true,则其他应用的组件也可以调用这个service并且可以与它进行互动,如果为false,则只有与service同一个应用或者相同user ID的应用可以开启或绑定此service。它的默认值取决于service是否有intent filters。如果一个filter都没有,就意味着只有指定了service的准确的类名才能调用,也就是说这个service只能应用内部使用——其他的应用不知道它的类名。这种情况下exported的默认值就为false。反之,只要有了一个filter,就意味着service是考虑到外界使用的情况的,这时exported的默认值就为true

    android:icon : 一个象征着这个service的icon

    android:isolatedProcess : 如果设置为true,这个service将运行在一个从系统中其他部分分离出来的特殊进程中,我们只能通过Service API来与它进行交流。默认为false。

    android:label : 显示给用户的这个service的名字。如果不设置,将会默认使用的label属性。

    android:name : 这个service的路径名,例如“com.lypeer.demo.MyService”。这个属性是唯一一个必须填的属性。

    android:permission : 其他组件必须具有所填的权限才能启动这个service。

    android:process : service运行的进程的name。默认启动的service是运行在主进程中的。

    启动Service

    startService

    图示:

    细节注意: 1) onStartCommand返回值

    START_NOT_STICKY : 如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 START_STICKY : 如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 START_REDELIVER_INTENT : 如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

    2)注意: 一定一定要销毁,因为service是与可见组件绑定,其几乎永远不会被停止或销毁.

    bindService

    首先我们来说明一下,就是说这个bindService是一种比startService复杂的方式,不过这个BindService的交互比startService更加复杂.

    客户端配置

    调用bindService()方法.

    public boolean bindService(Intent service, ServiceConnection conn, int flags) { return mBase.bindService(service, conn, flags); }

    参数分析:

    参数一:intent,指定启动哪一个service以及传递一些数据过去

    参数二:ServiceConnection 通信的关键类 重写两个回调方法: onServiceConnected(): 目的获取返回的IBinder接口,类似一个代理类的作用,然后通过这个代理类去获取Service,然后调用其内部的方法.

    onServiceDisconnected() Android系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统“绝对不会”调用该方法。

    参数三:int值,它是一个指示绑定选项的标志,通常应该是 BIND_AUTO_CREATE,以便创建尚未激活的服务。 其他可能的值为 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示无)。

    服务端配置

    根据上面绑定的Service需求,需要重写OnBind(),返回一个IBinder接口对象,用于交互.

    获取IBinder接口

    IBinder接口的,一个在整个Android系统中都非常重要的东西,是为高性能而设计的轻量级远程调用机制的核心部分.它不仅仅是内部调用,也可以远程调用. 我们只要记住一个重点:Binder类对象,客户端可以通过这个类似代理类的方式调用服务端的共有方法.

    1)继承Binder类(同一个进程间的) 1. service类中创建一个Binder实例(重点在于公共方法) 1) 包含客户端可调用的公共方法 2) 返回当前Service实例,其中有公共方法 3) 由当前service承载的其他类的实例,其中包含客户端可调用的公共方法 方式一:

    OnBind()方法返回这个Binder实例.

    客户端通过onServiceDisconnected(),接受这个对象,使用这个方法.

    2)使用Messenger Messenger核心是Message以及Handler进行线程间通信. 步骤:

    服务端实现一个Handler,接受客户端的调用的回调. 服务端创建Messenger对象通过Messenger得到IBinder对象,返回给客户端.客户端使用IBinder将Messenger实例化,然后将Message对象发给服务.服务端Handler接受Message对象,然后实现对应方法.

    这种是利用了Handler和Message的机制.

    //服务端 public class MessengerServiceDemo extends Service { static final int MSG_SAY_HELLO = 1; class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SAY_HELLO: //当收到客户端的message时,显示hello Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } final Messenger mMessenger = new Messenger(new ServiceHandler()); @Nullable @Override public IBinder onBind(Intent intent) { Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); //返回给客户端一个IBinder实例 return mMessenger.getBinder(); } }

    这里最主要的方法是我们需要在OnBind上面下功夫,要知道Messenger是可以关联Handler,并且可以返回IBinder的.

    当然,这里因为是跨进程通讯的,所以要让别的应用知道怎么找到我们Service.就是在注册文件上做文章.

    <service android:name=".ActivityMessenger" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.lypeer.messenger"></action> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </service>

    客户端:

    public class ActivityMessenger extends Activity { static final int MSG_SAY_HELLO = 1; Messenger mService = null; boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { //接收onBind()传回来的IBinder,并用它构造Messenger mService = new Messenger(service); mBound = true; } public void onServiceDisconnected(ComponentName className) { mService = null; mBound = false; } }; //调用此方法时会发送信息给服务端 public void sayHello(View v) { if (!mBound) return; //发送一条信息给服务端 Message msg = Message.obtain(null, MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onStart() { super.onStart(); //绑定服务端的服务,此处的action是service在Manifests文件里面声明的 Intent intent = new Intent(); intent.setAction("com.lypeer.messenger"); //不要忘记了包名,不写会报错 intent.setPackage("com.lypeer.ipcserver"); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } }

    细节: 1) 首先我们需要注意在OnCreate()上面,对Intent做文章,要设置包名,毕竟是跨进程. 2) onServiceConnected()方法获取服务端返回的IBinder,构造Messenger,然后通过Messenger发送Message进行交互. 3)Messenger是一个信使.

    AIDL和Messenger区别

    这么说,Messenger的内部实现还是AIDL. Messnger好处:它会把所有的请求排入队列,因此你几乎可以不用担心多线程可能会带来的问题。但是只能实现串行的信息交互. AIDL:可以令项目中存在大量的并发交互.

    IntentService

    默认情况下service将工作于应用的主线程,而这将会降低所有正在运行的Activity的性能。而IntentService就不同了. 不需要担心多线程问题,会自动请求停止服务,但是不绑定,会有一个工作队列.

    特色:

    创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样的话就永远不必担心多线程问题了 在处理完所有启动请求后停止服务,从此妈妈再也不用担心我忘记调用 stopSelf() 了 提供 onBind() 的默认实现(返回 null) 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现 因此我们只需要实现onHandleIntent()方法来完成具体的功能逻辑就可以了。

    例子:

    public class IntentServiceDemo extends IntentService { public IntentServiceDemo(String name) { super(name); //构造方法 } @Override protected void onHandleIntent(Intent intent) { //在这里根据intent进行操作 } } 接受Intent,会根据Intent进行操作,但是主要要是重写其他方法,就不要删除超类实现,而且不能处理多个请求.

    AIDL(面试大户)

    这个语言的目的是为了实现进程间通信. 在一个进程访问另外一个进程的数据,还有调用特定的方法.

    特点:

    文件类型: .aidl文件 数据类型: AIDL默认支持一些数据类型.非默认数据使用前必须导包.使用对象也需要导包. 默认: Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。 String 类型。 CharSequence类型。 List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。 Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。 定向Tag:除非是基本类型和String ,CharSequence.这些是默认的in in:客户端流向服务端 out:服务端流向客户端 inout:双向流动 大约思路: 客户端传递对象信息给客户端,服务端改动,客户端就会改动. AIDL类型: 1) 定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。 2) 定义方法接口,以供系统使用来完成跨进程通信的.定义接口,里面都是方法. 注意AIDL我们在乎的是定义并非实现.

    AIDL的实现:

    1) 实现Parcelable接口,目的是为了我们传送的数据能够在内存之间流通.过程可以说是序列化和反序列化. 客户端–>序列化对象–>服务端—>反序列化对象

    如何快速生成一个可序列化类?目的是为了我们的操作对象能够传. 注意:假如是默认就不需要这样了.

    编译器生成: 1. Bean类(成员变量,set,get); 2. 实现Parcelable接口,主动解决错误.(alt+enter) 3. 注意,因为默认是in定向tag,所以只有写出的这么一个方法,假如需要其他就添加.

    @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(price); } /** * 参数是一个Parcel,用它来存储与传输数据 * @param dest */ public void readFromParcel(Parcel dest) { //注意,此处的读值顺序应当是和writeToParcel()方法中一致的 name = dest.readString(); price = dest.readInt(); }

    2)书写AIDL Book.aild文件引入后,就能让其他类使用这么一个对象.

    AS: AIDL:直接生成,写好接口. 但是AS的文件路径需要我们处理一下,因为AS上面有分成Aidl包.这时候aidl文件就不能跟自己的.java文件对应了.(因为我们要求文件的报名要一样.) 因为我们的工具是自动到java下找java文件,我们要将java文件放到aidl包的的方式.

    修改 build.gradle 文件:在 android{} 中间加上下面的内容:

    sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }

    但是目录很难看.

    3)移植文件 我们必须保证客户端服务端都有对应的.aidl和.java文件. 直接复制aidl包就可以了.

    4)编写服务端代码 文件移植之后,假如我们clean,编译器就会根据AIDL文件生成.java文件.在服务端实现具体的方法,然后客户端调用方法接口.

    AIDL文件的结构: 1. 初始化 2. 重写Stub类中的方法,这其中有AIDL的方法接口的具体逻辑,可以说是引入AIDL文件生成的对象. 3. OnBind(),返回Stub代理类

    5)编写客户端代码. 1. 初始化AIDL生成的java类对象 2. 通过Intent进行bindService绑定. 3. Conn就利用Stub作为对象类.调用其中的方法.

    AIDL本质工作:

    1) AIDL目的是为了让编译器帮我们生成一个.java文件 2) 生成的.java文件会有一个类似Stub的代理类,服务端重写了之前AIDL中定义的方法,就是这个Stub中的方法,并且通过OnBind()返回. 3) 客户端捕获这个对象然后调用.

    我们的分析从应用开始: 1. 使用的是.java文件,但是它是一个接口类.实际我们调用的是它的一个实现类.获取的方法是通过BookManager.Stub.asInterface(BinderProxy 对象)方法获取. 2. BinderProxy是实现了BookManager接口的一个类,也就是我们上文使用的这么一个类.它是经过搜查得到的.也是我们最终需要进行交互的一个类.

    而Proxy的方法工作流程: 1,生成 _data 和 _reply 数据流,并向 _data 中存入客户端的数据。 2,通过 transact() 方法将它们传递给服务端,并请求服务端调用指定方法。 3,接收 _reply 数据流,并从中取出服务端传回来的数据。

    客户端–>存入_data–>transact()–>传给服务器–>reply()–>服务器返回的数据.

    服务端分析

    .java文件中有个方法onTransact(),负责接收传递过来的信息. 工作流程: 1,获取客户端传过来的数据,根据方法 ID 执行相应操作。 2,将传过来的数据取出来,调用本地写好的对应方法。 3,将需要回传的数据写入 reply 流,传回客户端。

    总结: 客户端<–>代理<–>服务端 其中我们的过程就是一个铜通过代理进行交互的过程.

    转载请注明原文地址: https://ju.6miu.com/read-1202218.html
    最新回复(0)