webview內(nèi)存泄漏終極解決方案

哈较屿,標(biāo)題似乎有些霸氣,但方案確實(shí)很有效派敷。

前言

我們知道在使用webview時(shí)笋粟,內(nèi)存增加比較大,而在頁面退出時(shí),卻沒有相應(yīng)的減少。
相信大家都查過很多網(wǎng)上的方案:
比如:

不在xml布局中添加webview標(biāo)簽,采用在代碼中new出來的方式详恼。

再接著當(dāng)頁面銷毀時(shí),調(diào)用一些各種各樣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設(shè)備上):
WelComeActivtiy點(diǎn)擊進(jìn)入MemoryTestActivtiy敞掘,這里加載了webview,onDestory中采取了上述的方式試圖釋放占用的內(nèi)存楣铁,但看到改頁面destory后回到WelcomeActivity時(shí)玖雁,增長的內(nèi)存并未減少


image.png

此后我們手動(dòng)GC三次(如下圖中三個(gè)垃圾桶的按鈕),內(nèi)存才有所下降盖腕,但開發(fā)過程中開發(fā)者總不能在代碼中粗暴地GC吧~


image.png

手動(dòng)GC后內(nèi)存有所下降大約38M左右赫冬,但似乎并沒有完全下降到原來的值浓镜,比如在未進(jìn)入MemoryTestActivity前,大約是33M劲厌,當(dāng)然也許4M左右可以略微忽視膛薛,也許是蜜汁存在。


image.png

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

因此采取了另一個(gè)方案补鼻,另起一個(gè)進(jìn)程加載webview哄啄,頁面銷毀后干掉這個(gè)進(jìn)程。來看一下此圖风范,ServcieLoginAcvitiy銷魂后咨跌,內(nèi)存幾乎回到跟進(jìn)入前差不多的水平值,

image.png

當(dāng)然此方案擁有不可忽視的問題硼婿。什么問題呢锌半?webview有業(yè)務(wù)邏輯需要交互,返回給主進(jìn)程處理加酵,于是就牽扯到進(jìn)程間通信的問題拳喻。也會(huì)面臨進(jìn)程不小心被干掉了(比如內(nèi)存增長過快哭当,GC需要回收部分內(nèi)存猪腕,根據(jù)策略優(yōu)先級(jí)回收的時(shí)候,可能就會(huì)干掉這個(gè)進(jìn)程)钦勘,導(dǎo)致主進(jìn)程無法接受到數(shù)據(jù)或者說其他的一些交流陋葡,出現(xiàn)嚴(yán)重的體驗(yàn)問題。
如果這個(gè)webview只是單純地加載瀏覽界面彻采,沒有復(fù)雜的其他邏輯(比如與Js的交互)腐缤,倒是不妨可以考慮。林外肛响,另起一個(gè)進(jìn)程岭粤,要占用CPU時(shí)間,主進(jìn)程需要等待接受信息特笋,那么勢必會(huì)慢一些剃浇。
總而言之,它的優(yōu)勢猎物、劣勢非常明顯虎囚。需要考慮具體的業(yè)務(wù)場景和環(huán)境。
哈蔫磨,先甩上demo地址:
https://github.com/chenjiajuan/AidlServiceDemo

看下文講解

實(shí)踐

涉及到的必備知識(shí)Service淘讥、AIDL,如果對(duì)這塊不夠了解得話堤如,建議先補(bǔ)充這塊知識(shí)蒲列。

1.作者君以在Activity啟動(dòng)Service窒朋,由于需要需要跟Activity交互采用bindService的方式啟動(dòng)。(如果獨(dú)立于調(diào)用者而運(yùn)行則可以采用startService嫉嘀,具體視業(yè)務(wù)需求而定)

2.由于需開辟進(jìn)程炼邀,因此需要通過AIDL來實(shí)現(xiàn)通信。service的onBinder需要返回一個(gè)Stub.asBinder(),建立ServiceConnection后可以獲取到該對(duì)象剪侮,調(diào)用service內(nèi)的方法拭宁。

