After many month of inactivity in this blog I would like to post some code that deals with various memory leaks/issues.

After a lot of analysis using HPROF and spending days testing my code, my activities don't leak any resources any more as far as I can tell.

Orientation changes, pressing Home buttons, AdMob ad refreshes, WebView usage ... all this should be dealt with.

 

Here are some articles related to the code I'll post:

I don't want to go into details why I did what I did but will just post some code samples.

The code deals with potential memory leaks (rotation of screen, pause/resume of Activities).

 

If you are using AdMob 4.1.0 put this into your onCreate method:

new Handler(new Handler.Callback() {
@Override public boolean handleMessage(Message msg) {
if (!isBeingDestroyed) {
final AdRequest adRequest = new AdRequest();
final AdView adView = (AdView) findViewById(R.id.ad);
adView.loadAd(adRequest);
}
}).sendEmptyMessageDelayed(0, 1000);

isBeingDestroyed is set to true in the onDestroy() method and prevents the code from crashing due to NPEs. Of course the code must be synchronized but I wanted to keep the sample code short.

Also call adView.destroy() in the activities' onDestroy() method.

 

Use the application context as often as possible instead of the activity context.

I use the following simple Application class:

public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate () {
context = this;
}
public static Context getContext() {
return context;
}
}

And yes I'm aware of this discussion: http://groups.google.com/group/android-developers/browse_thread/thread/da2f369a3a0ce3a3/3748582e5248ed66?lnk=raot but in this case the Application onCreate() method is called before any of my other code is called, so no issues with multithreading etc.

 

If you are using WebView in your layouts:

Add the WebView programmatically instead of adding it in an xml layout because that allows to use the application context instead of the activity context (I use a normal view as placeholder which is replaced by the webview once its created):

ViewGroup rootWebView = (ViewGroup) rootLayout.findViewById(R.id.rootwebview);
View view2Remove = rootWebView.findViewById(R.id.webview);
int index = rootWebView.indexOfChild(view2Remove);
WebView webView = new WebView(MyApplication.getContext());
rootWebView.removeView(view2Remove);
rootWebView.addView(webView, index);

Also destroy the webview when the activity is destroyed: webView.destroy()

 

OutOfMemoryErrors.

If you have some experience with Android you will have encountered OutOfMemoryErrors.

I call the following method in every activity's onDestroy() method to "deconstruct" the layout and unbind any possible callback method or possible reference to my activity (kind of what Romain Guy suggests in his article):

	/**
* Removes the reference to the activity from every view in a view hierarchy (listeners, images etc.).
* This method should be called in the onDestroy() method of each activity
* @param viewID normally the id of the root layout
* 
* see http://code.google.com/p/android/issues/detail?id=8488
*/
public static void unbindReferences(Activity activity, int viewID) {
try {
View view = activity.findViewById(viewID);
if (view!=null) {
View adView = view.findViewById(R.id.ad);
if (adView!=null && adView instanceof AdView) {
((AdView) adView).destroy();
}
unbindViewReferences(view);
if (view instanceof ViewGroup) unbindViewGroupReferences((ViewGroup) view);
}
System.gc();
}
catch (Throwable e) {
// whatever exception is thrown just ignore it because a crash is always worse than this method not doing what it's supposed to do
}
}
private static void unbindViewGroupReferences(ViewGroup viewGroup) { int nrOfChildren = viewGroup.getChildCount(); for (int i=0; i View view = viewGroup.getChildAt(i); unbindViewReferences(view); if (view instanceof ViewGroup) unbindViewGroupReferences((ViewGroup) view); } try { viewGroup.removeAllViews(); } catch (Throwable mayHappen) { // AdapterViews, ListViews and potentially other ViewGroups don't support the removeAllViews operation } }
private static void unbindViewReferences(View view) { // set all listeners to null (not every view and not every API level supports the methods) try {view.setOnClickListener(null);} catch (Throwable mayHappen) {}; try {view.setOnCreateContextMenuListener(null);} catch (Throwable mayHappen) {}; try {view.setOnFocusChangeListener(null);} catch (Throwable mayHappen) {}; try {view.setOnKeyListener(null);} catch (Throwable mayHappen) {}; try {view.setOnLongClickListener(null);} catch (Throwable mayHappen) {}; try {view.setOnClickListener(null);} catch (Throwable mayHappen) {};
// set background to null Drawable d = view.getBackground(); if (d!=null) d.setCallback(null); if (view instanceof ImageView) { ImageView imageView = (ImageView) view; d = imageView.getDrawable(); if (d!=null) d.setCallback(null); imageView.setImageDrawable(null); imageView.setBackgroundDrawable(null); }
// destroy webview if (view instanceof WebView) { ((WebView) view).destroyDrawingCache(); ((WebView) view).destroy(); } }

I agree that this might be overkill but it DOES address many issues related to memory leaks and it doesn't do any harm IMHO.

Of course there are many more things you should do to prevent memory issues but these are my most important findings and workarounds.

 

Emanuel Moecklin

People in this conversation

Add comment