Android 进程通讯之AIDL

        之前写过一篇进程通讯的文章 Android 进程通讯之Messenger

        其实之前也说到,Messenger的通讯,是基于在AIDL的上层封装。Messenger传递数据通过Bundle封装,所以Messenger通讯的数据类型只支持Bundle所提供的基础数据类型,比如:String,Int,Double等。如果想传递自定义的数据类型,必须通过AIDL。还有Messenger是以串行的方式处理客户端发来信息,如果有大量的并发请求,Messenger的效率就很低的不适合了。

        AIDL 全名是:Android Interface Define Language ,安卓接口定义语言。

        AIDL 分为两个模块,一个Client ,一个 Service

        Client  启动目标服务获取 Service 代理,通过代理进行通讯

===================================================

                                         只有 Client 端

===================================================

我们暂且不管服务端如何实现,就举个场景,服务端已经封装好了接口,客户端只需启动调用即可以。我就拿《商米钱箱》 的作为一个例子来讲。商米官网说需要打开他们的钱箱,需要通过AIDL 通讯,他们服务端的实现我们不知道,我们也不需要知道,他们只提供了接口出来。ICallback.aidl IWoyouService.aidl

他们提供的接口是这样:

package woyou.aidlservice.jiuiv5;

import woyou.aidlservice.jiuiv5.ICallback;


interface IWoyouService
{	

	/**
	* 打开钱柜
	*/
	void openDrawer(in ICallback callback);
	
	/**
	* 取钱柜累计打开次数
	*/		
	int getOpenDrawerTimes();

}
/**
 * 打印服务执行结果的回调
 */
interface ICallback {

	/**
	* 返回执行结果
	* @param isSuccess:	  true执行成功,false 执行失败
	*/
	oneway void onRunResult(boolean isSuccess, int code, String msg);
	
}

拿到接口,就照着他们的包名,新建AIDl 文件,首先,在和 Java 同级目录包,他们提供的接口包名是woyou.aidlservice.jiuiv5 ,那我们必须新建一个同名的包名目录

我们就在 woyou.aidlservice.jiuiv5 目录下  新建 ICallback IWoyouService 我 aidl 文件

新建好了,Studio 出现 aidl 目录,有对应我们刚新建的 aidl文件,新建的 IWoyouService.aidl 会有默认的接口。我们删除掉,写上服务器提供的接口

同理,我们再新建 ICallback.aidl

好了,新建的都完成了。现在就是创建通讯

package com.example.ipcdemo

import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import woyou.aidlservice.jiuiv5.ICallback
import woyou.aidlservice.jiuiv5.IWoyouService

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.tvStartServer).setOnClickListener {
            // 启动服务
            startMyServer()
        }
        findViewById<View>(R.id.tvAction).setOnClickListener {
            // 调用接口
            woyouService?.openDrawer(callback)
        }
    }

    var callback: ICallback = object : ICallback.Stub() {
        override fun onRunResult(isSuccess: Boolean, code: Int, msg: String?) {
            // 执行回调
        }
    }
    private var woyouService: IWoyouService? = null

    // 继承 ServiceConnection
    private val serviceConnection = object : ServiceConnection {

        override fun onServiceDisconnected(name: ComponentName?) {// 服务断开
            woyouService = null
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            // 服务连接成功 通过 Stub.asInterface 获取服务
            woyouService = IWoyouService.Stub.asInterface(service)
        }
    }

    // 启动绑定服务
    private fun startMyServer() {
        val intent = Intent()
        // 包名 必须是我们新建aidl 的包名,因为这个包名也是服务对应的包名
        intent.setPackage("woyou.aidlservice.jiuiv5")
        // 对应服务
        intent.action = "woyou.aidlservice.jiuiv5.IWoyouService"
        // 绑定服务
        applicationContext.bindService(intent, serviceConnection, 1)
    }
}

OK 这样就完成客户端的进程通讯了,可以直接调用别的进程的功能。

整个流程是:

1.客户端启动绑定目标服,这里的

包名为:woyou.aidlservice.jiuiv5

Action 为 :woyou.aidlservice.jiuiv5.IWoyouService

2.绑定成功后,通过ServiceConnect 回调一个 Binder对象,获得该Binder对象,转换成自己aidl接口实例。

3.调用实例方法,进行通讯。

===================================================

                                         Client 端  + Service 端

===================================================

但是,上面仅仅是客户端的功能,那么Client + Service 的怎么操作呢?

Client 已经介绍了一番了,先上一波Service 怎么操作

为了更方面明显的显示是进程通讯,我把Client 和 Service 分别单独写作一个App 里面,

========= Service ============

OK  我们先看一下Service 端怎么实现,其实就是三个文件需要,

1:aidl 接口

2:自定义Service

3:AndroidManifest.xml

aidl 接口

对应代码,这里定义三个接口,

// IMyAidlInterface.aidl
package com.example.ipcservice;

// Declare any non-default types here with import statements

interface IMyAidlInterface {

    void sayHi(String content);

    String getName();

    int getAge();
}

至于怎么新建aidl文件,上面已经说过了

自定义Service

