码迷,mamicode.com
首页 > 移动开发 > 详细

浅谈Android多屏幕的事

时间:2016-10-19 20:27:37      阅读:436      评论:0      收藏:0      [点我收藏+]

标签:

浅谈Android多屏幕的事


一部手机可以同时看片、聊天,还可以腾出一支手来撸!这么吊的功能(非N版本,非第三方也能实现,你不知道吧)摆在你面前,你不享用?不关注它是怎样实现的?你来,我就满足你的欲望!
一部手机可以同时看片、聊天,还可以腾出一支手来撸==!就像这样:
技术分享

是时候告别来回切换应用屏幕的酸爽了,还可以在分屏模式下两Activity间直接拖放数据!
好高大上的样子!这是怎么实现的?别急,我们一一道来:

kitkat(4.4)版本对多任务分屏的实现

由于相关的代码和功能被封装及隐藏起来,所以我们从dumpsys activity命令作手。
来,直接上一条命令:

am stack boxes

NetEasedeMac-Pro-8:~ netease$ adb shell am stack boxes
Box id=0 weight=0.0 vertical=false bounds=[0,75][1080,1776]
Stack=
  Stack id=0 bounds=[0,75][1080,1776]
    taskId=1: com.android.launcher/com.android.launcher2.Launcher
    taskId=4: com.android.systemui/com.android.systemui.recent.RecentsActivity

Box id=1 weight=0.0 vertical=false bounds=[0,75][1080,1776]
Stack=
  Stack id=1 bounds=[0,75][1080,1776]
    taskId=2: com.android.contacts/com.android.contacts.activities.PeopleActivity
    taskId=3: com.android.email/com.android.email.activity.Welcome

以上命令大致可以看出一些简单明显的东西,最高级为两个Box,每个Box有一个Stack,每个Stack下有n个taskId等等。既然提到am,那我们就去看一看Android源码Am这个类:com.android.commands.am.Am。在里面可以看到一堆的am命令,这些命令的执行在onRun()函数里,我们先看runStack()->runStackBoxes(),一直跟踪下去,会涉及到ActivityManagerService.getStackBoxes()->WindowManagerService.getStackBoxInfos()->DisplayContent.getStackBoxInfos()直接遍历DisplayContent里的mStackBoxes。

用简单的类图来表示其数据结构关系:

StackBox相关简单类图:
技术分享
TaskStack相关的简单类图:
技术分享

有这里Task的概念是什么?
官方定义:“A task (from the activity that started it to the next task activity)defines an atomic group of activities that the user can move to.”。
简单来讲:Task是为了完成一个功能的一系列相关的有序Activity集合,可以理解为用户与App之间对于特定功能的一次会话。一个Task中的Activity可以来自不同的App,比如在邮件App中需要看图片附件,然后会开imageview的Activity来显示它。

以上只是在WMS里分析了StackBox、TaskStack相关的简单数据结构,我们知道对于一个完整的窗口流程还主要涉及到AMS、SF联合管理及渲染显示。那么在AMS里是怎样进行管理的呢?不急,我们看第二个灵魂级的am命令

灵魂级的am命令:am stack create

am stack create <TASK_ID> <RELATIVE_STACK_BOX_ID> <POSITION> <WEIGHT>
注解如下:

am stack create: create a new stack relative to an existing one.
  <TASK_ID>: the task to populate the new stack with. Must exist.
  <RELATIVE_STACK_BOX_ID>: existing stack box‘s id.
  <POSITION>: 0: before <RELATIVE_STACK_BOX_ID>
              1: after <RELATIVE_STACK_BOX_ID>
              2: to left of <RELATIVE_STACK_BOX_ID>
              3: to right of <RELATIVE_STACK_BOX_ID>
              4: above <RELATIVE_STACK_BOX_ID>
              5: below <RELATIVE_STACK_BOX_ID>
 <WEIGHT>: float between 0.2 and 0.8 inclusive.\n" +

试一下:

NetEasedeMac-Pro-8:~ netease$ adb shell am stack create 3 1 4 0.7
createStack returned new stackId=2

直接上效果图:

技术分享

