歡迎光臨
每天分享高質量文章

webview記憶體泄漏終極解決方案

作者:陳佳娟

鏈接: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時,增長的記憶體並未減少

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


●編號356,輸入編號直達本文

●輸入m獲取到文章目錄

推薦↓↓↓

Java編程

更多推薦18個技術類公眾微信

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