• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Android WebView开发(四)WebView独立进程解决方案

武飞扬头像
红日666
帮助1

一、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"属性即可。如下:

  1.  
    <!--独立进程WebViewActivity-->
  2.  
    <activity
  3.  
    android:name=".SingleProcessActivity"
  4.  
    android:configChanges="orientation|keyboardHidden|screenSize"
  5.  
    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";
  1.  
    //允许js交互
  2.  
    webSettings.setJavaScriptEnabled(true);
  3.  
    JsRemoteInterface remoteInterface = new JsRemoteInterface();
  4.  
    remoteInterface.setListener(this);
  5.  
    //注册JS交互接口
  6.  
    mWebView.addJavascriptInterface(remoteInterface, "webview");
  7.  
    mWebView.loadUrl(CONTENT_SCHEME);

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

  1.  
    /**
  2.  
    * Create by zhongyao on 2021/12/14
  3.  
    * Description: 前端交互映射类
  4.  
    */
  5.  
    public class JsRemoteInterface {
  6.  
     
  7.  
    private static final String TAG = "JsRemoteInterface";
  8.  
    private final Handler mHandler = new Handler();
  9.  
    private IRemoteListener listener;
  10.  
     
  11.  
    /**
  12.  
    * 前端调用方法
  13.  
    * @param cmd
  14.  
    * @param param
  15.  
    */
  16.  
    @JavascriptInterface
  17.  
    public void post(final String cmd, final String param) {
  18.  
    mHandler.post(new Runnable() {
  19.  
    @Override
  20.  
    public void run() {
  21.  
    if (listener != null) {
  22.  
    try {
  23.  
    listener.post(cmd, param);
  24.  
    } catch (RemoteException e) {
  25.  
    e.printStackTrace();
  26.  
    }
  27.  
    }
  28.  
    }
  29.  
    });
  30.  
    }
  31.  
     
  32.  
    public void setListener(IRemoteListener remoteListener) {
  33.  
    listener = remoteListener;
  34.  
    }
  35.  
    }
学新通

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

  • remote_web.html:
  1.  
    </script>
  2.  
    <div class="item" style="font-size: 18px; color: #ffffff" onclick="callAppToast()">调用: showToast</div>
  3.  
    <div class="item" style="font-size: 18px; color: #ffffff" onclick="callCalculate()">调用: appCalculate</div>
  4.  
    <script src="remote_web.js" charset="utf-8"></script>
  1.  
    <script>
  2.  
    function callAppToast() {
  3.  
    dj.post("showToast", {message: "this is action from html"});
  4.  
    }
  5.  
     
  6.  
    function callCalculate() {
  7.  
    dj.postWithCallback("appCalculate", {firstNum: "1", secondNum: "2"}, function(res) {
  8.  
    dj.post("showToast", {message: JSON.stringify(res)});
  9.  
    });
  10.  
    }
  11.  
    </script>
  • remote_web.js: 
  1.  
    dj.post = function(cmd,para){
  2.  
    if(dj.os.isIOS){
  3.  
    var message = {};
  4.  
    message.meta = {
  5.  
    cmd:cmd
  6.  
    };
  7.  
    message.para = para || {};
  8.  
    window.webview.post(message);
  9.  
    }else if(window.dj.os.isAndroid){
  10.  
    window.webview.post(cmd,JSON.stringify(para));
  11.  
    }
  12.  
    };
  13.  
    dj.postWithCallback = function(cmd,para,callback,ud){
  14.  
    var callbackname = dj.callbackname();
  15.  
    dj.addCallback(callbackname,callback,ud);
  16.  
    if(dj.os.isIOS){
  17.  
    var message = {};
  18.  
    message.meta = {
  19.  
    cmd:cmd,
  20.  
    callback:callbackname
  21.  
    };
  22.  
    message.para = para;
  23.  
    window.webview.post(message);
  24.  
    }else if(window.dj.os.isAndroid){
  25.  
    para.callback = callbackname;
  26.  
    window.webview.post(cmd,JSON.stringify(para));
  27.  
    }
  28.  
    };
学新通

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

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

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

