IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android LowRam设备显示最近应用个数的限制 -> 正文阅读

[移动开发]Android LowRam设备显示最近应用个数的限制

背景

? ? ? ? Android12项目中,我们的LowRam设备被测试组报了个bug,说打开了十几个应用,但最近应用列表中最多只能有9个应用历史,而对比机pixel上则无此限制。

? ? ? ? 最初我以为是Launcher3的问题,因为launcher3分了好几个版本,其中就有针对GO设备的,因此从launcher3上入手追查了一下。从显示最近应用历史的view上入手,到获取最近应用列表的路线查。

分析

? ? ? ? 先从launcher3的RecentTasksList代码上入手,launcher显示最近应用列表的来源在:

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/RecentTasksList.java   
 @VisibleForTesting
    TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
        int currentUserId = Process.myUserHandle().getIdentifier();
        List<ActivityManager.RecentTaskInfo> rawTasks =
                mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
        // The raw tasks are given in most-recent to least-recent order, we need to reverse it
        Collections.reverse(rawTasks);
        ……
  }

? ? ? ? RecentTasksList调用loadTasksInBackground在后台从AMS中获取RecentTasks,所以最近应用列表实际上是AMS进行管理的,这个非常好理解,毕竟AMS是所有Activity的管理者,最近所启动的应用在AMS中必定能监控到。

? ? ? ? getRecentTasks方法第一个参数是获取最近应用的个数,那么最初是怀疑此处numTasks的值限制了low ram设备的launcher3最多只能显示9个最近应用,因此查了一下这个参数的来源,其来源如下:

?

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/RecentTasksList.java   
    public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
        // Kick off task loading in the background
        mLoadingTasksInBackground = true;
        UI_HELPER_EXECUTOR.execute(() -> {
            if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
                mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
            }
            TaskLoadResult loadResult = mResultsBg;
            mMainThreadExecutor.execute(() -> {
                mLoadingTasksInBackground = false;
                mResultsUi = loadResult;
                if (callback != null) {
                    ArrayList<Task> result = copyOf(mResultsUi);
                    callback.accept(result);
                }
            });
        });
        return requestLoadId;
    }

? ? ? ? 同样是RecentTasksList中,getTasks方法会去调用loadTasksInBackground方法去加载RecentTasks,而传入的第一个参数是int类型数据的最大值,即不做任何限制,因此可以排除掉是launcher3自身的限制。

? ? ? ? 排除掉launcher3的嫌疑后,只能把目光投向到AMS中。针对于GO设备而言,AMS可能为了节省内存,会去对RecentTasks的个数做了限制。因此我们继续去看AMS.getRecentTasks这个方法的具体实现。

? ? ? ? AMS.getRecentTasks最终是调用到RecentTask.getRecentTasksImpl中:

// frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java
    private ArrayList<ActivityManager.RecentTaskInfo> getRecentTasksImpl(int maxNum, int flags,
            boolean getTasksAllowed, int userId, int callingUid) {
        final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0;

        if (!isUserRunning(userId, FLAG_AND_UNLOCKED)) {
            Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
            return new ArrayList<>();
        }
        loadUserRecentsLocked(userId);

        final Set<Integer> includedUsers = getProfileIds(userId);
        includedUsers.add(Integer.valueOf(userId));

        final ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<>();
        final int size = mTasks.size();
        int numVisibleTasks = 0;
        for (int i = 0; i < size; i++) {
            final Task task = mTasks.get(i);

            if (isVisibleRecentTask(task)) {
                numVisibleTasks++;
                if (isInVisibleRange(task, i, numVisibleTasks, withExcluded)) {
                    // Fall through
                } else {
                    // Not in visible range
                    continue;
                }
            } else {
                // Not visible
                continue;
            }

            // Skip remaining tasks once we reach the requested size
            if (res.size() >= maxNum) {
                continue;
            }

            // Only add calling user or related users recent tasks
            if (!includedUsers.contains(Integer.valueOf(task.mUserId))) {
                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + task);
                continue;
            }

            if (task.realActivitySuspended) {
                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + task);
                continue;
            }

            if (!getTasksAllowed) {
                // If the caller doesn't have the GET_TASKS permission, then only
                // allow them to see a small subset of tasks -- their own and home.
                if (!task.isActivityTypeHome() && task.effectiveUid != callingUid) {
                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + task);
                    continue;
                }
            }

            if (task.autoRemoveRecents && task.getTopNonFinishingActivity() == null) {
                // Don't include auto remove tasks that are finished or finishing.
                if (DEBUG_RECENTS) {
                    Slog.d(TAG_RECENTS, "Skipping, auto-remove without activity: " + task);
                }
                continue;
            }
            if ((flags & RECENT_IGNORE_UNAVAILABLE) != 0 && !task.isAvailable) {
                if (DEBUG_RECENTS) {
                    Slog.d(TAG_RECENTS, "Skipping, unavail real act: " + task);
                }
                continue;
            }

            if (!task.mUserSetupComplete) {
                // Don't include task launched while user is not done setting-up.
                if (DEBUG_RECENTS) {
                    Slog.d(TAG_RECENTS, "Skipping, user setup not complete: " + task);
                }
                continue;
            }

            res.add(createRecentTaskInfo(task, true /* stripExtras */));
        }
        return res;
    }

