webview内存泄漏终极解决方案

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

前言

我们知道在使用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时,增长的内存并未减少


image.png

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


image.png

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


image.png

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

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

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<LoginWebViewService> 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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容