Bug 1257941 - Use LeakCanary to watch for fragment leaks. r?ahunt draft
authorSebastian Kaspari <s.kaspari@gmail.com>
Fri, 18 Mar 2016 20:00:49 +0100
changeset 342243 9439dd3cd0aae5e68f647289ac67619cf13230e7
parent 342242 ca8d1e1caeefdcd45bd608adec7a10c3632ceab5
child 516544 3b4ec184bb582c508d0661ea6ec706947590a653
push id13378
push users.kaspari@gmail.com
push dateFri, 18 Mar 2016 19:01:11 +0000
reviewersahunt
bugs1257941
milestone48.0a1
Bug 1257941 - Use LeakCanary to watch for fragment leaks. r?ahunt MozReview-Commit-ID: GhqcSH1SML
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java
mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java
mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -50,16 +50,24 @@ public class GeckoApplication extends Ap
         return instance;
     }
 
     public static RefWatcher getRefWatcher(Context context) {
         GeckoApplication app = (GeckoApplication) context.getApplicationContext();
         return app.mRefWatcher;
     }
 
+    public static void watchReference(Context context, Object object) {
+        if (context == null) {
+            return;
+        }
+
+        getRefWatcher(context).watch(object);
+    }
+
     @Override
     public Context getContext() {
         return this;
     }
 
     @Override
     public SharedPreferences getSharedPreferences() {
         return GeckoSharedPrefs.forApp(this);
--- a/mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java
@@ -55,16 +55,23 @@ public class RemoteClientsDialogFragment
         dialog.setArguments(args);
         return dialog;
     }
 
     public RemoteClientsDialogFragment() {
         // Empty constructor is required for DialogFragment.
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        GeckoApplication.watchReference(getActivity(), this);
+    }
+
     protected void notifyListener(List<RemoteClient> clients) {
         RemoteClientsListener listener;
         try {
             listener = (RemoteClientsListener) getTargetFragment();
         } catch (ClassCastException e) {
             try {
                 listener = (RemoteClientsListener) getActivity();
             } catch (ClassCastException f) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import java.util.EnumSet;
 
 import org.mozilla.gecko.EditBookmarkDialog;
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.reader.ReaderModeUtils;
 import org.mozilla.gecko.Restrictions;
 import org.mozilla.gecko.SnackbarHelper;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
@@ -111,16 +112,23 @@ public abstract class HomeFragment exten
         } else {
             mCanLoadHint = DEFAULT_CAN_LOAD_HINT;
         }
 
         mIsLoaded = false;
     }
 
     @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        GeckoApplication.watchReference(getActivity(), this);
+    }
+
+    @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
         if (!(menuInfo instanceof HomeContextMenuInfo)) {
             return;
         }
 
         HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
 
         // Don't show the context menu for folders.
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.preferences;
 
 import java.util.Locale;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.BrowserLocaleManager;
+import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.LocaleManager;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 import org.mozilla.gecko.fxa.AccountLoaderNative;
@@ -30,16 +31,18 @@ import android.content.res.Resources;
 import android.os.Bundle;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceScreen;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 
+import com.squareup.leakcanary.RefWatcher;
+
 /* A simple implementation of PreferenceFragment for large screen devices
  * This will strip category headers (so that they aren't shown to the user twice)
  * as well as initializing Gecko prefs when a fragment is shown.
 */
 public class GeckoPreferenceFragment extends PreferenceFragment {
 
     public static final int ACCOUNT_LOADER_ID = 1;
     private AccountLoaderCallbacks accountLoaderCallbacks;
@@ -252,16 +255,18 @@ public class GeckoPreferenceFragment ext
             PrefsHelper.removeObserver(mPrefsRequest);
             mPrefsRequest = null;
         }
 
         final int res = getResource();
         if (res == R.xml.preferences) {
             Telemetry.stopUISession(TelemetryContract.Session.SETTINGS);
         }
+
+        GeckoApplication.watchReference(getActivity(), this);
     }
 
     private class AccountLoaderCallbacks implements LoaderManager.LoaderCallbacks<Account> {
         @Override
         public Loader<Account> onCreateLoader(int id, Bundle args) {
             return new AccountLoaderNative(getActivity());
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tabs;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.support.v4.app.Fragment;
@@ -102,16 +103,18 @@ public class TabHistoryFragment extends 
         super.onPause();
         dismiss();
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         dismiss();
+
+        GeckoApplication.watchReference(getActivity(), this);
     }
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
         if (backStackId >= 0) {
             outState.putInt(BACK_STACK_ID, backStackId);
         }
     }
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java
@@ -1,15 +1,16 @@
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.ActivityHandlerHelper;
+import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -51,16 +52,23 @@ public class ExternalIntentDuringPrivate
                     public void onClick(final DialogInterface dialog, final int id) {
                         context.startActivity(intent);
                     }
                 })
                 .setNegativeButton(R.string.button_no, null /* we do nothing if the user rejects */ );
         return builder.create();
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        GeckoApplication.watchReference(getActivity(), this);
+    }
+
     /**
      * @return true if the Activity is started or a dialog is shown. false if the Activity fails to start.
      */
     public static boolean showDialogOrAndroidChooser(final Context context, final FragmentManager fragmentManager,
             final Intent intent) {
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         if (selectedTab == null || !selectedTab.isPrivate()) {
             return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
--- a/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
@@ -5,16 +5,17 @@
 package org.mozilla.search;
 
 import java.net.MalformedURLException;
 import java.net.URISyntaxException;
 import java.net.URL;
 
 import android.support.v4.content.ContextCompat;
 import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.search.providers.SearchEngine;
 
 import android.annotation.SuppressLint;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
@@ -69,16 +70,23 @@ public class PostSearchFragment extends 
     public void onDestroyView() {
         super.onDestroyView();
         webview.setWebChromeClient(null);
         webview.setWebViewClient(null);
         webview = null;
         progressBar = null;
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        GeckoApplication.watchReference(getActivity(), this);
+    }
+
     public void startSearch(SearchEngine engine, String query) {
         this.engine = engine;
 
         final String url = engine.resultsUriForQuery(query);
         // Only load urls if the url is different than the webview's current url.
         if (!TextUtils.equals(webview.getUrl(), url)) {
             resultsPageHost = null;
             webview.loadUrl(Constants.ABOUT_BLANK);
--- a/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
@@ -21,16 +21,17 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.AdapterView;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 
+import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener;
 import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener.OnDismissCallback;
 import org.mozilla.search.AcceptsSearchQuery.SuggestionAnimation;
@@ -88,16 +89,18 @@ public class PreSearchFragment extends F
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         getLoaderManager().destroyLoader(LOADER_ID_SEARCH_HISTORY);
         cursorAdapter.swapCursor(null);
         cursorAdapter = null;
+
+        GeckoApplication.watchReference(getActivity(), this);
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
         final View mainView = inflater.inflate(R.layout.search_fragment_pre_search, container, false);
 
         // Initialize listview.
         listView = (ListView) mainView.findViewById(R.id.list_view);