1. 程式人生 > >Android N分屏流程分析之一

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。

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中的屬性值入手