学新通

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

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

  1.  
    // CalculateInterface.aidl
  2.  
    package com.hongri.webview;
  3.  
     
  4.  
    // Declare any non-default types here with import statements
  5.  
     
  6.  
    interface CalculateInterface {
  7.  
    double doCalculate(double a, double b);
  8.  
    void showToast();
  9.  
    }

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

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

  1.  
    /**
  2.  
    * @author hongri
  3.  
    * @description 远程Service【Server】
  4.  
    * 【此Service位于主进程中】
  5.  
    */
  6.  
    public class RemoteService extends Service {
  7.  
     
  8.  
    private static final String TAG = "RemoteService";
  9.  
     
  10.  
    @Override
  11.  
    public IBinder onBind(Intent arg0) {
  12.  
    return mBinder;
  13.  
    }
  14.  
     
  15.  
    @Override
  16.  
    public boolean onUnbind(Intent intent) {
  17.  
    return super.onUnbind(intent);
  18.  
    }
  19.  
     
  20.  
    @Override
  21.  
    public void onCreate() {
  22.  
    super.onCreate();
  23.  
    }
  24.  
     
  25.  
    @Override
  26.  
    public int onStartCommand(Intent intent, int flags, int startId) {
  27.  
    return super.onStartCommand(intent, flags, startId);
  28.  
    }
  29.  
     
  30.  
    @Override
  31.  
    public void onDestroy() {
  32.  
    super.onDestroy();
  33.  
    }
  34.  
     
  35.  
     
  36.  
    private final CalculateInterface.Stub mBinder = new CalculateInterface.Stub() {
  37.  
    /**
  38.  
    * remoteWeb进程调用主进程的showToast方法,实现进程间的通信。
  39.  
    * @throws RemoteException
  40.  
    */
  41.  
    @Override
  42.  
    public void showToast() throws RemoteException {
  43.  
    Log.d(TAG, "showToast" " processName:" ProcessUtil.getProcessName(getApplicationContext()) " isMainProcess:" ProcessUtil.isMainProcess(getApplicationContext()));
  44.  
    Handler handler = new Handler(Looper.getMainLooper());
  45.  
    handler.post(new Runnable() {
  46.  
    @Override
  47.  
    public void run() {
  48.  
    Toast.makeText(getApplicationContext(), "remoteWeb进程调用了主进程的showToast方法", Toast.LENGTH_LONG).show();
  49.  
    }
  50.  
    });
  51.  
    }
  52.  
     
  53.  
    /**
  54.  
    * remoteWeb进程调用主进程的doCalculate方法,实现进程间通信。
  55.  
    * @param a
  56.  
    * @param b
  57.  
    * @return
  58.  
    * @throws RemoteException
  59.  
    */
  60.  
    @Override
  61.  
    public double doCalculate(double a, double b) throws RemoteException {
  62.  
    Calculate calculate = new Calculate();
  63.  
    final double result = calculate.calculateSum(a, b);
  64.  
    Handler handler = new Handler(Looper.getMainLooper());
  65.  
    handler.post(new Runnable() {
  66.  
    @Override
  67.  
    public void run() {
  68.  
    Toast.makeText(getApplicationContext(), "remoteWeb进程调用了主进程的doCalculate方法, 计算结果为:" result, Toast.LENGTH_LONG).show();
  69.  
    }
  70.  
    });
  71.  
    return result;
  72.  
    }
  73.  
    };
  74.  
    }
学新通

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

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

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

  1.  
    private CalculateInterface mRemoteService;
  2.  
     
  3.  
    private final ServiceConnection mConn = new ServiceConnection() {
  4.  
    @Override
  5.  
    public void onServiceConnected(ComponentName name, IBinder service) {
  6.  
    Log.d(TAG, "onServiceConnected");
  7.  
    //当Service绑定成功时,通过Binder获取到远程服务代理
  8.  
    mRemoteService = CalculateInterface.Stub.asInterface(service);
  9.  
    }
  10.  
     
  11.  
    @Override
  12.  
    public void onServiceDisconnected(ComponentName name) {
  13.  
    Log.d(TAG, "onServiceDisconnected");
  14.  
    mRemoteService = null;
  15.  
    }
  16.  
    };
学新通

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

  1.  
    @Override
  2.  
    public void post(String cmd, String param) throws RemoteException {
  3.  
    Log.d(TAG, "当前进程name:" ProcessUtil.getProcessName(this) " 主进程:" ProcessUtil.isMainProcess(this));
  4.  
    dealWithPost(cmd, param);
  5.  
    }
  6.  
     
  7.  
    /**
  8.  
    * 前端调用方法处理
  9.  
    *
  10.  
    * @param cmd
  11.  
    * @param param
  12.  
    * @throws RemoteException
  13.  
    */
  14.  
    private void dealWithPost(String cmd, String param) throws RemoteException {
  15.  
    if (mRemoteService == null) {
  16.  
    Log.e(TAG, "remote service proxy is null");
  17.  
    return;
  18.  
    }
  19.  
    switch (cmd) {
  20.  
    case "showToast":
  21.  
    Log.d(TAG, "showToast");
  22.  
    mRemoteService.showToast();
  23.  
    break;
  24.  
    case "appCalculate":
  25.  
    Log.d(TAG, "appCalculate --> " param);
  26.  
    try {
  27.  
    JSONObject jsonObject = new JSONObject(param);
  28.  
    double firstNum = Double.parseDouble(jsonObject.optString("firstNum"));
  29.  
    double secondNum = Double.parseDouble(jsonObject.optString("secondNum"));
  30.  
    double calculateResult = mRemoteService.doCalculate(firstNum, secondNum);
  31.  
    Log.d(TAG, "calculateResult:" calculateResult);
  32.  
    } catch (JSONException e) {
  33.  
    e.printStackTrace();
  34.  
    }
  35.  
    break;
  36.  
    default:
  37.  
    Log.d(TAG, "Native端暂未实现该方法");
  38.  
    break;
  39.  
    }
  40.  
    }
学新通

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

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

附GitHub源码:WebViewExplore

参考:

Android WebView独立进程解决方案

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfafhb
系列文章
更多 icon
同类精品
更多 icon
继续加载