一、Android WebView开发(一):基础应用
二、Android WebView开发(二):WebView与Native交互
三、Android WebView开发(三):WebView性能优化
四、Android WebView开发(四):WebView独立进程解决方案
五、Android WebView开发(五):自定义WebView工具栏

附GitHub源码:WebViewExplore


一、WebView面临的问题:

1、WebView导致的内存泄漏问题,及后续引起的OOM问题。

2、Android版本不同,采用了不同的内核,兼容性Crash。

3、WebView代码质量,WebView和Native版本不一致,导致Crash。

二、WebView独立进程的实现:

WebView独立进程的实现比较简单,只需要在AndroidManifest中找到对应的WebViewActivity,对其配置"android: process"属性即可。如下:

  <!--独立进程WebViewActivity--><activityandroid:name=".SingleProcessActivity"android:configChanges="orientation|keyboardHidden|screenSize"android:process=":remoteWeb" />

我们可以通过 adb shell ps|grep com.hongri.webview 指令查看验证,添加独立进程前后的对进程的打印情况:

可以看到已经在主进程的基础上,新生成了一个remoteWeb独立进程。

有两个进程就必然会涉及到进程间的通信,所以最终涉及到的交互及通信包括:

前端与Native端、独立进程与主进程间的通信。

后面会通过下面的demo逐个介绍,如下图独立进程中的SingleProcessActivity页面加载了一个本地的html前端页面,页面包含两个按钮,点击后分别最终会调用主进程的showToast方法doCalculate方法,从而实现前端及进程间的交互。

1、前端与Native端的交互:

[可参考:二、WebView与Native的交互]

(1)、注册JS映射接口,并实现:

加载本地的一个remote_web.html文件:

public static final String CONTENT_SCHEME = "file:///android_asset/remote/remote_web.html";
//允许js交互
webSettings.setJavaScriptEnabled(true);
JsRemoteInterface remoteInterface = new JsRemoteInterface();
remoteInterface.setListener(this);
//注册JS交互接口
mWebView.addJavascriptInterface(remoteInterface, "webview");
mWebView.loadUrl(CONTENT_SCHEME);

其中 JsRemoteInterface 为对应的前端交互映射类,源码如下:

/*** Create by zhongyao on 2021/12/14* Description: 前端交互映射类*/
public class JsRemoteInterface {private static final String TAG = "JsRemoteInterface";private final Handler mHandler = new Handler();private IRemoteListener listener;/*** 前端调用方法* @param cmd* @param param*/@JavascriptInterfacepublic void post(final String cmd, final String param) {mHandler.post(new Runnable() {@Overridepublic void run() {if (listener != null) {try {listener.post(cmd, param);} catch (RemoteException e) {e.printStackTrace();}}}});}public void setListener(IRemoteListener remoteListener) {listener = remoteListener;}
}

(2)、前端关键代码实现:

  • remote_web.html:
</script>
<div class="item" style="font-size: 18px; color: #ffffff" onclick="callAppToast()">调用: showToast</div>
<div class="item" style="font-size: 18px; color: #ffffff" onclick="callCalculate()">调用: appCalculate</div>
<script src="remote_web.js" charset="utf-8"></script>
<script>function callAppToast() {dj.post("showToast", {message: "this is action from html"});}function callCalculate() {dj.postWithCallback("appCalculate", {firstNum: "1", secondNum: "2"}, function(res) {dj.post("showToast", {message: JSON.stringify(res)});});}
</script>
  • remote_web.js:
dj.post = function(cmd,para){if(dj.os.isIOS){var message = {};message.meta = {cmd:cmd};message.para = para || {};window.webview.post(message);}else if(window.dj.os.isAndroid){window.webview.post(cmd,JSON.stringify(para));}
};
dj.postWithCallback = function(cmd,para,callback,ud){var callbackname = dj.callbackname();dj.addCallback(callbackname,callback,ud);if(dj.os.isIOS){var message = {};message.meta  = {cmd:cmd,callback:callbackname};message.para = para;window.webview.post(message);}else if(window.dj.os.isAndroid){para.callback = callbackname;window.webview.post(cmd,JSON.stringify(para));}
};

2、独立进程与主进程的交互:

(1)、定义一个AIDL文件 CalculateInterface:

并将暴露给其他进程的方法在该文件中声明,如下图:

