前面我们将了本地service的基本用法,今天来介绍的就是远程服务,就是Service端和Client端分别在不同的进程。
这里就不得不提到AIDL了。
什么是AIDL:
大家可以看一下官方文档的定义,简单来说AIDL就是Android系统提供的一套帮助开发者快速实现binder的工具。而什么是binder呢?binder是Android系统实现进程间通讯的机制。这个再再再后面讲……
使用AIDL:
既然是C/S模型那么为了方便演示,这里我们就直接创建2个mode,一个aidlService(服务端),一个aidlClient(客户端)。
aidlService端:
创建aidl文件:直接在项目main目录右键-new-aidl,然后输入文件名称即可。
系统会默认新建一个和Java目录平级的aidl目录,下面是和我们报名相同的路径,然后是我们创建的aidl文件。
先来编写接口:
[code]package com.example.aidlService;import com.example.aidlService.bean.BookBean;interface IMyAidlInterface {String getString();BookBean getBook();}
可以看到创建的aidl文件是一个接口,我们定义了2个方法,一个getString返回一个String,getBook返回我们自定义的实体类。这里讲一下aidl支持的数据类型:
除了基本数据类型外还有String、CharSequence、List、map、实现Parcelable的类。其中Parcelable接口类需要自己写import导入,就像上面的import com.example.aidlService.bean.BookBean;类,就是为了导入BookBean。
Parcelable接口是Android提供的可序列化的接口,简单看一下BookBean的实现:
[code]class BookBean() : Parcelable {var name = \"\"var price = 0.0fconstructor(name: String, price: Float) : this() {this.name = namethis.price = price}constructor(parcel: Parcel) : this() {name = parcel.readString().toString()price = parcel.readFloat()}override fun writeToParcel(dest: Parcel?, flags: Int) {dest?.let {it.writeString(name)it.writeFloat(price)}}override fun describeContents(): Int = 0override fun toString(): String {return \"BookBean(name=\'$name\', price=$price)\"}companion object CREATOR : Parcelable.Creator<BookBean> {override fun createFromParcel(parcel: Parcel): BookBean {return BookBean(parcel)}override fun newArray(size: Int): Array<BookBean?> {return arrayOf()}}}
Parcelable接口主要需要我们实现一个带Parcel参数的构造方法和writeToParcel方法,以及Creator接口,Creator接口主要是用来创建我们的BookBean实例的,在writeToParcel方法中我们需要将需要保存的属性通过Parcel的writexxx方法写入,然后在带有Parcel参数的构造方法中农使用readxxx读取出来。需要注意的是两边写入和读取的顺序要相同。
编写完aidl接口后我们需要让android studio同步一下代码,让它自动生成aidl文件对应的java文件。可以点击Build菜单的make mode来重新编译整个项目。
编译成功之后会在build目录下生成一个aidl同名的Java接口文件
里面的内容暂时先不用管,我们后面再说。先看怎么使用这个接口。
接下来我们就需要创建Service了
创建 一个AIDLService文件,这就是我们的Service,还记得上一章四大组件之Service(1)讲到的Service的启动方式和对应的生命周期函数吗?因为是要给客户端提供接口的,所以客户端必须使用bindService方法来启动我们的service,所以我们这里的Service只需要实现onBind并且返回Ibinder的接口实现类。
看看代码:
[code]class AIDLService : Service() {override fun onBind(intent: Intent): IBinder {return myAidlInterfaceBinder}private val myAidlInterfaceBinder = object : IMyAidlInterface.Stub() {override fun getBook(): BookBean {Log.d(\"ZLog AIDLService\", \"getBook: \")return BookBean(\"android 从入门到放弃\", 0.0f)}override fun getString(): String {Log.d(\"ZLog AIDLService\", \"getString: \")return \"这是服务端返回的String\"}}}
重点是我们在onBind中返回的是什么?我们这里的myAidlInterfaceBinde是一个直接继承ImyAidlInterface.Stub的类。IMyAIDLInterface是我们自己创建的aidl接口,那Stub是哪来的呢?并且还包含了我们定义的2个接口方法getString和getBook。回到我们刚刚创建好ImyAidlInterface.aidl后重新编译了项目生成的ImyAidlInterface.java接口,这个时候再来看看它里面的内容:
[code]public interface IMyAidlInterface extends android.os.IInterface {/*** Default implementation for IMyAidlInterface.*/public static class Default implements com.example.aidlService.IMyAidlInterface {@Overridepublic java.lang.String getString() throws android.os.RemoteException {return null;}@Overridepublic com.example.aidlService.bean.BookBean getBook() throws android.os.RemoteException {return null;}@Overridepublic android.os.IBinder asBinder() {return null;}}/*** Local-side IPC implementation stub class.*/public static abstract class Stub extends android.os.Binder implements com.example.aidlService.IMyAidlInterface {private static final java.lang.String DESCRIPTOR = \"com.example.aidlService.IMyAidlInterface\";/*** Construct the stub at attach it to the interface.*/public Stub() {this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.aidlService.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.example.aidlService.IMyAidlInterface asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin != null) && (iin instanceof com.example.aidlService.IMyAidlInterface))) {return ((com.example.aidlService.IMyAidlInterface) iin);}return new com.example.aidlService.IMyAidlInterface.Stub.Proxy(obj);}@Overridepublic android.os.IBinder asBinder() {return this;}@Overridepublic 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_getString: {data.enforceInterface(descriptor);java.lang.String _result = this.getString();reply.writeNoException();reply.writeString(_result);return true;}case TRANSACTION_getBook: {data.enforceInterface(descriptor);com.example.aidlService.bean.BookBean _result = this.getBook();reply.writeNoException();if ((_result != null)) {reply.writeInt(1);_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);} else {reply.writeInt(0);}return true;}default: {return super.onTransact(code, data, reply, flags);}}}private static class Proxy implements com.example.aidlService.IMyAidlInterface {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic java.lang.String getString() throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);boolean _status = mRemote.transact(Stub.TRANSACTION_getString, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().getString();}_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}@Overridepublic com.example.aidlService.bean.BookBean getBook() throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();com.example.aidlService.bean.BookBean _result;try {_data.writeInterfaceToken(DESCRIPTOR);boolean _status = mRemote.transact(Stub.TRANSACTION_getBook, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().getBook();}_reply.readException();if ((0 != _reply.readInt())) {_result = com.example.aidlService.bean.BookBean.CREATOR.createFromParcel(_reply);} else {_result = null;}} finally {_reply.recycle();_data.recycle();}return _result;}public static com.example.aidlService.IMyAidlInterface sDefaultImpl;}static final int TRANSACTION_getString = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_getBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);public static boolean setDefaultImpl(com.example.aidlService.IMyAidlInterface impl) {if (Stub.Proxy.sDefaultImpl == null && impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.example.aidlService.IMyAidlInterface getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}public java.lang.String getString() throws android.os.RemoteException;public com.example.aidlService.bean.BookBean getBook() throws android.os.RemoteException;}
内容很多,我们直接看里面的Stub类,它继承了我们上一章说的Binder类,并且实现了我们定义的ImyAidlInterface接口,所以我们可以在onBind中返回它。
其中有一个特殊的方法 asInterface()它接收一个Ibinder参数,返回的是我们定义的接口。看到这里大概有的朋友也知道了,这个方法就是客户端拿到我们定义的接口的方法。
好了,我们接着往下,接着我们需要配置一下service,在manifest.xml中我们给service增加一个action
[code]<serviceandroid:name=\"com.example.aidlService.AIDLService\"android:enabled=\"true\"android:process=\":aidlService\"android:exported=\"true\"><intent-filter><action android:name=\"com.example.aidlService.Service\" /></intent-filter></service>
我们添加了一个com.example.aidlService.Service的action,并且配置了service 的process属性,表示我们这个service是以一个独立的进程运行的。当然,这个配置不是必须的。
至此Service端的工作就做完了,接下来开始Client端。
首先要做的第一件事就是我们需要把service端所有的aidl都拷贝到client来,并且如果有传递了自定义的数据类也要拷贝过来,保证包名相同!保证包名相同!保证包名相同!
这样client端才能正常使用相关内容
然后就是在客户端启动service:
[code]private lateinit var aidlServiceIntent: Intentprivate lateinit var aidlInterface: IMyAidlInterfaceoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)aidlServiceIntent = Intent()aidlServiceIntent.action = \"com.example.aidlService.Service\"aidlServiceIntent.component =ComponentName(\"com.example.aidlService\", \"com.example.aidlService.AIDLService\")bindService(aidlServiceIntent, conn, Context.BIND_AUTO_CREATE)acMainBtGetData.setOnClickListener {if (this::aidlInterface.isInitialized) {acMainTvMsg.text = aidlInterface.book.toString()Log.d(\"ZLog MainActivity\", \"调用服务端getBook: ${aidlInterface.book}\")Log.d(\"ZLog MainActivity\", \"调用服务端getString: ${aidlInterface.book}\")} else {acMainTvMsg.text = \"绑定失败\"}}}private val conn = object : ServiceConnection {override fun onServiceDisconnected(name: ComponentName?) {}override fun onServiceConnected(name: ComponentName?, service: IBinder?) {Log.d(\"ZLog MainActivity\", \"onServiceConnected: $service\")aidlInterface = IMyAidlInterface.Stub.asInterface(service)}}
这里bindService的intent跟上一章将的不太一样,因为这里我们是没有Service的,只能通过action和包名来告诉系统我们要注册的service在哪。Android 5.0之后不允许隐式启动service,所以我们这里首先设置了intent的action,就是我们在服务端的manifest中添加的action。下面设置了ComponentName,第一个参数是服务端的包名,第二参数是服务端service的完整类名。这里有一个问题,因为这里的C、S都是我们自己写的,所以是知道包名这些参数的,如果是第三方或者其他人写的,而且只提供了action怎么办呢?推荐一个下面的方法来获取:
[code]private fun createExplicitFromImplicitIntent(implicitIntent: Intent): Intent? {val resolveInfo = packageManager.queryIntentServices(implicitIntent, 0)if (resolveInfo.size != 1) {return null}val serviceInfo = resolveInfo[0]val packageName = serviceInfo.serviceInfo.packageNameval className = serviceInfo.serviceInfo.nameval component = ComponentName(packageName, className)val explicitIntent = Intent(implicitIntent)explicitIntent.component = componentreturn explicitIntent}
这个方法的参数Intent只需要设置action即可,然后通过packageManager获取一个ResolveInfo列表,正常情况这个列表里面符合条件的就一项,然后通过ResolveInfo就可以拿到包名和类名了。
然后我们看绑定的ServiceConnection方法,在onServiceConnected方法中我们使用之前讲的IMyAidlInterface.Stub.asInterface方法就能获取IMyAIDLInterface接口了。
在button的点击事件中我们来调用接口的相关方法。
这里有一点要注意的是我们需要先启动service端的apk,在启动client端的apk。
好了,到这里aidl的基本用法就讲完了。