这里申明一下:由于我的模拟器有问题,上图是借用http://androidinternalsblog.blogspot.com/2014/03/split-screens-in-android-exist.html 里的,不过效果一样的。
这就是android分屏的鼻祖,这时候的stack boxes输出如下:

NetEasedeMac-Pro-8:~ netease$ adb shell am stack boxes
Box id=1 weight=0.5 vertical=true bounds=[0,75][1080,1776]
First child=
  Box id=2 weight=0.0 vertical=false bounds=[0,75][1080,925]
  Stack=
    Stack id=2 bounds=[0,75][1080,925]
      taskId=3: com.android.email/com.android.email.activity.Welcome
Second child=
  Box id=3 weight=0.0 vertical=false bounds=[0,925][1080,1776]
  Stack=
    Stack id=1 bounds=[0,925][1080,1776]
      taskId=2: com.android.contacts/com.android.contacts.activities.PeopleActivity

Box id=0 weight=0.0 vertical=false bounds=[0,75][1080,1776]
Stack=
  Stack id=0 bounds=[0,75][1080,1776]
    taskId=1: com.android.launcher/com.android.launcher2.Launcher

可以看到之前的Box_id1一分为二:Box_id2和Box_id3(充分说明StackBox的数据结构为树),另外stack_id3从原Box_id1(也可以理解成现在的Box_id2)的移到了Box_id3。

看看am stack create是怎么实现的吧,回到Am里的runStackCreate()->AMS.createStack()

  public int createStack(int taskId, int relativeStackBoxId, int position, float weight) {
        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
                "createStack()");
        if (DEBUG_STACK) Slog.d(TAG, "createStack: taskId=" + taskId + " relStackBoxId=" +
                relativeStackBoxId + " position=" + position + " weight=" + weight);
        synchronized (this) {
            long ident = Binder.clearCallingIdentity();
            try {
                int stackId = mStackSupervisor.createStack();
                mWindowManager.createStack(stackId, relativeStackBoxId, position, weight);
                if (taskId > 0) {
                    moveTaskToStack(taskId, stackId, true);
                }
                return stackId;
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

这里主要做了两件事,一个是向mStackSupervisor申请一个stackId,第二个是邮WMS去继续createStack,首先来看看mStackSupervisor.createStack()。

ActivityStackSupervisor.createStack()

int createStack() {
        while (true) {
            if (++mLastStackId <= HOME_STACK_ID) {
                mLastStackId = HOME_STACK_ID + 1;
            }
            if (getStack(mLastStackId) == null) {
                break;
            }
        }
        mStacks.add(new ActivityStack(mService, mContext, mLooper, mLastStackId));
        return mLastStackId;
    }

进入ActivityStackSupervisor,每申请一个stackId(对于AMS来说就是添加一个ActivityStack),都会把新添加的ActivityStack add 到 mStacks;除了createStack()之外,还可以看到很多关于stack的操作,不难发现ActivityStackSupervisor是stack的终级管理者,这里先不展开,后面再详细讨论它是怎样管理stack的,这里继续探讨stack create的事。

WMS.createStack()

public void createStack(int stackId, int relativeStackBoxId, int position, float weight) {
        synchronized (mWindowMap) {
            if (position <= StackBox.TASK_STACK_GOES_BELOW &&
                    (weight < STACK_WEIGHT_MIN || weight > STACK_WEIGHT_MAX)) {
                throw new IllegalArgumentException(
                        "createStack: weight must be between " + STACK_WEIGHT_MIN + " and " +
                        STACK_WEIGHT_MAX + ", weight=" + weight);
            }
            final int numDisplays = mDisplayContents.size();
            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
                TaskStack stack = displayContent.createStack(stackId, relativeStackBoxId, position,
                        weight);
                if (stack != null) {
                    mStackIdToStack.put(stackId, stack);
                    performLayoutAndPlaceSurfacesLocked();
                    return;
                }
            }
            Slog.e(TAG, "createStack: Unable to find relativeStackBoxId=" + relativeStackBoxId);
        }
    }

在WMS.createStack()里可以看到两个重要的信息,首先是遍历 SparseArray < DisplayContent> mDisplayContents,每一个DisplayContent再执行createStack(),然后把(stackId, stack)存储到mStackIdToStack里(从第一个命令里查stackBoxes的时候是从DisplayContent时获取的,那在WMS里存储mStackIdToStack很明显是为了更方便的管理stack信息)。

DisplayContent.createStack()

TaskStack createStack(int stackId, int relativeStackBoxId, int position, float weight) {
        TaskStack newStack = null;
        if (DEBUG_STACK) Slog.d(TAG, "createStack: stackId=" + stackId + " relativeStackBoxId="
                + relativeStackBoxId + " position=" + position + " weight=" + weight);
        if (stackId == HOME_STACK_ID) {
            if (mStackBoxes.size() != 1) {
                throw new IllegalArgumentException("createStack: HOME_STACK_ID (0) not first.");
            }
            newStack = mHomeStack;
        } else {
            int stackBoxNdx;
            for (stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) {
                final StackBox box = mStackBoxes.get(stackBoxNdx);
                if (position == StackBox.TASK_STACK_GOES_OVER
                        || position == StackBox.TASK_STACK_GOES_UNDER) {
                    // Position indicates a new box is added at top level only.
                    if (box.contains(relativeStackBoxId)) {
                        StackBox newBox = new StackBox(mService, this, null);
                        newStack = new TaskStack(mService, stackId, this);
                        newStack.mStackBox = newBox;
                        newBox.mStack = newStack;
                        final int offset = position == StackBox.TASK_STACK_GOES_OVER ? 1 : 0;
                        if (DEBUG_STACK) Slog.d(TAG, "createStack: inserting stack at " +
                                (stackBoxNdx + offset));
                        mStackBoxes.add(stackBoxNdx + offset, newBox);
                        break;
                    }
                } else {
                    // Remaining position values indicate a box must be split.
                    newStack = box.split(stackId, relativeStackBoxId, position, weight);
                    if (newStack != null) {
                        break;
                    }
                }
            }
            if (stackBoxNdx < 0) {
                throw new IllegalArgumentException("createStack: stackBoxId " + relativeStackBoxId
                        + " not found.");
            }
        }
        if (newStack != null) {
            layoutNeeded = true;
        }
        EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, relativeStackBoxId, position,
                (int)(weight * 100 + 0.5));
        return newStack;
    }

