Android WebView的问题
- WebView导致的OOM问题
- Android版本不同,采用了不同的内核,兼容性Crash
- WebView代码质量,WebView和Native版本不一致,导致Crash
Android App进程的内存使用是有限制的,通过以下两个方法可以查看App可用内存的大小:
ActivityManager.getMemoryClass()获得正常情况下可用内存的大小
ActivityManager.getLargeMemoryClass()可以获得开启largeHeap最大的内存大小
以Google Nexus 6P为例,正常情况下可用内存是192M,最大可用内存是512M。
Android WebView内存占用很大,在低配置手机上,常有这样的场景发生:连续开启多个WebView页面,此时栈底的Activity被销毁了,返回时Activity需要重新创建;或者连续开启多个Webview页面,App中的某些单例对象被系统回收,此时如果未做特殊处理,就容易报数据空指针错误。这些都是WebView导致的OOM的表现。WebView独立进程可以避免对主进程内存的占用。
问题2 3也是Webview容易发生的,国产手机各家的内核都不太一样,Web页面兼容没有做到位容易导致Crash;随着App版本和App-Web版本发布相互独立,web页面对native的依赖会变得复杂,版本兼容性没有做好,也会导致webview与native进行交互时发生crash。
微信经验介绍
启动微信时进程列表
打开微信公众号时进程列表
image.png
打开微信小程序时进程列表
image.png
以上是微信的进程list,简单分析一下各个进程的功能如下:
com.tencent.mm :微信主进程,会话和朋友圈相关
com.tencent.mm:push :微信push, 保活
com.tencent.mm:tools 和 com.tencent.mm:support : 功能支持,比如微信中打开一个独立网页是在tools进程中
com.tencent.mm:exdevice :估计是监控手机相关的
com.tencent.mm:sandbox :公众号进程,公众号打开的页面都是在该进程中运行
com.tencent.mm:appbrand :适用于小程序,测试发现微信每启动一个小程序,都会建立一个独立的进程 appbrand[0], 最多开5个进程
微信通过这样的进程分离,将网页、公众号、小程序分别分离到独立进程中,有效的增加了微信的内存使用,避免了WebView对主进程内存的占用导致的主进程服务异常;同时也通过这种独立进程方案避免了质量参差不齐的公众号网页和小程序Crash影响主进程的运行。由此可见,WebView独立进程方案是可行的,也是必要的。
如何实现WebView独立进程
WebView独立进程的实现
WebView独立进程的实现比较简单,只需要在AndroidManifest中找到对应的WebViewActivity,对其配置"android: process"属性即可。如下:
WebView进程与主进程间的数据通信
首先我们了解下为何两个进程间不能直接通信
image.png
Android多进程的通讯方式有很多种,主要的方式有以下几种:
- AIDL
- Messenger
- ContentProvider
- 共享文件
- 组件间Bundle传递
- Socket传输
考虑到WebView主要的通讯方式就是方法调用,所以采用AIDL方式。AIDL本质采用的是Binder机制,这里贴一张网上的Binder机制原理图,具体的AIDL的使用方式这里不赘述, 以下是几个核心AIDL文件
image.png
IBinderPool: Webview进程和主进程的通讯可能涉及到多个AIDL Binder,从功能上来讲,我们也会把不同功能的接口写成不同的AIDL Binder,所以IBinderPool用于满足调用方根据不同类型获取不同的Binder。
interface IBinderPool {
IBinder queryBinder(int binderCode); //查找特定Binder的方法
}
IWebAidlInterface: 最核心的AIDL Binder,这里把WebView进程对主进程的每一个调用看做一次action, 每个action都会有唯一的actionName, 主进程会提前注册好这些action,action 也有级别level,每次调用结束通过IWebAidlCallback返回结果
interface IWebAidlInterface {
/**
- actionName: 不同的action, jsonParams: 需要根据不同的action从map中读取并依次转成其他
*/
void handleWebAction(int level, String actionName, String jsonParams, in IWebAidlCallback callback);
}
IWebAidlCallback: 结果回调
interface IWebAidlCallback {
void onResult(int responseCode, String actionName, String response);
}
为了维护独立进程和主进程之间的连接,避免每次aidl调用时都去重新进行binder连接和获取,需要专门提供一个类去维护连接,并根据条件返回Binder. 这个类就叫做 RemoteWebBinderPool
public class RemoteWebBinderPool {
public static final int BINDER_WEB_AIDL = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile RemoteWebBinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private RemoteWebBinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static RemoteWebBinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (RemoteWebBinderPool.class) {
if (sInstance == null) {
sInstance = new RemoteWebBinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, MainProHandleRemoteService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() { // 5
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
};
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { // 6
@Override
public void binderDied() {
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
public static class BinderPoolImpl extends IBinderPool.Stub {
private Context context;
public BinderPoolImpl(Context context) {
this.context = context;
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_WEB_AIDL: {
binder = new MainProAidlInterface(context);
break;
}
default:
break;
}
return binder;
}
}
}
从代码中可以看到这个连接池连接的是主进程 MainProHandleRemoteService.
public class MainProHandleRemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Binder mBinderPool = new RemoteWebBinderPool.BinderPoolImpl(context);
return mBinderPool;
}
}
Native-Web交互和接口管理
一次完整的Web页面和Native交互过程是这样的:
- Native打开页面时注册接口:“webView.addJavascriptInterface(jsInterface, “webview”);” 其中jsInterface是JsRemoteInterface类的实例:
public final class JsRemoteInterface {
@JavascriptInterface
public void post(String cmd, String param) {
…
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后
你要问前端开发难不难,我就得说计算机领域里常说的一句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。
我特地针对初学者整理一套前端学习资料,免费分享给大家,戳这里即可免费领取
句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。
我特地针对初学者整理一套前端学习资料,免费分享给大家,戳这里即可免费领取
[外链图片转存中…(img-SlnP6IEX-1712083097400)]