哈较屿,標(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)存并未減少
此后我們手動(dòng)GC三次(如下圖中三個(gè)垃圾桶的按鈕),內(nèi)存才有所下降盖腕,但開發(fā)過程中開發(fā)者總不能在代碼中粗暴地GC吧~
手動(dòng)GC后內(nèi)存有所下降大約38M左右赫冬,但似乎并沒有完全下降到原來的值浓镜,比如在未進(jìn)入MemoryTestActivity前,大約是33M劲厌,當(dāng)然也許4M左右可以略微忽視膛薛,也許是蜜汁存在。
===========================================
因此采取了另一個(gè)方案补鼻,另起一個(gè)進(jìn)程加載webview哄啄,頁面銷毀后干掉這個(gè)進(jìn)程。來看一下此圖风范,ServcieLoginAcvitiy銷魂后咨跌,內(nèi)存幾乎回到跟進(jìn)入前差不多的水平值,
當(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;
}
}