DisplayContent.createStack()方法代码这么长,其最有价值的就是“灵魂事件—分屏”:box.split,box.split的第一层if判断是:新增的stack是否在同一layer(参数position如果是4或者5那么就不会执行下面的split)。那我们来看看TaskStack split里做了些什么。
TaskStack split

/**
     * Create a new TaskStack relative to a specified one by splitting the StackBox containing
     * the specified TaskStack into two children. The size and position each of the new StackBoxes
     * is determined by the passed parameters.
     * @param stackId The id of the new TaskStack to create.
     * @param relativeStackBoxId The id of the StackBox to place the new TaskStack next to.
     * @param position One of the static TASK_STACK_GOES_xxx positions defined in this class.
     * @param weight The percentage size of the parent StackBox to devote to the new TaskStack.
     * @return The new TaskStack.
     */
    TaskStack split(int stackId, int relativeStackBoxId, int position, float weight) {
        if (mStackBoxId != relativeStackBoxId) {
            // This is not the targeted StackBox.
            if (mStack != null) {
                return null;
            }
            // Propagate the split to see if the targeted StackBox is in either sub box.
            TaskStack stack = mFirst.split(stackId, relativeStackBoxId, position, weight);
            if (stack != null) {
                return stack;
            }
            return mSecond.split(stackId, relativeStackBoxId, position, weight);
        }

        // Found it!
        TaskStack stack = new TaskStack(mService, stackId, mDisplayContent);
        TaskStack firstStack;
        TaskStack secondStack;
        if (position == TASK_STACK_GOES_BEFORE) {
            // TODO: Test Configuration here for LTR/RTL.
            position = TASK_STACK_TO_LEFT_OF;
        } else if (position == TASK_STACK_GOES_AFTER) {
            // TODO: Test Configuration here for LTR/RTL.
            position = TASK_STACK_TO_RIGHT_OF;
        }
        switch (position) {
            default:
            case TASK_STACK_TO_LEFT_OF:
            case TASK_STACK_TO_RIGHT_OF:
                mVertical = false;
                if (position == TASK_STACK_TO_LEFT_OF) {
                    mWeight = weight;
                    firstStack = stack;
                    secondStack = mStack;
                } else {
                    mWeight = 1.0f - weight;
                    firstStack = mStack;
                    secondStack = stack;
                }
                break;
            case TASK_STACK_GOES_ABOVE:
            case TASK_STACK_GOES_BELOW:
                mVertical = true;
                if (position == TASK_STACK_GOES_ABOVE) {
                    mWeight = weight;
                    firstStack = stack;
                    secondStack = mStack;
                } else {
                    mWeight = 1.0f - weight;
                    firstStack = mStack;
                    secondStack = stack;
                }
                break;
        }

        mFirst = new StackBox(mService, mDisplayContent, this);
        firstStack.mStackBox = mFirst;
        mFirst.mStack = firstStack;

        mSecond = new StackBox(mService, mDisplayContent, this);
        secondStack.mStackBox = mSecond;
        mSecond.mStack = secondStack;

        mStack = null;
        return stack;
    }

