Android N 多窗口功能初探

本文參考了部分 Android 7.0中的多窗口-分屏-實現(xiàn)解析的內容滔吠。

從Android N(7.0)版本開始阻星,系統(tǒng)支持了多窗口功能盅惜。在有了多窗口支持之后糙捺,用戶可以同時打開和看到多個應用的界面伐割。并且系統(tǒng)還支持在多個應用之間進行拖拽候味。在大屏幕設備上,這一功能非常實用隔心。

本文將詳細探究Android系統(tǒng)中多窗口功能的實現(xiàn)白群。

三種多窗口模式

Android N上的多窗口功能有三種模式:

  1. 分屏模式
    這種模式可以在手機上使用。該模式將屏幕一分為二硬霍,同時顯示兩個應用的界面川抡。
  2. 畫中畫模式
    這種模式主要在TV上使用,在該模式下視頻播放的窗口可以一直在最頂層顯示须尚。
  3. Freeform模式
    這種模式類似于我們常見的桌面操作系統(tǒng)崖堤,應用界面的窗口可以自由拖動和修改大小。

新增屬性

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模式下的初始Gravity
  android:minWidth, android:minHeight 分屏和Freeform模式下的最小高度和寬度

分屏模式探究

** 如何啟動分屏模式怜森? **
  在Nexus 6P手機上速挑,分屏模式的啟動和退出是長按多任務虛擬按鍵。(打開相應的應用副硅,再長按多任務鍵)

** android:resizeableActivity 如何使用姥宝?**
  我們來寫一個小例子來測試 android:resizeableActivity 這個屬性。我們定義兩個 activity恐疲,HelloActivity腊满、MultipleWindowActivity, 定義如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.helloactivity">
    <application android:label="Hello, Activity!"
   android:resizeableActivity="true"
        >
        <activity android:name="HelloActivity"
            android:resizeableActivity="false"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        
        <activity android:name="MultipleWindowActivity" 
            android:resizeableActivity="true"
            android:supportsPictureInPicture="true"/>
    </application>
</manifest>                                                                                                            

我們在Android 7.0 平臺上進行編譯,下面通過表格整理下在application及activity標簽下定義android:resizeableActivity與能否分屏的關系培己。

| NO | Application resizeableActivity | HelloActivity resizeableActivity |HelloActivity Can split window |
| -----| ----- |:-------|: --------|
| 1 | / | / | yes |
| 2 | false | / | no |
| 3 | true | / | yes |
| 4 | false | true | yes |
| 5 | true | true | yes |
| 6 | false | false | no |
| 7 | true | false | no |

| NO | HelloActivity resizeableActivity | MultipleWindowActivity resizeableActivity |MultipleWindow Activity Can split window |
|:-------|:-------|: --------|:--------|:
| 6 | true | false | yes |
| 7 | true | true | yes |
| 8 | false | true | no |
| 9 | false | false | no |
| 10| / | true | yes |
| 11 | / | false | yes |

注1: 結果是 no 的嘗試切到分屏模式會提示“App doesn't support split screen”碳蛋,后面我們將進行分析;
注2:情況6,7雖然能進入分屏模式漱凝,但是會提示“App may not work with split-screen”, 后面我們將進行分析疮蹦;

從表格可以得出下面的結論:
1 沒設置Application resizeableActivity 和 activity resizeableActivity 時诸迟,模式能分屏(跟平臺有關茸炒,7.0默認可以);
2 只設置了Application resizeableActivity 時阵苇,能否分屏受Application resizeableActivity影響壁公;
3 同時設置了Application resizeableActivity 和 activity resizeableActivity 時,能否分屏受activity resizeableActivity影響
4 設置非 main activity 的 resizeableActivity 沒有效果绅项,非 main activity 能否分屏受 main activity 能否分屏影響紊册;

** 初探分屏模式的實現(xiàn) **
  我們從不能進入分屏模式時的提示“App doesn't support split screen” 著手,粗略看看分屏模式的實現(xiàn)快耿。搜索字符串囊陡,發(fā)現(xiàn)彈出提示的地方在 SystemUI 模塊的 Recents#dockTopTask 方法中,
如下

