前语

周六周日气候不错,出去和朋友爬山了,山底盘太低路崎岖车都开不进去走了一段,累并快乐着。

方案把组件化这块更新完,更新一下shadow-ASM-systrace-字节码部分

这两天发现之前的12个模块有些没加进去,所以又弥补了一些内容。独立于12模块之外的内容自称系统 《十二模块之弥补部分:Android知识系统》

重视大众号:初一十五a
解锁 《Android十三大板块PDF》 ;让学习更靠近未来实战。已构成PDF版

十三个模块PDF内容如下

1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试包罗万象
3.Android车载运用大合集,从零开端一同学
4.功能优化大合集,离别优化烦恼
5.Framework大合集,从里到外剖析的明明白白
6.Flutter大合集,进阶Flutter高档工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对作业需求
10.Android根底篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+项目实战+电子书
12.大厂高档Android组件化强化实战
13.十二模块之弥补部分:其他Android知识系统

整理不易,重视一下吧。开端进入正题,ღ( ・ᴗ・` )

从爱奇艺APP看组件化架构实践

1.关于组件化

跟着项目结构越来越巨大,模块与模块间的鸿沟逐步变得不明晰,代码保护越来越困难,乃至编译速度都成为影响开发功率的瓶颈。

组件化拆分是比较常见的解决方案,一方面解决模块间的耦合联系、将通用模块下沉,另一方面做到各模块代码和资源文件的阻隔,这样便能够定心进行模块按需编译、独自测试等等。

但随之而来的问题也更加突出,模块的精细化拆分不可防止的增加了模块间的通讯成本。通讯的两边是一个C/S架构,假如服务端与客户端同属一个进程咱们称之为本地服务,假如分属不同进程称之为长途服务。留意这儿的服务不只限于Android中的Service组件,而是一种能够对外供给功用或数据的才能。

关于同进程的通讯比较简略,经过注册本地接口和完结就能够完结,假如你现已接入ARouter,直接声明服务类继承IProvider+Router注解就完结了服务的注册。

可是关于跨进程的通讯就比较复杂了,在Android系统中IPC通讯经过Binder完结,对参与通讯的数据格式做了约束,也便是基本数据类型或许完结Parcelable接口的类型。

多进程的优点是能够占用更多的系统资源,而且独立中心进程能够免受非中心事务出现异常状况导致整个APP溃散不可用。

跨进程通讯事务场景比较复杂,既要确保服务端的可靠性,还需求支撑callback,一般Service是首选。

2.根据service的IPC通讯

咱们回想一下是怎么运用Service进行跨进程通讯的。

  • 声明供给服务的AIDL接口。
  • 创建Service,并在onBind办法回来完结Stub接口的Binder方针。
  • Client端经过intent bindService,并传入ServiceConnection方针,在onServiceConnected回调获取Service供给的Binder方针。

本质上是将Binder方针(精确的说是署理方针)在进程间进行传递,而Service仅仅一个载体。

在组件化的大事务背景下,模块间的通讯接口数量或许许多,按这套方案会有许多问题。

  • 需求书写AIDL文件和Service类。
  • bindService是异步操作,需求写回调,与本地服务调用办法不统一。
  • 没用统一的Binder办理者,怎么处理Binder Die,怎么完结Binder缓存等问题。 这样咱们能够总结出一个好的组件化通讯结构需求具有特点或许说要完结的诉求。

3.组件化跨进程通讯的中心诉求

  • 可不能够不写AIDL文件,用声明一般接口类的办法声明一个长途服务接口;可不能够不写Service,因为IPC通讯的本质仅仅传递Binder而已。
  • 咱们期望像调用本地服务一样调用长途服务,防止回调地狱,即长途服务的获取是阻塞式调用。
  • 怎么办理各个进程供给的长途服务,确保高可用。

4.Andromeda

Andromeda是爱奇艺开源的组件化IPC通讯解决方案,它解决了上述的问题2和3,同时不需求书写Service,可是仍需求写AIDL文件。

关于这个问题,饿了吗早前开源的 Hermes结构 能够做到,原理是运用动态署理+反射的办法来替换AIDL生成的静态署理,可是不支撑oneway、in、out、inout等修饰符。

再后来,爱奇艺又开源 InterStellar ,完结了不需求书写AIDL文件,当运用跨进程接口时,声明@oneway/@in等注解完结IPC修饰符的增加。这样算是彻底的完结了长途调用像本地调用一样简略。但不知为何与Andromeda没有合并到一个项目中,工程代码也好久没有人保护。

此外Andromeda还有一些Feature:

  • 加入了跨进程通讯的作业总线,即跨进程版EventBus。
  • 加入了对增强进程稳定性的考量,经过为各个进程预先插桩Service,在获取长途服务时用前台UI组件 (Activity/Fragment/View)绑定插桩的Service,终究提高后台服务进程优先级。
  • 支撑IPCCallback。
  • 支撑配置Binder分发办理中心(Dispatcher)所属进程。

Andromeda Github地址

咱们先来看一下简略的运用

//注册本地服务 第一个参数是接口class将来用作key,第二参数为接口完结类。
Andromeda.registerLocalService(ICheckApple.class, new CheckApple());
//运用本地服务
ICheckApple checkApple = Andromeda.getLocalService(ICheckApple.class);
------------------------------
//注册长途服务 第二个参数为IBinder类型,将来会在进程间传递
Andromeda.registerRemoteService(IBuyApple.class, BuyAppleImpl.getInstance());
//运用长途服务,传入UI组件(this)测验提高长途服务进程的优先级
Andromeda.with(this).getRemoteService(IBuyApple.class);

整体API的规划明晰且全部都是同步完结,具体运用见工程示例,本篇的要点是剖析内部原理。

尽管是源码剖析,但我不准备贴过多的源码,这样阅读体验并不好;我会尽量抑制,真实有需求的小伙伴请自行查阅源代码,我的方针是把中心思维讲清楚。

5.架构剖析

咱们先理清几个概念,无论是作业总线仍是服务分发都需求一个中转存储中心,这个中心在Andromeda结构中叫Dispatcher。

Dispatcher

它是一个AIDL接口,各个进程在注册服务时需求首要拿到DispatcherProxy,然后将本进程服务Binder传送给DispatcherProxy存储,当其他进程需求运用该服务时,也需求先获取一个DispatcherProxy,然后读取DispatcherProxy中的缓存Binder,并在自己进程存储一份缓存,这样本进程下次获取相同的服务时就不需求进行IPC调用了。

咱们来看一下Dispatcher供给了哪些功用。

# IDispatcher.aidl
interface IDispatcher {
   //经过服务称号获取Binder包装类BinderBean
   BinderBean getTargetBinder(String serviceCanonicalName);
   //保留接口暂时为空完结
   IBinder fetchTargetBinder(String uri);
   //注册本地的RemoteTransfer
   void registerRemoteTransfer(int pid,IBinder remoteTransferBinder);
   //注册/反注册长途服务
   void registerRemoteService(String serviceCanonicalName,String processName,IBinder Binder);
   void unregisterRemoteService(String serviceCanonicalName);
   //发送作业
   void publish(in Event event);
}

Dispatcher地点进程能够是主进程也能够用户自定义的进程,为什么要评论Dispatcher所属进程呢?因为作为组件化通讯中心的Center一旦狗带,将导致之前注册服务不可用,所以需求将它放在运用生命周期最长的进程中,一般这个进程是主进程,但关于相似音乐播放器相关的app来说,或许是一个独立的播放器进程,所以结构为咱们供给了一个配置项能够显式的声明Dispatcher地点进程。

#主工程的build.gradle增加声明
dispatcher{
    process ":downloader"
}

Dispatcher架构图

关于Android组件化的深度分析(五)爱奇艺篇

RemoteTransfer

上面说到各个进程自己本身也需求办理(缓存)从Dispatcher获取的Binder,防止重复的IPC恳求;别的因为作业总线的需求,各个进程需求向Dispatcher进程注册本进程组件办理员,这样当作业pubish后,Dispatcher才能将作业发送给各个进程,这个各个进程办理员便是RemoteTransfer。

IRemoteTransfer是一个AIDL接口,RemoteTransfer是它的完结类,RemoteTransfer还完结了IRemoteServiceTransfer接口。

这儿需求一张类图来帮你理清思路:

关于Android组件化的深度分析(五)爱奇艺篇

#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);
    }
}

长途服务

长途服务是结构的中心,对长途服务的操作便是两个,一是注册长途服务,二是获取长途服务。

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

关于Android组件化的深度分析(五)爱奇艺篇
1.客户端经过 registerRemoteService(String serviceCanonicalName, T stubBinder) 注册本进程可供给的长途服务,stubBinder即服务完结类。

  1. 调用RemoteTransfer的registerStubService办法。

  2. 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());
    }
}

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

关于Android组件化的深度分析(五)爱奇艺篇

  1. Andromeda入口经过getRemoteService获取长途服务。

2-4. 与提高进程优先级有关,咱们暂且不评论。

  1. 向RemoteTransfer恳求获取长途服务的包装bean。

6-7. RemoteTransfer恳求RemoteServiceTransfer帮助先从本进程的缓存中查找方针Binder,假如找到直接回来。

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

  1. RemoteServiceTransfer经过DispatcherProxy建议IPC通讯,恳求长途服务Binder。

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

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

  2. 将Binder回来给调用方。同样图中蓝色的节点表明获取服务的进程,也便是Client进程,赤色节点表明Dispatcher进程。至此,长途服务的注册与获取流程剖析结束。

进程优先级

上面说到在获取长途服务时,结构做了提高进程优先级的作业。一般状况下运用长途服务的端(简称Client端)处于前台进程,而Server端进程现已注册结束,往往处于后台。为了提高Server端的稳定性,最好能将Server端的进程优先级与Client坚持接近,不然简单出现被LMK(Low Memory Killer)回收的状况。

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

整套流程终究经过AMS的updateOomAdjLocked办法完结。

关于Android组件化的深度分析(五)爱奇艺篇
回到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进程发生在上一章节获取长途服务时,流程如下图:

关于Android组件化的深度分析(五)爱奇艺篇
图中模块根据地点进程分为三部分:

  • 蓝色表明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。

#DispatcherCursor
public static IBinder stripBinder(Cursor cursor) {
    if (null == cursor) {
        return null;
    }
    Bundle bundle = cursor.getExtras();
    //从cursor中取出bundle需求设置classLoader
    bundle.setClassLoader(BinderWrapper.class.getClassLoader());
    BinderWrapper BinderWrapper = bundle.getParcelable(KEY_Binder_WRAPPER);
    return null != BinderWrapper ? BinderWrapper.getBinder() : null;
}

因为默认状况下bundle传输运用的ClassLoaderBootClassLoader,而BootClassLoader只能加载系统类,咱们本工程的class需求运用PathClassLoader进行加载,因而需求额外的调用bundle的setClassLoader办法设置类加载器,详见Bundle.setClassLoader()办法解析 。

缺点

  • 服务需求手动注册,这个时机不好把握。最好能供给一个自动注册服务的开关,上层不需求重视服务的注册。
  • 发送一次作业需求多次IPC调用功率低,有优化空间。
  • 仍需求书写AIDL文件。

至此,Andromeda中心的原理咱们就剖析完了,尽管有些问题有待完善,但现已给咱们供给了许多优秀的解决问题的思路,无论是继续优化仍是精简一下本地化都是不错的挑选。

重视大众号:初一十五a
解锁 《Android十三大板块PDF》 ;让学习更靠近未来实战。已构成PDF版

十三个模块PDF内容如下

1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试包罗万象
3.Android车载运用大合集,从零开端一同学
4.功能优化大合集,离别优化烦恼
5.Framework大合集,从里到外剖析的明明白白
6.Flutter大合集,进阶Flutter高档工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对作业需求
10.Android根底篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+项目实战+电子书
12.大厂高档Android组件化强化实战 13.十二模块之弥补部分:其他Android知识系统

整理不易,重视一下吧。ღ( ・ᴗ・` )