? ? ? ? 从getRecentTasksImpl的源码中推测,此方法并没有什么明显的限制,最多只是判断了Task的合法性,那么只能怀疑是mTasks中的个数可能真的是最多只有9个,因此,在此处加了个打印,在调用进来是将mTasks的元素个数值打印出来,发现确实到9后,再打开新的应用,此时该值仍是9。因此继续追查mTasks会在哪有remove的操作,将超出限制的元素清除掉。在源码中搜索mTasks.remove,排查所有调用的地方。

? ? ? ? 下面就是可疑之处,然后加上打印,发现确实每次打印新的应用,增加新的RecentTask时,就会被触发,trimInactiveRecentTasks方法中有两处mTasks.remove调用:

// frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java
    private void trimInactiveRecentTasks() {
        if (mFreezeTaskListReordering) {
            // Defer trimming inactive recent tasks until we are unfrozen
            return;
        }

        int recentsCount = mTasks.size();

        // Remove from the end of the list until we reach the max number of recents
        while (recentsCount > mGlobalMaxNumTasks) {
            final Task task = mTasks.remove(recentsCount - 1);
            notifyTaskRemoved(task, true /* wasTrimmed */, false /* killProcess */);
            recentsCount--;
            if (DEBUG_RECENTS_TRIM_TASKS) {
                Slog.d(TAG, "Trimming over max-recents task=" + task
                        + " max=" + mGlobalMaxNumTasks);
            }
        }

        // Remove any tasks that belong to currently quiet profiles
        final int[] profileUserIds = getCurrentProfileIds();
        mTmpQuietProfileUserIds.clear();
        for (int userId : profileUserIds) {
            final UserInfo userInfo = getUserInfo(userId);
            if (userInfo != null && userInfo.isManagedProfile() && userInfo.isQuietModeEnabled()) {
                mTmpQuietProfileUserIds.put(userId, true);
            }
            if (DEBUG_RECENTS_TRIM_TASKS) {
                Slog.d(TAG, "User: " + userInfo
                        + " quiet=" + mTmpQuietProfileUserIds.get(userId));
            }
        }

        // Remove any inactive tasks, calculate the latest set of visible tasks.
        int numVisibleTasks = 0;
        for (int i = 0; i < mTasks.size(); ) {
            final Task task = mTasks.get(i);

            if (isActiveRecentTask(task, mTmpQuietProfileUserIds)) {
                if (!mHasVisibleRecentTasks) {
                    // Keep all active tasks if visible recent tasks is not supported
                    i++;
                    continue;
                }

                if (!isVisibleRecentTask(task)) {
                    // Keep all active-but-invisible tasks
                    i++;
                    continue;
                } else {
                    numVisibleTasks++;
                    if (isInVisibleRange(task, i, numVisibleTasks, false /* skipExcludedCheck */)
                            || !isTrimmable(task)) {
                        // Keep visible tasks in range
                        i++;
                        continue;
                    } else {
                        // Fall through to trim visible tasks that are no longer in range and
                        // trimmable
                        if (DEBUG_RECENTS_TRIM_TASKS) {
                            Slog.d(TAG,
                                    "Trimming out-of-range visible task=" + task);
                        }
                    }
                }
            } else {
                // Fall through to trim inactive tasks
                if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming inactive task=" + task);
            }

            // Task is no longer active, trim it from the list
            mTasks.remove(task);
            notifyTaskRemoved(task, true /* wasTrimmed */, false /* killProcess */);
            notifyTaskPersisterLocked(task, false /* flush */);
        }
    }

? ? ? ? 其中一处是由mGlobalMaxNumTasks这个值来控制,当mTasks元素个数超过这个值时,则会remove掉超出的Task。经过打印,mGlobalMaxNumTasks这个值并不是9,这个值得来源如下:

