Fixing leaks in LauncherPreview
> Do not add listeners when binding FolderIcon for preview
> Cleaning up preview object when the caller is no longer holding
on to the communication channel for preview.
Bug: 393086035
Flag: EXEMPT bugfix
Test: Verified manually
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:986d7cb9c09a22915c922cf8e72c03ce36883742)
Merged-In: I4b758e6ce103c5201ef05ab824dd4e02f98c40b6
Change-Id: I4b758e6ce103c5201ef05ab824dd4e02f98c40b6
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index de1bcc3..9ff6475 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -177,12 +177,16 @@
FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
folder.setFolderIcon(icon);
folder.bind(folderInfo);
+
icon.setFolder(folder);
+ folderInfo.addListener(icon);
return icon;
}
/**
- * Builds a FolderIcon to be added to the Launcher
+ * Builds a FolderIcon to be added to the activity.
+ * This method doesn't add any listeners to the FolderInfo, and hence any changes to the info
+ * will not be reflected in the folder.
*/
public static FolderIcon inflateIcon(int resId, ActivityContext activity,
@Nullable ViewGroup group, FolderInfo folderInfo) {
@@ -228,8 +232,6 @@
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
- folderInfo.addListener(icon);
-
return icon;
}
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 7367f2e..836ae98 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -51,11 +51,12 @@
import com.android.launcher3.util.RunnableList;
import com.android.systemui.shared.Flags;
+import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
/**
@@ -121,7 +122,7 @@
// Set of all active previews used to track duplicate memory allocations
private final Set<PreviewLifecycleObserver> mActivePreviews =
- Collections.newSetFromMap(new WeakHashMap<>());
+ Collections.newSetFromMap(new ConcurrentHashMap<>());
@Override
public boolean onCreate() {
@@ -317,8 +318,15 @@
Bundle result = new Bundle();
result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
- Messenger messenger =
- new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer));
+ mActivePreviews.add(observer);
+ lifeCycleTracker.add(() -> mActivePreviews.remove(observer));
+
+ // Wrap the callback in a weak reference. This ensures that the callback is not kept
+ // alive due to the Messenger's IBinder
+ Messenger messenger = new Messenger(new Handler(
+ UI_HELPER_EXECUTOR.getLooper(),
+ new WeakCallbackWrapper(observer)));
+
Message msg = Message.obtain();
msg.replyTo = messenger;
result.putParcelable(KEY_CALLBACK, msg);
@@ -400,4 +408,34 @@
&& plo.renderer.getDisplayId() == renderer.getDisplayId();
}
}
+
+ /**
+ * A WeakReference wrapper around Handler.Callback to avoid passing hard-reference over IPC
+ * when using a Messenger
+ */
+ private static class WeakCallbackWrapper implements Handler.Callback {
+
+ private final WeakReference<Handler.Callback> mActual;
+ private final Message mCleanupMessage;
+
+ WeakCallbackWrapper(Handler.Callback actual) {
+ mActual = new WeakReference<>(actual);
+ mCleanupMessage = new Message();
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ Handler.Callback actual = mActual.get();
+ return actual != null && actual.handleMessage(message);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ Handler.Callback actual = mActual.get();
+ if (actual != null) {
+ actual.handleMessage(mCleanupMessage);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 3000b25..a7cf1a7 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -105,7 +105,6 @@
private final SurfaceControlViewHost mSurfaceControlViewHost;
private boolean mDestroyed = false;
- private LauncherPreviewRenderer mRenderer;
private boolean mHideQsb;
@Nullable private FrameLayout mViewRoot = null;
@@ -224,9 +223,8 @@
* @param hide True to hide and false to show.
*/
public void hideBottomRow(boolean hide) {
- if (mRenderer != null) {
- mRenderer.hideBottomRow(hide);
- }
+ mHideQsb = hide;
+ loadAsync();
}
/**
@@ -368,15 +366,16 @@
if (mDestroyed) {
return;
}
+ LauncherPreviewRenderer renderer;
if (Flags.newCustomizationPickerUi()) {
- mRenderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
+ renderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
mWallpaperColors, launcherWidgetSpanInfo);
} else {
- mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
+ renderer = new LauncherPreviewRenderer(inflationContext, idp,
mWallpaperColors, launcherWidgetSpanInfo);
}
- mRenderer.hideBottomRow(mHideQsb);
- View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
+ renderer.hideBottomRow(mHideQsb);
+ View view = renderer.getRenderedView(dataModel, widgetProviderInfoMap);
// This aspect scales the view to fit in the surface and centers it
final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
mHeight / (float) view.getMeasuredHeight());