最近生活有些變動所以斷更好久屿岂,不過雖遲到但永遠(yuǎn)不會缺席践宴。ChatGPT 浪潮還在持續(xù)擴(kuò)大,各位同學(xué)一定要體驗(yàn)體驗(yàn)丫~
這篇主要介紹最近需求中遇到的問題爷怀,希望能幫助后來者少踩坑阻肩。先說結(jié)論:Android 原生畫中畫功能并不完善,如果可以接受 APP 有兩個任務(wù)棧則可以使用运授;否則趁早自己用浮窗自定義實(shí)現(xiàn)畫中畫的功能吧烤惊。
1. PiP 簡介
Android PiP 模式也稱之為畫中畫模式,允許用戶在使用應(yīng)用程序的同時(shí)吁朦,在屏幕的一角或一側(cè)浮動顯示另一個應(yīng)用程序或視頻柒室。這使得用戶可以同時(shí)進(jìn)行多項(xiàng)任務(wù),而不必切換應(yīng)用程序或中斷正在進(jìn)行的任務(wù)逗宜。如下所示:
(注:B站的 PiP 是自定義實(shí)現(xiàn)的雄右,未使用系統(tǒng) PiP)
2. 準(zhǔn)備工作,跑通 Demo
官方文檔:https://developer.android.google.cn/guide/topics/ui/picture-in-picture?hl=zh-cn
官方Demo:https://github.com/android/media-samples/tree/main/PictureInPictureKotlin
打開官方 Demo纺讲,首先得改一下 minSdkVersion擂仍,demo 里設(shè)置的是 API 31(Android 12.0),不滿足實(shí)際應(yīng)用需求熬甚,這里改為 23(Android 6.0). 但 PiP 功能只能在 Android8.0 及以上的系統(tǒng)上使用逢渔,所以用到一些方法時(shí),需要注明 @RequiresApi(Build.VERSION_CODES.O)
则涯。所以复局,如果需要在 Android 8.0 以下的設(shè)備支持 PiP,只能使用自定義懸浮窗實(shí)現(xiàn)粟判。
還需要注釋掉 setAutoEnterEnabled(true)
亿昏、setSeamlessResizeEnabled(false)
這兩個方法。因?yàn)樗鼈冎荒茉?Android 12.0 及以上系統(tǒng)使用档礁,且對于 PiP 的主體功能沒有影響角钩。setAutoEnterEnabled
用于設(shè)置 Activity 在退到后臺時(shí)是否自動進(jìn)入 PiP 模式,當(dāng)設(shè)置為 true呻澜,則在用戶點(diǎn)擊 Home 鍵回到主屏幕時(shí)递礼,Activity 可自動進(jìn)入 PiP 模式,而不用開發(fā)者手動調(diào)用 enterPictureInPictureMode
方法羹幸;setSeamlessResizeEnabled
用于設(shè)置非視頻畫中畫時(shí)的動畫效果脊髓,不影響功能。
按照上述的內(nèi)容設(shè)置完后就可以將 Demo 跑通了栅受。
3. 示例代碼分析
僅分析查看了 Demo 中的 MovieActivity 中的 PiP 相關(guān)的代碼将硝。比較重要的代碼如下:
// code 1
@RequiresApi(Build.VERSION_CODES.O)
private fun minimize() {
enterPictureInPictureMode(updatePictureInPictureParams())
}
調(diào)用 enterPictureInPictureMode(@NonNull PictureInPictureParams params)
方法就可以進(jìn)入 PiP恭朗,聲明如下:
// code 2
public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) {
···
}
方法簡介:它是 Activity 類中的方法,需要傳遞一個 PictureInPictureParams 類型對象依疼。當(dāng)系統(tǒng)成功將該 Activity 切換到 PiP 模式或已經(jīng)處于 PiP痰腮,則返回值為 true;如果設(shè)備不支持 PiP 則返回 false律罢。
再來看下構(gòu)建 PictureInPictureParams 類型對象的 updatePictureInPictureParams()
方法:
// code 3
@RequiresApi(Build.VERSION_CODES.O)
private fun updatePictureInPictureParams(): PictureInPictureParams {
// 1膀值、計(jì)算出 PiP 小窗的寬高比,這里直接使用播放視頻的控件寬和高計(jì)算
val aspectRatio = Rational(binding.movie.width, binding.movie.height)
// 2误辑、將播放視頻的控件binding.movie設(shè)置為 PiP 中要展示的部分
val visibleRect = Rect()
binding.movie.getGlobalVisibleRect(visibleRect)
val params = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
// 3沧踏、指定進(jìn)入畫中畫的屏幕部分。系統(tǒng)根據(jù)這個可實(shí)現(xiàn)平滑動畫效果稀余。這里就把之前生成的 visibleRect 傳值過去
.setSourceRectHint(visibleRect)
.build()
setPictureInPictureParams(params)
return params
}
updatePictureInPictureParams
方法作用是構(gòu)建出進(jìn)入 PiP 的一些參數(shù)悦冀,比如進(jìn)入小窗的控件趋翻,小窗的寬高比等睛琳。注釋很清楚,源碼直接拿來套用就行踏烙。需要注意的點(diǎn):只能指定 PiP 模式的寬高比师骗,并不能直接設(shè)置寬和高的具體值,系統(tǒng)會根據(jù)設(shè)置的寬高比自己計(jì)算具體值讨惩。
如果在播放器控件上層有其他的操作按鈕等辟癌,還需要在 onPictureInPictureModeChanged
回調(diào)中進(jìn)行處理,即進(jìn)入 PiP 后隱藏這些按鈕荐捻;退出后恢復(fù)這些按鈕的狀態(tài)黍少。 如下是 Demo 中的實(shí)現(xiàn):
// code 4
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean, newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
// Hide the controls in picture-in-picture mode.
binding.movie.hideControls()
} else {
// Show the video controls if the video is not playing
if (!binding.movie.isPlaying) {
binding.movie.showControls()
}
}
}
通過這個方法可以監(jiān)聽 PiP 的進(jìn)入和退出。
還有一些是 PiP 模式下的播放/暫停处面、上一個/下一個 操作按鈕厂置,即下圖紅框中的這三個按鈕,相關(guān)的使用方式 Demo 中已有示例魂角,這里不再贅述昵济。
除此之外,還要在需要進(jìn)入 PiP 的 Activity 的 AndroidManifest 中設(shè)置支持 PiP 的屬性以及處理布局配置更改野揪。這樣一來访忿,如果在 PiP 模式轉(zhuǎn)換期間出現(xiàn)布局更改,該 Activity 就不會重新啟動斯稳。
// code 5
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
...
這些官方文檔中就有海铆,這里也不再多說。
4. 功能實(shí)現(xiàn)及踩坑匯總
4.1 實(shí)現(xiàn)點(diǎn)擊 Back 鍵及 Home 鍵自動進(jìn)入 PiP
用戶在觀看視頻時(shí)挣惰,點(diǎn)擊返回鍵或 Home 鍵卧斟,當(dāng)前 Activity 需要進(jìn)入 PiP 繼續(xù)播放系草,這是個常見的功能,實(shí)現(xiàn)起來也比較簡單:
// code 6
// 實(shí)現(xiàn)點(diǎn)擊返回鍵進(jìn)入 PiP
@RequiresApi(Build.VERSION_CODES.O)
override fun onBackPressed() {
enterPictureInPictureMode(updatePictureInPictureParams())
}
// 實(shí)現(xiàn)點(diǎn)擊 Home 鍵進(jìn)入 PiP
@RequiresApi(Build.VERSION_CODES.O)
override fun onUserLeaveHint() {
super.onUserLeaveHint()
enterPictureInPictureMode(updatePictureInPictureParams())
}
如果設(shè)置了之前提到的 setAutoEnterEnabled(true)
方法唆涝,則可以不用在 onUserLeaveHint()
回調(diào)里主動調(diào)用 enterPictureInPictureMode
方法進(jìn)入 PiP找都。但建議還是不用 setAutoEnterEnabled
,因?yàn)樗荒茉?Android 12 上使用廊酣。能耻。。
onUserLeaveHint()
方法也是 Activity 中的方法亡驰,當(dāng) Activity 進(jìn)入后臺時(shí)就會調(diào)用它晓猛,比如用戶點(diǎn)擊 Home 鍵就會回調(diào)它。但有來電時(shí)凡辱,來電的 Activity 會自動帶到前臺戒职,這時(shí)被退到后臺的 Activity 的 onUserLeaveHint
方法并不會被調(diào)用。onUserLeaveHint
的調(diào)用時(shí)機(jī)是在 onPause
方法之前透乾,這點(diǎn)需要注意洪燥。
4.2 實(shí)現(xiàn) Activity 處于 PiP 時(shí)再次進(jìn)入更新視頻
假設(shè) MovieActivity 已處于 PiP 并正在播放視頻,用戶點(diǎn)擊另外一個視頻又要跳轉(zhuǎn)到 MovieActivity 的情形乳乌。如果不進(jìn)行處理就會出現(xiàn)有兩個 MovieActivity 同時(shí)播放視頻的情況捧韵,即小窗播放的同時(shí),還有一個另一個 MovieActivity 也在播放汉操。如下所示再来,本來只有一個 PiP 在播放視頻,然后點(diǎn)擊 WATCH VIDEO TWO 按鈕又進(jìn)入了 MovieActivity磷瘤,此時(shí)有兩個視頻同時(shí)在播放:
查看堆棧信息確實(shí)有兩個 MovieActivity:
這種情況下是需要將 MovieActivity 由 PiP 恢復(fù)到正常狀態(tài)并播放新的視頻芒篷,如果視頻內(nèi)容沒有變則接著播放原視頻。官方 Demo 也有說明如何處理采缚,需要兩個步驟:
1)將 MovieActivity 的 launchMode
設(shè)置為 singleTask
针炉;
2)在 MovieActivity 的 onNewIntent
方法里處理更新數(shù)據(jù)等邏輯;
比如我在打開 MovieActivity 時(shí)通過 Intent
傳遞不同的 video 來播放不同的視頻仰担,那么在 onNewIntent
中就需要接收傳遞的參數(shù)并更新:
// code 7
// MainActivity.kt 通過 Intent 傳入不同的視頻
binding.btnWatchVid1.setOnClickListener {
val intent = Intent(this, MovieActivity::class.java)
intent.putExtra(MovieActivity.KEY_VIDEO_ID, R.raw.vid_bigbuckbunny)
startActivity(intent)
}
binding.btnWatchVid2.setOnClickListener {
val intent = Intent(this, MovieActivity::class.java)
intent.putExtra(MovieActivity.KEY_VIDEO_ID, R.raw.vid_dajiang)
startActivity(intent)
}
// code 8
// MovieActivity.kt onNewIntent 接收并更新
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val newVideoId = intent?.getIntExtra(KEY_VIDEO_ID, R.raw.vid_bigbuckbunny)
newVideoId?.let {
// 更新視頻
binding.movie.setVideoResourceId(it)
}
}
在實(shí)際中可能更加復(fù)雜糊识,但大體思路是一致的。
4.3 實(shí)現(xiàn)跳轉(zhuǎn)其他 Activity 時(shí)摔蓝,當(dāng)前 Activity 自動進(jìn)入 PiP
場景:正在 MovieActivity 里播放視頻赂苗,用戶點(diǎn)擊某個按鈕跳轉(zhuǎn)到其他 Activity,MovieActivity 此時(shí)需進(jìn)入 PiP贮尉,用戶可以在新打開的 Activity 頁面進(jìn)行操作拌滋。
官方文檔里并沒有針對這一場景進(jìn)行說明和提示,所以一開始以為很簡單猜谚,直接跟之前一樣調(diào)用 enterPictureInPictureMode(updatePictureInPictureParams())
不就可以了么败砂?于是就有了下面的代碼:
// code 9
binding.btnJumpTestOne.setOnClickListener {
enterPictureInPictureMode(updatePictureInPictureParams())
startActivity(Intent(this@MovieActivity, TestOneActivity::class.java))
}
想著先將當(dāng)前的 MovieActivity 進(jìn)入 PiP赌渣,再跳轉(zhuǎn)到其他的 Activity,結(jié)果 MovieActivity 直接退出了昌犹,也沒有錯誤信息坚芜。看棧信息發(fā)現(xiàn)其實(shí)要跳轉(zhuǎn)的新 Activity —— TestOneActivity 已經(jīng)打開了斜姥。鸿竖。。
打開失敗的動圖:
在 MovieActivity 中點(diǎn)擊 JUMP TO TESTONEACTIVITY 按鈕跳轉(zhuǎn)之后铸敏,堆棧信息如下缚忧,可以看到 pid = 21126 的進(jìn)程就是 Demo 程序,TestOneActivity 確實(shí)打開了杈笔,MovieActivity 已退出:
加延時(shí)再試:
// code 10
binding.btnJumpTestOne.setOnClickListener {
lifecycleScope.launch {
enterPictureInPictureMode(updatePictureInPictureParams())
delay(1000)
startActivity(Intent(this@MovieActivity, TestOneActivity::class.java))
}
}
確實(shí)進(jìn)入 PiP 了闪水,但后面跳轉(zhuǎn)的 TestOneActivity 也在 PiP 了。蒙具。
如果是先跳轉(zhuǎn) TestOneActivity 再進(jìn)入 PiP 球榆,經(jīng)測試只會跳轉(zhuǎn)并不會進(jìn)入 PiP,這里就不再展示了店量。
經(jīng)分析和實(shí)踐發(fā)現(xiàn)芜果,只能先進(jìn)入 PiP 再進(jìn)行跳轉(zhuǎn),之所以會出現(xiàn)在 PiP 里跳轉(zhuǎn)融师,是因?yàn)楹竺嫣D(zhuǎn)的 TestOneActivity 進(jìn)入了 MovieActivity 所在的任務(wù)棧。Activity 在沒有設(shè)置 taskAffinity
屬性時(shí)蚁吝,都會放在默認(rèn)的同一個任務(wù)棧中旱爆。
所以想到的第一個方法就是,修改 MovieActivity 的 launchMode
窘茁,改為 singleInstance
怀伦。這樣既可以保證任務(wù)棧中只有一個 MovieActivity 的實(shí)例,也可以將 MovieActivity 放在獨(dú)立的任務(wù)棧中山林。試了下果然可以了房待,但會在多任務(wù)切換頁里出現(xiàn)同一個 App 有兩個任務(wù)棧的現(xiàn)象:
這是第一個問題,這個問題直到最后也無法解決驼抹,在 AndroidManifest 文件中添加 autoRemoveFromRecents
和 excludeFromRecents
都沒用桑孩,還是會在多任務(wù)切換頁出現(xiàn)兩個棧。
還有一個問題即問題二框冀,還是 singleInstance
引起的流椒。當(dāng) MovieActivity 正在以非小窗模式播放視頻時(shí),先進(jìn)入多任務(wù)切換頁明也,再按 Home 鍵回到主屏幕宣虾,然后再點(diǎn)擊 App 圖標(biāo)進(jìn)入時(shí)惯裕,發(fā)現(xiàn)進(jìn)入的不是 MovieActivity,而是 MovieActivity 的上個頁面绣硝,即 MainActivity蜻势,此時(shí)再進(jìn)入多任務(wù)切換頁面,會發(fā)現(xiàn) MovieActivity 所在那個任務(wù)棧已經(jīng)消失了鹉胖。這里其實(shí)有兩個問題:
1)回到主屏幕后再點(diǎn)擊 App 圖標(biāo)應(yīng)該回到 MovieActivity咙边;
2)用戶并沒有關(guān)閉 MovieActivity,但進(jìn)入多任務(wù)切換頁面后無法找到 MovieActivity 了次员。如下動圖:
問題二的兩個問題得先解決 2)才能解決 1)败许。2)之所以會出現(xiàn)是因?yàn)橐粋€ App 出現(xiàn)了兩個任務(wù)棧,這兩個任務(wù)棧的 taskAffinity
參數(shù)默認(rèn)是一樣的淑蔚,一山不容二虎市殷,那么點(diǎn)擊桌面圖標(biāo)后,就會把之前的任務(wù)棧移到前臺刹衫,然后會把另一個任務(wù)棧干掉醋寝。
所以首先要保留這兩個任務(wù)棧,給 MovieActivity 設(shè)置一個單獨(dú)的 taskAffinity
名稱带迟,這就可以得以保留音羞,問題 2)就解決了。只有先保留任務(wù)棧仓犬,才能解決問題 1)嗅绰。
導(dǎo)致問題1)的原因是因?yàn)橛脩粼邳c(diǎn)擊 App 圖標(biāo)時(shí),會將 MainActivity 所在的棧移到前臺搀继,那么首先可以想到的方法是窘面,在點(diǎn)擊 App 圖標(biāo)時(shí),將含有 MovieActivity 的棧移到前臺顯示叽躯。所以我們可以注冊一個生命周期監(jiān)聽财边,在 onResume
時(shí),去遍歷 App 的所有任務(wù)棧点骑,找到含有 MovieActivity 的棧并將其移到前臺即可:
// code 11 DemoApplication.kt onCreate方法中
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks{
···
override fun onActivityResumed(activity: Activity) {
val appCompatActivity = if (activity is AppCompatActivity) {
activity
} else {
return
}
// 限制條件:所有的 activity 必須為 AppCompatActivity 或其子類
val activityManager = appCompatActivity
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (i in activityManager.appTasks.indices) {
val appTask = activityManager.appTasks[i]
val taskInfo = appTask.taskInfo
if (taskInfo.topActivity == null) continue
val topActivityName = taskInfo.topActivity?.className
if ((!topActivityName.isNullOrBlank() && topActivityName.contains(
MovieActivity::class.java.simpleName
)) && i != 0
) {
// 如果存在視頻播放頁且所在的 Task 不在前臺酣难,則需要將其移到前臺
activityManager.moveTaskToFront(
taskInfo.id,
ActivityManager.MOVE_TASK_NO_USER_ACTION
)
}
}
}
···
})
很明顯這個方法并不好,App 中每個 Activity 在調(diào)用 onResume
時(shí)都會走一遍這個邏輯黑滴;且 App 中所有的 Activity 必須為 AppCompatActivity
或它的子類憨募。還得需要申請 REORDER_TASKS
權(quán)限:
// code 12 AndroidManifest.xml
<!-- 申請可排序任務(wù)棧權(quán)限 -->
<uses-permission android:name="android.permission.REORDER_TASKS" />
并且這里還遇到一個問題:當(dāng)在 MovieActivity 跳轉(zhuǎn)到 TestOneActivity 時(shí),進(jìn)入 PiP跷跪,此時(shí)點(diǎn)擊 PiP 中的關(guān)閉按鈕關(guān)閉 PiP馋嗜,然后點(diǎn)擊 Home 回到桌面,再點(diǎn)擊 App 圖標(biāo)會發(fā)現(xiàn)進(jìn)入的是 MovieActivity 頁吵瞻,而并不是 TestOneActivity:
經(jīng)分析葛菇,原因是 PiP 的關(guān)閉按鈕點(diǎn)擊后甘磨,只是將 MovieActivity 退到了后臺,并沒有銷毀眯停。济舆。。所以退到后臺莺债,再點(diǎn)擊 App 圖標(biāo)時(shí)滋觉,會將包含 MovieActivity 的任務(wù)棧顯示到前臺,而不顯示 TestOneActivity 所在的任務(wù)棧齐邦。那么我們就需要在關(guān)閉 PiP 按鈕的回調(diào)中直接關(guān)閉 Activity椎侠,但我們開發(fā)者拿不到關(guān)閉按鈕的回調(diào),所以就有了下面的問題:
如何在用戶點(diǎn)擊 PiP 里的關(guān)閉按鈕時(shí)措拇,關(guān)閉 PiP 所在的 Activity我纪?
經(jīng)多次實(shí)驗(yàn)得知,PiP 雖然沒有關(guān)閉小窗的回調(diào)丐吓,但會先調(diào)用 onStop
然后會調(diào)用 onPictureInPictureModeChanged
方法浅悉。所以可以根據(jù)是否回調(diào)了 onStop
來間接判斷是否點(diǎn)擊了 PiP 小窗里的關(guān)閉按鈕。
// code 13
// MovieActivity 的 ViewModel
class MovieViewModel: ViewModel() {
//進(jìn)入或退出畫中畫模式所在Activity的事件 true: 進(jìn)入券犁; false: 退出
val enterOrExitPiPMode = MutableLiveData<Boolean>()
}
// MovieActivity.kt
@RequiresApi(Build.VERSION_CODES.O)
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean, newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
···
} else {
···
// PiP沒有關(guān)閉小窗的回調(diào)术健,但會先回調(diào) onStop 然后回調(diào) onPictureInPictureModeChanged 方法≌吵模可以根據(jù)
// 是否回調(diào)了 onStop 來間接判斷是否點(diǎn)擊了PiP小窗里的關(guān)閉按鈕荞估,這里需要在用戶主動關(guān)閉小窗后 finish 掉
// MovieActivity
if (lifecycle.currentState < Lifecycle.State.STARTED) {
movieViewModel.enterOrExitPiPMode.value = false
}
}
}
// MovieActivity.kt
// 進(jìn)入 or 退出畫中畫模式
movieViewModel.enterOrExitPiPMode.observe(this) {
if (it) {
// 這里暫沒有操作
} else {
// 關(guān)閉畫中畫模式事件,需要直接 finish 掉 MovieActivity
finish()
}
}
這里使用 LiveData 是因?yàn)樵谄渌捻撁婵赡芤残枰P(guān)閉 PiP色难,所以可以先獲得 ViewModel泼舱,通過更新 enterOrExitPiPMode
的值去關(guān)閉 PiP。
4.4 去掉 PiP 下方自帶的三個按鈕
PiP 小窗上的 6個按鈕只有底部的三個按鈕可自定義枷莉,另外的三個按鈕無法修改,這也是為什么無法拿到關(guān)閉按鈕回調(diào)的原因尺迂。如果需要針對底部的三個按鈕進(jìn)行自定義笤妙,通過設(shè)置 PictureInPictureParams 參數(shù)實(shí)現(xiàn),但最多只能自定義 3個噪裕,我們這里不需要這三個按鈕蹲盘,就可以設(shè)置一個透明按鈕間接去掉:
// code 14
// 第一步:新建一個 RemoteAction list
@RequiresApi(Build.VERSION_CODES.O)
private fun initPiPActions(): List<RemoteAction> {
//去掉原生小窗中默認(rèn)自帶的 上一個、暫停膳音、下一個 三個按鈕
val actions = mutableListOf<RemoteAction>()
val emptyIntent = PendingIntent.getBroadcast(requireContext(), 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
actions.add(RemoteAction(Icon.createWithResource(requireContext(), R.drawable.divider_transparent), "", "", emptyIntent))
return actions
}
// 第二步:設(shè)置到 PictureInPictureParams 參數(shù)中
val params = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
// Specify the portion of the screen that turns into the picture-in-picture mode.
// This makes the transition animation smoother.
.setSourceRectHint(visibleRect)
.setActions(initPiPActions())
.build()
自定義底部三個按鈕的方法有兩種:一是通過實(shí)現(xiàn) RemoteAction 的方法召衔;二是官方 Demo 中的方法。關(guān)于這個內(nèi)容參考文獻(xiàn)2 更加詳實(shí)祭陷,可以借鑒苍凛。
5. 難以解決的問題
以上的坑基本趟完了趣席,但下面的坑實(shí)在是難以解決,這里也歡迎大佬們能給出建議醇蝴。
5.1 App 出現(xiàn)兩個任務(wù)棧
為了實(shí)現(xiàn)從 MovieActivity 跳轉(zhuǎn)到其他 Activity 時(shí)宣肚,MovieActivity 自身進(jìn)入 PiP,必須將 MovieActivity 放到獨(dú)立的任務(wù)棧中悠栓,所以就會出現(xiàn)這個問題霉涨。以上文中也有說明。
5.2 PiP 模式下跳轉(zhuǎn)一個 singleTask 的 Activity 會在 PiP 中跳轉(zhuǎn)
官方 Demo 中將 MovieActivity 的 launchMode
設(shè)置為 singleTask
且不設(shè)置 taskAffinity
時(shí)惭适,當(dāng) MovieActivity 正處于 PiP 模式下笙瑟,跳轉(zhuǎn)到另一個 Activity 時(shí),目標(biāo) Activity 的 launchMode
不能為 singleTask
癞志,否則目標(biāo) Activity 會在 PiP 中跳轉(zhuǎn)往枷。
這里將 TestOneActivity 的 launchMode
設(shè)置為 singleTask
,然后從 MovieActivity 跳到 TestOneActivity 時(shí)今阳,TestOneActivity 出現(xiàn)在了 PiP 中师溅。而通常項(xiàng)目中會有許多 Activity 的 launchMode
設(shè)置為了 singleTask
,所以原生 PiP 方案最終被否盾舌。墓臭。。
github 上也有 issue:https://github.com/android/media-samples/issues/85
6. 小知識點(diǎn)匯總
6.1 ActivityManager.MOVE_TASK_NO_USER_ACTION 的作用
常用于 activityManager.moveTaskToFront
方法中妖谴,意思是不把當(dāng)前的操作看作是用戶觸發(fā)的行為窿锉,即不會調(diào)用當(dāng)前 Activity 的 onUserLeaveHint
方法。還有一個是 ActivityManager.MOVE_TASK_WITH_HOME
膝舅,這個就會調(diào)用當(dāng)前 Activity 的 onUserLeaveHint
方法嗡载。實(shí)際應(yīng)用中貌似很少用到。
6.2 autoRemoveFromRecents 和 excludeFromRecents 的用法
6.2.1 android:autoRemoveFromRecents
用法
android:autoRemoveFromRecents
是在任務(wù)棧中的最后一個 Activity 完成之前仍稀,由具有此屬性的 Activity 啟動的任務(wù)棧是否保留在多任務(wù)切換頁面中洼滚。即 autoRemoveFromRecents
指定了當(dāng) Activity 被系統(tǒng)回收時(shí),是否保留在多任務(wù)切換頁面中技潘。默認(rèn)值為 false遥巴。
當(dāng)設(shè)置為 true 時(shí): 當(dāng) Activity 被系統(tǒng)回收時(shí),從最近使用的多任務(wù)切換頁中移除該 Activity 所在的任務(wù)棧享幽;當(dāng)設(shè)置為 false 時(shí): 當(dāng) Activity 被系統(tǒng)回收時(shí)铲掐,不從最近使用的多任務(wù)切換頁中移除該 Activity 所在的任務(wù)棧。
這個屬性主要用于:
1)一些臨時(shí) Activity值桩,當(dāng)它們被銷毀后摆霉,不希望它們出現(xiàn)在多任務(wù)切換頁中,可以設(shè)置為 true;
2)一些沒有重要數(shù)據(jù)的 Activity携栋,如果設(shè)置為 true搭盾,當(dāng)內(nèi)存不足被系統(tǒng)回收后,由于它已經(jīng)從多任務(wù)切換頁移除刻两,用戶不太可能再去恢復(fù)它增蹭,及時(shí)移除有利于內(nèi)存回收;
3)一些包含敏感數(shù)據(jù)的 Activity磅摹,為了安全考慮滋迈,不希望它出現(xiàn)在多任務(wù)切換頁中,可以設(shè)置為 true。
所以户誓,總體來說饼灿,這個屬性主要是出于內(nèi)存管理和安全考慮,控制 Activity 在被系統(tǒng)回收后是否從多任務(wù)切換頁中移除帝美。
6.2.2 android:excludeFromRecents
用法
android:excludeFromRecents
也是一個 Activity 屬性碍彭,它指定了是否從多任務(wù)切換頁中排除該 Activity 所在的任務(wù)棧。默認(rèn)值為 false悼潭。
當(dāng)設(shè)置為 true 時(shí):該 Activity 所在的任務(wù)棧不會出現(xiàn)在多任務(wù)切換頁中庇忌;當(dāng)設(shè)置為 false 時(shí):不從多任務(wù)切換頁中排除該 Activity 所在的任務(wù)棧。
這個屬性與 android:autoRemoveFromRecents
很像舰褪,它們的區(qū)別是:
android:autoRemoveFromRecents
是當(dāng) Activity 被系統(tǒng)回收時(shí)皆疹,所在棧是否從多任務(wù)切換頁中移除;
android:excludeFromRecents
是 Activity 所在的棧從一開始就不會出現(xiàn)在任務(wù)列表中占拍。
更多內(nèi)容略就,歡迎關(guān)注公眾號:修之竹
或者查看 修之竹的 Android 專輯
贊人玫瑰,手留余香晃酒!歡迎點(diǎn)贊表牢、轉(zhuǎn)發(fā)~ 轉(zhuǎn)發(fā)請注明出處~