3比如:作者君的應(yīng)用場景是二維碼登錄界面
由于service和app不在用一個(gè)進(jìn)程需要通過aidl進(jìn)行通訊,因此先建立了兩個(gè)aidl文件瓣俯,定義了一些方法和參數(shù)(具體視業(yè)務(wù)場景而設(shè)計(jì)杰标,此處只是舉例)。
IWebViewCallback.aldl :service內(nèi)將拿到這個(gè)call對(duì)象彩匕,返回各種狀態(tài)給上層業(yè)務(wù)(比如:在Activity需要處理UI交互腔剂,存儲(chǔ)數(shù)據(jù)等等)

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);

}

另一個(gè)
IWebViewService.aidl
上層業(yè)務(wù)需要調(diào)用servcie內(nèi)的方法

interface IWebViewService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void doLoadWebViewJsUrl(in IWebViewCallback webViewCallback);
}

那么先來看一下Activity內(nèi),作者君的場景是二維碼登錄頁面

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);

    }

    /**
     * 設(shè)置連接,獲取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 {
              //調(diào)用service內(nèi)的方法
                webViewService.doLoadWebViewJsUrl(webViewCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    /**
     * 設(shè)置callback掸犬,處理數(shù)據(jù)
     */
    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中注冊(cè)進(jìn)程

<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);
        //設(shè)置監(jiān)聽
        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的回調(diào)绪爸,通過Activity內(nèi)的callback湾碎,返回狀態(tài)給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中獲取到這個(gè)對(duì)象,操作service內(nèi)的方法
    @Override
    public IBinder onBind(Intent intent) {
        return webViewService.asBinder();
    }
    @Override
    public boolean onUnbind(Intent intent) {
        super.onUnbind(intent);
        //干掉進(jìn)程5旎酢=槿臁!递惋!
        System.exit(0);
        return true;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柔滔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子萍虽,更是在濱河造成了極大的恐慌睛廊,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杉编,死亡現(xiàn)場離奇詭異超全,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)王财,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門卵迂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绒净,你說我怎么就攤上這事见咒。” “怎么了挂疆?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵改览,是天一觀的道長下翎。 經(jīng)常有香客問我,道長宝当,這世上最難降的妖魔是什么视事? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮庆揩,結(jié)果婚禮上俐东,老公的妹妹穿的比我還像新娘。我一直安慰自己订晌,他們只是感情好虏辫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锈拨,像睡著了一般砌庄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奕枢,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天娄昆,我揣著相機(jī)與錄音,去河邊找鬼缝彬。 笑死萌焰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跌造。 我是一名探鬼主播杆怕,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼族购,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼壳贪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寝杖,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤违施,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瑟幕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磕蒲,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年只盹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辣往。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡殖卑,死狀恐怖站削,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情孵稽,我是刑警寧澤许起,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布十偶,位于F島的核電站,受9級(jí)特大地震影響园细,放射性物質(zhì)發(fā)生泄漏惦积。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一猛频、第九天 我趴在偏房一處隱蔽的房頂上張望狮崩。 院中可真熱鬧,春花似錦鹿寻、人聲如沸厉亏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爱只。三九已至,卻和暖如春招刹,著一層夾襖步出監(jiān)牢的瞬間恬试,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工疯暑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留训柴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓妇拯,卻偏偏與公主長得像幻馁,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子越锈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,866評(píng)論 25 707
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,343評(píng)論 8 265
  • 真正地放棄一個(gè)人是無聲無息的仗嗦,不會(huì)把他拉入黑名單,不會(huì)刪掉他的電話甘凭,看到他過得好可以毫不羨慕地點(diǎn)贊稀拐,即便路上碰見也...
    溫不溫暖都是心閱讀 255評(píng)論 0 0
  • 1、gradle的簡單介紹 Gradle是可以用于Android開發(fā)的新一代的Build System丹弱,也是And...
    非著名程序員閱讀 2,541評(píng)論 0 4
  • 簡介 關(guān)于Pygame的基本信息德撬,pygame是什么,誰會(huì)被Pygame吸引躲胳,并且在哪里找到它蜓洪。 Pygame是被...
    3767d46199be閱讀 9,775評(píng)論 0 1