Android進階手寫IPC通信框架告別繁瑣AIDL
正文
對于進程間通信,很多項目中可能根本沒有涉及到多進程,很多公司的app可能就一個主進程,但是對于進程間通信,我們也是必須要了解的。
如果在Android中想要實現(xiàn)進程間通信,有哪些方式呢?
(1)發(fā)廣播(sendBroadcast):e.g. 兩個app之間需要通信,那么可以通過發(fā)送廣播的形式進行通信,如果只想單點通信,可以指定包名。但是這種方式存在的弊端在于發(fā)送方無法判斷接收方是否接收到了廣播,類似于UDP的通信形式,而且存在丟數(shù)據(jù)的形式;
(2)Socket通信:這種屬于Linux層面的進程間通信了,除此之外,還包括管道、信號量等,像傳統(tǒng)的IPC進程間通信需要數(shù)據(jù)二次拷貝,這種效率是最低的;
(3)AIDL通信:這種算是Android當(dāng)中主流的進程間通信方案,通過Service + Binder的形式進行通信,具備實時性而且能夠通過回調(diào)得知接收方是否收到數(shù)據(jù),弊端在于需要管理維護aidl接口,如果不同業(yè)務(wù)方需要使用不同的aidl接口,維護的成本會越來越高。
那么本篇文章并不是說完全丟棄掉AIDL,它依然不失為一個很好的進程間通信的手段,只是我會封裝一個適用于任意業(yè)務(wù)場景的IPC進程間通訊框架,這個也是我在自己的項目中使用到的,不需要維護很多的AIDL接口文件。
有需要源碼的伙伴,可以去我的github首頁獲取 FastIPC源碼地址,分支:feature/v0.0.1-snapshot,有幫助的話麻煩給點個star??????
1 服務(wù)端 - register
首先這里先說明一下,就是對于傳統(tǒng)的AIDL使用方式,這里就不再過多介紹了,這部分還是比較簡單的,有興趣的伙伴們可以去前面的文章中查看,本文將著重介紹框架層面的邏輯。
那么IPC進程間通信,需要兩個端:客戶端和服務(wù)端。服務(wù)端會提供一個注冊方法,例如客戶端定義的一些服務(wù),通過向服務(wù)端注冊來做一個備份,當(dāng)客戶端調(diào)用服務(wù)端某個方法的時候來返回值。
object IPC {
//==========================================
/**
* 服務(wù)端暴露的接口,用于注冊服務(wù)使用
*/
fun register(service: Class<*>) {
Registry.instance.register(service)
}
}
其實在注冊的時候,我們的目的肯定是能夠方便地拿到某個服務(wù),并且能夠調(diào)用這個服務(wù)提供的方法,拿到我想要的值;所以在定義服務(wù)的時候,需要注意以下兩點:
(1)需要定義一個與當(dāng)前服務(wù)一一對應(yīng)的serviceId,通過serviceId來獲取服務(wù)的實例;
(2)每個服務(wù)當(dāng)中定義的方法同樣需要對應(yīng)起來,以便拿到服務(wù)對象之后,通過反射調(diào)用其中的方法。
所以在注冊的時候,需要從這兩點入手。
1.1 定義服務(wù)唯一標(biāo)識serviceId
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
annotation class ServiceId(
val name: String
)
一般來說,如果涉及到反射,最常用的就是通過注解給Class做標(biāo)記,因為通過反射能夠拿到類上標(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) {
//這里需要注意,因為方法中存在重載方法,所以不能把方法名當(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對象的時候,首先獲取到服務(wù)上標(biāo)記的@ServiceId注解,注意這里如果要注冊必須標(biāo)記,否則直接拋異常;拿到serviceId之后,存入到serviceMaps中。
然后需要獲取服務(wù)中的全部方法,因為考慮到重載方法的存在,所以不能單單以方法名作為key,而是需要把參數(shù)也加上,因此這里做了一個邏輯就是將方法名與參數(shù)名組合一個key,存入到方法表中。
這樣注冊任務(wù)就完成了,其實還是比較簡單的,關(guān)鍵在于完成2個表:服務(wù)表和方法表的初始化以及數(shù)據(jù)存儲功能。
1.2 使用方式
@ServiceId("UserManagerService")
interface IUserManager {
fun getUserInfo(): User?
fun setUserInfo(user: User)
fun getUserId(): Int
fun setUserId(id: Int)
}
假設(shè)項目中有一個用戶信息管理的服務(wù),這個服務(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
}
}
用戶中心可以注冊這個服務(wù),并且調(diào)用setUserInfo方法保存用戶信息,那么其他App(客戶端)連接這個服務(wù)之后,就可以調(diào)用getUserInfo這個方法,獲取用戶信息,從而完成進程間通信。
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方法之后,每個方法的key值都是跟參數(shù)綁定在一起,這樣服務(wù)端注冊就完成了。
2 客戶端與服務(wù)端的通信協(xié)議
對于客戶端的連接,其實就是綁定服務(wù),那么這里就會使用到AIDL通信,但是跟傳統(tǒng)的相比,我們是將AIDL封裝到框架層內(nèi)部,對于用戶來說是無感知的。
2.1 創(chuàng)建IPCService
這個服務(wù)就是用來完成進程間通信的,客戶端需要與這個服務(wù)建立連接,通過服務(wù)端分發(fā)消息,或者接收客戶端發(fā)送來的消息。
abstract class IPCService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
這里我定義了一個抽象的Service基類,為啥要這么做,前面我們提到過是因為整個項目中不可能只有一個服務(wù),因為業(yè)務(wù)眾多,為了保證單一職責(zé),需要劃分不同的類型,所以在框架中會衍生多個實現(xiàn)類,不同業(yè)務(wù)方可以注冊這些服務(wù),當(dāng)然也可以自定義服務(wù)繼承IPCService。
class IPCService01 : IPCService() {
}
在IPCService的onBind需要返回一個Binder對象,因此需要創(chuàng)建aidl文件。
2.2 定義通訊協(xié)議
像我們在請求接口的時候,通常也是向服務(wù)端發(fā)起一個請求(Request),然后得到服務(wù)端的一個響應(yīng)(Response),因此在IPC通信的的時候,也可以根據(jù)這種方式建立通信協(xié)議。
data class Request(
val type: Int,
val serviceId: String?,
val methodName: String?,
val params: Array<Parameters>?
) : Serializable, Parcelable {
//=====================================
/**請求類型*/
//獲取實例的對象
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ā)起請求,請求實體類Request參數(shù)介紹如下:
type表示請求的類型,包括兩種分別是:執(zhí)行靜態(tài)方法和執(zhí)行普通方法(考慮到反射傳參);
serviceId表示請求的服務(wù)id,要請求哪個服務(wù),便可以獲取到這個服務(wù)的實例對象,調(diào)用服務(wù)中提供的方法;
methodName表示要請求的方法名,也是在serviceId服務(wù)中定義的方法;
params表示請求的方法參數(shù)集合,我們在服務(wù)端注冊的時候,方法名 + 參數(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ù)介紹:
result表示請求成功或者失??;
value表示服務(wù)端返回的結(jié)果,是一個json字符串。
因此定義aidl接口文件如下,輸入一個請求之后,返回一個服務(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í)行某個方法的時候,首先服務(wù)端會先向Registery中查詢注冊的服務(wù),從而找到這個要執(zhí)行的方法,這個流程是在內(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)
}
//存儲實例對象
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ù)封裝到Request中,在服務(wù)端接收到請求后,就會解析這些參數(shù),變成Method執(zhí)行時需要傳入的參數(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方法時,需要傳入一個User實體類,如下所示:
UserManager().setUserInfo(User("ming",25))
那么在調(diào)用這個方法的時候,首先會把這個實體類轉(zhuǎn)成一個JSON字符串,例如:
{
"name":"ming",
"age":25
}
為啥要”多此一舉“呢?其實這種處理方式是最快速直接的,轉(zhuǎn)成json字符串之后,能夠最大限度地降低數(shù)據(jù)傳輸?shù)拇笮?,等到服?wù)端處理這個方法的時候,再把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()
}
那么在查找注冊方法的時候就簡單多了,直接抽絲剝繭一層一層取到最終的Method。在拿到Method之后,這里是有2種處理方式,一種是通過靜態(tài)單例的形式拿到實例對象,并保存在服務(wù)端;另一種就是執(zhí)行普通方法,因為在反射的時候需要拿到類的實例對象才能調(diào)用,所以才在GET_INSTANCE的時候存一遍。
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)的不同進程
intent.setClass(context, service)
} else {
//不同APP之間進行通信
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ù)這塊,相信伙伴們也很熟悉了,這個需要說一點的就是,在Android 5.0以后,啟動服務(wù)不能只依賴action啟動,還需要指定應(yīng)用包名,否則就會報錯。
在服務(wù)連接成功之后,即回調(diào)onServiceConnected方法的時候,需要拿到服務(wù)端的一個代理對象,即IIPCServiceInterface的實例對象,然后存儲在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 {
//====================================
/**每個服務(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)代理獲取接口實例
回到1.2小節(jié)中,我們定義了一個IUserManager接口,通過前面我們定義的通信協(xié)議,只要我們獲取了IUserManager的實例對象,那么就能夠調(diào)用其中的任意普通方法,所以在客戶端需要設(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) {
//請求成功,返回接口實例對象
return Proxy.newProxyInstance(
classType.classLoader,
arrayOf(classType),
IPCInvocationHandler()
) as T
}
return null
}
當(dāng)我們通過客戶端發(fā)送一個獲取單例的請求后,如果成功了,那么就直接返回這個接口的單例對象,這里直接使用動態(tài)代理的方式返回一個接口實例對象,那么后續(xù)執(zhí)行這個接口的方法時,會直接走到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
}
}
因為服務(wù)端在拿到Method的返回結(jié)果時,將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)
同時在服務(wù)端需要注冊一個IPCService的實例,這里用的是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的實例,注意這里要和服務(wù)端注冊的UserManager2是同一個ServiceId,而且接口、javabean需要存放在與服務(wù)端一樣的文件夾下。
val userManager = IPC.getInstanceWithName(
IPCService01::class.java,
IUserManager::class.java,
"getDefault",
null
)
val info = userManager?.getUserInfo()
通過動態(tài)代理拿到接口的實例對象,只要調(diào)用接口中的方法,就會進入到InvocationHandler中的invoke方法,在這個方法中,通過查找服務(wù)端注冊的方法名從而找到對應(yīng)的Method,通過反射調(diào)用拿到UserManager中的方法返回值。
這樣其實就通過5-6行代碼,就完成了進程間通信,是不是比我們在使用AIDL的時候要方便地許多。
4 總結(jié)
如果我們面對下面這個類,如果這個類是個私有類,外部沒法調(diào)用,想通過反射的方式調(diào)用其中某個方法。
@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)
其實這個框架的原理就是上面這幾行代碼所能夠完成的事;通過服務(wù)端注冊的形式,將UserManager2中所有的方法Method收集起來;當(dāng)另一個進程,也就是客戶端想要調(diào)用其中某個方法的時候,通過方法名來獲取到對應(yīng)的Method,調(diào)用這個方法得到最終的返回值。
以上就是Android進階手寫IPC通信框架告別繁瑣AIDL的詳細(xì)內(nèi)容,更多關(guān)于Android IPC通信框架的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中Retrofit+OkHttp進行HTTP網(wǎng)絡(luò)編程的使用指南
Retrofit和OkHttp都是Square在GitHub上開源的第三方HTTP支持包,兩個包可以搭配使用,本文即是來講解Android中Retrofit+OkHttp進行HTTP網(wǎng)絡(luò)編程的使用指南:2016-07-07
Android實現(xiàn)簡單斷點續(xù)傳和下載到本地功能
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)簡單斷點續(xù)傳和下載到本地功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
Android studio git創(chuàng)建與刪除標(biāo)簽(Tag)的教程詳解
這篇文章主要介紹了Android studio git創(chuàng)建與刪除標(biāo)簽(Tag)的教程詳解,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12