mGlobalMaxNumTasks = ActivityTaskManager.getMaxRecentTasksStatic();
// frameworks/base/core/java/android/app/ActivityTaskManager.java
    public static int getMaxRecentTasksStatic() {
        if (sMaxRecentTasks < 0) {
            return sMaxRecentTasks = ActivityManager.isLowRamDeviceStatic() ? 36 : 48;
        }
        return sMaxRecentTasks;
    }

? ? ? ? 对于LowRam设备而言,此值为36,也就是说,GO设置最多只能有36条RecentTask记录,而非GO最多只能有48个。

? ? ? ? 但这个值仍不是这个问题的限制值,在加上打印是,可以得知trimInactiveRecentTasks方法remove超出的Task是方法的结束地方,回到trimInactiveRecentTasks方法,可以看到,这个方法会遍历mTasks的所有元素,然后将不符合要求的Task remove掉,而这些条件中,有一个方法是最符合我们的想法的 -- isInVisibleRange:

// frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java
    private boolean isInVisibleRange(Task task, int taskIndex, int numVisibleTasks,
            boolean skipExcludedCheck) {
        if (!skipExcludedCheck) {
            // Keep the most recent task even if it is excluded from recents
            final boolean isExcludeFromRecents =
                    (task.getBaseIntent().getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
                            == FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
            if (isExcludeFromRecents) {
                if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\texcludeFromRecents=true");
                return taskIndex == 0;
            }
        }

        if (mMinNumVisibleTasks >= 0 && numVisibleTasks <= mMinNumVisibleTasks) {
            // Always keep up to the min number of recent tasks, after that fall through to the
            // checks below
            return true;
        }

        // The given task if always treated as in visible range if it is the origin of pinned task.
        if (task.mChildPipActivity != null) return true;

        if (mMaxNumVisibleTasks >= 0) {
            // Always keep up to the max number of recent tasks, but return false afterwards
            return numVisibleTasks <= mMaxNumVisibleTasks;
        }

        if (mActiveTasksSessionDurationMs > 0) {
            // Keep the task if the inactive time is within the session window, this check must come
            // after the checks for the min/max visible task range
            if (task.getInactiveDuration() <= mActiveTasksSessionDurationMs) {
                return true;
            }
        }

        return false;
    }

? ? ? ? 上面的isInVisibleRange方法中,可以看到一个很显眼的变量 --?mMaxNumVisibleTasks,当mMaxNumVisibleTasks这个值大于等于0时,则表明生效,如果可见的RecentTasks个数小于mMaxNumVisibleTasks的值,则跳过mMaxNumVisibleTasks中的remove操作,但如果超出了,则会执行remove操作。

? ? ? ? 再看mMaxNumVisibleTasks这个值的来源:

? ? ? ? 1.RecentTasks.setParameters,通过源码的搜索及注解,可以认为此接口只为测试所用。

? ? ? ? 2.RecentTasks.loadParametersFromResources,这个是根据不同的方案类型读取了不同的值:

// frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java
    void loadParametersFromResources(Resources res) {
        if (ActivityManager.isLowRamDeviceStatic()) {
            mMinNumVisibleTasks = res.getInteger(
                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam);
            mMaxNumVisibleTasks = res.getInteger(
                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam);
        } else if (SystemProperties.getBoolean("ro.recents.grid", false)) {
            mMinNumVisibleTasks = res.getInteger(
                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid);
            mMaxNumVisibleTasks = res.getInteger(
                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid);
        } else {
            mMinNumVisibleTasks = res.getInteger(
                    com.android.internal.R.integer.config_minNumVisibleRecentTasks);
            mMaxNumVisibleTasks = res.getInteger(
                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks);
        }
        final int sessionDurationHrs = res.getInteger(
                com.android.internal.R.integer.config_activeTaskDurationHours);
        mActiveTasksSessionDurationMs = (sessionDurationHrs > 0)
                ? TimeUnit.HOURS.toMillis(sessionDurationHrs)
                : -1;
    }

? ? ? ? 可以看到LowRam设备的值来源于config_maxNumVisibleRecentTasks_lowRam这个配置。原生配置中,此值就是9:

./core/res/res/values/config.xml:2915:    <integer name="config_maxNumVisibleRecentTasks_lowRam">9</integer>

? ? ? ? 问题澄清。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 09:28:21  更:2021-08-29 09:30:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 6:18:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码