TaskStack split在维护stackBox树结构的同时实现了分屏,我还是少说话,大家多多品味吧。

到此,对于stack create已说完,回应前面提到的ActivityStackSupervisor,这里就来谈谈它是怎样管理stack的,首先看看里面引入了ActivityStack的两个重要成员:

    /** The stack containing the launcher app */
    private ActivityStack mHomeStack;

    /** All the non-launcher stacks */
    private ArrayList<ActivityStack> mStacks = new ArrayList<ActivityStack>();

同样,用简单的类关系来说明stack在AMS里的层次结构:
技术分享

与前面WMS里的stack结构相比较:
技术分享

两相比较,其结构一样,但是AMS与WMS两者的职能不一样,前者主要管理Activity,Service和Process等信息,WMS管理应用和系统窗口,维护窗口的布局,z _order的管理,发生变化时通知app作出调整,同时为SF提供layery计算等等,因此两个成为了不同的模块,固然在stack _task的管理上有了相同的结构不同的“view”。

继续看看分屏相关其它的am命令

am stack move task

  • 语法:am stack movetask < TASK_ID> < STACK_ID> [true|false]
  • 注解: move < TASK_ID> from its current stack to the top (true) or bottom (false) of < STACK_ID>.
    由参数及注解可以看出,该命令不是单一的删除一个task,而是把一个task remove to a pointed stack。
  • 实现:
    Am.runStackMoveTask()->Ams.moveTaskToStack(int taskId, int stackId, boolean toTop)->ActivityStackSupervisor.moveTaskToStack(int taskId, int stackId, boolean toTop)

ActivityStackSupervisor.moveTaskToStack

 void moveTaskToStack(int taskId, int stackId, boolean toTop) {
        final TaskRecord task = anyTaskForIdLocked(taskId);
        if (task == null) {
            return;
        }
        final ActivityStack stack = getStack(stackId);
        if (stack == null) {
            Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
            return;
        }
        removeTask(task);
        stack.addTask(task, toTop);
        mWindowManager.addTask(taskId, stackId, toTop);
        resumeTopActivitiesLocked();
    }
void removeTask(TaskRecord task) {
        mWindowManager.removeTask(task.taskId);
        final ActivityStack stack = task.stack;
        final ActivityRecord r = stack.mResumedActivity;
        if (r != null && r.task == task) {
            stack.mResumedActivity = null;
        }
        if (stack.removeTask(task) && !stack.isHomeStack()) {
            if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack " + stack);
            mStacks.remove(stack);
            final int stackId = stack.mStackId;
            final int nextStackId = mWindowManager.removeStack(stackId);
            // TODO: Perhaps we need to let the ActivityManager determine the next focus...
            if (mFocusedStack == null || mFocusedStack.mStackId == stackId) {
                // If this is the last app stack, set mFocusedStack to null.
                mFocusedStack = nextStackId == HOME_STACK_ID ? null : getStack(nextStackId);
            }
        }
    }

