Android split screen process analysis

This article is based on Android 11.

The Divider in the SystemUI module manages all split-screen objects:

  • DividerView (split screen dividing line, split screen display interface)
  • SplitScreenTaskOrganizer (split screen Task organizer, split screen logic)

The focus here is to implement split-screen logic SplitScreenTaskOrganizer.

The Devider class implements DisplayController.OnDisplaysChangedListener and calls back onDisplayAdded() after the system starts:

// Devider.java
@Override
public void onDisplayAdded(int displayId) {
    
    
    mSplits.init();
}

The init() method of the SplitScreenTaskOrganizer object is called:

class SplitScreenTaskOrganizer extends TaskOrganizer {
    
    
    void init() throws RemoteException {
    
    
        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
        synchronized (this) {
    
    
            try {
    
    
                mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
                        WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
                mSecondary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
                        WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
            } catch (Exception e) {
    
    
                // teardown to prevent callbacks
                unregisterOrganizer();
                throw e;
            }
        }
    }
}

SplitScreenTaskOrganizer inherits TaskOrganizer, which provides an interface for managing tasks to ActivityTaskManager/WindowManager.

/**
 * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
 */

TaskOrganizer is managed uniformly by TaskOrganizerController.

/**
 * Stores the TaskOrganizers associated with a given windowing mode and
 * their associated state.
 */

Looking back at the implementation of the init() method, there are mainly two methods:

  • registerOrganizer()
  • TaskOrganizer.createRootTask()

1.registerOrganizer

The registerOrganizer(int windowingMode) method receives the windowingMode parameter of type int, and passes in the WINDOWING_MODE_SPLIT_SCREEN_PRIMARY and WINDOWING_MODE_SPLIT_SCREEN_SECONDARY parameters respectively. The registerOrganizer() method is implemented in the parent class TaskOrganizer and is registered through the TaskOrganizerController object.

  • WINDOWING_MODE_SPLIT_SCREEN_PRIMARY (main screen in split screen)
  • WINDOWING_MODE_SPLIT_SCREEN_SECONDARY (secondary screen in split screen)

1.1 TaskOrganizerController

// TaskOrganizerController.java

private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();

@Override
public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
    
    
    
    // 添加到变量 mTaskOrganizersForWindowingMode
    LinkedList<IBinder> orgs = mTaskOrganizersForWindowingMode.get(windowingMode);
    if (orgs == null) {
    
    
        orgs = new LinkedList<>();
        mTaskOrganizersForWindowingMode.put(windowingMode, orgs);
    }
    orgs.add(organizer.asBinder());
    
    // 添加到 mTaskOrganizerStates 管理。
    if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
    
    
        mTaskOrganizerStates.put(organizer.asBinder(),
                new TaskOrganizerState(organizer, uid));
    }
    
    // 更新task状态,通知task对象已经被organized,关联TaskOrganizer对象,task.setTaskOrganizer(ITaskOrganizer organizer)
    mService.mRootWindowContainer.forAllTasks((task) -> {
    
    
        if (task.getWindowingMode() == windowingMode) {
    
    
            task.updateTaskOrganizerState(true /* forceUpdate */);
        }
    });
}

TaskOrganizerController adds the ITaskOrganizer object to mTaskOrganizerStates management. mTaskOrganizerStates is a HashMap, the key is the Binder object, and the value is the internal proxy class TaskOrganizerState. When the corresponding task state changes callback, it is taken out from this HashMap; the task state is updated to notify the task object that it has been Is organized, associated with the TaskOrganizer object, task.setTaskOrganizer(ITaskOrganizer organizer).

1.2 Task

// Task.java
boolean updateTaskOrganizerState(boolean forceUpdate) {
    
    
    // 从TaskOrganizerController.mTaskOrganizersForWindowingMode获取ITaskOrganizer对象
    final ITaskOrganizer org =
            mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
    final boolean result = setTaskOrganizer(org);
    mLastTaskOrganizerWindowingMode = windowingMode;
    return result;
}

The setTaskOrganizer() method associates the ITaskOrganizer object and updates the variable mLastTaskOrganizerWindowingMode.

// Task.java
boolean setTaskOrganizer(ITaskOrganizer organizer) {
    
    
    mTaskOrganizer = organizer;
}

@Override
boolean isOrganized() {
    
    
    return mTaskOrganizer != null;
}

If the task status changes subsequently, use the isOrganized() method to determine whether a callback is needed to notify mTaskOrganizer.

// ActivityStack.java
@Override
void onChildPositionChanged(WindowContainer child) {
    
    
    if (isOrganized()) {
    
    
        mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
    }
}

ActivityStack inherits Task. OnChildPositionChanged() is called in onChildAdded(), onChildRemoved(), positionChildAt() and other methods that change the parent-child level. If the mTaskOrganizer object is not null, event information is distributed through mTaskOrganizerController, and then mTaskOrganizerController gets it from the mTaskOrganizerStates variable. The object's proxy object TaskOrganizerState callbacks onTaskAppeared(), onTaskVanished(), onTaskInfoChanged(), etc.

2.TaskOrganizer.createRootTask

The TaskOrganizer.createRootTask() method is very simple. Create two empty Tasks with modes WINDOWING_MODE_SPLIT_SCREEN_PRIMARY and WINDOWING_MODE_SPLIT_SCREEN_SECONDARY in Display.DEFAULT_DISPLAY. No content will be displayed. Unlike other tasks, its mCreatedByOrganizer is true.

If it is WINDOWING_MODE_SPLIT_SCREEN_PRIMARY mode, TaskDisplayArea will also save its reference to the mRootSplitScreenPrimaryTask variable so that the corresponding task can be found when split screen is turned on later.

// TaskDisplayArea.java

