Android组件化跨进程通信框架Andromeda解析

@Override

public Object getLocalService(String module) {

return serviceMap.get(module);

}

@Override

public void registerService(String module, Object serviceImpl) {

serviceMap.put(module, serviceImpl);

}

@Override

public void unregisterService(String module) {

serviceMap.remove(module);

}

}

远程服务

远程服务是框架的核心,对远程服务的操作就是两个,一是注册远程服务,二是获取远程服务。

我们先来看服务的注册,时序图如下 ↓

  1. 客户端通过<T extends IBinder> registerRemoteService(String serviceCanonicalName, T stubBinder)注册本进程可提供的远程服务,stubBinder即服务实现类。

  2. 调用RemoteTransfer的registerStubService方法。

  3. registerStubService内部先初始化DispatcherProxy,如果为空跳转3.1。

  • 3.1-3.2 要实现服务的同步注册,本质上是同步获取DispatcherProxy,这是一次IPC通信,Andromeda的方案是在Dispatcher进程插桩一个ContentProvider,然后返回一个包含DispatcherProxy的Cursor给客户端进程,客户端解析Cursor拿到DispatcherProxy。
  1. RemoteTransfer请求RemoteServiceTransfer帮忙完成真正的注册。

  2. RemoteServiceTransfer通过第3步获取的DispatcherProxy,做一次IPC通信,将Binder传递到Dispatcher进程。

  3. Dispatcher进程请求ServiceDispatcher类帮忙完成服务的注册,其实就是将Binder存储在一个Map当中。

图中蓝色的节点表示注册服务的当前进程,也就是Server进程,红色节点表示Dispatcher进程。

整个过程重点在第三步,我们再重点分析一下:

RemoteTransfer

private void initDispatchProxyLocked() {

if (null == dispatcherProxy) {

//从contentprovider取Binder

IBinder dispatcherBinder = getIBinderFromProvider();

if (null != dispatcherBinder) {

//取出后asInterface创建远程代理对象

dispatcherProxy = IDispatcher.Stub.asInterface(dispatcherBinder);

registerCurrentTransfer();

}

}

}

private void registerCurrentTransfer() {

//向Dispatcher注册自己这个进程的RemoteTransfer Binder

dispatcherProxy.registerRemoteTransfer(android.os.Process.myPid(), this.asBinder());

}

private IBinder getIBinderFromProvider() {

Cursor cursor = null;

try {

//通过contentprovider拿到cursor

cursor = context.getContentResolver().query(getDispatcherProviderUri(), DispatcherProvider.PROJECTION_MAIN,

null, null, null);

if (cursor == null) {

return null;

}

return DispatcherCursor.stripBinder(cursor);

} finally {

IOUtils.closeQuietly(cursor);

}

}

我们来看这个DispatcherProvider

public class DispatcherProvider extends ContentProvider {

@Override

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

//将Binder封装到cursor中返回

return DispatcherCursor.generateCursor(Dispatcher.getInstance().asBinder());

}

}


接下来我们看服务的获取,同样的先看时序图 ↓

1. Andromeda入口通过getRemoteService获取远程服务。

2-4. 与提升进程优先级有关,我们暂且不讨论。

5. 向RemoteTransfer请求获取远程服务的包装bean。

6-7. RemoteTransfer请求RemoteServiceTransfer帮忙先从本进程的缓存中查找目标Binder,如果找到直接返回。

7.2. 如果没有命中缓存调用getAndSaveIBinder方法,通过方法名可知,获取后会将Binder缓存起来,这就是6-7步读取的缓存。

8. RemoteServiceTransfer通过DispatcherProxy发起IPC通信,请求远程服务Binder。

9-10. Dispatcher请ServiceDispatcher帮忙查找进程中的服务注册表。

11. 回到客户端进程将Binder缓存。

12. 将Binder返回给调用方。

