在混合型app中它是主角,一切由它呈现,如58同城,赶集网等;在另一些超级app中亦有它的影子,微信,qq,支付宝,没有一个超级app能少了它,既能展示最新最潮的实时资讯,又能扮演盘踞一方的全功能型网站,与native结合后又能扮演诸如公众号之内的应用等等,其能力可想而知。
webview在android端的演化可谓曲折,2015年google宣布不在支持4.4版本一下的webview[1],这意味着目前扔有近四分之一的Android用户因无法获得Google的支持而受到安全威胁。对于深度依赖webview的巨头开始自家的内核及上层sdk以求提升整体稳定性及其性能,腾讯x5内核是比较通用的一种.
目前大致的webview内存处理方式分为两类:
这个方法被运用于类似qq,微信这样的超级app中,这也是解决任何webview内存问题屡试不爽的方法
对于封装的webactivity,在manifest.xml
中设置
<activity android:name=".webview.WebViewActivity" android:launchMode="singleTop" android:process=":remote" android:screenOrientation="unspecified" />
然后在关闭webactivity时销毁进程
@Overrideprotected void onDestroy() {
super.onDestroy();
System.exit(0);
}
关闭浏览器后便销毁整个进程,这样一般95%
的情况下不会造成内存泄漏之类的问题,但这就涉及到android进程间通讯,比较不方便处理, 优劣参半,也是可选的一个方案.
相比系统内置的webview的支持自2005年之后就没有了,而首推google的chrome。 腾讯的x5webview对h5的兼容性与稳定性与安全性逐渐凸显出来,并自成一系, 下面以使用x5webview为例做说明:
webView = new BridgeWebView(getContext().getApplicationContext());
webFrameLayout.addView(webView, 0);
在使用了这个方式后,基本上90%的webview内存泄漏的问题便得以解决.
fragment
中,使用ondetach
的释放webview是比较好的时机[2]public void onDetach() {
releaseWebViews();
super.onDetach();
}
public synchronized void releaseWebViews() {
if(webView != null) {
try {
if(webView.getParent() != null) {
((ViewGroup) webView.getParent()).removeView(webView);
}
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//this is causing the segfault occasionally below 4.2
webView.destroy();
// }
}catch (IllegalArgumentException e) {
DLog.p(e);
}
RefWatcher refWatcher = FApplication.getRefWatcher();
refWatcher.watch(webView);
webView = null;
}
}
其中webview自身的销毁代码如下:
@Override
public void destroy()
//flushMessageQueue();
clearCache(true);
clearFormData();
clearMatches();
clearSslPreferences();
clearDisappearingChildren();
clearHistory();
//@Deprecated
//clearView();
clearAnimation();
loadUrl("about:blank");
removeAllViews();
freeMemory();
super.destroy();
}
反射
来清理webview的引用:public void setConfigCallback(WindowManager windowManager) {
try {
Field field = WebView.class.getDeclaredField("mWebViewCore");
field = field.getType().getDeclaredField("mBrowserFrame");
field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true);
Object configCallback = field.get(null);
if (null == configCallback) {
return;
}
field = field.getType().getDeclaredField("mWindowManager");
field.setAccessible(true);
field.set(configCallback, windowManager);
} catch(Exception e) {
}
}
然后在activity中加入
public void onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setConfigCallback((WindowManager);
getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}
publicvoidonDestroy()
{
setConfigCallback(null);
super.onDestroy();
}
private static String[] misbehavingClasses = new String[]{
"com.google.android.gms.ads",
"com.android.org.chromium.android_webview.AwContents$AwComponentCallbacks",
};
public static boolean isMisbehavingCallBacks(String name){
for(String s : misbehavingClasses){
if(name.startsWith(s)){
return true;
}
}
return false;
}
然后重写applicaiton类记录这些callback, 并在适当的时机删掉:
@Override
public void registerComponentCallbacks(ComponentCallbacks callback) {
super.registerComponentCallbacks(callback);
ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.onComponentCallbacksRegistered(callback);
}
@Override
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.onComponentCallbacksUnregistered(callback);
super.unregisterComponentCallbacks(callback);
}
public void forceUnregisterComponentCallbacks() {
ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.unregisterAll(this);
}
private static class ComponentCallbacksBehavioralAdjustmentToolIcs {
private static final String TAG = "componentCallbacks";
static ComponentCallbacksBehavioralAdjustmentToolIcs INSTANCE = new ComponentCallbacksBehavioralAdjustmentToolIcs();
private WeakHashMap mCallbacks = new WeakHashMap<>();
private boolean mSuspended = false;
public void onComponentCallbacksRegistered(ComponentCallbacks callback) {
Throwable thr = new Throwable("Callback registered here.");
ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(thr);
if (FApplication.DEBUG) DLog.w(TAG, "registerComponentCallbacks: " + callback.getClass().getName(), thr);
if (!mSuspended) {
if (BugFix.isMisbehavingCallBacks(callback.getClass().getName())) {
mCallbacks.put(callback, ci);
}
// TODO: other classes may still prove to be problematic? For now, only watch for .gms.ads, since we know those are misbehaving
} else {
if (FApplication.DEBUG) DLog.e(TAG, "ComponentCallbacks was registered while tracking is suspended!");
}
}
public void onComponentCallbacksUnregistered(ComponentCallbacks callback) {
if (!mSuspended) {
if (FApplication.DEBUG) {
DLog.i(TAG, "unregisterComponentCallbacks: " + callback, new Throwable());
}
mCallbacks.remove(callback);
}
}
public void unregisterAll(Context context) {
mSuspended = true;
for (Map.Entry entry : mCallbacks.entrySet()) {
ComponentCallbacks callback = entry.getKey();
if (callback == null) continue;
if (FApplication.DEBUG) {
DLog.w(TAG, "Forcibly unregistering a misbehaving ComponentCallbacks: " + entry.getKey());
DLog.w(TAG, entry.getValue().stackTrace);
}
try {
context.unregisterComponentCallbacks(entry.getKey());
} catch (Exception exc) {
if (FApplication.DEBUG) DLog.e(TAG, "Unable to unregister ComponentCallbacks", exc);
}
}
mCallbacks.clear();
mSuspended = false;
}
}
application
的oncreate
里面,我们加入private static void enableStrictMode() {
StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog();
StrictMode.VmPolicy.Builder vmPolicyBuilder =
new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog();
threadPolicyBuilder.penaltyFlashScreen();
vmPolicyBuilder.setClassInstanceLimit(WebActivity.class, 1);
StrictMode.setThreadPolicy(threadPolicyBuilder.build());
StrictMode.setVmPolicy(vmPolicyBuilder.build());
}
if (FApplication.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
webfragment
,webactivity
,webview
ondestory后加上 RefWatcher refWatcher = FApplication.getRefWatcher();
refWatcher.watch(obj);
总结: 如果你只是简单地用 webview 做呈现, 使用application context启动webview已经足够了,但如果你需要webview来播放视频,处理弹窗等复杂工作, 新建一个进程来处理会更可靠.