webview内存泄漏终极解决方案

code小生,一个专注 Android 领域的技术平台

公众号回复 Android 加入我的安卓技术群

作者:陈佳娟
链接:https://www.jianshu.com/p/aa5a99b565e7
声明:本文已获
陈佳娟授权发表,转发等请联系原作者授权

哈,标题似乎有些霸气,但方案确实很有效。

前言

我们知道在使用webview时,内存增加比较大,而在页面退出时,却没有相应的减少。
相信大家都查过很多网上的方案:
比如:

不在xml布局中添加webview标签,采用在代码中new出来的方式。

再接着当页面销毁时,调用一些各种各样webview提供的方法,回收资源,比如:

        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
         ..........................
        mWebView.destroy();
        mWebView=null;

作者君也试过觉着不是很有效。具体来看一下这张图(Android 8.0设备上):
WelComeActivtiy点击进入MemoryTestActivtiy,这里加载了webview,onDestory中采取了上述的方式试图释放占用的内存,但看到改页面destory后回到WelcomeActivity时,增长的内存并未减少

webview内存泄漏终极解决方案_第1张图片 image.png

此后我们手动GC三次(如下图中三个垃圾桶的按钮),内存才有所下降,但开发过程中开发者总不能在代码中粗暴地GC吧~

webview内存泄漏终极解决方案_第2张图片 image.png

手动GC后内存有所下降大约38M左右,但似乎并没有完全下降到原来的值,比如在未进入MemoryTestActivity前,大约是33M,当然也许4M左右可以略微忽视,也许是蜜汁存在。

webview内存泄漏终极解决方案_第3张图片 image.png

===========================================

因此采取了另一个方案,另起一个进程加载webview,页面销毁后干掉这个进程。来看一下此图,ServcieLoginAcvitiy销魂后,内存几乎回到跟进入前差不多的水平值,

webview内存泄漏终极解决方案_第4张图片 image.png

当然此方案拥有不可忽视的问题。什么问题呢?webview有业务逻辑需要交互,返回给主进程处理,于是就牵扯到进程间通信的问题。也会面临进程不小心被干掉了(比如内存增长过快,GC需要回收部分内存,根据策略优先级回收的时候,可能就会干掉这个进程),导致主进程无法接受到数据或者说其他的一些交流,出现严重的体验问题。
如果这个webview只是单纯地加载浏览界面,没有复杂的其他逻辑(比如与Js的交互),倒是不妨可以考虑。林外,另起一个进程,要占用CPU时间,主进程需要等待接受信息,那么势必会慢一些。

总而言之,它的优势、劣势非常明显。需要考虑具体的业务场景和环境。
哈,先甩上demo地址:
https://github.com/chenjiajuan/AidlServiceDemo

看下文讲解

实践

涉及到的必备知识Service、AIDL,如果对这块不够了解得话,建议先补充这块知识。

1.作者君以在Activity启动Service,由于需要需要跟Activity交互采用bindService的方式启动。(如果独立于调用者而运行则可以采用startService,具体视业务需求而定)

2.由于需开辟进程,因此需要通过AIDL来实现通信。service的onBinder需要返回一个Stub.asBinder(),建立ServiceConnection后可以获取到该对象,调用service内的方法。

3.比如:作者君的应用场景是二维码登录界面
由于service和app不在用一个进程需要通过aidl进行通讯,因此先建立了两个aidl文件,定义了一些方法和参数(具体视业务场景而设计,此处只是举例)。
IWebViewCallback.aldl :service内将拿到这个call对象,返回各种状态给上层业务(比如:在Activity需要处理UI交互,存储数据等等)

interface IWebViewCallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */

      void showQRCode(in String url);
      void onQRLoginSuccess(in String userInfo);
      void onQRLoginFailure(in int code, in String msg);
      void onQRScanCodeSuccess(in int code, in String msg);
      void onQRRefresh(in int code, in String msg);
}

另一个
IWebViewService.aidl
上层业务需要调用servcie内的方法

interface IWebViewService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */

    void doLoadWebViewJsUrl(in IWebViewCallback webViewCallback);
}