需要注意的是,该文件的包名需要跟java文件夹下的源码的主包名一致。

aidl文件源码如下,定义了上面描述的两个方法:

// CalculateInterface.aidl
package com.hongri.webview;// Declare any non-default types here with import statementsinterface CalculateInterface {double doCalculate(double a, double b);void showToast();
}

(2)、主进程中定义一个远程服务RemoteService【可看做Server】:

此服务用来监听客户端的连接请求,并实现该AIDL接口,完整代码如下:

/*** @author hongri* @description 远程Service【Server】* 【此Service位于主进程中】*/
public class RemoteService extends Service {private static final String TAG = "RemoteService";@Overridepublic IBinder onBind(Intent arg0) {return mBinder;}@Overridepublic boolean onUnbind(Intent intent) {return super.onUnbind(intent);}@Overridepublic void onCreate() {super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();}private final CalculateInterface.Stub mBinder = new CalculateInterface.Stub() {/*** remoteWeb进程调用主进程的showToast方法,实现进程间的通信。* @throws RemoteException*/@Overridepublic void showToast() throws RemoteException {Log.d(TAG, "showToast" + " processName:" + ProcessUtil.getProcessName(getApplicationContext()) + " isMainProcess:" + ProcessUtil.isMainProcess(getApplicationContext()));Handler handler = new Handler(Looper.getMainLooper());handler.post(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "remoteWeb进程调用了主进程的showToast方法", Toast.LENGTH_LONG).show();}});}/*** remoteWeb进程调用主进程的doCalculate方法,实现进程间通信。* @param a* @param b* @return* @throws RemoteException*/@Overridepublic double doCalculate(double a, double b) throws RemoteException {Calculate calculate = new Calculate();final double result = calculate.calculateSum(a, b);Handler handler = new Handler(Looper.getMainLooper());handler.post(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "remoteWeb进程调用了主进程的doCalculate方法, 计算结果为:" + result, Toast.LENGTH_LONG).show();}});return result;}};
}

(3)、在独立进程SingleProcessActivity中绑定远程服务RemoteService:

    /*** 绑定远程服务RemoteService*/private void initService() {Intent intent = new Intent();intent.setComponent(new ComponentName("com.hongri.webview", "com.hongri.webview.service.RemoteService"));bindService(intent, mConn, BIND_AUTO_CREATE);}

(4)、绑定成功后,将服务端返回的Binder对象(service)转换成AIDL接口所属的类型(CalculateInterface):

    private CalculateInterface mRemoteService;private final ServiceConnection mConn = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.d(TAG, "onServiceConnected");//当Service绑定成功时,通过Binder获取到远程服务代理mRemoteService = CalculateInterface.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.d(TAG, "onServiceDisconnected");mRemoteService = null;}};

(5)、此时就可以根据前端post方法的调用,来根据 mRemoteService 实例针对性的调用主进程的showToast与doCalculate方法:

    @Overridepublic void post(String cmd, String param) throws RemoteException {Log.d(TAG, "当前进程name:" + ProcessUtil.getProcessName(this) + " 主进程:" + ProcessUtil.isMainProcess(this));dealWithPost(cmd, param);}/*** 前端调用方法处理** @param cmd* @param param* @throws RemoteException*/private void dealWithPost(String cmd, String param) throws RemoteException {if (mRemoteService == null) {Log.e(TAG, "remote service proxy is null");return;}switch (cmd) {case "showToast":Log.d(TAG, "showToast");mRemoteService.showToast();break;case "appCalculate":Log.d(TAG, "appCalculate --> " + param);try {JSONObject jsonObject = new JSONObject(param);double firstNum = Double.parseDouble(jsonObject.optString("firstNum"));double secondNum = Double.parseDouble(jsonObject.optString("secondNum"));double calculateResult = mRemoteService.doCalculate(firstNum, secondNum);Log.d(TAG, "calculateResult:" + calculateResult);} catch (JSONException e) {e.printStackTrace();}break;default:Log.d(TAG, "Native端暂未实现该方法");break;}}

点击前端页面的doCalculate方法,调用结果如下:

表示最终实现了remoteWeb 独立进程与主进程之间的通信。

附GitHub源码:WebViewExplore

参考:

Android WebView独立进程解决方案

