作者:陳佳娟
連結: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時,增長的記憶體並未減少
此後我們手動GC三次(如下圖中三個垃圾桶的按鈕),記憶體才有所下降,但開發過程中開發者總不能在程式碼中粗暴地GC吧~
手動GC後記憶體有所下降大約38M左右,但似乎並沒有完全下降到原來的值,比如在未進入MemoryTestActivity前,大約是33M,當然也許4M左右可以略微忽視,也許是蜜汁存在。
===========================================
因此採取了另一個方案,另起一個行程載入webview,頁面銷毀後幹掉這個行程。來看一下此圖,ServcieLoginAcvitiy銷魂後,記憶體幾乎回到跟進入前差不多的水平值,
當然此方案擁有不可忽視的問題。什麼問題呢?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、資料庫、運維等。