前言
最近正好在做WebView独立进程的是事情,也趁此机会,写一下我这边的方案以及实现吧。参考的文章链接也放到了下面,有兴趣的小伙伴可以康康。
背景
我这边的业务场景主要是游戏内打开H5活动。因为WebView本身占用内存其实很大,而游戏本身的内存已经很高了,主进程再启动WebView又是一笔额外的内存消耗,因此想着需要将WebView放到独立的进程进行运行,这样即使WebView崩溃也不会导致游戏的崩溃。并且由于WebView虽然是独立进程但仍然挂靠于主进程,因此应用内诸多逻辑需要多进程复用。
方案
可行性
为什么独立进程方案对于我来说是可行的?因为项目中WebView的启动是以Activity的形式启动,意味着可在打开WebView的时候开启新的进程。
独立进程
恭喜你,你已经打开独立进程了,好吧,我开玩笑的,这只是最简单的一步。
通信方式
独立进程方案其实最大的难点就在于独立进程与主进程之间的通信。项目架构采用的是SPI结构(对外提供api层,实现在内部impl层),还不支持跨进程通信的,因此这个跨进程通信只能我们自己动手做啦。由于WebView与各方交互以方法调用为主,因此我们选择使用AIDL作为进程通信的手段。
项目中因为涉及到较多的为H5调用Native方法较多,因此本文主要也从这一方面进行讲解。
独立进程设计初衷是为了让WebView进程只负责WebView相关事宜,因此更多的是WebView进程向主进程发送消息并通过提供的AIDL回调接口将所需数据/通信结果返还给WebView进程。
因为是SPI结构,并且为了让进程通信的交互收敛到impl层(不对外暴露),我是在将WebView事先做成了独立组件,所有访问都需要从IWebViewService
暴露的接口进入。而WebView进程与主进程的通信就在WebViewService(impl层)
通过AIDL进行实现。
具体AIDL实现内容可以参考官方文档以及文末链接所给出的实现demo。
统一方法调用的接口
由于H5针对Native不同的模块调用相应的能力不同,我们需要给到每个模块统一的执行接口(Command
),这样,只要注册了这一统一接口并执行这一接口(handleWebAction
)(PS:这里觉得原博主好聪明呀,给了一个统一的注入接口,这样,实现可以在各自模块实现即可)。
在api层提供Command的接口
举例如下:
private final Command oneMethod = new Command()
@Override
public String name() {
return "oneMethod";
}
@Override
public void execute(Context context, Map params, IWebAidlResultCallback resultCallback) {
try {
boolean a = (boolean) params.get("a");
Map res = oneMethod(context, a);
Log.d(TAG, res.toString());
resultCallback.onResult(name(), res);
} catch (Exception e) {
Log.d(TAG, e.toString());
}
}
};
提供注册Command
的方法、执行Command
的方法
Command内部管理与调度执行
内部管理和调度执行可以参考下文末链接内容,对于不同层级的命令(如涉及UI/不涉及UI)可以归纳一个抽象类命令集,再由不同层级命令集进行继承。
接着再根据执行Command
方法传入的层级归到相应的命令集,再由命令管理进行统一管理CommandsManager
。
最后调度中心CommandDispatcher
进行命令的执行环境(主进程 or WebView进程)(这里别忘记两个进程是同一应用)。
Binder连接池
在说连接池之前,这里放一张AIDL的Binder机制图。
主进程和WebView进程的设计完之后,还需要考虑如何将二者连接在一起,那就是在主进程内开启Service,WebView进程去bindService
。
保持连接
保持连接其实很简单,就只需要在binder连接的时候加上binder死亡通知IBinder.DeathRecipient
,然后重新连接即可。
多个AIDL功能接口
虽然项目中使用H5与Native即主进程交互最为频繁,但是主进程还有些其他需要监听WebView状态的地方,比如WebView的退出、打开链接的错误返回等等,这些是较为固定且也需要进程间通信的功能,而Service声明多个似乎有些多余。考虑到这种情况,就需要思考下如何使得Service管理多个binder。
binder连接池的概念就提出来了。通过AIDL接口方式提供一个BinderPool
用来提供不同AIDL功能的Binder。
实现该AIDL的代理类WebviewBinderPool
,根据不同的binderType
返回不同的binder。在ServiceonBind
过程进行绑定webviewBinderPool
即可。
WebviewBinderPool binderPool = WebviewBinderPool.getInstance(context);
IBinder binder = binderPool.queryBinder(binderType1);
webAidlInterface = IWebAidlInterface.Stub.asInterface(binder);
总结
至此,WebView独立进程方案中我所认为的一些重点和需要处理的内容都已经展现给了大家。最好可以以Activity形式启动WebView,而后只要处理好进程间的通信,WebView独立进程方案还是容易想通的。这一独立进程方案优点很明显,当然也会有着一些反向缺点。
优点:
- 减少对游戏主进程的内存占用
- 游戏进程不会因WebView的崩溃而崩溃,减少OOM率
缺点:
- WebView进程与主进程的连接可能会失败
- 游戏内存占用过大时,WebView进程启动可能会导致游戏主进程被杀掉
针对缺点,其实连接失败的可能性比较小,可以加上失败重连机制。主要是第二点还需要通过其他途径手段来减小内存呀。
OK,那么WebView独立进程方案分享给就到这里啦,最后贴上我的参考资料。
参考资料: