mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-20 11:18:21 +00:00
Pre-inflate BubbleTextViews into Launcher/TaskBar All Apps RV
This CL ensures no inflation of BubbleTextView happens while binding applications, and reduces jank on slow device. 1. Let active/inactive all apps RVs share the same AllAppsRecyclerViewPool 2. Use worker thread to pre-inflate BubbleTextViews and add them to shared view pool on main thread Bug: 287523421 Test: See before/after screenshot/video/trace attached in bug Change-Id: I00213407be2c7c2d329997552785d0aa56c4d057
This commit is contained in:
@@ -18,6 +18,7 @@ package com.android.launcher3.allapps;
|
||||
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
|
||||
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
|
||||
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
|
||||
@@ -79,7 +80,6 @@ import com.android.launcher3.keyboard.FocusedItemDecorator;
|
||||
import com.android.launcher3.model.StringCache;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
@@ -141,7 +141,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
private final SearchTransitionController mSearchTransitionController;
|
||||
private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Rect mInsets = new Rect();
|
||||
private final AllAppsStore mAllAppsStore = new AllAppsStore();
|
||||
private final AllAppsStore mAllAppsStore;
|
||||
private final RecyclerView.OnScrollListener mScrollListener =
|
||||
new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
@@ -194,6 +194,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mActivityContext = ActivityContext.lookupContext(context);
|
||||
mAllAppsStore = new AllAppsStore(mActivityContext);
|
||||
|
||||
mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
|
||||
mHeaderThreshold = getResources().getDimensionPixelSize(
|
||||
@@ -559,6 +560,13 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
|
||||
mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
|
||||
mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
|
||||
if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
|
||||
// Let main and work rv share same view pool.
|
||||
((RecyclerView) mViewPager.getChildAt(0))
|
||||
.setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
|
||||
((RecyclerView) mViewPager.getChildAt(1))
|
||||
.setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
|
||||
}
|
||||
if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
|
||||
mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
|
||||
mWorkManager.newScrollListener());
|
||||
|
||||
@@ -15,24 +15,30 @@
|
||||
*/
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
|
||||
import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
|
||||
import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
|
||||
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
|
||||
import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -45,8 +51,10 @@ import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A utility class to maintain the collection of all apps.
|
||||
*
|
||||
* @param <T> The type of the context.
|
||||
*/
|
||||
public class AllAppsStore {
|
||||
public class AllAppsStore<T extends Context & ActivityContext> {
|
||||
|
||||
// Defer updates flag used to defer all apps updates to the next draw.
|
||||
public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
|
||||
@@ -64,20 +72,36 @@ public class AllAppsStore {
|
||||
private int mModelFlags;
|
||||
private int mDeferUpdatesFlags = 0;
|
||||
private boolean mUpdatePending = false;
|
||||
private final AllAppsRecyclerViewPool mAllAppsRecyclerViewPool = new AllAppsRecyclerViewPool();
|
||||
|
||||
private final T mContext;
|
||||
|
||||
public AppInfo[] getApps() {
|
||||
return mApps;
|
||||
}
|
||||
|
||||
public AllAppsStore(@NonNull T context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
|
||||
* the current set of apps.
|
||||
*/
|
||||
public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
|
||||
public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
|
||||
mApps = apps;
|
||||
mModelFlags = flags;
|
||||
notifyUpdate();
|
||||
mPackageUserKeytoUidMap = map;
|
||||
// Preinflate all apps RV when apps has changed, which can happen after unlocking screen,
|
||||
// rotating screen, or downloading/upgrading apps.
|
||||
if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
|
||||
mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
RecycledViewPool getRecyclerViewPool() {
|
||||
return mAllAppsRecyclerViewPool;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,8 +32,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
@@ -401,6 +401,12 @@ public final class FeatureFlags {
|
||||
"ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
|
||||
"Enables new workspace grid calculations method.");
|
||||
|
||||
// TODO(Block 33): Clean up flags
|
||||
|
||||
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
|
||||
"ENABLE_ALL_APPS_RV_PREINFLATION", DISABLED,
|
||||
"Enables preinflating all apps icons to avoid scrolling jank.");
|
||||
|
||||
public static class BooleanFlag {
|
||||
|
||||
private final boolean mCurrentValue;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.recyclerview
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.android.launcher3.BubbleTextView
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter
|
||||
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
|
||||
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
import java.util.concurrent.Future
|
||||
|
||||
private const val PREINFLATE_ICONS_ROW_COUNT = 4
|
||||
private const val EXTRA_ICONS_COUNT = 2
|
||||
|
||||
/**
|
||||
* An [RecycledViewPool] that preinflates app icons ([ViewHolder] of [BubbleTextView]) of all apps
|
||||
* [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
|
||||
* will be added to [RecycledViewPool] on main thread.
|
||||
*/
|
||||
class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
|
||||
|
||||
private var future: Future<Void>? = null
|
||||
|
||||
/**
|
||||
* Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
|
||||
*/
|
||||
fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
|
||||
val appsView = context.appsView ?: return
|
||||
val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
|
||||
val preInflateCount = getPreinflateCount(context)
|
||||
if (preInflateCount <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Because we perform onCreateViewHolder() on worker thread, we need a separate
|
||||
// adapter/inflator object as they are not thread-safe. Note that the adapter
|
||||
// just need to perform onCreateViewHolder(parent, VIEW_TYPE_ICON) so it doesn't need
|
||||
// data source information.
|
||||
val adapter: RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> =
|
||||
object : BaseAllAppsAdapter<T>(context, context.appsView.layoutInflater, null, null) {
|
||||
override fun setAppsPerRow(appsPerRow: Int) = Unit
|
||||
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
|
||||
}
|
||||
|
||||
// Inflate view holders on background thread, and added to view pool on main thread.
|
||||
future?.cancel(true)
|
||||
future =
|
||||
VIEW_PREINFLATION_EXECUTOR.submit<Void> {
|
||||
val viewHolders =
|
||||
Array(preInflateCount) {
|
||||
adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
|
||||
}
|
||||
MAIN_EXECUTOR.execute {
|
||||
for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
|
||||
putRecycledView(viewHolders[i])
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After testing on phone, foldable and tablet, we found [PREINFLATE_ICONS_ROW_COUNT] rows of
|
||||
* app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
|
||||
* suffice fast scrolling.
|
||||
*/
|
||||
fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
|
||||
val targetPreinflateCount =
|
||||
PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
|
||||
EXTRA_ICONS_COUNT
|
||||
val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON)
|
||||
return targetPreinflateCount - existingPreinflateCount
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import android.os.Process;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@@ -58,6 +59,11 @@ public class Executors {
|
||||
new LooperExecutor(
|
||||
createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND));
|
||||
|
||||
|
||||
/** A background executor to preinflate views. */
|
||||
public static final ExecutorService VIEW_PREINFLATION_EXECUTOR =
|
||||
java.util.concurrent.Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Utility method to get a started handler thread statically
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user