From dffd572afc4ca57d892a1d4acce949f5577c32c5 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Wed, 1 Jun 2016 20:05:55 -0400 Subject: [PATCH] Fix memory leaks caused by the android framework --- .../acr/browser/lightning/app/BrowserApp.java | 13 +++ .../lightning/utils/MemoryLeakUtils.java | 80 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 app/src/main/java/acr/browser/lightning/utils/MemoryLeakUtils.java diff --git a/app/src/main/java/acr/browser/lightning/app/BrowserApp.java b/app/src/main/java/acr/browser/lightning/app/BrowserApp.java index 110f4aa..5f398a9 100644 --- a/app/src/main/java/acr/browser/lightning/app/BrowserApp.java +++ b/app/src/main/java/acr/browser/lightning/app/BrowserApp.java @@ -1,9 +1,11 @@ package acr.browser.lightning.app; +import android.app.Activity; import android.app.Application; import android.content.Context; import android.os.Build; import android.support.annotation.NonNull; +import android.util.Log; import android.webkit.WebView; import com.squareup.leakcanary.LeakCanary; @@ -16,9 +18,12 @@ import javax.inject.Inject; import acr.browser.lightning.BuildConfig; import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.utils.MemoryLeakUtils; public class BrowserApp extends Application { + private static final String TAG = BrowserApp.class.getSimpleName(); + private static AppComponent mAppComponent; private static final Executor mIOThread = Executors.newSingleThreadExecutor(); private static final Executor mTaskThread = Executors.newCachedThreadPool(); @@ -38,6 +43,14 @@ public class BrowserApp extends Application { if (!isRelease() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(true); } + + registerActivityLifecycleCallbacks(new MemoryLeakUtils.LifecycleAdapter() { + @Override + public void onActivityDestroyed(Activity activity) { + Log.d(TAG, "Cleaning up after the Android framework"); + MemoryLeakUtils.clearNextServedView(BrowserApp.this); + } + }); } @NonNull diff --git a/app/src/main/java/acr/browser/lightning/utils/MemoryLeakUtils.java b/app/src/main/java/acr/browser/lightning/utils/MemoryLeakUtils.java new file mode 100644 index 0000000..646d225 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/utils/MemoryLeakUtils.java @@ -0,0 +1,80 @@ +package acr.browser.lightning.utils; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; + +import java.lang.reflect.Method; + +public class MemoryLeakUtils { + + private static final String TAG = MemoryLeakUtils.class.getSimpleName(); + + private static Method sFinishInputLocked = null; + + /** + * Clears the mNextServedView and mServedView in + * InputMethodManager and keeps them from leaking. + * + * @param application the application needed to get + * the InputMethodManager that is + * leaking the views. + */ + public static void clearNextServedView(@NonNull Application application) { + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { + // This shouldn't be a problem on N + return; + } + + InputMethodManager imm = (InputMethodManager) application.getSystemService(Context.INPUT_METHOD_SERVICE); + + if (sFinishInputLocked == null) { + try { + sFinishInputLocked = InputMethodManager.class.getDeclaredMethod("finishInputLocked"); + } catch (NoSuchMethodException e) { + Log.d(TAG, "Unable to find method in clearNextServedView", e); + } + } + + if (sFinishInputLocked != null) { + sFinishInputLocked.setAccessible(true); + try { + sFinishInputLocked.invoke(imm); + } catch (Exception e) { + Log.d(TAG, "Unable to invoke method in clearNextServedView", e); + } + } + + } + + public static abstract class LifecycleAdapter implements Application.ActivityLifecycleCallbacks { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} + + @Override + public void onActivityStarted(Activity activity) {} + + @Override + public void onActivityResumed(Activity activity) {} + + @Override + public void onActivityPaused(Activity activity) {} + + @Override + public void onActivityStopped(Activity activity) {} + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) {} + } + + +}