IRemoteTransfer是一个AIDL接口,RemoteTransfer是它的实现类,RemoteTransfer还实现了IRemoteServiceTransfer接口。
这里需要一张类图来帮你理清思路:
#IRemoteTransfer.aidl
interface IRemoteTransfer {
//① 将Dispatcher代理返回给RemoteTransfer
oneway void registerDispatcher(IBinder dispatcherBinder);
oneway void unregisterRemoteService(String serviceCanonicalName);
oneway void notify(in Event event);
}
#IRemoteServiceTransfer.java
public interface IRemoteServiceTransfer {
//②获取远程服务包装
BinderBean getRemoteServiceBean(String serviceCanonicalName);
//注册/反注册 远程服务
void registerStubService(String serviceCanonicalName, IBinder stubBinder);
void unregisterStubService(String serviceCanonicalName);
}
两个问题需要注意:
① 方法的调用方在Dispatcher中,这样就把Dispatcher的远程代理回传给了当前进程,之后注册远程服务就可以通过这个DispatcherProxy完成。
② 无论是注册还是获取远程服务,都是不能直接传递Binder的,因为Binder并没有实现Parcelable接口,因此需要将Binder包装在一个实现了Parcelable接口的类中传递,BinderBean就是其中一个包装类。
主体逻辑已经讲清楚了,我们正式开始分析功能。
-
通过ContentProvider方式同步的获取Dispatcher的代理,这个ContentProvider属于Dispatcher进程,且通过插桩的方式织入manifeset文件。
-
获取远程服务时传递当前进程的Activity或Fragment,并bind预先插桩好的StubService,这个StubService属于远程服务所在进程。
这是整个Andromeda工程最最核心的原理,你是不是快看不懂了,没关系,下面会结合时序图、关系图详细分析实现过程。
本地服务
本地服务没什么讲的,内部通过维护一个Map关系表,来记录注册服务的名称和实现类。
LocalServiceHub
public class LocalServiceHub implements ILocalServiceHub {
private Map<String, Object> serviceMap = new ConcurrentHashMap<>();
@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);
}
}
远程服务
远程服务是框架的核心,对远程服务的操作就是两个,一是注册远程服务,二是获取远程服务。
我们先来看服务的注册,时序图如下 ↓
-
客户端通过
<T extends IBinder> registerRemoteService(String serviceCanonicalName, T stubBinder)
注册本进程可提供的远程服务,stubBinder即服务实现类。 -
调用RemoteTransfer的registerStubService方法。
-
registerStubService内部先初始化DispatcherProxy,如果为空跳转3.1。
- 3.1-3.2 要实现服务的同步注册,本质上是同步获取DispatcherProxy,这是一次IPC通信,Andromeda的方案是在Dispatcher进程插桩一个ContentProvider,然后返回一个包含DispatcherProxy的Cursor给客户端进程,客户端解析Cursor拿到DispatcherProxy。
-
RemoteTransfer请求RemoteServiceTransfer帮忙完成真正的注册。
-
RemoteServiceTransfer通过第3步获取的DispatcherProxy,做一次IPC通信,将Binder传递到Dispatcher进程。
-
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过程发生在上一章节获取远程服务时,流程如下图:
图中模块根据所在进程分为三部分:
-
蓝色表示Client进程,发起获取远程服务请求。
-
浅灰色表示Server进程,它事先将服务注册到Dispatcher中。
-
紫色表示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的做法,这些方法最终被转换为两类:
-
具备生命周期的UI组件,最终是Activity或Fragment。
-
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一下。
事件到达订阅进程后会根据事件名称,提取所有关于此名称的监听器,最终发送给监听者。
注意:这里的监听器常常使用的是Activity,但显然RemoteTransfer是属于进程生命周期的,因此保存监听器时需使用弱引用。
插桩
上面分析原理过程中反复提到了插桩,总结一下共有几处:
-
将属于Dispatcher进程的DispatcherProvider和DispatcherService插入到manifest中(StubServiceGenerator)。
-
将各个进程的预置StubService插入到manifest中(StubServiceGenerator)。
-
将进程名与StubService的关系表插入到StubServiceMatcher类的map中(StubServiceMatchInjector)。
对于manifest的操作,框架内提供了不少工具方法,比如获取所有声明的进程,值得好好学习一下;对于class的操作使用的是javasisst,这在之前的AOP文章中也介绍过,感兴趣的同学自行查阅。
在读源码过程中发现两个值得关注的问题:
一是DispatcherProvider伪造的DispatcherCursor继承MatrixCursor,它通常用于返回几条固定的已知记录,不需要从数据库查询这种场景。
二是跨进程传递bundle对象时,如果bundle中存放了parcelable对象需要手动设置setClassLoader。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/2a1e85640f22e13fb8653435b3c35cc1.jpeg)
最后,面试前该准备哪些资源复习?
其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
这里再分享一下我面试期间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)
《Android开发七大模块核心知识笔记》
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
试时先看看自己复习到了哪个阶段就好。
这里再分享一下我面试期间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)
《Android开发七大模块核心知识笔记》
[外链图片转存中…(img-6HYfGziG-1712238314761)]
[外链图片转存中…(img-yF9FwhSu-1712238314762)]
《960全网最全Android开发笔记》
[外链图片转存中…(img-tIKlIP96-1712238314762)]
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。