ActivityStackSupervisor里先从历史数据中remove掉task,再把task加到目标stack中,不管是remove还是add在stackSupervisor里进行相关操作的时候,都会通知WMS作相应的处理。不过这里有一个细节,就是如果一个非homeStack删除掉最后一个task的时候,该stack也会被干掉,同样除了在ActivityStackSupervisor里的mStacks里remove外,也会通知WMS remove掉该stack,在WMS里的remove操作其实就是对stack数据的destory,在这里不再展示。

WMS的removeTask和addTask,同样遵守“先下后上”原则:

   public void removeTask(int taskId) {
        synchronized (mWindowMap) {
            Task task = mTaskIdToTask.get(taskId);
            if (task == null) {
                if (DEBUG_STACK) Slog.i(TAG, "removeTask: could not find taskId=" + taskId);
                return;
            }
            final TaskStack stack = task.mStack;
            EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask");
            stack.removeTask(task);
            stack.getDisplayContent().layoutNeeded = true;
        }
    }
public void addTask(int taskId, int stackId, boolean toTop) {
        synchronized (mWindowMap) {
            Task task = mTaskIdToTask.get(taskId);
            if (task == null) {
                return;
            }
            TaskStack stack = mStackIdToStack.get(stackId);
            stack.addTask(task, toTop);
            final DisplayContent displayContent = stack.getDisplayContent();
            displayContent.layoutNeeded = true;
            performLayoutAndPlaceSurfacesLocked();
        }
    }

在WMS里taks迁移成功后,调用performLayoutAndPlaceSurfacesLocked()进行窗口绘。

am stack resize

  • 语法 :am stack resize < STACK_ID> < WEIGHT>
  • 注解:am stack resize: change < STACK_ID> relative size to new < WEIGHT>
  • 实现:AM.runStackBoxResize()->AMS.resizeStackBox(int stackBoxId, float weight)->WMS.resizeStackBox(int stackBoxId, float weight)

WMS.resizeStackBox

public void resizeStackBox(int stackBoxId, float weight) {
        if (weight < STACK_WEIGHT_MIN || weight > STACK_WEIGHT_MAX) {
            throw new IllegalArgumentException(
                    "resizeStack: weight must be between " + STACK_WEIGHT_MIN + " and " +
                    STACK_WEIGHT_MAX + ", weight=" + weight);
        }
        synchronized (mWindowMap) {
            final int numDisplays = mDisplayContents.size();
            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
                if (mDisplayContents.valueAt(displayNdx).resizeStack(stackBoxId, weight)) {
                    performLayoutAndPlaceSurfacesLocked();
                    return;
                }
            }
        }
        throw new IllegalArgumentException("resizeStack: stackBoxId " + stackBoxId
                + " not found.");
    }

遍历mDisplayContents,每一个DisplayContent都调用resizeStack,然后进行重绘。

DisplayContent.resizeStack

    /** Refer to {@link WindowManagerService#resizeStackBox(int, float)} */
    boolean resizeStack(int stackBoxId, float weight) {
        for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) {
            final StackBox box = mStackBoxes.get(stackBoxNdx);
            if (box.resize(stackBoxId, weight)) {
                layoutNeeded = true;
                return true;
            }
        }
        return false;
    }

DisplayContent.resizeStack里,实际是相应的StackBox进行了resize,继续查看StackBox.resize。

StackBox.resize

 boolean resize(int stackBoxId, float weight) {
        if (mStackBoxId != stackBoxId) {
            return mStack == null &&
                    (mFirst.resize(stackBoxId, weight) || mSecond.resize(stackBoxId, weight));
        }
        // Don‘t change weight on topmost stack.
        if (mParent != null) {
            mParent.mWeight = isFirstChild() ? weight : 1.0f - weight;
        }
        return true;
    }

之前讲过StackBox是一个树结构,StackBox.resize的时候它会一直遍历下去,直到匹配到stackBoxId为至。别外注意的是这里的weight是0?1的float值,它在屏幕上体现是一个相对值。