同样图中蓝色的节点表示获取服务的进程,也就是Client进程,红色节点表示Dispatcher进程。

至此,远程服务的注册与获取流程分析结束。

进程优先级

上面提到在获取远程服务时,框架做了提升进程优先级的事情。通常情况下使用远程服务的端(简称Client端)处于前台进程,而Server端进程已经注册完毕,往往处于后台。为了提升Server端的稳定性,最好能将Server端的进程优先级与Client保持接近,否则容易出现被LMK(Low Memory Killer)回收的情况。

那如何提升Server端进程的优先级呢?这里的做法是用前台的UI组件(Activity/Fragment/View)bind一个Server端预先插桩好的Service。

整套流程最终通过AMS的updateOomAdjLocked方法实现。

回到Andromeda实现,这个预先插桩的Service如下:

public class CommuStubService extends Service {

public CommuStubService() {}

@Override

public IBinder onBind(Intent intent) {

return new ICommuStub.Stub() {

@Override

public void commu(Bundle args) throws RemoteException {

//do nothing now

}

};

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

//这样可以使Service所在进程的保活效果好一点

return Service.START_STICKY;

}

public static class CommuStubService0 extends CommuStubService {}

public static class CommuStubService1 extends CommuStubService {}

public static class CommuStubService2 extends CommuStubService {}

public static class CommuStubService14 extends CommuStubService {}

}

可见框架预置了15个Service供进程使用,也就是最多支持15个进程,这绝大数场景下足够了;另外维护了一个进程名和Service名称的映射表,否则怎么知道应该bind那个Service,这个映射表也是在编译阶段插桩完成的。

这个service的bind过程发生在上一章节获取远程服务时,流程如下图:

图中模块根据所在进程分为三部分:

  1. 蓝色表示Client进程,发起获取远程服务请求。

  2. 浅灰色表示Server进程,它事先将服务注册到Dispatcher中。

  3. 紫色表示Dispatcher进程,内部缓存了各个进程的服务的Binder对象。

我们重点关注的是蓝色模块ConnectionManager部分,实际上当Client向Dispatcher请求远程服务之后,会立即通过ConnectionManager绑定这个远程服务所在进程的插桩的StubService,如此一来就提升了Server所在进程的优先级。

至此bind操作已经完成了,那何时unbind呢?显然是当UI组件销毁时,因为此时已不在前台,需要降低进程优先级。

如此一来就需要监听UI组件的生命周期,在onDestroy时进行unbind操作。

这就是图中RemoteManager做的事情,它内部维护了前台组件的生命周期。Andromeda提供了几种with方法,用于获取对应RemoteManager:

public static IRemoteManager with(android.app.Fragment fragment) {return getRetriever().get(fragment);}

public static IRemoteManager with(Fragment fragment) {return getRetriever().get(fragment);}

public static IRemoteManager with(FragmentActivity fragmentActivity) {return getRetriever().get(fragmentActivity);}

public static IRemoteManager with(Activity activity) {return getRetriever().get(activity);}

public static IRemoteManager with(Context context) {return getRetriever().get(context);}

public static IRemoteManager with(View view) {return getRetriever().get(view);}

这是借鉴Glide的做法,这些方法最终被转换为两类:

  1. 具备生命周期的UI组件,最终是Activity或Fragment。

  2. ApplicationContext。

对于第一种情况,框架会为当前Activity或Fragment添加一个不可见的RemoteManagerFragment以监听生命周期。

对于使用ApplicationContext,获取远程服务的场景不做unbind操作。

事实上用Jetpack lifecycle组件也可以方便的监听Activity/Fragment的生命周期,但是这有个前提,那就是Activity必须继承android.support.v4.app.FragmentActvity,而Fragment必须继承 android.support.v4.app.Fragment,且v4库的版本必须大于等于26.1.0,从这个版本开始支持了Lifecycle。

事件总线

在上述通信框架基础之上,实现事件总线简直易如反指。

