Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL
正文
對于進(jìn)程間通信,很多項(xiàng)目中可能根本沒有涉及到多進(jìn)程,很多公司的app可能就一個(gè)主進(jìn)程,但是對于進(jìn)程間通信,我們也是必須要了解的。
如果在Android中想要實(shí)現(xiàn)進(jìn)程間通信,有哪些方式呢?
(1)發(fā)廣播(sendBroadcast):e.g. 兩個(gè)app之間需要通信,那么可以通過發(fā)送廣播的形式進(jìn)行通信,如果只想單點(diǎn)通信,可以指定包名。但是這種方式存在的弊端在于發(fā)送方無法判斷接收方是否接收到了廣播,類似于UDP的通信形式,而且存在丟數(shù)據(jù)的形式;
(2)Socket通信:這種屬于Linux層面的進(jìn)程間通信了,除此之外,還包括管道、信號量等,像傳統(tǒng)的IPC進(jìn)程間通信需要數(shù)據(jù)二次拷貝,這種效率是最低的;
(3)AIDL通信:這種算是Android當(dāng)中主流的進(jìn)程間通信方案,通過Service + Binder的形式進(jìn)行通信,具備實(shí)時(shí)性而且能夠通過回調(diào)得知接收方是否收到數(shù)據(jù),弊端在于需要管理維護(hù)aidl接口,如果不同業(yè)務(wù)方需要使用不同的aidl接口,維護(hù)的成本會越來越高。
那么本篇文章并不是說完全丟棄掉AIDL,它依然不失為一個(gè)很好的進(jìn)程間通信的手段,只是我會封裝一個(gè)適用于任意業(yè)務(wù)場景的IPC進(jìn)程間通訊框架,這個(gè)也是我在自己的項(xiàng)目中使用到的,不需要維護(hù)很多的AIDL接口文件。
有需要源碼的伙伴,可以去我的github首頁獲取 FastIPC源碼地址,分支:feature/v0.0.1-snapshot,有幫助的話麻煩給點(diǎn)個(gè)star??????
1 服務(wù)端 - register
首先這里先說明一下,就是對于傳統(tǒng)的AIDL使用方式,這里就不再過多介紹了,這部分還是比較簡單的,有興趣的伙伴們可以去前面的文章中查看,本文將著重介紹框架層面的邏輯。
那么IPC進(jìn)程間通信,需要兩個(gè)端:客戶端和服務(wù)端。服務(wù)端會提供一個(gè)注冊方法,例如客戶端定義的一些服務(wù),通過向服務(wù)端注冊來做一個(gè)備份,當(dāng)客戶端調(diào)用服務(wù)端某個(gè)方法的時(shí)候來返回值。
object IPC { //========================================== /** * 服務(wù)端暴露的接口,用于注冊服務(wù)使用 */ fun register(service: Class<*>) { Registry.instance.register(service) } }
其實(shí)在注冊的時(shí)候,我們的目的肯定是能夠方便地拿到某個(gè)服務(wù),并且能夠調(diào)用這個(gè)服務(wù)提供的方法,拿到我想要的值;所以在定義服務(wù)的時(shí)候,需要注意以下兩點(diǎn):
(1)需要定義一個(gè)與當(dāng)前服務(wù)一一對應(yīng)的serviceId,通過serviceId來獲取服務(wù)的實(shí)例;
(2)每個(gè)服務(wù)當(dāng)中定義的方法同樣需要對應(yīng)起來,以便拿到服務(wù)對象之后,通過反射調(diào)用其中的方法。
所以在注冊的時(shí)候,需要從這兩點(diǎn)入手。
1.1 定義服務(wù)唯一標(biāo)識serviceId
@Target(AnnotationTarget.TYPE) @Retention(AnnotationRetention.RUNTIME) annotation class ServiceId( val name: String )
一般來說,如果涉及到反射,最常用的就是通過注解給Class做標(biāo)記,因?yàn)橥ㄟ^反射能夠拿到類上標(biāo)記的注解,就能夠拿到對應(yīng)的serviceId。
class Registry { //======================================= /**用于存儲 serviceId 對應(yīng)的服務(wù) class對象*/ private val serviceMaps: ConcurrentHashMap<String, Class<*>> by lazy { ConcurrentHashMap() } /**用于存儲 服務(wù)中全部的方法*/ private val methodsMap: ConcurrentHashMap<Class<*>, ConcurrentHashMap<String, Method>> by lazy { ConcurrentHashMap() } //======================================= /** * 服務(wù)端注冊方法 * @param service 服務(wù)class對象 */ fun register(service: Class<*>) { // 獲取serviceId與服務(wù)一一對應(yīng) val serviceIdAnnotation = service.getAnnotation(ServiceId::class.java) ?: throw IllegalArgumentException("只有標(biāo)記@ServiceId的服務(wù)才能夠被注冊") //獲取serviceId val name = serviceIdAnnotation.name serviceMaps[name] = service //temp array val methods: ConcurrentHashMap<String, Method> = ConcurrentHashMap() // 獲取服務(wù)當(dāng)中的全部方法 for (method in service.declaredMethods) { //這里需要注意,因?yàn)榉椒ㄖ写嬖谥剌d方法,所以不能把方法名當(dāng)做key,需要加上參數(shù) val buffer = StringBuffer() buffer.append(method.name).append("(") val params = method.parameterTypes if (params.size > 0) { buffer.append(params[0].name) } for (index in 1 until params.size) { buffer.append(",").append(params[index].name) } buffer.append(")") //保存 methods[buffer.toString()] = method } //存入方法表 methodsMap[service] = methods } companion object { val instance by lazy { Registry() } } }
通過上面的register方法,當(dāng)傳入定義的服務(wù)class對象的時(shí)候,首先獲取到服務(wù)上標(biāo)記的@ServiceId注解,注意這里如果要注冊必須標(biāo)記,否則直接拋異常;拿到serviceId之后,存入到serviceMaps中。
然后需要獲取服務(wù)中的全部方法,因?yàn)榭紤]到重載方法的存在,所以不能單單以方法名作為key,而是需要把參數(shù)也加上,因此這里做了一個(gè)邏輯就是將方法名與參數(shù)名組合一個(gè)key,存入到方法表中。
這樣注冊任務(wù)就完成了,其實(shí)還是比較簡單的,關(guān)鍵在于完成2個(gè)表:服務(wù)表和方法表的初始化以及數(shù)據(jù)存儲功能。
1.2 使用方式
@ServiceId("UserManagerService") interface IUserManager { fun getUserInfo(): User? fun setUserInfo(user: User) fun getUserId(): Int fun setUserId(id: Int) }
假設(shè)項(xiàng)目中有一個(gè)用戶信息管理的服務(wù),這個(gè)服務(wù)用于給所有的App提供用戶信息查詢。
@ServiceId("UserManagerService") class UserManager : IUserManager { private var user: User? = null private var userId: Int = 0 override fun getUserInfo(): User? { return user } override fun setUserInfo(user: User) { this.user = user } override fun getUserId(): Int { return userId } override fun setUserId(id: Int) { this.userId = id } }
用戶中心可以注冊這個(gè)服務(wù),并且調(diào)用setUserInfo方法保存用戶信息,那么其他App(客戶端)連接這個(gè)服務(wù)之后,就可以調(diào)用getUserInfo這個(gè)方法,獲取用戶信息,從而完成進(jìn)程間通信。
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: entrySet key class com.lay.learn.asm.binder.UserManager
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key setUserInfo(com.lay.learn.asm.binder.User)
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public void com.lay.learn.asm.binder.UserManager.setUserInfo(com.lay.learn.asm.binder.User)
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key getUserInfo()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public com.lay.learn.asm.binder.User com.lay.learn.asm.binder.UserManager.getUserInfo()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key getUserId()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public int com.lay.learn.asm.binder.UserManager.getUserId()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key setUserId(int)
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public void com.lay.learn.asm.binder.UserManager.setUserId(int)
我們看調(diào)用register方法之后,每個(gè)方法的key值都是跟參數(shù)綁定在一起,這樣服務(wù)端注冊就完成了。
2 客戶端與服務(wù)端的通信協(xié)議
對于客戶端的連接,其實(shí)就是綁定服務(wù),那么這里就會使用到AIDL通信,但是跟傳統(tǒng)的相比,我們是將AIDL封裝到框架層內(nèi)部,對于用戶來說是無感知的。
2.1 創(chuàng)建IPCService
這個(gè)服務(wù)就是用來完成進(jìn)程間通信的,客戶端需要與這個(gè)服務(wù)建立連接,通過服務(wù)端分發(fā)消息,或者接收客戶端發(fā)送來的消息。
abstract class IPCService : Service() { override fun onBind(intent: Intent?): IBinder? { return null } }
這里我定義了一個(gè)抽象的Service基類,為啥要這么做,前面我們提到過是因?yàn)檎麄€(gè)項(xiàng)目中不可能只有一個(gè)服務(wù),因?yàn)闃I(yè)務(wù)眾多,為了保證單一職責(zé),需要?jiǎng)澐植煌念愋?,所以在框架中會衍生多個(gè)實(shí)現(xiàn)類,不同業(yè)務(wù)方可以注冊這些服務(wù),當(dāng)然也可以自定義服務(wù)繼承IPCService。
class IPCService01 : IPCService() { }
在IPCService的onBind需要返回一個(gè)Binder對象,因此需要?jiǎng)?chuàng)建aidl文件。
2.2 定義通訊協(xié)議
像我們在請求接口的時(shí)候,通常也是向服務(wù)端發(fā)起一個(gè)請求(Request),然后得到服務(wù)端的一個(gè)響應(yīng)(Response),因此在IPC通信的的時(shí)候,也可以根據(jù)這種方式建立通信協(xié)議。
data class Request( val type: Int, val serviceId: String?, val methodName: String?, val params: Array<Parameters>? ) : Serializable, Parcelable { //===================================== /**請求類型*/ //獲取實(shí)例的對象 val GET_INSTANCE = "getInstance" //執(zhí)行方法 val INVOKE_METHOD = "invokeMethod" //======================================= constructor(parcel: Parcel) : this( parcel.readInt(), parcel.readString(), parcel.readString(), parcel.createTypedArray(Parameters.CREATOR) ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeInt(type) parcel.writeString(serviceId) parcel.writeString(methodName) } override fun describeContents(): Int { return 0 } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Request if (type != other.type) return false if (serviceId != other.serviceId) return false if (methodName != other.methodName) return false if (params != null) { if (other.params == null) return false if (!params.contentEquals(other.params)) return false } else if (other.params != null) return false return true } override fun hashCode(): Int { var result = type result = 31 * result + (serviceId?.hashCode() ?: 0) result = 31 * result + (methodName?.hashCode() ?: 0) result = 31 * result + (params?.contentHashCode() ?: 0) return result } companion object CREATOR : Parcelable.Creator<Request> { override fun createFromParcel(parcel: Parcel): Request { return Request(parcel) } override fun newArray(size: Int): Array<Request?> { return arrayOfNulls(size) } } }
對于客戶端來說,致力于發(fā)起請求,請求實(shí)體類Request參數(shù)介紹如下:
type表示請求的類型,包括兩種分別是:執(zhí)行靜態(tài)方法和執(zhí)行普通方法(考慮到反射傳參);
serviceId表示請求的服務(wù)id,要請求哪個(gè)服務(wù),便可以獲取到這個(gè)服務(wù)的實(shí)例對象,調(diào)用服務(wù)中提供的方法;
methodName表示要請求的方法名,也是在serviceId服務(wù)中定義的方法;
params表示請求的方法參數(shù)集合,我們在服務(wù)端注冊的時(shí)候,方法名 + 參數(shù)名 作為key,因此需要知道請求的方法參數(shù),以便獲取到Method對象。
data class Response( val value:String?, val result:Boolean ):Parcelable { @SuppressLint("NewApi") constructor(parcel: Parcel) : this( parcel.readString(), parcel.readBoolean() ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(value) parcel.writeByte(if (result) 1 else 0) } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<Response> { override fun createFromParcel(parcel: Parcel): Response { return Response(parcel) } override fun newArray(size: Int): Array<Response?> { return arrayOfNulls(size) } } }
對于服務(wù)端來說,在接收到請求之后,需要針對具體的請求返回相應(yīng)的結(jié)果,Response實(shí)體類參數(shù)介紹:
result表示請求成功或者失??;
value表示服務(wù)端返回的結(jié)果,是一個(gè)json字符串。
因此定義aidl接口文件如下,輸入一個(gè)請求之后,返回一個(gè)服務(wù)端的響應(yīng)。
interface IIPCServiceInterface { Response send(in Request request); }
這樣IPCService就可以將aidl生成的Stub類作為Binder對象返回。
abstract class IPCService : Service() { override fun onBind(intent: Intent?): IBinder? { return BINDERS } companion object BINDERS : IIPCServiceInterface.Stub() { override fun send(request: Request?): Response? { when(request?.type){ REQUEST.GET_INSTANCE.ordinal->{ } REQUEST.INVOKE_METHOD.ordinal->{ } } return null } } }
2.3 內(nèi)部通訊協(xié)議完善
當(dāng)客戶端發(fā)起請求,想要執(zhí)行某個(gè)方法的時(shí)候,首先服務(wù)端會先向Registery中查詢注冊的服務(wù),從而找到這個(gè)要執(zhí)行的方法,這個(gè)流程是在內(nèi)部完成。
override fun send(request: Request?): Response? { //獲取服務(wù)對象id val serviceId = request?.serviceId val methodName = request?.methodName val params = request?.params // 反序列化拿到具體的參數(shù)類型 val neededParams = parseParameters(params) val method = Registry.instance.findMethod(serviceId, methodName, neededParams) Log.e("TAG", "method $method") Log.e("TAG", "neededParams $neededParams") when (request?.type) { REQUEST_TYPE.GET_INSTANCE.ordinal -> { //==========執(zhí)行靜態(tài)方法 try { var instance: Any? = null instance = if (neededParams == null || neededParams.isEmpty()) { method?.invoke(null) } else { method?.invoke(null, neededParams) } if (instance == null) { return Response("instance == null", -101) } //存儲實(shí)例對象 Registry.instance.setServiceInstance(serviceId ?: "", instance) return Response(null, 200) } catch (e: Exception) { return Response("${e.message}", -102) } } REQUEST_TYPE.INVOKE_METHOD.ordinal -> { //==============執(zhí)行普通方法 val instance = Registry.instance.getServiceInstance(serviceId) if (instance == null) { return Response("instance == null ", -103) } //方法執(zhí)行返回的結(jié)果 return try { val result = if (neededParams == null || neededParams.isEmpty()) { method?.invoke(instance) } else { method?.invoke(instance, neededParams) } Response(gson.toJson(result), 200) } catch (e: Exception) { Response("${e.message}", -104) } } } return null }
當(dāng)客戶端發(fā)起請求時(shí),會將請求的參數(shù)封裝到Request中,在服務(wù)端接收到請求后,就會解析這些參數(shù),變成Method執(zhí)行時(shí)需要傳入的參數(shù)。
private fun parseParameters(params: Array<Parameters>?): Array<Any?>? { if (params == null || params.isEmpty()) { return null } val objects = arrayOfNulls<Any>(params.size) params.forEachIndexed { index, parameters -> objects[index] = gson.fromJson(parameters.value, Class.forName(parameters.className)) } return objects }
例如用戶中心調(diào)用setUserInfo方法時(shí),需要傳入一個(gè)User實(shí)體類,如下所示:
UserManager().setUserInfo(User("ming",25))
那么在調(diào)用這個(gè)方法的時(shí)候,首先會把這個(gè)實(shí)體類轉(zhuǎn)成一個(gè)JSON字符串,例如:
{ "name":"ming", "age":25 }
為啥要”多此一舉“呢?其實(shí)這種處理方式是最快速直接的,轉(zhuǎn)成json字符串之后,能夠最大限度地降低數(shù)據(jù)傳輸?shù)拇笮?,等到服?wù)端處理這個(gè)方法的時(shí)候,再把Request中的params反json轉(zhuǎn)成User對象即可。
fun findMethod(serviceId: String?, methodName: String?, neededParams: Array<Any?>?): Method? { //獲取服務(wù) val serviceClazz = serviceMaps[serviceId] ?: return null //獲取方法集合 val methods = methodsMap[serviceClazz] ?: return null return methods[rebuildParamsFunc(methodName, neededParams)] } private fun rebuildParamsFunc(methodName: String?, params: Array<Any?>?): String { val stringBuffer = StringBuffer() stringBuffer.append(methodName).append("(") if (params == null || params.isEmpty()) { stringBuffer.append(")") return stringBuffer.toString() } stringBuffer.append(params[0]?.javaClass?.name) for (index in 1 until params.size) { stringBuffer.append(",").append(params[index]?.javaClass?.name) } stringBuffer.append(")") return stringBuffer.toString() }
那么在查找注冊方法的時(shí)候就簡單多了,直接抽絲剝繭一層一層取到最終的Method。在拿到Method之后,這里是有2種處理方式,一種是通過靜態(tài)單例的形式拿到實(shí)例對象,并保存在服務(wù)端;另一種就是執(zhí)行普通方法,因?yàn)樵诜瓷涞臅r(shí)候需要拿到類的實(shí)例對象才能調(diào)用,所以才在GET_INSTANCE的時(shí)候存一遍。
3 客戶端 - connect
在第二節(jié)中,我們已經(jīng)完成了通訊協(xié)議的建設(shè),最終一步就是客戶端通過綁定服務(wù),向服務(wù)端發(fā)起通信了。
3.1 bindService
/** * 綁定服務(wù) * */ fun connect( context: Context, pkgName: String, action: String = "", service: Class<out IPCService> ) { val intent = Intent() if (pkgName.isEmpty()) { //同app內(nèi)的不同進(jìn)程 intent.setClass(context, service) } else { //不同APP之間進(jìn)行通信 intent.setPackage(pkgName) intent.setAction(action) } //綁定服務(wù) context.bindService(intent, IpcServiceConnection(service), Context.BIND_AUTO_CREATE) } inner class IpcServiceConnection(val simpleService: Class<out IPCService>) : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val mService = IIPCServiceInterface.Stub.asInterface(service) as IIPCServiceInterface binders[simpleService] = mService } override fun onServiceDisconnected(name: ComponentName?) { //斷連之后,直接移除即可 binders.remove(simpleService) } }
對于綁定服務(wù)這塊,相信伙伴們也很熟悉了,這個(gè)需要說一點(diǎn)的就是,在Android 5.0以后,啟動服務(wù)不能只依賴action啟動,還需要指定應(yīng)用包名,否則就會報(bào)錯(cuò)。
在服務(wù)連接成功之后,即回調(diào)onServiceConnected方法的時(shí)候,需要拿到服務(wù)端的一個(gè)代理對象,即IIPCServiceInterface的實(shí)例對象,然后存儲在binders集合中,key為綁定的服務(wù)類class對象,value就是對應(yīng)的服務(wù)端的代理對象。
fun send( type: Int, service: Class<out IPCService>, serviceId: String, methodName: String, params: Array<Parameters> ): Response? { //創(chuàng)建請求 val request = Request(type, serviceId, methodName, params) //發(fā)起請求 return try { binders[service]?.send(request) } catch (e: Exception) { null } }
當(dāng)拿到服務(wù)端的代理對象之后,就可以在客戶端調(diào)用send方法向服務(wù)端發(fā)送消息。
class Channel { //==================================== /**每個(gè)服務(wù)對應(yīng)的Binder對象*/ private val binders: ConcurrentHashMap<Class<out IPCService>, IIPCServiceInterface> by lazy { ConcurrentHashMap() } //==================================== /** * 綁定服務(wù) * */ fun connect( context: Context, pkgName: String, action: String = "", service: Class<out IPCService> ) { val intent = Intent() if (pkgName.isEmpty()) { intent.setClass(context, service) } else { intent.setPackage(pkgName) intent.setAction(action) intent.setClass(context, service) } //綁定服務(wù) context.bindService(intent, IpcServiceConnection(service), Context.BIND_AUTO_CREATE) } inner class IpcServiceConnection(val simpleService: Class<out IPCService>) : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val mService = IIPCServiceInterface.Stub.asInterface(service) as IIPCServiceInterface binders[simpleService] = mService } override fun onServiceDisconnected(name: ComponentName?) { //斷連之后,直接移除即可 binders.remove(simpleService) } } fun send( type: Int, service: Class<out IPCService>, serviceId: String, methodName: String, params: Array<Parameters> ): Response? { //創(chuàng)建請求 val request = Request(type, serviceId, methodName, params) //發(fā)起請求 return try { binders[service]?.send(request) } catch (e: Exception) { null } } companion object { private val instance by lazy { Channel() } /** * 獲取單例對象 */ fun getDefault(): Channel { return instance } } }
3.2 動態(tài)代理獲取接口實(shí)例
回到1.2小節(jié)中,我們定義了一個(gè)IUserManager接口,通過前面我們定義的通信協(xié)議,只要我們獲取了IUserManager的實(shí)例對象,那么就能夠調(diào)用其中的任意普通方法,所以在客戶端需要設(shè)置一個(gè)獲取接口實(shí)例對象的方法。
fun <T> getInstanceWithName( service: Class<out IPCService>, classType: Class<T>, clazz: Class<*>, methodName: String, params: Array<Parameters> ): T? { //獲取serviceId val serviceId = clazz.getAnnotation(ServiceId::class.java) val response = Channel.getDefault() .send(REQUEST.GET_INSTANCE.ordinal, service, serviceId.name, methodName, params) Log.e("TAG", "response $response") if (response != null && response.result) { //請求成功,返回接口實(shí)例對象 return Proxy.newProxyInstance( classType.classLoader, arrayOf(classType), IPCInvocationHandler() ) as T } return null }
當(dāng)我們通過客戶端發(fā)送一個(gè)獲取單例的請求后,如果成功了,那么就直接返回這個(gè)接口的單例對象,這里直接使用動態(tài)代理的方式返回一個(gè)接口實(shí)例對象,那么后續(xù)執(zhí)行這個(gè)接口的方法時(shí),會直接走到IPCInvocationHandler的invoke方法中。
class IPCInvocationHandler( val service: Class<out IPCService>, val serviceId: String? ) : InvocationHandler { private val gson = Gson() override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? { //執(zhí)行客戶端發(fā)送方法請求 val response = Channel.getDefault() .send( REQUEST.INVOKE_METHOD.ordinal, service, serviceId, method?.name ?: "", args ) //拿到服務(wù)端返回的結(jié)果 if (response != null && response.result) { //反序列化得到結(jié)果 return gson.fromJson(response.value, method?.returnType) } return null } }
因?yàn)榉?wù)端在拿到Method的返回結(jié)果時(shí),將javabean轉(zhuǎn)換為了json字符串,因此在IPCInvocationHandler中,當(dāng)調(diào)用接口中方法獲取結(jié)果之后,用Gson將json轉(zhuǎn)換為javabean對象,那么就直接獲取到了結(jié)果。
3.3 框架使用
服務(wù)端:
UserManager2.getDefault().setUserInfo(User("ming", 25)) IPC.register(UserManager2::class.java)
同時(shí)在服務(wù)端需要注冊一個(gè)IPCService的實(shí)例,這里用的是IPCService01
<service android:name=".UserService" android:enabled="true" android:exported="true" /> <service android:name="com.lay.ipc.service.IPCService01" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.GET_USER_INFO" /> </intent-filter> </service>
客戶端:
調(diào)用connect方法,需要綁定服務(wù)端的服務(wù),傳入包名和action
IPC.connect( this, "com.lay.learn.asm", "android.intent.action.GET_USER_INFO", IPCService01::class.java )
首先獲取IUserManager的實(shí)例,注意這里要和服務(wù)端注冊的UserManager2是同一個(gè)ServiceId,而且接口、javabean需要存放在與服務(wù)端一樣的文件夾下。
val userManager = IPC.getInstanceWithName( IPCService01::class.java, IUserManager::class.java, "getDefault", null ) val info = userManager?.getUserInfo()
通過動態(tài)代理拿到接口的實(shí)例對象,只要調(diào)用接口中的方法,就會進(jìn)入到InvocationHandler中的invoke方法,在這個(gè)方法中,通過查找服務(wù)端注冊的方法名從而找到對應(yīng)的Method,通過反射調(diào)用拿到UserManager中的方法返回值。
這樣其實(shí)就通過5-6行代碼,就完成了進(jìn)程間通信,是不是比我們在使用AIDL的時(shí)候要方便地許多。
4 總結(jié)
如果我們面對下面這個(gè)類,如果這個(gè)類是個(gè)私有類,外部沒法調(diào)用,想通過反射的方式調(diào)用其中某個(gè)方法。
@ServiceId(name = "UserManagerService") public class UserManager2 implements IUserManager { private static UserManager2 userManager2 = new UserManager2(); public static UserManager2 getDefault() { return userManager2; } private User user; @Nullable @Override public User getUserInfo() { return user; } @Override public void setUserInfo(@NonNull User user) { this.user = user; } @Override public int getUserId() { return 0; } @Override public void setUserId(int id) { } }
那么我們可以這樣做:
val method = UserManager2::class.java.getDeclaredMethod("getUserInfo") method.isAccessible = true method.invoke(this,params)
其實(shí)這個(gè)框架的原理就是上面這幾行代碼所能夠完成的事;通過服務(wù)端注冊的形式,將UserManager2中所有的方法Method收集起來;當(dāng)另一個(gè)進(jìn)程,也就是客戶端想要調(diào)用其中某個(gè)方法的時(shí)候,通過方法名來獲取到對應(yīng)的Method,調(diào)用這個(gè)方法得到最終的返回值。
以上就是Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL的詳細(xì)內(nèi)容,更多關(guān)于Android IPC通信框架的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL
這篇文章主要為大家介紹了Android進(jìn)階手寫IPC通信框架告別繁瑣AIDL實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01android中實(shí)現(xiàn)背景圖片顏色漸變方法
這篇文章主要介紹了android中實(shí)現(xiàn)背景圖片顏色漸變方法,本文直接使用配置文件實(shí)現(xiàn)了這個(gè)效果,需要的朋友可以參考下2015-05-05Android自定義View實(shí)現(xiàn)心形圖案
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)心形圖案,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09kotlin實(shí)現(xiàn)強(qiáng)制下線功能
這篇文章主要為大家詳細(xì)介紹了kotlin實(shí)現(xiàn)強(qiáng)制下線功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06Android中Retrofit+OkHttp進(jìn)行HTTP網(wǎng)絡(luò)編程的使用指南
Retrofit和OkHttp都是Square在GitHub上開源的第三方HTTP支持包,兩個(gè)包可以搭配使用,本文即是來講解Android中Retrofit+OkHttp進(jìn)行HTTP網(wǎng)絡(luò)編程的使用指南:2016-07-07Android實(shí)現(xiàn)簡單斷點(diǎn)續(xù)傳和下載到本地功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡單斷點(diǎn)續(xù)傳和下載到本地功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android studio git創(chuàng)建與刪除標(biāo)簽(Tag)的教程詳解
這篇文章主要介紹了Android studio git創(chuàng)建與刪除標(biāo)簽(Tag)的教程詳解,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12