Android多窗口探究(2)

按照上一篇的结尾来讲,先从最近任务开始入手。

系统使用这个名字的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播放还是存在因为某个窗口失去焦点,就会暂停播放的问题。

    原文作者:Kelvin wu
    原文地址: https://zhuanlan.zhihu.com/p/20530318
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