那么先来看一下Activity内,作者君的场景是二维码登录页面

public class LoginServiceActivity extends Activity {
    private static final String TAG = "LoginServiceActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setPackage(this.getPackageName());
        intent.setAction("com.chenjiajuan.webview");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

    }

    /**
     * 设置连接,获取service,绑定callback
     */

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IWebViewService webViewService = IWebViewService.Stub.asInterface(service);
            if (webViewService == null)
                return;
            try {
              //调用service内的方法
                webViewService.doLoadWebViewJsUrl(webViewCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    /**
     * 设置callback,处理数据
     */

    private IWebViewCallback webViewCallback = new IWebViewCallback.Stub() {
        @Override
        public void showQRCode(String url) throws RemoteException {
        }
        @Override
        public void onQRLoginSuccess(String userInfo) throws RemoteException {
        }
        @Override
        public void onQRLoginFailure(int code, String msg) throws RemoteException {
        }

        @Override
        public void onQRScanCodeSuccess(int code, String msg) throws RemoteException {
        }

        @Override
        public void onQRRefresh(int code, String msg) throws RemoteException {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }

再来看一下Service:
在xml中注册进程

<service
           android:name=".LoginWebViewService"
            android:enabled="true"
            android:process=":remoteProcess">

            <intent-filter>
                <action android:name="com.chenjiajuan.webview" />
            intent-filter>
    service>

LoginWebViewService类如下:

public class LoginWebViewService extends Service {
    private static final String TAG = "LoginWebViewService";
    private LoginWebView loginWebView;
    private IWebViewCallback callback;
    private WebViewHandler webViewHandler = new WebViewHandler(this);
    @Override
    public void onCreate() {
        super.onCreate();
        //初始化webview
        loginWebView = new LoginWebView(this);
        //设置监听
        loginWebView.setQrTaskListener(new LoginQRTask());
    }

    /**
     * 由于加载webview页面必须在主线程,所以此处采用了handler
     */

    private IWebViewService webViewService = new IWebViewService.Stub() {
        @Override
        public void doLoadWebViewJsUrl(final IWebViewCallback webViewCallback) throws RemoteException {
            Message message=new Message();
            message.what=0;
            message.obj=webViewCallback;
            webViewHandler.sendMessage(message);

        }
    };

    private static class WebViewHandler extends Handler {
        private WeakReference weakReference;
        public WebViewHandler(LoginWebViewService webViewService) {
            this.weakReference = new WeakReference<>(webViewService);
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    weakReference.get().callback = (IWebViewCallback) msg.obj;
                    weakReference.get().loginWebView.showQRCode(new LoginWebView.QRCodeListener() {
                        @Override
                        public void fetchLoginUrl(String url) {
                            try {
                                weakReference.get().callback.showQRCode(url);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    break;
            }
        }
    }


    /**
     * 使用Webview的回调,通过Activity内的callback,返回状态给Activity
     * webveiw--->Service-->Activity
     */

    private class LoginQRTask implements QRTaskListener {
        public LoginQRTask() {
          //.............
        }

        @Override
        public void onQRLoginSuccess(String userInfo) {
            try {
                callback.onQRLoginSuccess(userInfo);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onQRLoginFailure(int code, String msg) {
            try {
                callback.onQRLoginFailure(code, msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onQRScanCodeSuccess(int code, String msg) {
            try {
                callback.onQRScanCodeSuccess(code, msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onQRRefresh(int code, String msg) {
            try {
                callback.onQRRefresh(code, msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
//onServiceConnected中获取到这个对象,操作service内的方法
    @Override
    public IBinder onBind(Intent intent) {
        return webViewService.asBinder();
    }
    @Override
    public boolean onUnbind(Intent intent) {
        super.onUnbind(intent);
        //干掉进程!!!!
        System.exit(0);
        return true;
    }
}

WebView

监听WebView页面上所有的图片

WebView 开车指南之最全实用案例

webview内存泄漏终极解决方案_第5张图片

分享技术我是认真的

你可能感兴趣的:(webview内存泄漏终极解决方案)