在Android N上,谷歌制作了一个分屏的效果,即进入应用后,长按Recents键,进入到分屏状态。
左侧是进入分屏的应用,中间区域是可以滑动的线,右侧是recents列表。
下面是进入分屏的完整的交互图:
下面开始一步步的详细介绍:
在上一篇文章中已经介绍了Navigationbar button添加显示和事件绑定的流程分析记录,这里不再赘述。接下来,我就按照用户的操作,一步步的分析界面的变化。
1.进入分屏场景分析:
用户长按recents键,从响应长按事件开始。代码如下所示:
private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mRecents == null || !ActivityManager.supportsMultiWindow()
|| !getComponent(Divider.class).getView().getSnapAlgorithm()
.isSplitScreenFeasible()) {
return false;
}
toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
return true;
}
};
@Override
protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
return;
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
EventBus.getDefault().send(new UndockingTaskEvent());
if (metricsUndockAction != -1) {
MetricsLogger.action(mContext, metricsUndockAction);
}
}
}
这段代码位于:framework/base/packages/SystemUI/src/.../PhoneStatusBar.java
这段首先会判断mRecents是否为空,mRecents是Recents.class,在BaseStatusBar.java中有说明。
判断当前分屏状态,通过这段代码可以获取到分屏的状态,判断是否处于分屏状态,如果当前是非分屏状态,那么获取到的值就是WindowManager.DOCKED_INVALID。
int dockSide = WindowManagerProxy.getInstance().getDockSide();
我们分析的是进入分屏的流程,那么获取到的值就是WindowManager.DOCKED_INVALID。这时就会调用到Recents.dockTopTask(...)方法。
2.我们已经获取到了分屏的状态,下面开始处理。
@Override
public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return false;
}
Point realSize = new Point();
if (initialBounds == null) {
mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
.getRealSize(realSize);
initialBounds = new Rect(0, 0, realSize.x, realSize.y);
}
int currentUser = sSystemServicesProxy.getCurrentUser();
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
boolean screenPinningActive = ssp.isScreenPinningActive();
boolean isRunningTaskInHomeStack = runningTask != null &&
SystemServicesProxy.isHomeStack(runningTask.stackId);
if (runningTask != null && !isRunningTaskInHomeStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
if (runningTask.isDockable) {
if (metricsDockAction != -1) {
MetricsLogger.action(mContext, metricsDockAction,
runningTask.topActivity.flattenToShortString());
}
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
initialBounds);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
} else {
Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
}
}
mDraggingInRecentsCurrentUser = currentUser;
return true;
} else {
EventBus.getDefault().send(new ShowUserToastEvent(
R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT));
return false;
}
} else {
return false;
}
}
代码位置:framework/base/packages/System/.../Recents.java
传进来的四个参数值:
int dragMode:NavigationBarGestureHelper.DRAG_MODE_NONE
int stackCreateMode :ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
Rect initialBounds:null
int metricsDockAction:MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS
由于initialBounds是null,会给他一个值,这个值就是屏幕的大小,例如当前屏幕分辨率是1920*1200,那么initialBounds就是(0,0,1920,1200),即全屏大小。
获取当前正在运行的task信息,这个task就是在长按recents时正在运行的应用,也可以将是焦点所在的应用。
我们需要保证能够正常拿到task,否则后面就无法处理task信息。
判断当前task的stackid是否是homestack,这里需要说明在Android N上系统默认设置了五种Activitystack:
stack名称 | stack_id |
homestack | 0 |
fullscreenstack | 1 |
freeformstack | 2 |
dcokstack | 3 |
pinstack | 4 |
homestack中记录了两个应用:launcher和SystemUI,也就解释了为什么我们在lacunher界面长按recents键是进不了分屏的。为什么加这个判断我会在后面解释。
runningTask.isDockable是一个属性,只有这个属性是true时,应用才能进入分屏,否者就弹出一个toast提醒用户无法进入分屏,例如我们在相机应用中想进入分屏就不可以。在实际开发中有些应用不想让它进入到分屏中,可以修改isDockable属性,它是由TaskRecord.java中的canGoInDockedStack()方法控制的(在能进入分屏的应用mResizeMode是2,不能进入分屏的应用mResizeMode值为0)。
后面判断是否是SystemUser,如果是,则执行RecentsImpl中的dockTopTask(...),否则就是RecentsImplProxy.dockTopTask(..)。不过是执行RecentsImplProxy.dockTopTask(..)也会调用到Recents中的方法。。
PS:Taskrecord是记录一个应用全部信息,它通过唯一的taskid来标识应用,如果想扩展或者修改应用的行为,可以从修改TaskRecord.java中的属性值入手