对应代码,重写 onBind() ,当服务被启动绑定时,会回调此方法,把定义的Binder实例返回给调用者即可。

    问题:为什么aidl接口实例就是一个binder对象呢?

    回答

             其实aidl新建完了之后,编译文件在:build\generated目录下,点进去可以看到,编译后的aidl文件继承于binder的,所以返回去的就是一个Binder对象。其实进程通讯就是依赖于这个Binder,因为这个知识点不属本文的知识点,所以笔者就不多说了,有兴趣的同学可以自己去研究

好了,下面就是自定义的Service

package com.example.ipcservice

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class MyService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d("ServiceLog", "onCreate")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("ServiceLog", "onDestroy")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("ServiceLog", "onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(intent: Intent?): IBinder {
        Log.d("ServiceLog", "onBind")
        return MyBinder()
    }

    class MyBinder : IMyAidlInterface.Stub() {
        override fun getName() = "My Name is Leonardo Dicaprio"

        override fun getAge() = 40

        override fun sayHi(content: String?) {
            Log.d("ServiceLog", "say Hi : $content")
        }

    }

}

AndroidManifest.xml   

提一下,自定义Action 要配置好

Ok,Service 已经配置好

========= Client ============

那么到了 Client 端了,其实就是最上面那样,

1:创建 aidl 文件

2:自定义 connectService 获得 服务端代理

3:调用接口 进行通讯

创建 aidl 文件

自定义 connectService 获得 服务端代理

package com.example.ipcclient

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.ipcservice.IMyAidlInterface

class MainActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "LogClient"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.tvStartService).setOnClickListener {
            Log.d(TAG, "StartService click")
            startMyServer()
        }
        findViewById<View>(R.id.tvSayHi).setOnClickListener {
            Log.d(TAG, "SayHi click")
            mIMyAidlInterface?.sayHi("Hello my name is Pitt")
        }
        findViewById<View>(R.id.tvGetName).setOnClickListener {
            Log.d(TAG, "GetName click")
            Log.d(TAG, "GetName ${mIMyAidlInterface?.name ?: ""}")

        }
        findViewById<View>(R.id.tvGetAge).setOnClickListener {
            Log.d(TAG, "GetAge click")
            Log.d(TAG, "GetName ${mIMyAidlInterface?.age ?: ""}")
        }
    }

    private var mIMyAidlInterface: IMyAidlInterface? = null

    // 继承 ServiceConnection
    private val serviceConnection = object : ServiceConnection {

        override fun onServiceDisconnected(name: ComponentName?) {// 服务断开
            Log.d(TAG, "onServiceDisconnected")
            mIMyAidlInterface = null
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d(TAG, "onServiceConnected")
            // 服务连接成功 通过 Stub.asInterface 获取服务
            mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service)
        }
    }

    // 启动绑定服务
    private fun startMyServer() {
        val intent = Intent()
        // 包名 必须是我们新建aidl 的包名,因为这个包名也是服务对应的包名
        intent.setPackage("com.example.ipcservice")
        // 对应服务
        intent.action = "com.example.ipcservice.MyService"
        // 绑定服务
        applicationContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

这个我是写在 Activity 里面

看下运行结果

Client 端:

Service端:

OK ,运行结果没问题

===================================================

                                         Client 端  + Service 端   自定义数据类型

===================================================

上面只是显示了基础数据类型,现在就加上自定义数据类型

新建前,说点题外话,其实Client端都不需要新建任何文件,涉及到通讯的类,都在Service端  copy 过去就可以。这样可以避免在新建时写错或泄露。但必须保证包名一致。

好了,先在Service端新建一个自定义People类,这个必须是实现 Parcelable接口的

类代码是:

package com.example.ipcservice

import android.os.Parcel
import android.os.Parcelable

/**
 * Time:2021/6/17 11:33
 * Author: Leonardo Dicaprio
 * Description:
 */
class People() : Parcelable {

    var age: Int = 0
    var name: String = ""

    constructor(parcel: Parcel) : this() {
        age = parcel.readInt()
        name = parcel.readString().toString()
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(age)
        parcel.writeString(name)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<People> {
        override fun createFromParcel(parcel: Parcel): People {
            return People(parcel)
        }

        override fun newArray(size: Int): Array<People?> {
            return arrayOfNulls(size)
        }
    }

    override fun toString(): String {
        return "芳名: $name  芳龄: $age"
    }
}

然后在AIDL 目录新建 People.aidl

代码是:

// People.aidl
package com.example.ipcservice;
import com.example.ipcservice.People;
parcelable People;

然后在接口里,新增一个获取People的接口。

我们得Service返回一个People

OK,这样Service端就完成

现在到了Client端更新一下接口,可以把Service端 aidl 目录,整个目录Copy过来

然后把 Service 的 People类也Copy 过来,如下:

调用一下接口,测试一下:

OK 测试通过

同理,也可以加个List 测试下

Service端

客户端直接Copy服务端的 IMyAidlInterface.aidl 即可

测试通过

Demo :

https://github.com/LeoLiang23/IpcClient.git

https://github.com/LeoLiang23/IpcService.git

猜你喜欢

转载自blog.csdn.net/Leo_Liang_jie/article/details/117957917