Skip to content

Instantly share code, notes, and snippets.

@laaptu
Created October 16, 2013 14:58
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save laaptu/7009064 to your computer and use it in GitHub Desktop.
Save laaptu/7009064 to your computer and use it in GitHub Desktop.
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class NonLeakingWebView extends WebView {
private static Field sConfigCallback;
static {
try {
sConfigCallback = Class.forName("android.webkit.BrowserFrame")
.getDeclaredField("sConfigCallback");
sConfigCallback.setAccessible(true);
} catch (Exception e) {
// ignored
}
}
public NonLeakingWebView(Context context) {
super(context.getApplicationContext());
setWebViewClient(new MyWebViewClient((Activity) context));
}
public NonLeakingWebView(Context context, AttributeSet attrs) {
super(context.getApplicationContext(), attrs);
setWebViewClient(new MyWebViewClient((Activity) context));
}
public NonLeakingWebView(Context context, AttributeSet attrs, int defStyle) {
super(context.getApplicationContext(), attrs, defStyle);
setWebViewClient(new MyWebViewClient((Activity) context));
}
@Override
public void destroy() {
super.destroy();
try {
if (sConfigCallback != null)
sConfigCallback.set(null, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected static class MyWebViewClient extends WebViewClient {
protected WeakReference<Activity> activityRef;
public MyWebViewClient(Activity activity) {
this.activityRef = new WeakReference<Activity>(activity);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
final Activity activity = activityRef.get();
if (activity != null)
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse(url)));
} catch (RuntimeException ignored) {
// ignore any url parsing exceptions
}
return true;
}
}
}
///OnActivity finish or destroy call
@Override
public void finish() {
webView.destroy();
super.finish();
}
@qiuping345
Copy link

Hi Iaaptu
I hope everything is going on well with you.
If your app has only 1 webview, this might work; or else, it will leak every Activity with a webview. About 3 years ago, somebody in my team made this change, then the app crashed a lot. finally I found the reason and removed this change.
Please check the code:
Line 209-215
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.5_r1/android/webkit/BrowserFrame.java#BrowserFrame.ConfigCallback

Line 298-301
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.5_r1/android/view/ViewRoot.java#ViewRoot.addConfigCallback%28android.content.ComponentCallbacks%29

and the declaration of 'sConfigCallbacks', it's a static ArrayList that holds references(BrowserFrame.ConfigCallback in the case of WebView).

Every time we click BACK key, and the onDestroy was called in the WebviewActivity, then, BrowserFrame.sConfigCallback was set to null. Then if we go to another WebviewActivity, an WebView is created (so it is with BrowserFrame). If the BrowserFrame.sConfig is null, then a new one would be instantiated and added to a static ArrayList, including the member 'mWindowManger', who has a reference to the WebviewActivity indirectly. So the leak happens.

just FYI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment