[深入SystemUI]-瞭解recents的啟動流程(一)

baijimao發表於2019-04-26

本文流程基於Android 9.0
preloadRecentApps()流程介紹

[深入SystemUI]-瞭解recents的啟動流程(一)

1. PhoneWindowManager的事件分發

PhoneWindowManager處理點選事件的方法是interceptKeyBeforeDispatching()。相應處理邏輯如下: 點選switch

} else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
    //不處於鎖屏介面
    if (!keyguardOn) {
        //ACTION_DOWN並且只點了一次
        if (down && repeatCount == 0) {
            //預載入recent
            preloadRecentApps();
        } else if (!down) {
            //當不處於ACTION_DOWN時,可能是up或者cancel
            toggleRecentApps();
        }
    }
    return -1;
}
複製程式碼

2.呼叫StatusBaraManbagerService的preloadRecentApps

private void preloadRecentApps() {
    mPreloadedRecentApps = true;
    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
    if (statusbar != null) {
        //呼叫statusbar的preloadRecentApps()
        statusbar.preloadRecentApps();
    }
}
複製程式碼

這裡的是statusbar是通過getStatusBarManagerInternal()獲取的,getStatusBarManagerInternal()實現如下:

StatusBarManagerInternal getStatusBarManagerInternal() {
    synchronized (mServiceAquireLock) {
        if (mStatusBarManagerInternal == null) {
            mStatusBarManagerInternal =
                    LocalServices.getService(StatusBarManagerInternal.class);
        }
        return mStatusBarManagerInternal;
    }
}
複製程式碼

那麼通過LocalServices.getService()是怎麼得到的呢?其實在StatusBarManagerService初始化的時候,就將StatusBarManagerInernal的實現加進了LocalService中。

/**
 * Construct the service, add the status bar view to the window manager
 */
public StatusBarManagerService(Context context, WindowManagerService windowManager) {
    mContext = context;
    mWindowManager = windowManager;

    LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
    LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
}
複製程式碼

mInternalService的實現如下:

/**
 * Private API used by NotificationManagerService.
 */
private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
    private boolean mNotificationLightOn;

    // 介面其他一些方法的實現

    @Override
    public void toggleRecentApps() {
        if (mBar != null) {
            try {
                mBar.toggleRecentApps();
            } catch (RemoteException ex) {}
        }
    }

    @Override
    public void setCurrentUser(int newUserId) {
        if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
        mCurrentUserId = newUserId;
    }


    @Override
    public void preloadRecentApps() {
        if (mBar != null) {
            try {
                mBar.preloadRecentApps();
            } catch (RemoteException ex) {}
        }
    }

    @Override
    public void cancelPreloadRecentApps() {
        if (mBar != null) {
            try {
                mBar.cancelPreloadRecentApps();
            } catch (RemoteException ex) {}
        }
    }

    @Override
    public void showRecentApps(boolean triggeredFromAltTab) {
        if (mBar != null) {
            try {
                mBar.showRecentApps(triggeredFromAltTab);
            } catch (RemoteException ex) {}
        }
    }

    @Override
    public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
        if (mBar != null) {
            try {
                mBar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
            } catch (RemoteException ex) {}
        }
    }
    // 介面其他一些方法的實現
};
複製程式碼

3.跨程式呼叫preloadRecentApps的實際實現

可以看出,呼叫mInternalService實際上是呼叫mBar:IStatusBar的方法。

mBar對應的物件時什麼呢?

// ================================================================================
// Callbacks from the status bar service.
// ================================================================================
@Override
public void registerStatusBar(IStatusBar bar, List<String> iconSlots,
        List<StatusBarIcon> iconList, int switches[], List<IBinder> binders,
        Rect fullscreenStackBounds, Rect dockedStackBounds) {
    enforceStatusBarService();

    Slog.i(TAG, "registerStatusBar bar=" + bar);
    mBar = bar;
    // code...
}
複製程式碼

registerStatusBar在哪裡呼叫呢?在StatusBarstart()方法中呼叫到:

@Override
public void start() {
    //code...
    try {
        mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
                fullscreenStackBounds, dockedStackBounds);
    } catch (RemoteException ex) {
        // If the system process isn't there we're doomed anyway.
    }
    //code...
}
複製程式碼

CommandQueue其實是IStatusBar的一個代理類,呼叫mCommandQueuepreloadRecentApps()方法其實是呼叫CommandQueue內部介面CallbackspreloadRecentApps(),而實現CommandQueue.Callbacks是在Recents類中實現的,對應的preloadRecentApps()的實際實現也是在Recents中,具體實現如下:

/**
 * Preloads info for the Recents activity.
 */
@Override
public void preloadRecentApps() {
    // Ensure the device has been provisioned before allowing the user to interact with
    // recents
    if (!isUserSetup()) {
        return;
    }

    if (mOverviewProxyService.getProxy() != null) {
        // TODO: Proxy to Launcher
        return;
    }

    int currentUser = sSystemServicesProxy.getCurrentUser();
    //判斷當前使用者是否為系統使用者
    if (sSystemServicesProxy.isSystemUser(currentUser)) {
        //mImpl為RecentsImpl,呼叫的實際上是RecentsImpl的preloadRecents()
        mImpl.preloadRecents();
    } else {
        if (mSystemToUserCallbacks != null) {
            IRecentsNonSystemUserCallbacks callbacks =
                    mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
            if (callbacks != null) {
                try {
                    callbacks.preloadRecents();
                } catch (RemoteException e) {
                    Log.e(TAG, "Callback failed", e);
                }
            } else {
                Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
            }
        }
    }
}
複製程式碼

4.從AMS中獲取任務佇列,加入到任務棧TaskStack中

RecentsImplpreloadRecents方法:

public void preloadRecents() {
    //如果螢幕固定,直接返回,螢幕固定功能是在Android5.1上增加的,當開啟此功能時,不允許切換到其他介面
    if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
        return;
    }

    // Skip preloading recents when keyguard is showing
    final StatusBar statusBar = getStatusBar();
    if (statusBar != null && statusBar.isKeyguardShowing()) {
        return;
    }

    // Preload only the raw task list into a new load plan (which will be consumed by the
    // RecentsActivity) only if there is a task to animate to.  Post this to ensure that we
    // don't block the touch feedback on the nav bar button which triggers this.
    mHandler.post(() -> {
        SystemServicesProxy ssp = Recents.getSystemServices();
        // 判斷最近任務不可見
        if (!ssp.isRecentsActivityVisible(null)) {
            ActivityManager.RunningTaskInfo runningTask =
                    ActivityManagerWrapper.getInstance().getRunningTask();
            //如果獲取到的runningTask(執行著的任務)為null時,則直接返回
            if (runningTask == null) {
                return;
            }

            RecentsTaskLoader loader = Recents.getTaskLoader();
            sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
            loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
            TaskStack stack = sInstanceLoadPlan.getTaskStack();
            if (stack.getTaskCount() > 0) {
                // Only preload the icon (but not the thumbnail since it may not have been taken
                // for the pausing activity)
                preloadIcon(runningTask.id);

                // At this point, we don't know anything about the stack state.  So only
                // calculate the dimensions of the thumbnail that we need for the transition
                // into Recents, but do not draw it until we construct the activity options when
                // we start Recents
                updateHeaderBarLayout(stack, null /* window rect override*/);
            }
        }
    });
}
複製程式碼

RecentsTaskLoaderpreloadTasks()方法:

/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
    preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
}

/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
        int currentUserId) {
    try {
        Trace.beginSection("preloadPlan");
        plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
    } finally {
        Trace.endSection();
    }
}
複製程式碼

RecentsTaskLoadPlanpreloadPlan()方法,將從AMS中獲取的任務佇列加入到任務棧TaskStack中:

/**
 * Preloads the list of recent tasks from the system. After this call, the TaskStack will
 * have a list of all the recent tasks with their metadata, not including icons or
 * thumbnails which were not cached and have to be loaded.
 *
 * The tasks will be ordered by:
 * - least-recent to most-recent stack tasks
 *
 * Note: Do not lock, since this can be calling back to the loader, which separately also drives
 * this call (callers should synchronize on the loader before making this call).
 */
public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
        int currentUserId) {
    Resources res = mContext.getResources();
    ArrayList<Task> allTasks = new ArrayList<>();
    if (mRawTasks == null) {
        //從ActivityManagerService中獲取原始的任務列表
        mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
                ActivityManager.getMaxRecentTasksStatic(), currentUserId);

        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
        Collections.reverse(mRawTasks);
    }

    int taskCount = mRawTasks.size();
    //通過遍歷,將RawTask變為最近任務中可以用於載入顯式的Task型別,最終加入到最近任務棧中mStack
    for (int i = 0; i < taskCount; i++) {
        ActivityManager.RecentTaskInfo t = mRawTasks.get(i);

        // Compose the task key
        final ComponentName sourceComponent = t.origActivity != null
                // Activity alias if there is one
                ? t.origActivity
                // The real activity if there is no alias (or the target if there is one)
                : t.realActivity;
        final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
        TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
                sourceComponent, t.userId, t.lastActiveTime);

        boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
        boolean isStackTask = !isFreeformTask;
        boolean isLaunchTarget = taskKey.id == runningTaskId;

        ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
        if (info == null) {
            continue;
        }

        // Load the title, icon, and color
        String title = opts.loadTitles
                ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
                : "";
        String titleDescription = opts.loadTitles
                ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
                : "";
        Drawable icon = isStackTask
                ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, false)
                : null;
        ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
                false /* loadIfNotCached */, false /* storeInCache */);
        int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
        int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
        boolean isSystemApp = (info != null) &&
                ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);

        // TODO: Refactor to not do this every preload
        if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
            mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
        }
        boolean isLocked = mTmpLockedUsers.get(t.userId);

        // Add the task to the stack
        Task task = new Task(taskKey, icon,
                thumbnail, title, titleDescription, activityColor, backgroundColor,
                isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
                t.taskDescription, t.resizeMode, t.topActivity, isLocked);

        allTasks.add(task);
    }

    // Initialize the stacks
    mStack = new TaskStack();
    //這裡是預先將任務佇列載入顯式到TaskStackView上
    mStack.setTasks(allTasks, false /* notifyStackChanges */);
}
複製程式碼

注意:儲存任務佇列的都是一些臨時變數,會伴隨著gc回收掉。

參考:Android輸入事件從讀取到分發五:事件分發前的攔截過程

相關文章