private ActivityStack mRootSplitScreenPrimaryTask;

void addStackReferenceIfNeeded(ActivityStack stack) {
    
    
    if (windowingMode == WINDOWING_MODE_PINNED) {
    
    
        mRootPinnedTask = stack;
    } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
    
    
    	mRootSplitScreenPrimaryTask = stack;
    }
}

To enable split-screen mode later, set the tasks to be split-screen to their sub-tasks.

3. Turn on split screen

        ActivityOptions options = ActivityOptions.makeBasic();
        options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
        options.setSplitScreenCreateMode(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
        Bundle optsBundle = options == null ? null : options.toBundle();
        ActivityTaskManager.getService().startActivityFromRecents(taskId, optsBundle);

To enable split screen, you need to add ActivityOptions at startup, setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY), and enable it through ActivityTaskManagerService.startActivityFromRecents(int taskId, Bundle bOptions).

The general implementation of the split-screen logic is to find the task that corresponds to the specific task to be displayed. If it does not exist, recreate it, then find the mPrimary and mSecondary created by TaskOrganizer.createRootTask() at the beginning, and set them to the corresponding parent-child relationship through reparent() or addChild().

3.1 Home screen initialization

// ActivityTaskManagerService.java
int startActivityFromRecents(int callingPid, int callingUid, int taskId,
        SafeActivityOptions options) {
    
    
    
    final ActivityOptions activityOptions = options != null
                ? options.getOptions(this)
                : null;
    // 主屏
    task = mRootWindowContainer.anyTaskForId(taskId,
                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
    
    // 副屏
    return mService.getActivityStartController().startActivityInPackage(task.mCallingUid,
                callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
                null, 0, 0, options, userId, task, "startActivityFromRecents",
                false /* validateIncomingUser */, null /* originatingPendingIntent */,
                false /* allowBackgroundActivityStart */);
}

When the user turns on split screen, the main screen is usually an existing task and does not need to be restarted. The corresponding task can be found through mRootWindowContainer.anyTaskForId().

The secondary screen is generally opened by the user by clicking on the icon, and needs to be started through ActivityStater through mService.getActivityStartController().startActivityInPackage().

// RootWindowContainer.java
Task anyTaskForId(int id, @RootWindowContainer.AnyTaskForIdMatchTaskMode int matchMode,
        @Nullable ActivityOptions aOptions, boolean onTop) {
    
    
    
    // 具体要显示的task
    final PooledPredicate p = PooledLambda.obtainPredicate(
        Task::isTaskId, PooledLambda.__(Task.class), id);
    Task task = getTask(p);
    p.recycle();
    
    if (task != null) {
    
    
        if (aOptions != null) {
    
    
            
            // 主屏task: mode=split-screen-primary
            final ActivityStack launchStack =
                    getLaunchStack(null, aOptions, task, onTop);
            if (launchStack != null && task.getStack() != launchStack) {
    
    
                final int reparentMode = onTop
                        ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
                task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
                        "anyTaskForId");
            }
        }
        return task;
    }
}

First find the specific task to be displayed through the taskId, and then find the previously created main screen task through the getLaunchStack() method. The mRootSplitScreenPrimaryTask reference saved in TaskDisplayArea in Section 2 is set as the parent task of the task, task.reparent().

3.2 Secondary screen initialization

The initialization process of the secondary screen is similar to that of the main screen. Generally, you need to re-create a new task through ActivityStarter and set the secondary screen task as its parent task.

// TaskDisplayArea.java
ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId,
        boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {
    
    
    
    // 找到副屏 task
    Task launchRootTask = createdByOrganizer ? null : updateLaunchRootTask(windowingMode);
    if (launchRootTask != null) {
    
    
        // Since this stack will be put into a root task, its windowingMode will be inherited.
        windowingMode = WINDOWING_MODE_UNDEFINED;
    }
    
    // 创建具体要显示的task
    final ActivityStack stack = new ActivityStack(mAtmService, stackId, activityType,
           info, intent, createdByOrganizer);
    if (launchRootTask != null) {
    
    
        launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
        if (onTop) {
    
    
            positionStackAtTop((ActivityStack) launchRootTask, false /* includingParents */);
        }
    } else {
    
    
        addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
        stack.setWindowingMode(windowingMode, true /* creating */);
    }
    return stack;
}

Compare the window container structure before and after split screen is turned on:

  • Before opening:
    Before split screen is enabled
  • After opening:
    After split screen is turned on

4. Split screen interface display

After selecting the main screen task, the system enters the split-screen interface. As mentioned earlier, SplitScreenTaskOrganizer will be notified by callbacks regarding status changes of the main and secondary screen tasks, including the above setting subtask operations (reparent, addChild), callbacks onTaskInfoChanged(), handleTaskInfoChanged() .

// SplitScreenTaskOrganizer.java
private void handleTaskInfoChanged(RunningTaskInfo info) {
    
    
    
    final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
    final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
    if (info.token.asBinder() == mPrimary.token.asBinder()) {
    
    
        mPrimary = info;
    } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
    
    
        mSecondary = info;
    }
    final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
    final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
    
    if (primaryIsEmpty || secondaryIsEmpty) {
    
    
        if (mDivider.isDividerVisible()) {
    
    
            mDivider.startDismissSplit();
        } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
    
    
            // 显示分屏界面
            mDivider.startEnterSplit();
        }
    } else if (secondaryImpliesMinimize) {
    
    
        mDivider.ensureMinimizedSplit();
    } else {
    
    
        mDivider.ensureNormalSplit();
    }
}

You can see that when the main screen is filled, the split-screen interface begins to display.

Guess you like

Origin blog.csdn.net/qq_36063677/article/details/130350002