前文
Android U 多任务启动分屏——Launcher流程(上分屏)
前文讲了Launcher中上分屏流程,现在我们看看下分屏的流程。
最近任务onClick事件的监听
在最近任务中每个任务都是一个TaskView,对TaskView操作,就是每个任务的操作。
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskView.java
public TaskView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
//从给定的context获取当前的StatefulActivity实例
//绑定TaskView和Activity之间的关系
mActivity = StatefulActivity.fromContext(context);
//监听当前TaskView的点击事件
setOnClickListener(this::onClick);
//初始化绘制相关参数
mCurrentFullscreenParams = new FullscreenDrawParams(context);
//把当前的Activity以及其对应的TaskView传递给DigitalWellBeingToast
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
//判断是否需要启用键盘焦点高亮
boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|| DesktopTaskView.DESKTOP_MODE_SUPPORTED;
//如果不需要启用键盘焦点高亮,则setWillNotDraw(true),表示这个视图不需要绘制
setWillNotDraw(!keyboardFocusHighlightEnabled);
//使用TypedArray从XML属性中读取TaskView的自定义属性
TypedArray ta = context.obtainStyledAttributes(
attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
//如果启用了键盘焦点高亮,则创建一个BorderAnimator实例。
mBorderAnimator = !keyboardFocusHighlightEnabled
? null
: new BorderAnimator(
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderColor= */ ta.getColor(
R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
/* borderAnimationParams= */ new BorderAnimator.SimpleParams(
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
/* boundsBuilder= */ this::updateBorderBounds,
/* targetView= */ this));
//回收TypedArray对象以避免内存泄漏。
ta.recycle();
}
private void onClick(View view) {
if (getTask() == null) {
return;
}
//是否选择第二个分屏的应用
if (confirmSecondSplitSelectApp()) {
return;
}
//运行当前选择的Task
launchTasks();
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
}
onClick
方法中,如果我们选择了第二个分屏的应用那么就进入到分屏流程confirmSecondSplitSelectApp()
,这个方法中同样会进行一些判断当前是否进入分屏,还是直接运行当前TasklaunchTasks()
选择下分屏
确定选择下分屏,播放相关动画,调用SytemUI接口进入到真正分屏
确定下分屏task
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskView.java
/**
* @return {@code true} if user is already in split select mode and this tap was to choose the
* second app. {@code false} otherwise
*/
protected boolean confirmSecondSplitSelectApp() {
//获取最后一次点击的Task的索引
int index = getLastSelectedChildTaskIndex();
//获取该索引对应的Task相关参数
TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
//判断TaskIdAttributeContainer对象是否为空
if (container != null) {
//进入confirmSplitSelect方法判断分屏的后续流程
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView().getDrawable(), container.getThumbnailView(),
container.getThumbnailView().getThumbnail(), /* intent */ null,
/* user */ null);
}
return false;
}
public RecentsView getRecentsView() {
return (RecentsView) getParent();
}
前文我们分析过,这里的getRecentsView()
返回的是当前TaskView
的父View
,类型强制转换为了RecentsView
,而RecentsView
是抽象类,优先调用的应该是其子类的confirmSplitSelect
方法。
getRecentsView()
对应的就是com.android.quickstep.views.LauncherRecentsView{b2fe033 V.ED..... ........ 0,0-1440,2960 #7f09022a app:id/overview_panel}
,即TaskView
父View
是LauncherRecentsView
。
LauncherRecentsView
是RecentsView
的子类之一,但是LauncherRecentsView
中没有confirmSplitSelect
方法,所以这里调用的是其父类RecentsView
中的confirmSplitSelect
方法(儿子没有的找父亲要,总之就是找老登爆点金币)。
confirmSplitSelect
方法传递的参数this
指的就是选择的下分屏task。
动画准备和启动
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/RecentsView.java
/**
* Confirms the selection of the next split task. The extra data is passed through because the
* user may be selecting a subtask in a group.
*
* @param containerTaskView If our second selected app is currently running in Recents, this is
* the "container" TaskView from Recents. If we are starting a fresh
* instance of the app from an Intent, this will be null.
* @param task The Task corresponding to our second selected app. If we are starting a fresh
* instance of the app from an Intent, this will be null.
* @param drawable The Drawable corresponding to our second selected app's icon.
* @param secondView The View representing the current space on the screen where the second app
* is (either the ThumbnailView or the tapped icon).
* @param intent If we are launching a fresh instance of the app, this is the Intent for it. If
* the second app is already running in Recents, this will be null.
* @param user If we are launching a fresh instance of the app, this is the UserHandle for it.
* If the second app is already running in Recents, this will be null.
* @return true if waiting for confirmation of second app or if split animations are running,
* false otherwise
*/
public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user) {
//检查是否可以全屏启动任务:如果可以,直接返回false,表示不需要分屏
if (canLaunchFullscreenTask()) {
return false;
}
//检查是否已确认两个分屏应用:就是确认两个taskId是否不为空。
if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
return true;
}
// Second task is selected either as an already-running Task or an Intent
//如果task不为null,检查任务是否支持分屏(isDockable)。
//如果task为null,则使用intent和user设置下分屏任务
if (task != null) {
//如果不支持,显示不支持分屏的提示,并返回true
if (!task.isDockable) {
// Task does not support split screen
mSplitUnsupportedToast.show();
return true;
}
//保存下分屏任务的task
mSplitSelectStateController.setSecondTask(task);
} else {
//保存下分屏任务的intent
mSplitSelectStateController.setSecondTask(intent, user);
}
//使用RectF和Rect对象存储第一个和第二个任务的起始和结束边界。
RectF secondTaskStartingBounds = new RectF();//初始化底部的区域
Rect secondTaskEndingBounds = new Rect();
// TODO(194414938) starting bounds seem slightly off, investigate
Rect firstTaskStartingBounds = new Rect();//初始化顶部的区域
Rect firstTaskEndingBounds = mTempRect;
//根据设备是否为平板计算动画时长(timings)
boolean isTablet = mActivity.getDeviceProfile().isTablet;
SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
//创建分屏动画
PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
int halfDividerSize = getResources()
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
//获取对应的一个上屏幕区域和下屏幕区域截至区域firstTaskEndingBounds,secondTaskEndingBounds
mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
//设置mFirstFloatingTaskView和mSecondFloatingTaskView布局的属性和动画
//获取当前的上屏幕区域坐标情况放入firstTaskStartingBounds
mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
//有了初始区域和结束区域,进行相关的动画配置
mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
false /* fadeWithThumbnail */, true /* isStagedTask */);
safeRemoveDragLayerView(mSecondFloatingTaskView);
//与mFirstFloatingTaskView同理
mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, secondView,
thumbnail, drawable, secondTaskStartingBounds);
mSecondFloatingTaskView.setAlpha(1);
mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
//设置动画透明度
pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
timings.getInstructionsFadeStartOffset(),
timings.getInstructionsFadeEndOffset()));
//设置动画监听,动画结束时,调用launchSplitTasks启动分屏
//其传递的callback用于重置分屏选择状态。
pendingAnimation.addEndListener(aBoolean -> {
mSplitSelectStateController.launchSplitTasks(
aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
});
mSecondSplitHiddenView = containerTaskView;
//如果containerTaskView不为null,隐藏下屏的TaskView
if (mSecondSplitHiddenView != null) {
mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
mSplitSelectStateController.getSecondTaskId());
}
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
// Fade out all other views underneath placeholders
//使用ObjectAnimator淡出所有其他视图。
ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
//启动动画
pendingAnimation.buildAnim().start();
return true;
}
参数:
containerTaskView
:如果第二个选中的应用在最近任务中运行,这是从最近任务中获取的“容器”TaskView。如果是通过Intent启动的新实例,则为null。
task
:对应于第二个选中应用的Task。如果是通过Intent启动的新实例,则为null。
drawable
:第二个选中应用的图标对应的Drawable。
secondView
:表示屏幕上第二个应用当前位置的View(可能是缩略图视图或点击的图标)。
thumbnail
:第二个应用的缩略图,可以为null。
intent
:如果启动的是第二个应用的新实例,这是对应的Intent。如果应用已在最近任务中运行,则为null。
user
:如果启动的是第二个应用的新实例,这是对应的UserHandle。如果应用已在最近任务中运行,则为null。
方法作用:
这个方法处理分屏功能中第二个应用的确认选择,包括检查是否支持分屏、设置第二个任务、计算动画边界、创建并启动分屏动画、处理隐藏视图以及性能监控。它返回一个布尔值,指示是否正在等待确认或动画是否正在运行。
关键方法:
-
下分屏任务的保存
// Second task is selected either as an already-running Task or an Intent //如果task为null,则使用intent和user设置下分屏任务 if (task != null) { ...... //保存下分屏任务的task mSplitSelectStateController.setSecondTask(task); } else { //保存下分屏任务的intent mSplitSelectStateController.setSecondTask(intent, user); }
这里不管是用
task
还是intent
的方式,都会调用到SplitSelectStateController
的etSecondTask
方法。
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java/** * To be called as soon as user selects the second task (even if animations aren't complete) * @param task The second task that will be launched. */ public void setSecondTask(Task task) { mSecondTaskId = task.key.id; if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) { mSplitSelectDataHolder.setSecondTask(task.key.id); } } /** * To be called as soon as user selects the second app (even if animations aren't complete) * @param intent The second intent that will be launched. * @param user The user of that intent. */ public void setSecondTask(Intent intent, UserHandle user) { mSecondTaskIntent = intent; mSecondUser = user; if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) { mSplitSelectDataHolder.setSecondTask(intent, user); } }
ENABLE_SPLIT_LAUNCH_DATA_REFACTOR
的默认值为true
,因此会调用到SplitSelectDataHolder的setSecondTask方法。
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt/** * To be called as soon as user selects the second task (even if animations aren't complete) * @param taskId The second task that will be launched. */ fun setSecondTask(taskId: Int) { secondTaskId = taskId } /** * To be called as soon as user selects the second app (even if animations aren't complete) * @param intent The second intent that will be launched. * @param user The user of that intent. */ fun setSecondTask(intent: Intent, user: UserHandle) { secondIntent = intent secondUser = user }
从代码上看,这两个方法就是保存
task
和intent
。
目的就是把这些初始的参数保存在SplitSelectDataHolder
中,对ENABLE_SPLIT_LAUNCH_DATA_REFACTOR
默认值为true
的情况时,为后续的分屏做准备(上分屏流程中RecentsView
的initiateSplitSelect
里面,调用流程中同样有此操作)。 -
监听动画结束流程启动分屏
pendingAnimation.addEndListener(aBoolean -> { mSplitSelectStateController.launchSplitTasks( aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()); InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER); });
动画结束后,调用的这个方法也是我们真正启动分屏的方法。
启动分屏
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
/**
* To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
* to be launched. Call after launcher side animations are complete.
*/
public void launchSplitTasks(Consumer<Boolean> callback) {
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
instanceIds.first);
mStatsLogManager.logger()
.withItemInfo(mItemInfo)
.withInstanceId(instanceIds.second)
.log(mSplitEvent);
}
这里我们主要关注其中的launchTasks
方法
launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
instanceIds.first);
这些参数传递的就是需要分屏的两个应用的TaskId
和Intent
,分屏的启动方式一般就是通过TaskId
和Intent
来确定分屏应用
mInitialTaskId, mInitialTaskIntent
:第一个应用的TaskId
和Intent
mSecondTaskId, mSecondTaskIntent
:第二个应用的TaskId
和Intent
mInitialStagePosition
:分屏的初始位置(从前面上分屏流程中TaskView.initiateSplitSelect
传递过来的值,其值为0)
freezeTaskList
:判断是否冻结最近任务列表,这里直接给的值是false
DEFAULT_SPLIT_RATIO
:默认的分屏比例
注:其中Intent
参数可以为null
/**
* To be called when we want to launch split pairs from Overview. Split can be initiated from
* either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
* fill in intent with a taskId2 are set.
* @param intent1 is null when split is initiated from Overview
* @param stagePosition representing location of task1
* @param shellInstanceId loggingId to be used by shell, will be non-null for actions that
* create a split instance, null for cases that bring existing instaces to the
* foreground (quickswitch, launching previous pairs from overview)
*/
public void launchTasks(int taskId1, @Nullable Intent intent1, int taskId2,
@Nullable Intent intent2, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
@Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
//ENABLE_SPLIT_LAUNCH_DATA_REFACTOR的值为true
//通过launchTasksRefactored方法进入分屏
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
return;
}
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
}
boolean hasSecondaryPendingIntent = mSecondPendingIntent != null;
//根据ENABLE_SHELL_TRANSITIONS(shell动画配置开关)的值决定进入哪个流程
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
final RemoteTransition remoteTransition = getShellRemoteTransition(taskId1, taskId2,
callback);
if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
null /* options2 */, stagePosition, splitRatio, remoteTransition,
shellInstanceId);
} else if (intent2 == null && !hasSecondaryPendingIntent) {
launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
splitRatio, remoteTransition, shellInstanceId);
} else if (intent1 == null) {
launchIntentOrShortcut(intent2, mSecondUser, options1, taskId1,
getOppositeStagePosition(stagePosition), splitRatio, remoteTransition,
shellInstanceId);
} else {
mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
mInitialUser.getIdentifier(), getShortcutInfo(intent1, mInitialUser),
options1.toBundle(), hasSecondaryPendingIntent
? mSecondPendingIntent
: getPendingIntent(intent2, mSecondUser),
mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
null /* options2 */, stagePosition, splitRatio, remoteTransition,
shellInstanceId);
}
} else {
final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(taskId1, taskId2,
callback);
if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
shellInstanceId);
} else if (intent2 == null && !hasSecondaryPendingIntent) {
launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
stagePosition, splitRatio, adapter, shellInstanceId);
} else if (intent1 == null) {
launchIntentOrShortcutLegacy(intent2, mSecondUser, options1, taskId1,
getOppositeStagePosition(stagePosition), splitRatio, adapter,
shellInstanceId);
} else {
mSystemUiProxy.startIntentsWithLegacyTransition(
getPendingIntent(intent1, mInitialUser), mInitialUser.getIdentifier(),
getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
hasSecondaryPendingIntent
? mSecondPendingIntent
: getPendingIntent(intent2, mSecondUser),
mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
}
}
}
ENABLE_SPLIT_LAUNCH_DATA_REFACTOR
的默认值为true
,进入launchTasksRefactored
方法。
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
return;
}
其后面的代码本质上都是在符合条件的情况下通过调用SystemUiProxy的分屏方法,在launchTasksRefactored
方法中调用方法类似,不再赘述。从堆栈上看一般情况走的也是launchTasksRefactored的流程,这里跟踪launchTasksRefactored
方法即可。
注:在android 13中没有该方法,感兴趣的朋友可以跳过该方法看后面的流程即可。
private void launchTasksRefactored(Consumer<Boolean> callback, boolean freezeTaskList,
float splitRatio, @Nullable InstanceId shellInstanceId) {
final ActivityOptions options1 = ActivityOptions.makeBasic();
//判断是否冻结最近任务列表,前面传递的值是false
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
}
//获取之前保存在SplitSelectDataHolder中的数据
SplitSelectDataHolder.SplitLaunchData launchData =
mSplitSelectDataHolder.getSplitLaunchData();
int firstTaskId = launchData.getInitialTaskId();
int secondTaskId = launchData.getSecondTaskId();
ShortcutInfo firstShortcut = launchData.getInitialShortcut();
ShortcutInfo secondShortcut = launchData.getSecondShortcut();
PendingIntent firstPI = launchData.getInitialPendingIntent();
PendingIntent secondPI = launchData.getSecondPendingIntent();
int firstUserId = launchData.getInitialUserId();
int secondUserId = launchData.getSecondUserId();
int initialStagePosition = launchData.getInitialStagePosition();
//获取基本的Bundle
Bundle optionsBundle = options1.toBundle();
//判断ENABLE_SHELL_TRANSITIONS
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
//获取远程动画
final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
secondTaskId, callback);
//根据launchData.getSplitLaunchType()的值选择SystemUiProxy调用哪个方法
switch (launchData.getSplitLaunchType()) {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_TASK_PENDINGINTENT ->
mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_TASK_SHORTCUT ->
mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_PENDINGINTENT_TASK ->
mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle,
secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_PENDINGINTENT_PENDINGINTENT ->
mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
optionsBundle, secondPI, secondUserId, secondShortcut,
null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_SHORTCUT_TASK ->
mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
}
} else {
final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId,
callback);
switch (launchData.getSplitLaunchType()) {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
secondTaskId, null /* options2 */, initialStagePosition,
splitRatio, adapter, shellInstanceId);
case SPLIT_TASK_PENDINGINTENT ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
secondUserId, optionsBundle, firstTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
case SPLIT_TASK_SHORTCUT ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
splitRatio, adapter, shellInstanceId);
case SPLIT_PENDINGINTENT_TASK ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
case SPLIT_PENDINGINTENT_PENDINGINTENT ->
mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId,
firstShortcut, optionsBundle, secondPI, secondUserId,
secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
adapter, shellInstanceId);
case SPLIT_SHORTCUT_TASK ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
}
}
}
这个方法主要做了这几件事:
1.获取之前保存在SplitSelectDataHolder中的数据,上下分屏taskId、intent、分屏位置等等。
2.根据TaskAnimationManager.ENABLE_SHELL_TRANSITIONS
和launchData.getSplitLaunchType()
的值来确定调用哪个方法。
3.通过SystemUiProxy跨进程调用到SystemUi进程
TaskAnimationManager.ENABLE_SHELL_TRANSITIONS
默认值为true
,我们只需要关注launchData.getSplitLaunchType()
的值是多少即可。
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
/**
* Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
* Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
*/
@VisibleForTesting
@SplitLaunchType
fun getSplitLaunchType(): Int {
if (initialIntent != null || secondIntent != null) {
throw IllegalStateException("Intents need to be converted")
}
// Prioritize task launches first
if (initialTaskId != INVALID_TASK_ID) {
if (secondTaskId != INVALID_TASK_ID) {
return SPLIT_TASK_TASK
}
if (secondShortcut != null) {
return SPLIT_TASK_SHORTCUT
}
if (secondPendingIntent != null) {
return SPLIT_TASK_PENDINGINTENT
}
}
......
}
这里initialTaskId
和secondTaskId
均有值(前面有讲解保存到SplitSelectDataHolder中的流程),因此该方法返回值为SPLIT_TASK_TASK
。
最终我们知道其调用的是startTasks
方法:
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
从参数中可以看到,我们主要传递的就是,需要分屏的两个taskId相关参数
firstTaskId, optionsBundle
:第一个应用的TaskId
和Bundle
对象
这里Bundle
对象通过ActivityOptions
获取
final ActivityOptions options1 = ActivityOptions.makeBasic();
Bundle optionsBundle = options1.toBundle();
可以添加打印看看里面最终是什么
for(String key : optionsBundle.keySet()){
Slog.i("splitscreen","launchTasksRefactored"+
" optionsBundle Key="+key + " value="+ optionsBundle.get(key)+"\n");
}
这里的打印的结果只有一对键值对:
Key=android.activity.splashScreenStyle value=-1
secondTaskId, null
:第二个应用的TaskId
和Bundle
对象(值为null)
initialStagePosition
:分屏的初始位置
splitRatio
:分屏的比例(值为前面launchSplitTasks
方法传递的DEFAULT_SPLIT_RATIO
,即0.5f)
remoteTransition
:远程动画,可以为null
,这是通过getShellRemoteTransition
方法获取。
其中Bundle
对象后续在SystemUI流程中会用到。
跨进程调用到SystemUI
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTasks");
}
}
}
其中mSplitScreen
为ISplitScreen
对象,ISplitScreen
的实现在SplitScreenController.java
中。
后续流程
其他补充
ISystemUiProxy初始化
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
public static class TISBinder extends IOverviewProxy.Stub {
......
@BinderThread
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_BUBBLES));
ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
KEY_EXTRA_SHELL_SPLIT_SCREEN));
IOneHanded onehanded = IOneHanded.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
ISysuiUnlockAnimationController launcherUnlockAnimationController =
ISysuiUnlockAnimationController.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
unfoldTransition, dragAndDrop);
tis.initInputMonitor("TISBinder#onInitialize()");
tis.preloadOverview(true /* fromInit */);
}));
sIsInitialized = true;
}
......
}
调用初始化的地方
代码路径:frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
......
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
mUnfoldTransitionProgressForwarder.ifPresent(
unfoldProgressForwarder -> params.putBinder(
KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER,
unfoldProgressForwarder.asBinder()));
// Add all the interfaces exposed by the shell
mShellInterface.createExternalInterfaces(params);
}
......
}