努力尽今夕,少年犹可夸。这篇文章主要讲述浅谈Android多屏幕的事相关的知识,希望能为你提供帮助。
浅谈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.RecentsActivityBox 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.PeopleActivityBox 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)
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)
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."
);
}
【浅谈Android多屏幕的事】遍历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, 在这里不再展示。
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 |
1、 如何分屏
通过以下方式切换到多窗口模式:
- 若打开 Overview 屏幕并长按 Activity 标题, 则可以拖动该 Activity 至屏幕突出显示的区域, 使 Activity 进入多窗口模式。
- 若长按 Overview 按钮, 设备上的当前 Activity 将进入多窗口模式, 同时将打开 Overview 屏幕, 用户可在该屏幕中选择要共享屏幕的另一个 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。
处于多窗口模式中时, 某些功能会被禁用或忽略:
- 某些系统 UI 自定义选项将被禁用; 例如, 在非全屏模式中, 应用无法隐藏状态栏。
- 系统将忽略对 android:screenOrientation 属性所作的更改。
多窗口模式状态的监控: - Activity.isInMultiWindowMode(): Activity 是否处于多窗口模式。
- Activity.onMultiWindowModeChanged():
Activity 进入或退出多窗口模式时系统将调用此方法。 在 Activity 进入多窗口模式时,
系统向该方法传递 true 值,
在退出多窗口模式时,
则传递 false 值。
每个方法对于 Fragment 也支持, 例如 Fragment.isInMultiWindowMode()。
在启动新 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/122500397、 官网上也提到如何测试分屏应用, 这里不在赘述!
https://developer.android.com/preview/features/multi-window.html#running
推荐阅读
- Android实现文章+评论(MVP,RxJava,Dagger2,ButterKnife)
- 一名Android开发者的微信小程序填坑之路
- §1.2 Android项目结构及“Hello World”应用解析
- §1.1 创建Android项目
- android array根据一个或多个属性排序问题
- Android Studio 运行出现 Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForD
- 刚知道的android属性
- Android 基于XMPP Smack openfire 开发的聊天室
- android menu的问题