我们来看一下使用

//订阅事件,这里MainActivity实现了EventListener接口

Andromeda.subscribe(EventConstants.APPLE_EVENT,MainActivity.this);

//发布事件

Bundle bundle = new Bundle();

bundle.putString(“Result”, “gave u five apples!”);

Andromeda.publish(new Event(EventConstants.APPLE_EVENT, bundle));

这里的Event是事件传递的载体。

public class Event implements Parcelable {

private String name;

private Bundle data;

}

至于原理,回想一下我们在注册远程服务的过程中,同时将本进程的RemoteTransfer的Binder也注册到了Dispatcher中。

当我们订阅一个事件时,只是将Event名称和监听器存储在了本进程的RemoteTransfer中,当另一个进程发布事件时,会通过一次IPC调用将Event对象发送到Dispatcher,Dispatcher收到事件后,会向注册过的RemoteTransfer依次发送回调信息,也就是说这一步可能进行多次IPC调用,效率问题需diss一下。

事件到达订阅进程后会根据事件名称,提取所有关于此名称的监听器,最终发送给监听者。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

作者2013年从java开发,转做Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

参与过不少面试,也当面试官 面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我整理了一份阿里P7级别的最系统的Android开发主流技术,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你想深入系统学习Android开发,成为一名合格的高级工程师,可以收藏一下这些Android进阶技术选型

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

高级UI与自定义view;
自定义view,Android开发的基本功。

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

NDK开发;
未来的方向,高薪必会。

前沿技术;
组件化,热升级,热修复,框架设计

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,GitHub可见;《Android架构视频+学习笔记》

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

;**
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-XDkzcntE-1710571582058)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-HEZLvaaY-1710571582058)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-GTM3q5rj-1710571582058)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,GitHub可见;《Android架构视频+学习笔记》

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AndroBench 是一款 Android 平台上的存储性能测试工具,用于评估设备的存储性能。下面是一个简单的 AndroBench 自动测试脚本,可以在 Python 环境下运行。 ```python import os import subprocess import time # AndroBench 的安装路径 androbench_path = "/mnt/sdcard/Download/AndroBench.apk" # 待测试的设备名称 device_name = "emulator-5554" # 待测试的存储类型和测试次数 test_cases = [("Internal Storage", 3), ("SD Card", 3)] # AndroBench 测试结果保存路径 result_path = "/mnt/sdcard/AndroBench_results/" # 启动测试前先清除旧结果 subprocess.run(["adb", "-s", device_name, "shell", "rm", "-rf", result_path]) # 安装 AndroBench subprocess.run(["adb", "-s", device_name, "install", "-r", androbench_path]) # 启动测试 for storage_type, test_count in test_cases: for i in range(test_count): # 执行测试命令 timestamp = int(time.time()) test_name = f"{storage_type}_{timestamp}" test_path = os.path.join(result_path, test_name) command = f"am start -n 'com.andromeda.androbench2/.Androbench' -e TestName {test_name} -e StorageType '{storage_type}' -e TestCount 1 -e TestPath {test_path}" subprocess.run(["adb", "-s", device_name, "shell", command]) # 等待测试结果生成 time.sleep(30) # 拷贝测试结果到本地 local_result_path = os.path.join(os.getcwd(), f"{test_name}.csv") subprocess.run(["adb", "-s", device_name, "pull", test_path, local_result_path]) print(f"{storage_type} test {i+1}/{test_count} finished") ``` 该脚本首先清除 AndroBench 的测试结果,然后安装 AndroBench 应用程序。接下来,它在指定的存储类型和测试次数下执行测试,并将测试结果拷贝到本地目录。在测试过程中,该脚本会输出当前测试的状态信息。 请注意,该脚本仅提供了 AndroBench 的基本测试功能。如果需要更多的测试选项和结果分析功能,可以参考 AndroBench 的官方文档进行扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值