Android WebView开发(四):WebView独立进程解决方案相关推荐

  1. Android画板开发(四) 添加背景和保存画板内容为图片

    Android画板开发(一) 基本画笔的实现 Android画板开发(二) 橡皮擦实现 Android画板开发(三) 撤销反撤销功能实现 Android画板开发(四) 添加背景和保存画板内容为图片 A ...

  2. Android音频开发(四):音频播放模式

    一.Android音频开发(一):音频基础知识 二.Android音频开发(二):录制音频(WAV及MP3格式) 三.Android音频开发(三):使用ExoPlayer播放音频 四.Android音 ...

  3. 【安卓开发 】Android初级开发(八)WebView网页

    1.网页的基本组成 2.WebView的常用方法 WebView webView = findViewById(R.id.webvv);//加载线上网页webView.loadUrl("ht ...

  4. android应用开发(25)---进程和应用程序生命周期

    进程和应用程序生命周期 在大多数情况下,每个Android应用程序都在其自己的Linux进程中运行.当需要运行某些代码时,会为应用程序创建此进程,并且该进程将保持运行状态,直到不再需要它为止,并且 系 ...

  5. 【安卓开发 】Android初级开发(四)ListView

    ListView的实现步骤 1.单独一行的布局可以如下 <?xml version="1.0" encoding="utf-8"?> <Lin ...

  6. WebView独立进程方案-基于腾讯X5的二次封装

    文章目录 更新记录 2021-12-06 2021-10-20 前言概述 基本使用 架构流程 命令模式 预初始化 前端使用文档与代码 参考 更新记录 2021-12-06 WebView支持多进程多任 ...

  7. WebView独立进程方案

    前言 最近正好在做WebView独立进程的是事情,也趁此机会,写一下我这边的方案以及实现吧.参考的文章链接也放到了下面,有兴趣的小伙伴可以康康. 背景 我这边的业务场景主要是游戏内打开H5活动.因为W ...

  8. 在独立进程中运行webview

    App中大量Web页面的使用容易导致App内存占用巨大,存在内存泄露,崩溃率高等问题,WebView独立进程的使用是解决Android WebView相关问题的一个合理的方案. 为什么要采用WebVi ...

  9. WebView开发(三):WebView性能优化

    一.Android WebView开发(一):基础应用 二.Android WebView开发(二):WebView与Native交互 三.Android WebView开发(三):WebView性能 ...

最新文章

  1. seaweedfs 源码笔记(一)
  2. 史上最全 Python Re 模块讲解(一)
  3. [Bug]The maximum array length quota (16384) has been exceeded while reading XML data.
  4. html5 canvas实现图片玻璃碎片特效
  5. 【Linux学习】常用指令-sortunique
  6. 博客园“图灵杯”第3届博问大赛比赛结果
  7. HTML,CSS的class与id命名规则
  8. 因为安全层不能与远程计算机协商兼容的参数,L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到了一个处理错误(转)...
  9. java打印计算机_在Java程序中实现高精度打印
  10. An internal error occurred during: Launching web on MyEclipse Tomcat
  11. 3月AV-Comparatives杀毒软件测试结果出炉
  12. Android面试题整理
  13. PPT嵌入视频,添加控件按钮控制视频播放
  14. 三极管当做开关的导通方式
  15. 文学写作素材网站分享
  16. 《腾讯传》四、从寄生虫到蜕变上市—企鹅的成人礼
  17. office2016、office365和office其它版本JH
  18. 免费在线格式转换网站
  19. 【OpenCV入门学习--python】Image Segmentation with Distance Transform and Watershed Algorithm图像分割
  20. FreeRTOS STM32CubeMX port.c(483): error: A1586E: Bad operand types (UnDefOT, Constant) ...

热门文章

  1. 【STM32学习笔记】(4)—— STM32工程文件详解
  2. 计算机应用等基本知识,计算机应用基本常识
  3. BAN和GNY逻辑的区别
  4. 普通io口模拟串口通信
  5. mysql双因子认证_目前主流的双因子认证方式有哪些?
  6. Pr:自动匹配到序列
  7. 数据分析面试经验(给自己一个梳理的过程)
  8. qt opencv mingw转msvc
  9. java监听焦点事件_java焦点事件监听器是什么?其他事件有哪些?
  10. Mybatis源码分析-getMapper()源码分析