am stack box

  • 语法 :am stack box
  • 注解:am stack box: list the hierarchy of stack boxes rooted at
  • 实现:am stack box 的流程现在理解就非常的简单了,与之前的stack boxes流程一样,只不过boxes是取all boxInfo,box只取 pointed stack_box_id的bixInfo。

总结

综合以上命令的实现(主要就是分屏的管理与实现),其涉及到相关的主要类及关系如下图:

技术分享

正常情况下,一条实线走到底的啊,这里的三条虚线是特殊情况,从上至下我们把三条虚线分别标号为1、2、3。

  • 第1条虚线是在create stack 的时候stack_id需要由ActivityStackSuperviosr生成,有了这个stack_id后再交给WMS进行create;
  • 第2条虚线,在处理moveTaskToStack的流程是先由ActivityStackSuperviosr进行相关的remove and add 处理,再由 ActivityStackSuperviosr去通知WMS进行remove and add 处理。
  • 第3条虚线,就是如果一个非homeStack删除掉最后一个task的时候,该stack也会被干掉,同样除了在ActivityStackSupervisor里的mStacks里remove外,也会通知WMS remove掉该stack,在WMS里的remove操作其实就是对stack数据的destory,在这里不再展示。

对于APP、AMS、WMS及SF之间的协作关系,其它网站、博客、书籍已有大量的介绍,这里不再赘述。

M(6.0)版本对多任务分屏的实现

这里主要谈谈与4.4kitkat的不同之处,核心内容其实在4.4kitkat已经出现,为什么不谈5.0版本,因为5.0与6.0之间在多任务分屏模块变化不大,理东西我们只需抓住头和尾就行了,那为什么不直接谈N版本,我也想,可是源码还没有出来。。。

Am与分屏相关主要命令的变化

这一小节主要介绍各版本中am命令的变化,来分析及概括分屏路的前世今生。

kikat(4.4) M(6.0) N
stack create stack start stack start
stack move task stack move task stack move task
stack resize stack resize stack resize
stack boxes stack resize-animated
stack box stack resize-docked-stack
stack size-docked-stack-test
stack split stack move-top-activity-to-pinned-stack
stack positiontask
stack list stack list
stack info stack info
stack remove
task lock task lock
task lock stop
task resizeable task resizeable
task resize task resize
task drag-task-test
task size-task-test

N版本分屏,我们要做的事

以上内容都是系统在做,那我们该做些什么、注意些什么呢?

1?如何分屏
通过以下方式切换到多窗口模式:

  • 若打开 Overview 屏幕并长按 Activity 标题,则可以拖动该 Activity 至屏幕突出显示的区域,使 Activity 进入多窗口模式。
  • 若长按 Overview 按钮,设备上的当前 Activity 将进入多窗口模式,同时将打开 Overview 屏幕,用户可在该屏幕中选择要共享屏幕的另一个 Activity。

用户可以在两个 Activity 共享屏幕的同时在这两个 Activity 之间拖放数据 (在此之前,用户只能在一个 Activity 内部拖放数据)。

2?多窗口的配置应用
在清单的 或 节点中设置该属性,启用或禁用多窗口显示(在N版本里默认为true):

android:resizeableActivity=["true" | "false"]

若为false相应的Activity不支持多窗口模式,且用户尝试在多窗口模式下启动 Activity,该 Activity 将全屏显示。

对于 Android N,清单文件元素支持以下几种属性,这些属性影响 Activity 在多窗口模式中的行为:

<activity android:name=".MyActivity">
    <layout android:defaultHeight="500dp"
          android:defaultWidth="600dp"
          android:gravity="top|end"
          android:minimalHeight="450dp"
          android:minimalWidth="300dp" />
</activity>

defaultWidth、defaultHeight分别是以自由形状模式启动时 Activity 的默认宽度和高度。。
gravity 以自由形状模式启动时 Activity 的初始位置。
min_size分屏和自由形状模式中 Activity 的最小高度和最小宽度。 如果用户在分屏模式中移动分界线,使 Activity 尺寸低于指定的最小值,系统会将 Activity 裁剪为用户请求的尺寸。

