Android 7.0中的多視窗實現解析
在以往的Android系統上,所有Activity都是全屏的,如果不設定透明效果,一次只能看到一個Activity介面。
但是從Android N(7.0)版本開始,系統支援了多視窗功能。在有了多視窗支援之後,使用者可以同時開啟和看到多個應用的介面。並且系統還支援在多個應用之間進行拖拽。在大螢幕裝置上,這一功能非常實用。
本文將詳細講解Android系統中多視窗功能的實現。
多視窗功能介紹
概述
Android 從 Android N(7.0)版本開始引入了多視窗的功能。
關於Android N的新特性,請參見這裡:Android 7.0 for Developers
關於多視窗的詳細說明,請參見這裡:Multi-Window Support
Android N上的多視窗功能有三種模式:
1. 分屏模式
這種模式可以在手機上使用。該模式將螢幕一分為二,同時顯示兩個應用的介面。如下圖所示:
2. 畫中畫模式
這種模式主要在TV上使用,在該模式下視訊播放的視窗可以一直在最頂層顯示。如下圖所示:
3. Freeform模式
這種模式類似於我們常見的桌面作業系統,應用介面的視窗可以自由拖動和修改大小。如下圖所示:
生命週期
多視窗不影響和改變原先Activity的生命週期。
在多視窗模式,多個Activity可以同時可見,但只有一個Activity是最頂層的,即:獲取焦點的Activity。
所有其他Activity都會處於Paused狀態(儘管它們是可見的)。
在以下三種場景下,系統會通知應用有狀態變化,應用可以進行處理:
- 當使用者以多視窗的模式啟動的應用
- 當使用者改變了Activity的視窗大小
- 當使用者將應用視窗從多視窗模式改為全屏模式
關於應用如何進行狀態變化的處理,請參見這裡:Handling Runtime Changes,這裡不再贅述。
開發者相關
Android從API Level 24開始,提供了以下一些機制來配合多視窗功能的使用。
- Manifest新增屬性
android:resizeableActivity=["true" | "false"]
- 這個屬性可以用在<activity>或者<application> 上。置為true,表示可以以分屏或者Freeform模式啟動。false表示不支援多視窗模式。對於API目標Level為24的應用來說,這個值預設是true。
-android:supportsPictureInPicture=["true" | "false"]
這個屬性用在<activity>上,表示是否支援畫中畫模式。如果android:resizeableActivity
為false,這個屬性值將被忽略。 - Layout新增屬性
android:defaultWidth,android:defaultHeight
Freeform模式下的預設寬度和高度android:gravity
Freeform模式下的初始Gravityandroid:minWidth, android:minHeight
分屏和Freeform模式下的最小高度和寬度
這裡是一段程式碼示例:
<activity android:name=".MyActivity"> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minHeight="450dp" android:minWidth="300dp" /> </activity>
- 新增API
Activity.isInMultiWindowMode()
查詢是否處於多視窗模式Activity.isInPictureInPictureMode()
查詢是否處於畫中畫模式Activity.onMultiWindowModeChanged()
多視窗模式變化時進行通知(進入或退出多視窗)Activity.onPictureInPictureModeChanged()
畫中畫模式變化時進行通知(進入或退出畫中畫模式)Activity.enterPictureInPictureMode()
呼叫這個介面進入畫中畫模式,如果系統不支援,這個呼叫無效ActivityOptions.setLaunchBounds()
在系統已經處於Freeform模式時,可以通過這個引數來控制新啟動的Activity大小,如果系統不支援,這個呼叫無效
- 拖拽相關 Android N之前,系統只允許在一個Activity內部進行拖拽。但從Android N開始,系統支援在多個Activity之間進行拖拽,下面是一些相關的API。具體說明請參見官方文件。
DragAndDropPermissions
View.startDragAndDrop()
View.cancelDragAndDrop()
View.updateDragShadow()
Activity.requestDragAndDropPermissions()
相關模組和主要類
本文,我們主要關注多視窗的功能實現。這裡列出了多視窗功能實現的主要類和模組。
這裡的程式碼路徑是指AOSP的原始碼路徑,關於如何獲取AOSP原始碼請參見這裡:Downloading the Source。
ActivityManager
程式碼路徑:/frameworks/base/services/core/java/com/android/server/am
- ActivityManagerService 負責執行時管理的系統服務,這個類掌管了Android系統的四大元件(Activity,Service,BroadcastReceiver,ContentProvider),應用程式的啟動退出,程式優先順序的控制。說它是Framework中最重要的系統服務都不為過。
- TaskRecord,ActivityStack 管理Activity的容器,多視窗的實現強烈依賴於ActivityStack,下文會詳細講解。
- ActivityStackSupervisor 顧名思義,專門負責管理ActivityStack。
- ActivityStarter Android N新增類。掌控Activity的啟動。
WindowManager
程式碼路徑:/frameworks/base/services/core/java/com/android/server/wm
- WindowManagerService 負責視窗管理的系統服務。
- Task,TaskStack 管理視窗物件的容器,與TaskRecord和ActivityStack對應。
- WindowLayersController Android N新增類,專門負責Z-Order的計算。Z-Order決定了視窗的上下關係。
Framework API
程式碼路徑:frameworks/base/core/java/
- ActivityManager 提供了管理Activity的介面和常量。
- ActivityOptions 提供了啟動Activity的引數選項,例如,在Freefrom模式下,設定視窗大小。
SystemUI
程式碼路徑:/frameworks/base/packages/SystemUI/
顧名思義:系統UI,這裡包括:NavigationBar,StatusBar,Keyguard等。
- PhoneStatusBar SystemUI中非常重要的一個類,負責了很多元件的初始化和控制。
為了便於說明,下文將直接使用這裡提到的類。如果你想檢視這些類的原始碼,請參閱這裡的路徑。
多視窗的功能實現
多視窗功能的實現主要依賴於ActivityManagerService與WindowManagerService這兩個系統服務,它們都位於system_server程式中。該程式是Android系統中一個非常重要的系統程式。Framework中的很多服務都位於這個程式中。
整個Android的架構是CS的模型,應用程式是Client,而system_server程式就是對應的Server。
應用程式呼叫的很多API都會傳送到system_server程式中對應的系統服務上進行處理,例如startActivity這個API,最終就是由ActivityManagerService進行處理。
而由於應用程式和system_server在各自獨立的程式中執行,因此對於系統服務的請求需要通過Binder進行程式間通訊(IPC)來完成呼叫,以及呼叫結果的返回。
兩個系統服務簡介
ActivityManagerService負責Activity管理。
對於應用中建立的每一個Activity,在ActivityManagerService中都會有一個與之對應的ActivityRecord,這個ActivityRecord記錄了應用程式中的Activity的狀態。ActivityManagerService會利用這個ActivityRecord作為標識,對應用程式中的Activity程式排程,例如生命週期的管理。
實際上,ActivityManagerService的職責遠超出的它的名稱,ActivityManagerService負責了所有四大元件(Activity,Service,BroadcastReceiver,ContentProvider)的管理,以及應用程式的程式管理。
WindowManagerService負責Window管理。包括:
- 視窗的建立和銷燬
- 視窗的顯示與隱藏
- 視窗的佈局
- 視窗的Z-Order管理
- 焦點的管理
- 輸入法和桌布管理
等等 每一個Activity都會有一個自己的視窗,在WindowManagerService中便會有一個與之對應的WindowState。WindowManagerService以此標示應用程式中的視窗,並用這個WindowState來儲存,查詢和控制視窗的狀態。
ActivityManagerService與WindowManagerService需要緊密配合在一起工作,因為無論是建立還是銷燬Activity都牽涉到Actiivty物件和視窗物件的建立和銷燬。這兩者是既相互獨立,又緊密關聯在一起的。
Activity啟動過程
Activity的啟動過程主要包含以下幾個步驟:
- Intent的解析(Intent可能是隱式的:關於Intents and Intent Filters)
- Activity的匹配(符合Intent的Activity可能會有多個)
- 應用程式的建立
- Task,Stack的獲取或者建立
- Activity視窗的建立
- Activity生命週期的排程(onCreate,onResume等)
本文不打算講解Activity啟動的詳細過程,對於這部分內容有興趣的讀者請參閱其他資料,比如這篇Android中Activity啟動過程探究
Task和Stack
Android系統中的每一個Activity都位於一個Task中。一個Task可以包含多個Activity,同一個Activity也可能有多個例項。 在AndroidManifest.xml中,我們可以通過android:launchMode來控制Activity在Task中的例項。
另外,在startActivity的時候,我們也可以通過setFlag 來控制啟動的Activity在Task中的例項。
Task管理的意義還在於近期任務列表以及Back棧。 當你通過多工鍵(有些裝置上是長按Home鍵,有些裝置上是專門提供的多工鍵)調出多工時,其實就是從ActivityManagerService獲取了最近啟動的Task列表。
Back棧管理了當你在Activity上點選Back鍵,當前Activity銷燬後應該跳轉到哪一個Activity的邏輯。關於Task和Back棧,請參見這裡:Tasks and Back Stack。
其實在ActivityManagerService與WindowManagerService內部管理中,在Task之外,還有一層容器,這個容器應用開發者和使用者可能都不會感覺到或者用到,但它卻非常重要,那就是Stack。 下文中,我們將看到,Android系統中的多視窗管理,就是建立在Stack的資料結構上的。 一個Stack中包含了多個Task,一個Task中包含了多個Activity(Window),下圖描述了它們的關係:
另外還有一點需要注意的是,ActivityManagerService和WindowManagerService中的Task和Stack結構是一一對應的,對應關係對於如下:
- ActivityStack <–> TaskStack
- TaskRecord <–> Task
即,ActivityManagerService中的每一個ActivityStack或者TaskRecord在WindowManagerService中都有對應的TaskStack和Task,這兩類物件都有唯一的id(id是int型別),它們通過id進行關聯。
多視窗與Stack
用過macOS或者Ubuntu的人應該都會用過虛擬桌面的功能,如下圖所示:
這裡建立了多個“虛擬桌面”,並在最上面一排列出了出來。 每個虛擬桌面裡面都可以放置一個或多個應用視窗,虛擬桌面可以作為一個整體進行切換。
Android為了支援多視窗,在執行時建立了多個Stack,Stack就是類似這裡虛擬桌面的作用。
每個Stack會有一個唯一的Id,在ActivityManager.java中定義了這些Stack的Id:
/** First static stack ID. */ public static final int FIRST_STATIC_STACK_ID = 0; /** Home activity stack ID. */ public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID; /** ID of stack where fullscreen activities are normally launched into. */ public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1; /** ID of stack where freeform/resized activities are normally launched into. */ public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1; /** ID of stack that occupies a dedicated region of the screen. */ public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1; /** ID of stack that always on top (always visible) when it exist. */ public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
由此我們可以知道,系統中可能會包含這麼幾個Stack:
- 【Id:0】Home Stack,這個是Launcher所在的Stack。 其實還有一些系統介面也執行在這個Stack上,例如近期任務
- 【Id:1】FullScren Stack,全屏的Activity所在的Stack。 但其實在分屏模式下,Id為1的Stack只佔了半個螢幕。
- 【Id:2】Freeform模式的Activity所在Stack
- 【Id:3】Docked Stack 下文中我們將看到,在分屏模式下,螢幕有一半執行了一個固定的應用,這個就是這裡的Docked Stack
- 【Id:4】Pinned Stack 這個是畫中畫Activity所在的Stack
需要注意的是,這些Stack並不是系統一啟動就全部建立好的。而是在需要用到的時候才會建立。上文已經提到過,ActivityStackSupervisor負責ActivityStack的管理。
有了以上這些背景知識之後,我們再來具體講解一下Android系統中的三種多視窗模式。
分屏模式
在Nexus 6P手機上,分屏模式的啟動和退出是長按多工虛擬按鍵。 下圖是在Nexus 6P上啟動分屏模式的樣子:
在啟動分屏模式的之後,系統會將螢幕一分為二。當前開啟的應用移到螢幕上方(如果是橫屏那就是左邊),其他所有開啟的應用,在下方(如果是橫屏那就是右邊)以多工形式列出。
之後使用者在操作的時候,下方的半屏保持了原先的使用方式:可以啟動或退出應用,可以啟動多工後進行切換,而上方的應用保持不變。 前面我們已經提到過,其實這裡處於上半屏固定不變的應用就是處在Docked的Stack中(Id為3),下半屏是之前全屏的Stack進行了Resize(Id為1)。
下面,我們就順著長按多工按鈕為線索,來調查一下分屏模式是如何啟動的: 其實無論是NavigationBar(螢幕最下方的三個虛擬按鍵)還是StatusBar(螢幕最上方的狀態列)都是在SystemUI中。我們可以以此為入口來調查。
PhoneStatusBar#prepareNavigationBarView
為NavigationBar初始化了UI。同時也在這裡為按鈕設定了事件監聽器。這裡包括我們感興趣的近期任務按鈕的長按事件監聽器:
private void prepareNavigationBarView() { mNavigationBarView.reorient(); ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); recentsButton.setOnClickListener(mRecentsClickListener); recentsButton.setOnTouchListener(mRecentsPreloadOnTouchListener); recentsButton.setLongClickable(true); recentsButton.setOnLongClickListener(mRecentsLongClickListener); ... }
在mRecentsLongClickListener中,主要的邏輯就是呼叫toggleSplitScreenMode。
toggleSplitScreenMode這個方法的名稱很明顯的告訴我們,這裡是在切換分屏模式
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; } };
再順著往下看PhoneStatusBar#toggleSplitScreenMode
的程式碼:
這裡我們看到,通過查詢WindowManagerProxy.getInstance().getDockSide();
來確定當前是否處於分屏模式,如果沒有則將Top Task移到Docked的Stack上。這裡的Top Task就是我們在長按多工按鍵之前開啟的當前應用。
@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); } } }
之後便會呼叫到ActivityManagerService#moveTaskToDockedStack中。後面的大部分邏輯在ActivityStackSupervisor#moveTaskToStackLocked中,在這個方法中,會做如下幾件事情:
- 通過指定的taskId獲取對應的TaskRecord
- 為當前Activity替換視窗(因為要從FullScreen的Stack切換的Docked Stack上)
- 呼叫mWindowManager.deferSurfaceLayout通知WindowManagerService暫停佈局
- 將當前TaskRecord移動到Docked Stack上
- 為移動後的Task和Stack設定Bounds,並且進行resize。這裡還會通知Activity
onMultiWindowModeChanged
- 呼叫mWindowManager.continueSurfaceLayout(); 通知WindowManagerService繼續開始佈局
而Resize和佈局就完全是WindowManagerService的事情,這裡面需要計算兩個Stack各自的大小,然後根據大小來對Stack中的Task和Activity視窗進行重新佈局。
由於篇幅關係,這裡不再貼出更多的程式碼。如果有興趣,請自行獲取AOSP的程式碼然後檢視。
下圖總結了啟動分屏模式的執行邏輯:
這裡需要注意的是:
黃色標記的模式是執行在SystemUI的程式中。
藍色標記的模式是執行在system_server程式中。
moveTaskToDockedStack是一個Binder呼叫,通過IPC呼叫到了ActivityManagerService。
畫中畫模式
當應用程式呼叫Activity#enterPictureInPictureMode便進入了畫中畫模式。
Activity#enterPictureInPictureMode會通過Binder呼叫到ActivityManagerService中對應的方法,該方法程式碼如下:
public void enterPictureInPictureMode(IBinder token) { final long origId = Binder.clearCallingIdentity(); try { synchronized(this) { if (!mSupportsPictureInPicture) { throw new IllegalStateException("enterPictureInPictureMode: " + "Device doesn't support picture-in-picture mode."); } final ActivityRecord r = ActivityRecord.forTokenLocked(token); if (r == null) { throw new IllegalStateException("enterPictureInPictureMode: " + "Can't find activity for token=" + token); } if (!r.supportsPictureInPicture()) { throw new IllegalArgumentException("enterPictureInPictureMode: " + "Picture-In-Picture not supported for r=" + r); } // Use the default launch bounds for pinned stack if it doesn't exist yet or use the // current bounds. final ActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID); final Rect bounds = (pinnedStack != null) ? pinnedStack.mBounds : mDefaultPinnedStackBounds; mStackSupervisor.moveActivityToPinnedStackLocked( r, "enterPictureInPictureMode", bounds); } } finally { Binder.restoreCallingIdentity(origId); } }
這裡的 mStackSupervisor.getStack(PINNED_STACK_ID);
是在獲取Pinned Stack,當這個Stack不存在時,會將其建立。 IBinder token是呼叫enterPictureInPictureMode的Activity的Binder標示,通過這個標示可以獲取到Activity對應的ActivityRecord物件,然後就是將這個ActivityRecord移動到Pinned Stack上。
Pinned Stack的預設大小來自於mDefaultPinnedStackBounds,這個值是從Internal的Resource上獲取的:
mDefaultPinnedStackBounds = Rect.unflattenFromString(res.getString( com.android.internal.R.string.config_defaultPictureInPictureBounds));
而com.android.internal.R.string.config_defaultPictureInPictureBounds的值是從配置檔案:/frameworks/base/core/res/res/values/config.xml 中讀取的。
為什麼一旦將Activity移動到Pinned Stack上,該視窗就能一直在最上層顯示呢?這就是由Z-Order控制的,Z-Order決定了視窗的上下關係。 Android N中新增了一個類WindowLayersController來專門負責Z-Order的計算。在計算Z-Order的時候,有幾類視窗會進行特殊處理,處於Pinned Stack上的視窗便是其中之一。
下面這段程式碼是在統計哪些視窗是需要特殊處理的,這裡可以看到,除了Pinned Stack上的視窗,還有分屏模式下的視窗以及輸入法視窗都需要特殊處理:
private void collectSpecialWindows(WindowState w) { if (w.mAttrs.type == TYPE_DOCK_DIVIDER) { mDockDivider = w; return; } if (w.mWillReplaceWindow) { mReplacingWindows.add(w); } if (w.mIsImWindow) { mInputMethodWindows.add(w); return; } final TaskStack stack = w.getStack(); if (stack == null) { return; } if (stack.mStackId == PINNED_STACK_ID) { mPinnedWindows.add(w); } else if (stack.mStackId == DOCKED_STACK_ID) { mDockedWindows.add(w); } }
在統計完這些特殊視窗之後,在計算Z-Order時會對它們進行特殊處理:
private void adjustSpecialWindows() { int layer = mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER; // For pinned and docked stack window, we want to make them above other windows also when // these windows are animating. while (!mDockedWindows.isEmpty()) { layer = assignAndIncreaseLayerIfNeeded(mDockedWindows.remove(), layer); } layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer); if (mDockDivider != null && mDockDivider.isVisibleLw()) { while (!mInputMethodWindows.isEmpty()) { final WindowState w = mInputMethodWindows.remove(); // Only ever move IME windows up, else we brake IME for windows above the divider. if (layer > w.mLayer) { layer = assignAndIncreaseLayerIfNeeded(w, layer); } } } // We know that we will be animating a relaunching window in the near future, which will // receive a z-order increase. We want the replaced window to immediately receive the same // treatment, e.g. to be above the dock divider. while (!mReplacingWindows.isEmpty()) { layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer); } while (!mPinnedWindows.isEmpty()) { layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer); } }
這段程式碼保證了處於Pinned Stack上的視窗(即處於畫中畫模式的視窗)的會在普通的應用視窗之上。
Freeform模式
在Andorid N裝置上開啟Freeform模式很簡單,只需以下兩個步驟:
- 執行以下命令:
adb shell settings put global enable_freeform_support 1
- 然後重啟手機:
adb reboot
重啟之後,在近期任務介面會出現一個按鈕,這個按鈕可以將視窗切換到Freeform模式,如下圖所示:
這個按鈕的作用其實就是將當前應用移到Freeform Stack上,相關邏輯在:
ActivityStackSupervisor中,程式碼如下:
void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options, String reason, boolean forceNonResizeable) { ... if (task.isResizeable() && options != null) { int stackId = options.getLaunchStackId(); if (canUseActivityOptionsLaunchBounds(options, stackId)) { final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); task.updateOverrideConfiguration(bounds); if (stackId == INVALID_STACK_ID) { stackId = task.getLaunchStackId(); } if (stackId != task.stack.mStackId) { final ActivityStack stack = moveTaskToStackUncheckedLocked( task, stackId, ON_TOP, !FORCE_FOCUS, reason); stackId = stack.mStackId; ... }
這段程式碼通過查詢stackId,然後呼叫moveTaskToStackUncheckedLocked移動Task。 而這裡通過task.getLaunchStackId()
獲取到的stackId,其實就是FREEFORM_WORKSPACE_STACK_ID,相關程式碼如下:
TaskRecord#getLaunchStackId程式碼如下:
int getLaunchStackId() { if (!isApplicationTask()) { return HOME_STACK_ID; } if (mBounds != null) { return FREEFORM_WORKSPACE_STACK_ID; } return FULLSCREEN_WORKSPACE_STACK_ID; }
即,如果設定了Bound,便表示該Task會在Freeform Stack上啟動。
PS:這裡將應用切換到Freeform模式,必須先開啟應用,然後在近期任務中切換。
如果想要開啟應用就直接進入Freeform模式,可以看一下這篇文章:
Taskbar lets you enable Freeform mode on Android Nougat without root or adb
如果沒有Google Play,可以到這裡下載上文中提到的Taskbar應用:Taskbar
有興趣的讀者也可以去GitHub上獲取TaskBar的原始碼:farmerbb/Taskbar
其實TaskBar的原理就是在啟動Activity的時候設定了Activity的Bounds,相關程式碼如下:
public static void launchPhoneSize(Context context, Intent intent) { DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); int width1 = display.getWidth() / 2; int width2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_width) / 2; int height1 = display.getHeight() / 2; int height2 = context.getResources().getDimensionPixelSize(R.dimen.phone_size_height) / 2; try { context.startActivity(intent, ActivityOptions.makeBasic().setLaunchBounds(new Rect( width1 - width2, height1 - height2, width1 + width2, height1 + height2 )).toBundle()); } catch (ActivityNotFoundException e) { /* Gracefully fail */ } }
而在ActivityStarter#computeStackFocus中會判斷如果新啟動的Activity設定了Bounds,
則在FULLSCREEN_WORKSPACE_STACK_ID這個Stack上啟動Activity,相關程式碼如下:
final int stackId = task != null ? task.getLaunchStackId() : bounds != null ? FREEFORM_WORKSPACE_STACK_ID : FULLSCREEN_WORKSPACE_STACK_ID; stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP); if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" + r + " stackId=" + stack.mStackId); return stack;
下圖是啟動Activity時建立Stack的呼叫過程:
當你在全屏應用以及Freeform模式應用來回切換的時候,系統所做的其實就是在FullScreen Stack和Freeform Stack上來回切換而已,這和前面提到的虛擬桌面的幾乎是一樣的。
至此,三種多視窗模式就都分析完了,回過頭來再看一下,三種多視窗模式的實現其實都是依賴於Stack結構,明白其原理,發現也沒有特別神祕的地方。
參考資料
https://developer.android.com/guide/topics/ui/multi-window.html
https://developer.android.com/guide/components/tasks-and-back-stack.html
http://arstechnica.com/gadgets/2016/03/this-is-android-ns-freeform-window-mode/
相關文章
- Android 懸浮視窗的實現Android
- Android 7.0 多視窗模式Android模式
- Android 多視窗程式設計Android程式設計
- Android N新特性--多視窗支援Android
- 建立多視窗的Windowsform程式WindowsORM
- vim 多視窗 多檔案
- Hystrix指標視窗實現原理指標
- js實現在彈出視窗中重新整理主視窗JS
- C#中初始化視窗或歡迎視窗實現C#
- 支援多視窗 Android N預覽版映象公佈Android
- iOS怎麼實現視窗的抖動效果iOS
- 用什麼類實現框架的視窗分割?框架
- Android 視窗是如何建立的?Android
- 兩個視窗如何實現通訊
- android視窗管理剖析Android
- Android 列表視訊的全屏、自動小視窗優化實踐Android優化
- 懸浮窗的一種實現 | Android懸浮窗Window應用Android
- weex 原始碼解析(一) — 整體實現思路(Android視角)原始碼Android
- Android 音視訊 - EGL 原始碼解析以及 C++ 實現Android原始碼C++
- weex 原始碼解析(一) -- 整體實現思路(Android視角)原始碼Android
- Android應用內懸浮窗的實現方案Android
- jQuery實現的在新視窗開啟連結jQuery
- Android的左滑關閉視窗Android
- Android安全防護的「多工視窗中的介面高斯模糊處理」其實是個騙局?Android
- js實現window.open()彈出視窗和父視窗之間相互操作JS
- 基於Android 4.4 開發的多視窗系統 開放原始碼Android原始碼
- goland 把多個專案視窗合併到一個視窗GoLand
- java鍵盤監聽之視窗監聽的實現Java
- 如何實現在指定的時間後關閉視窗
- jQuery實現的div在視窗中垂直水平居中效果jQuery
- jQuery實現的點選彈出登陸視窗效果jQuery
- js實現的拖動改變視窗大小功能JS
- Android視窗管理分析(2):WindowManagerService視窗管理之Window新增流程Android
- Android視窗管理分析(3):視窗分組及Z-order的確定Android
- 基於 WPF 的酷炫 GUI 視窗的簡易實現GUI
- 【轉載】使用WindowManage實現Android懸浮窗Android
- android的視窗機制分析------ViewRoot類AndroidView
- Mac 多螢幕視窗管理神器Mac