409     @Override                                                                                                           
410     public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,                                   
411             int metricsDockAction) {                                                                                    
                   ....
433             if (runningTask.isDockable) {              
                   ....                                                                 
458             } else {                                                                                                    
459                 Log.d(TAG, "dockTopTask," + Log.getStackTraceString(new Throwable()));                            
460                 EventBus.getDefault().send(new ShowUserToastEvent(                                                      
461                         R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT));                                
462                 return false;                                                                                           
463             }                                                                                                           

第 459 行是我們加的 log掀亥,打印調用關系撞反,結果如下:

at com.android.systemui.recents.Recents.dockTopTask(Recents.java:459)
at com.android.systemui.statusbar.phone.PhoneStatusBar.toggleSplitScreenMode(PhoneStatusBar.java:1652)
at com.android.systemui.statusbar.BaseStatusBar.toggleSplitScreen(BaseStatusBar.java:1322)
at com.android.systemui.statusbar.CommandQueue$H.handleMessage(CommandQueue.java:519)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6124)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:926)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:788)

大致可以看出參與觸發(fā)分屏模式的有 PhoneStatusBar、Recents等類搪花。而真正執(zhí)行分屏模式的代碼在 runningTask.isDockable 為 true 的代碼塊遏片,后面將進行講解。

畫中畫模式探究

** 如何進入畫中畫模式撮竿?**
  當應用程序調用Activity#enterPictureInPictureMode便進入了畫中畫模式吮便。來做個實驗,我們在MultipleWindowActivity里添加一個按鈕幢踏,點擊后調用 enterPictureInPictureMode 方法:

 51     public void onClick(View v) {                                                                                       
 52         switch (v.getId()) {                                                                                            
 53             case R.id.btn:                                                                                              
 54                 Log.d(TAG, "onclick");                                                                            
 55                 enterPictureInPictureMode();                                                                            
 56                 break;                                                                                                  
 57         }                                                                                                               
 58     }                                                                                                                   

運行后髓需,出現(xiàn)頁面閃退,log 如下:

E ActivityManager: Activity Manager Crash                                                
E ActivityManager: java.lang.IllegalStateException: enterPictureInPictureMode: Device doesn't support picture-in-picture mode.
E ActivityManager:   at com.android.server.am.ActivityManagerService.enterPictureInPictureMode(ActivityManagerService.java:8082)
E ActivityManager:   at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:2924)
E ActivityManager:   at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3045)
E ActivityManager:   at android.os.Binder.execTransact(Binder.java:565)                  
E AndroidRuntime: FATAL EXCEPTION: main                                                  
E AndroidRuntime: Process: com.example.android.helloactivity, PID: 9965                  
E AndroidRuntime: java.lang.IllegalStateException: enterPictureInPictureMode: Device doesn't support picture-in-picture mode.
E AndroidRuntime:    at android.os.Parcel.readException(Parcel.java:1692)                
E AndroidRuntime:    at android.os.Parcel.readException(Parcel.java:1637)                
E AndroidRuntime:    at android.app.ActivityManagerProxy.enterPictureInPictureMode(ActivityManagerNative.java:6986)
E AndroidRuntime:    at android.app.Activity.enterPictureInPictureMode(Activity.java:2042)
E AndroidRuntime:    at com.example.android.helloactivity.MultipleWindowActivity.onClick(MultipleWindowActivity.java:54)
E AndroidRuntime:    at android.view.View.performClick(View.java:5646)                   
E AndroidRuntime:    at android.view.View$PerformClick.run(View.java:22554)              
E AndroidRuntime:    at android.os.Handler.handleCallback(Handler.java:751)              
E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java:95)              
E AndroidRuntime:    at android.os.Looper.loop(Looper.java:154)                          
E AndroidRuntime:    at android.app.ActivityThread.main(ActivityThread.java:6124)        
E AndroidRuntime:    at java.lang.reflect.Method.invoke(Native Method)                   
E AndroidRuntime:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:926)
E AndroidRuntime:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:788)     

異常在 ActivityManagerService.java 中被拋出房蝉,代碼如下:

 8081                 if (!mSupportsPictureInPicture) {                                                                       
 8082                     throw new IllegalStateException("enterPictureInPictureMode: "                                       
 8083                             + "Device doesn't support picture-in-picture mode.");                                       
 8084                 }                                                                                                       

來看看 mSupportsPictureInPicture 的初始化:

13795         final boolean supportsPictureInPicture =                                                                        
13796                 mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);                              

13798         final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow(); 
                                     
