按照上一篇的结尾来讲,先从最近任务开始入手。
系统使用这个名字的property来完成:persist.sys.debug.multi_window
触发RecentsActivity,是长按HOME键(或者是一般键盘的Alt+Tab键),具体code在PhoneWindowManager中有对该键值的处理。
private void showRecentApps(boolean triggeredFromAltTab) {
try {
IStatusBarService statusbar = getStatusBarService();
if (statusbar != null) {
statusbar.showRecentApps(triggeredFromAltTab);
}
} catch (RemoteException e) {
......
}
}
主要是获取StatusBarService,调用它的showRecentApps()
IStatusBarService getStatusBarService() {
synchronized (mServiceAquireLock) {
if (mStatusBarService == null) {
mStatusBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService("statusbar"));
}
return mStatusBarService;
}
}
也就是从ServiceManager中找到注册名字为statusbar的service的client端—StatusBarManagerService,这个Service是在SystemServer中被起起来的。
在StatusBarManagerService.java中
@Override
public void showRecentApps(boolean triggeredFromAltTab) {
if (mBar != null) {
try {
mBar.showRecentApps(triggeredFromAltTab);
} catch (RemoteException ex) {}
}
}
这里看到实际去show的是mBar,类型为IStatusBar
registerStatusBar函数的第一个入参,赋值给mBar。
SystemUI中的BaseStatusBar调用registerStatusBar,将CommandQueue传入
所以showRecentApps的实现还要进到CommandQueue中去看
public void showRecentApps(boolean triggeredFromAltTab) {
synchronized (mList) {
mHandler.removeMessages(MSG_SHOW_RECENT_APPS);
mHandler.obtainMessage(MSG_SHOW_RECENT_APPS,
triggeredFromAltTab ? 1 : 0, 0, null).sendToTarget();
}
}
case MSG_SHOW_RECENT_APPS:
mCallbacks.showRecentApps(msg.arg1 != 0);
break;
再trace mCallbacks的由来,BaseStatusBar实现callback
终于找到了核心实现:
protected void showRecents(boolean triggeredFromAltTab) {
if (mRecents != null) {
sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
mRecents.showRecents(triggeredFromAltTab, getStatusBarView());
}
}
1、先关闭系统窗口
ActivityManagerNative.getDefault().closeSystemDialogs(reason);
调用AMS的关闭系统对话框,遍历wms中所有window,让每个window去关闭systemdialog
2、显示最近任务
显示最近任务的功能集成在SystemUI当中
这个的具体实现在TaskViewHeader.java中创建一个RecentsResizeTaskDialog
核心函数如下
private void placeTasks(int arrangement) {
Rect rect = mSsp.getWindowRect();
/*step1.将所有矩形数组赋值为整个屏的size*/
for (int i = 0; i < mBounds.length; ++i) {
mBounds[i].set(rect);
if (i != 0) {//task数组除去第一个之外都是null,每次进行这种切换的时候都会进行一次该操作
mTasks[i] = null;
}
}
int additionalTasks = 0;
switch (arrangement) {
case PLACE_LEFT:
mBounds[0].right = mBounds[0].centerX();
mBounds[1].left = mBounds[0].right;
additionalTasks = 1;
break;
case PLACE_RIGHT:
mBounds[1].right = mBounds[1].centerX();
mBounds[0].left = mBounds[1].right;
additionalTasks = 1;
break;
case PLACE_TOP:
mBounds[0].bottom = mBounds[0].centerY();
mBounds[1].top = mBounds[0].bottom;
additionalTasks = 1;
break;
case PLACE_BOTTOM:
mBounds[1].bottom = mBounds[1].centerY();
mBounds[0].top = mBounds[1].bottom;
additionalTasks = 1;
break;
case PLACE_TOP_LEFT: // TL, TR, BL, BR
mBounds[0].right = mBounds[0].centerX();
mBounds[0].bottom = mBounds[0].centerY();
mBounds[1].left = mBounds[0].right;
mBounds[1].bottom = mBounds[0].bottom;
mBounds[2].right = mBounds[0].right;
mBounds[2].top = mBounds[0].bottom;
mBounds[3].left = mBounds[0].right;
mBounds[3].top = mBounds[0].bottom;
additionalTasks = 3;
break;
case PLACE_TOP_RIGHT: // TR, TL, BR, BL
mBounds[0].left = mBounds[0].centerX();
mBounds[0].bottom = mBounds[0].centerY();
mBounds[1].right = mBounds[0].left;
mBounds[1].bottom = mBounds[0].bottom;
mBounds[2].left = mBounds[0].left;
mBounds[2].top = mBounds[0].bottom;
mBounds[3].right = mBounds[0].left;
mBounds[3].top = mBounds[0].bottom;
additionalTasks = 3;
break;
case PLACE_BOTTOM_LEFT: // BL, BR, TL, TR
mBounds[0].right = mBounds[0].centerX();
mBounds[0].top = mBounds[0].centerY();
mBounds[1].left = mBounds[0].right;
mBounds[1].top = mBounds[0].top;
mBounds[2].right = mBounds[0].right;
mBounds[2].bottom = mBounds[0].top;
mBounds[3].left = mBounds[0].right;
mBounds[3].bottom = mBounds[0].top;
additionalTasks = 3;
break;
case PLACE_BOTTOM_RIGHT: // BR, BL, TR, TL
mBounds[0].left = mBounds[0].centerX();
mBounds[0].top = mBounds[0].centerY();
mBounds[1].right = mBounds[0].left;
mBounds[1].top = mBounds[0].top;
mBounds[2].left = mBounds[0].left;
mBounds[2].bottom = mBounds[0].top;
mBounds[3].right = mBounds[0].left;
mBounds[3].bottom = mBounds[0].top;
additionalTasks = 3;
break;
case PLACE_FULL:
// Nothing to change.
break;
}
// Get the other tasks.
//使用多屏的话就会产生额外的task
/*
这里是最近任务作为多窗口入口的交互逻辑核心
当前选择需要分屏的任务肯定需要占有屏幕的一块,那问题就是谁将会去占领下一块
答案:就是在该任务后面的那一个或者连续3个,如果任务栈的底部了就返回到头部继续
这样来说就会限制用户的自由度,可改进
*/
for (int i = 1; i <= additionalTasks && mTasks[i - 1] != null; ++i) {
mTasks[i] = mRecentsView.getNextTaskOrTopTask(mTasks[i - 1]);//通过recentsView来获取需要填充到界面中的task
// Do stop if we circled back to the first item.
if (mTasks[i] == mTasks[0]) {
mTasks[i] = null;
}
}
// Get rid of the dialog.
dismiss();
mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation();
/*这里是核心:将每个task进行resize操作*/
// Resize all tasks beginning from the "oldest" one.
for (int i = additionalTasks; i >= 0; --i) {
if (mTasks[i] != null) {
//这里的resize其实都是在做task到taskstack的搬移
//被搬移到新的stack上的task肯定会跑到栈的顶部来用于显示,因为在调用movetasktostack的时候top标志位是加了true的!!!
//其他没有涉及到的task应该就存放在原有的栈中
mSsp.resizeTask(mTasks[i].key.id, mBounds[i]);
}
}
// Show tasks as they might not be currently visible - beginning with the oldest so that
// the focus ends on the selected one.
for (int i = additionalTasks; i >= 0; --i) {
if (mTasks[i] != null) {
mRecentsView.launchTask(mTasks[i]);
}
}
}
操作的核心就是这个resizetask,改变task的大小
mSsp.resizeTask(mTasks[i].key.id, mBounds[i]);
核心功能的实现是ActivityManagerService中,在Android 6.0中,提供了一个5.x中没有个API
public void resizeTask(int taskId, Rect bounds)
在其实现中,进而会调用
mStackSupervisor.resizeTaskLocked(task, bounds);
由StackSupervisor来完成核心功能
/*
保证输入的task是存在于一个确定的边界的栈中(无论是通过resize当前的栈还是创建一个需要的边界)
*/
void resizeTaskLocked(TaskRecord task, Rect bounds) {
task.mResizeable = true;
//task===>stack 多个task是对应于一个stack的
final ActivityStack currentStack = task.stack;
if (currentStack.isHomeStack()) {
// Can't move task off the home stack. Sorry!不能将home stack的task 移动出来
return;
}
//从windowManager中查询具有某个边界的stack号码
//key1:功能:从wm中查找到某个边界的栈
final int matchingStackId = mWindowManager.getStackIdWithBounds(bounds);
if (matchingStackId != -1) {
// 在wm中存在这个大小的
// There is already a stack with the right bounds!
if (currentStack != null && currentStack.mStackId == matchingStackId) {
// Nothing to do here. Already in the right stack...
return;
}
// Move task to stack with matching bounds.
//key2:将某个task移动另外一个栈中
moveTaskToStackLocked(task.taskId, matchingStackId, true);
//除了在wms上移动stack和更新视觉,还需要在ams中做对应操作
return;
}
//当前现存所有stack中没有需求的size时
//1.就把当前的stack resize掉
if (currentStack != null && currentStack.numTasks() == 1) {
// Just resize the current stack since this is the task in it.
//key3:改变某个stack的size
resizeStackLocked(currentStack.mStackId, bounds);
return;
}
//2.重新建stack
// Create new stack and move the task to it.
final int displayId = (currentStack != null && currentStack.mDisplayId != -1)
? currentStack.mDisplayId : Display.DEFAULT_DISPLAY;
ActivityStack newStack = createStackOnDisplay(getNextStackId(), displayId);
if (newStack == null) {
Slog.e(TAG, "resizeTaskLocked: Can't create stack for task=" + task);
return;
}
moveTaskToStackLocked(task.taskId, newStack.mStackId, true);
resizeStackLocked(newStack.mStackId, bounds);
}
通过上面的代码可以知道几点:
1.系统中在开启多窗口的情况下,会支持多个stack。之前一般情况下仅有两个stack,home和apps tack
2.每个stack在windowmanager中会有一个bound,也就是这个stack中的Activity都会有size大小,例如这个stack的size是半个屏幕,即所有Activity都只会占用半个屏
3.task能够在各个不同大小的stack之间move,move过去之后,自动调节size
到此基本上把整个流程分析到了,有一些结论猜想。下一篇将会深入到ams中了解多任务栈的实现。
======================================================================5/5更新
这里对前面ams和wms有交集的部分进行补充
1.从wms中查询某个bound的TaskStack
public int getStackIdWithBounds(Rect bounds) {
Rect stackBounds = new Rect();
synchronized (mWindowMap) {
for (int i = mStackIdToStack.size() - 1; i >= 0; --i) {
TaskStack stack = mStackIdToStack.valueAt(i);
if (stack.mStackId != HOME_STACK_ID) {
stack.getBounds(stackBounds);
if (stackBounds.equals(bounds)) {
return stack.mStackId;
}
}
}
}
return -1;
}
其中mStackIdToStack
SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();是wms中维护任务栈的数据,这个跟ams中的任务栈是对称对应的。
2.在查询到了wms中符合要求的stack之后,这里就有很多wm和am的数据同步操作
//这个函数调用时机:
//1.在wm查找了哪个任务栈的bound符合大小要求,stackId参数就是上一步得到的
//
/*
参数1:taskId:要移动的任务的ID号
参数2:stackId:wms中符合要求的stackId号
*/
void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
return;
}
final ActivityStack stack = getStack(stackId);//key1:wms中的stack ID号需要保持和ams中的一致
if (stack == null) {
Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
return;
}
mWindowManager.moveTaskToStack(taskId, stackId, toTop);//key2:wms中处理task的数据结构的搬移
if (task.stack != null) {
task.stack.removeTask(task, "moveTaskToStack", false /* notMoving */);
}
stack.addTask(task, toTop, true);//key3.ams端的task数据搬移
// The task might have already been running and its visibility needs to be synchronized with
// the visibility of the stack / windows.
stack.ensureActivitiesVisibleLocked(null, 0);
resumeTopActivitiesLocked();
}
但是实际使用原生的过程中,对比了TV和手机设备
TV的交互还比较烂,遥控器按键跨界面的问题是个难题,否则多窗口在TV上对用户来说是个麻烦事
手机相对而言交互流畅很多
但是针对多窗口的media播放还是存在因为某个窗口失去焦点,就会暂停播放的问题。