3?多窗口的生命周期
多窗口模式不会更改 Activity 生命周期。但是要注意以下几点:

  • Activity状态的不同,活动状态(顶级)>暂停状态(可见)>暂停状态(不可见)。用户与其中一个暂停的 Activity 交互,该 Activity 将恢复,而之前的顶级 Activity 将暂停。
  • 用户仍可以看到处于暂停状态的应用,因此在暂停状态下可能仍需要继续其操作。如视频播放应用在 onPause() 中不应该暂停播放,应在 onStop() 中暂停,onStart() 中恢复。
    • Activity配置变更:分屏与全屏的相互切换时触发,与纵、横变换时Activity生命周期相同,但设备不但交换尺寸,还会变理尺寸;Activity可以自行处理配置变更,或允许系统销毁 Activity,并以新的尺寸重新创建该 Activity。

4?在多窗口模式中运行应用
处于多窗口模式中时,某些功能会被禁用或忽略:

  • 某些系统 UI 自定义选项将被禁用;例如,在非全屏模式中,应用无法隐藏状态栏。
  • 系统将忽略对 android:screenOrientation 属性所作的更改。
    多窗口模式状态的监控:
  • Activity.isInMultiWindowMode(): Activity 是否处于多窗口模式。
  • Activity.onMultiWindowModeChanged():Activity 进入或退出多窗口模式时系统将调用此方法。 在 Activity 进入多窗口模式时,系统向该方法传递 true 值,在退出多窗口模式时,则传递 false 值。
    每个方法对于 Fragment 也支持,例如 Fragment.isInMultiWindowMode()。

5?多窗口模式中启动新的Activity
在启动新 Activity 时,用户可以提示系统如果可能,应将新 Activity 显示在当前 Activity 旁边。 要执行此操作,可使用标志 Intent.FLAG_ACTIVITY_LAUNCH_TO_ADJACENT。 传递此标志将请求以下行为:

如果设备处于分屏模式,系统会尝试在启动系统的 Activity 旁创建新 Activity,这样两个 Activity 将共享屏幕。 系统并不一定能实现此操作,但如果可以,系统将使两个 Activity 处于相邻的位置。
如果设备不处于分屏模式,则该标志无效。
如果设备处于自由形状模式,则在启动新 Activity 时,用户可通过调用 ActivityOptions.setLaunchBounds() 指定新 Activity 的尺寸和屏幕位置。 如果设备不处于多窗口模式,则该方法无效。

注:如果您在任务栈中启动 Activity,该 Activity 将替换屏幕上的 Activity,并继承其所有的多窗口属性。 如果要在多窗口模式中以单独的窗口启动新 Activity,则必须在新的任务栈中启动此 Activity。

6? 拖放功能的实现
用户可以在两个 Activity 共享屏幕的同时在这两个 Activity 之间拖放数据 (在此之前,用户只能在一个 Activity 内部拖放数据)。
android.view.DropPermissions:令牌对象,负责指定对接收拖放数据的应用授予的权限。
View.startDragAndDrop() :View.startDrag() 的新别名。要启用跨 Activity 拖放,请传递新标志 View.DRAG_FLAG_GLOBAL。 如需对接收拖放数据的 Activity 授予 URI 权限,可根据情况传递新标志 View.DRAG_FLAG_GLOBAL_URI_READ 或 View.DRAG_FLAG_GLOBAL_URI_WRITE。
View.cancelDragAndDrop():取消当前正在进行的拖动操作。只能由发起拖动操作的应用调用。
View.updateDragShadow():替换当前正在进行的拖动操作的拖动阴影。只能由发起拖动操作的应用调用。
Activity.requestDropPermissions():请求使用 DragEvent 中包含的 ClipData 传递的内容 URI 的权限。

在android4.0中应用View.startDrag()实现View的拖动:

http://blog.csdn.net/krislight/article/details/12250039

7?官网上也提到如何测试分屏应用,这里不在赘述!

https://developer.android.com/preview/features/multi-window.html#running

浅谈Android多屏幕的事

标签:

原文地址:http://blog.csdn.net/wangyi_onepiece/article/details/51815469

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!