13806         final boolean forceResizable = Settings.Global.getInt(                                                          
13807                 resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;                                              

13821         synchronized (this) {                                                                                           
                  ......
13829             if (supportsMultiWindow || forceResizable) {                                                                
13830                 mSupportsMultiWindow = true;                                                                            
13831                 mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;                         
13832                 mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;                                 
13833             } else {                                                                                                    
13834                 mSupportsMultiWindow = false;                                                                           
13835                 mSupportsFreeformWindowManagement = false;                                                              
13836                 mSupportsPictureInPicture = false;                                                                      
13837             }
                  ......
              }

mSupportsPictureInPicture 由三個變量決定:
** 1 supportsMultiWindow **

    /**
     * Returns true if the system supports at least one form of multi-window.
     * E.g. freeform, split-screen, picture-in-picture.
     * @hide
     */
    ActivityManager.supportsMultiWindow() 

系統(tǒng)至少支持一種多窗口形式時返回 true授账;顯然這里是 true枯跑;
2 forceResizable

Settings.Global.getInt(                                                          
                 resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;    

這是一個 Setting 項,通過搜索發(fā)現(xiàn)它與“開發(fā)者選項”的“Force activities to be resizable”對應白热。
3 supportsPictureInPicture

    /**
     * Check whether the given feature name is one of the available features as
     * returned by {@link #getSystemAvailableFeatures()}. This tests for the
     * presence of <em>any</em> version of the given feature name; use
     * {@link #hasSystemFeature(String, int)} to check for a minimum version.
     *
     * @return Returns true if the devices supports the feature, else false.
     */
    mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE); 

這是一個系統(tǒng)特性敛助,我們可以通過下面的方法打印系統(tǒng)支持的特性:

FeatureInfo[] features = getPackageManager().getSystemAvailableFeatures();
for (FeatureInfo info : features) {
    Log.d(TAG, "Name=" + info.name);
}

可以推測出 supportsMultiWindow 為 true, forceResizable 和 supportsPictureInPicture 都為 false屋确; 顯然我們不太容易控制 supportsPictureInPicture 這個系統(tǒng)特性纳击,但是 forceResizable 可以控制。我們打開“開發(fā)者選項”的“Force activities to be resizable”選項試一下攻臀,打開后發(fā)現(xiàn)還是崩潰焕数,但是 DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES 值確實變化了,為什么不行呢?通過分析代碼刨啸,我們發(fā)現(xiàn) mSupportsPictureInPicture 的初始化時機比較早堡赔,雖然我們改變了 DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES 的值,但是并沒有走到改變 mSupportsPictureInPicture 的邏輯设联,所以善已,把 DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES 開關打開后,我們重啟一下手機試試离例。果然重啟后不會發(fā)生崩潰了换团。
  我們來看看畫中畫模式的效果。進入時頁面會縮小到屏幕左上角宫蛆,變成一個小黑塊艘包,這顯然不是我們想要的。我們需要能控制縮小后的大小耀盗,以及監(jiān)聽縮小的動作以做邏輯切換想虎。如何實現(xiàn)呢?這個問題我們后面再去研究叛拷。

Freeform 模式探究

[TODO]

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末舌厨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胡诗,更是在濱河造成了極大的恐慌邓线,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煌恢,死亡現(xiàn)場離奇詭異骇陈,居然都是意外死亡,警方通過查閱死者的電腦和手機瑰抵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門你雌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事婿崭〔ν兀” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵氓栈,是天一觀的道長渣磷。 經常有香客問我,道長授瘦,這世上最難降的妖魔是什么醋界? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮提完,結果婚禮上形纺,老公的妹妹穿的比我還像新娘。我一直安慰自己徒欣,他們只是感情好逐样,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著打肝,像睡著了一般脂新。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闯睹,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天戏羽,我揣著相機與錄音担神,去河邊找鬼楼吃。 笑死,一個胖子當著我的面吹牛妄讯,可吹牛的內容都是我干的孩锡。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼亥贸,長吁一口氣:“原來是場噩夢啊……” “哼躬窜!你這毒婦竟也來了?” 一聲冷哼從身側響起炕置,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤荣挨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后朴摊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體默垄,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年甚纲,在試婚紗的時候發(fā)現(xiàn)自己被綠了口锭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡介杆,死狀恐怖鹃操,靈堂內的尸體忽然破棺而出韭寸,到底是詐尸還是另有隱情,我是刑警寧澤荆隘,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布恩伺,位于F島的核電站,受9級特大地震影響椰拒,放射性物質發(fā)生泄漏莫其。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一耸三、第九天 我趴在偏房一處隱蔽的房頂上張望乱陡。 院中可真熱鬧,春花似錦仪壮、人聲如沸憨颠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爽彤。三九已至,卻和暖如春缚陷,著一層夾襖步出監(jiān)牢的瞬間适篙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工箫爷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嚷节,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓虎锚,卻偏偏與公主長得像硫痰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窜护,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內容