哈,标题似乎有些霸气,但方案确实很有效。
前言
我们知道在使用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<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;
}
}