Android N分屏流程分析之一
在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。
我們分析的是進入分屏的流程,那麼獲取到的值就是WindowManager.DOCKED_INVALID。這時就會呼叫到Recents.dockTopTask(...)方法。int dockSide = WindowManagerProxy.getInstance().getDockSide();
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中的屬性值入手