致讀者:本人簡書即將停止更新魔种,如需聯(lián)系請(qǐng)加V析二,或公號(hào)見,謝謝~
好久不見~ 最近幾個(gè)月變化挺大的节预,不論是自己的家庭還是社會(huì)環(huán)境叶摄,把我們能做的做好,慢慢適應(yīng)新的變化安拟,這也是一種不可或缺的能力吧蛤吓!
Android14 即將正式發(fā)布,作為開發(fā)者需要注意哪些內(nèi)容糠赦?長話短說会傲,一起來看看吧~
主要分為兩部分:
一是影響所有的 Android 應(yīng)用锅棕,這些改動(dòng)會(huì)影響所有的 App,只要你的 App 安裝在了 Android14 的設(shè)備上淌山,都會(huì)受到這些影響裸燎;
二是當(dāng) targetSdkVersion 升級(jí)到 34 后,我們的 App 所受到的影響泼疑。這一篇先來說說第一部分的內(nèi)容德绿,即現(xiàn)有 App 安裝到 Android14 手機(jī)上,會(huì)有哪些影響退渗。
1. SCHEDULE_EXACT_ALARM 權(quán)限默認(rèn)關(guān)閉
這個(gè)權(quán)限的全稱是 android.permission.SCHEDULE_EXACT_ALARM
脆炎,用于是否開啟設(shè)置精確鬧鐘的權(quán)限。精確的鬧鐘適用于用戶指定時(shí)間的通知氓辣,或是在確切的時(shí)間需要執(zhí)行的操作秒裕。
如果 App 的 targetSdkVersion 設(shè)置的是 33(Android13)或更高,在 Android14 的設(shè)備上運(yùn)行時(shí)钞啸,這個(gè)權(quán)限就是默認(rèn)關(guān)閉的几蜻。所以,當(dāng) App 中有用到精確鬧鐘体斩,需要在確切的時(shí)間點(diǎn)去做操作梭稚,那么就需要在 Manifest 文件中顯式地申請(qǐng)這個(gè)權(quán)限并需要在使用時(shí)動(dòng)態(tài)向用戶獲取該權(quán)限。
具體地說就是絮吵,當(dāng)使用 AlarmManager
中的
setExact(int type, long triggerAtMillis, PendingIntent operation)
弧烤、
setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)
、
setAlarmClock(AlarmManager.AlarmClockInfo info, PendingIntent operation)
這三個(gè)函數(shù)時(shí)蹬敲,如果 targetSdkVersion >= 33暇昂,且在 Android14 設(shè)備上沒有顯式申請(qǐng)?jiān)摍?quán)限,則會(huì)拋出一個(gè) SecurityException
異常伴嗡。
特殊情況:
1)如果用戶通過“備份與恢復(fù)”功能將 App 傳輸?shù)揭粋€(gè) Android14 的設(shè)備上急波,則此 App 的該權(quán)限默認(rèn)仍是關(guān)閉的;
2)如果一個(gè) App 已經(jīng)開啟了該權(quán)限瘪校,當(dāng)設(shè)備升級(jí)到 Android14 后澄暮,此 App 的該權(quán)限是開啟的狀態(tài);
3)當(dāng)精確鬧鐘是通過 OnAlarmListener
設(shè)置的阱扬,則無需申請(qǐng)?jiān)摍?quán)限泣懊。例如:setExact(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)
這個(gè)方法就無需申請(qǐng)。
用的比較多的 API:
1)boolean canScheduleExactAlarms()
判斷是否可以設(shè)置精確鬧鐘(API >= 31 才有此判斷方法)麻惶;
2)AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED
廣播消息常用來監(jiān)聽用戶開啟或關(guān)閉該權(quán)限的回調(diào)(API >= 31 才有此廣播消息類型)馍刮。
不建議的使用場景:
1)如果 App 在生命周期內(nèi)安排重復(fù)性的操作,可以使用 Handler
中的 postAtTime
等來替代用踩。相反渠退,如果是要設(shè)置 30min 后或者明天下午 2 點(diǎn)的操作忙迁,則建議使用;
2)安排在后臺(tái)進(jìn)行的一些操作碎乃,例如:下載更新App或者上傳日志等姊扔。建議使用 WorkManager
而不是精確鬧鐘;
3)當(dāng)系統(tǒng)處于空閑時(shí)梅誓,在大概的時(shí)間點(diǎn)處理事務(wù)恰梢,則可以調(diào)用非精確鬧鐘的一些 API 處理,例如使用 setAndAllowWhileIdle()
而不是 setExactAndAllowWhileIdle()
方法梗掰;
4)用戶指定的在大概特定時(shí)間點(diǎn)發(fā)生的嵌言,或者在一個(gè)時(shí)間窗口內(nèi)發(fā)生的事務(wù);
適配流程:
1)調(diào)用 alarmManager.canScheduleExactAlarms()
檢查是否有該權(quán)限及穗;
2)如果沒有權(quán)限摧茴,則需要通過 Intent
,設(shè)置 Action
為 ACTION_REQUEST_SCHEDULE_EXACT_ALARM
并加上應(yīng)用包名調(diào)起設(shè)置頁面埂陆,讓用戶賦予權(quán)限苛白,返回后在 onResume
回調(diào)中判斷是否權(quán)限是否已申請(qǐng)。
下面是一個(gè)例子:
// code 1
// MyFragment.kt 中的代碼
private val ALARM_REQUEST_CODE = 123
private var getExactSchedulePermission = false
@RequiresApi(Build.VERSION_CODES.S)
private fun scheduleAlarm() {
// 創(chuàng)建一個(gè) Intent焚虱,用于指定定時(shí)任務(wù)觸發(fā)時(shí)要執(zhí)行的操作
val intent = Intent(requireContext(), AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
requireContext(),
ALARM_REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE
)
// 獲取 AlarmManager 實(shí)例
val alarmManager = requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
// 觸發(fā)時(shí)間(這里使用相對(duì)時(shí)間)
val triggerTime = SystemClock.elapsedRealtime() + 5000 // 5秒后觸發(fā)
// 設(shè)置定時(shí)任務(wù)
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent)
} else {
// 如果沒有權(quán)限則打開設(shè)置頁购裙,讓用戶授予該 App 的精確鬧鐘權(quán)限
startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
getExactSchedulePermission = true
}
}
@RequiresApi(Build.VERSION_CODES.S)
override fun onResume() {
super.onResume()
if (getExactSchedulePermission) {
scheduleAlarm()
getExactSchedulePermission = false
}
}
// AlarmReceiver.kt
class AlarmReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "Alarm triggered!", Toast.LENGTH_SHORT).show()
}
}
當(dāng)運(yùn)行 scheduleAlarm() 方法后,過 5 秒就會(huì)有 Toast 出現(xiàn)~
日歷或鬧鐘應(yīng)用需要在應(yīng)用停止運(yùn)行時(shí)發(fā)送日歷提醒鹃栽、喚醒鬧鐘或提醒躏率。這些應(yīng)用可以請(qǐng)求 USE_EXACT_ALARM
常規(guī)權(quán)限。系統(tǒng)將在安裝時(shí)授予 USE_EXACT_ALARM
權(quán)限民鼓,擁有此權(quán)限的應(yīng)用將能夠像具有 SCHEDULE_EXACT_ALARM
權(quán)限的應(yīng)用一樣設(shè)置精確鬧鐘薇芝。
小結(jié):能不用就不用。如果之前已用到精確鬧鐘摹察,則需要新增權(quán)限獲取邏輯恩掷。
2. 動(dòng)態(tài)廣播當(dāng) App 進(jìn)入緩存態(tài)時(shí)將會(huì)入隊(duì)保存
在 Android14 中倡鲸,我們使用 Context
上下文注冊(cè)的動(dòng)態(tài)廣播接收器供嚎,可以在 App 進(jìn)入緩存狀態(tài)時(shí),將已發(fā)送還未接收的廣播放入到一個(gè)隊(duì)列中保存峭状。當(dāng) App 離開緩存狀態(tài)(比如進(jìn)入前臺(tái))克滴,則系統(tǒng)會(huì)傳遞所有已加入隊(duì)列的廣播。某些廣播的多個(gè)實(shí)例可以合并為一個(gè)廣播优床。
而在 Manifest 文件中注冊(cè)的靜態(tài)廣播接收器劝赔,則不能進(jìn)入隊(duì)列,它們會(huì)在 App 從緩存狀態(tài)中被移除銷毀時(shí)胆敞,進(jìn)行廣播傳遞着帽。
什么是緩存狀態(tài)下的 App杂伟?簡單理解就是在后臺(tái)的 App,目前不在前臺(tái)的進(jìn)程仍翰,因此赫粥,如果系統(tǒng)其他地方需要內(nèi)存,系統(tǒng)可以根據(jù)需要自由地終止這些進(jìn)程予借。當(dāng)然終止的順序是最老未使用的最先被終止越平。
3. App 只能終止自己的后臺(tái)進(jìn)程
從 Android14 開始,調(diào)用 killBackgroundProcesses()
時(shí)灵迫,只能終止自己應(yīng)用的后臺(tái)進(jìn)程秦叛。如果傳入另一個(gè)應(yīng)用的軟件包名稱,此方法對(duì)該應(yīng)用的后臺(tái)進(jìn)程沒有影響瀑粥,并且 Logcat 中會(huì)顯示以下消息:
Invalid packageName: com.example.anotherapp
官方給出的解釋是:
您的應(yīng)用不應(yīng)使用 killBackgroundProcesses() API挣跋,也不得以其他方式嘗試影響其他應(yīng)用的進(jìn)程生命周期,即使在舊版操作系統(tǒng)上也是如此狞换。Android 旨在讓緩存應(yīng)用在后臺(tái)運(yùn)行浆劲,并在系統(tǒng)需要內(nèi)存時(shí)自動(dòng)終止它們。如果您的應(yīng)用不必要地終止其他應(yīng)用哀澈,則由于之后需要完全重啟這些應(yīng)用牌借,因此可能會(huì)降低系統(tǒng)性能并增加耗電量,這比恢復(fù)現(xiàn)有緩存應(yīng)用所消耗的資源要多得多割按。
該 API 是 ActivityManager
提供的膨报,完整的方法聲明:
// code 2
public void killBackgroundProcesses (String packageName)
此外,使用它還需要在 Manifest 文件中申請(qǐng)權(quán)限 Manifest.permission.KILL_BACKGROUND_PROCESSES
.
經(jīng)測(cè)試适荣,我發(fā)現(xiàn)這個(gè) API 有點(diǎn)奇怪:被殺死的后臺(tái)進(jìn)程馬上又會(huì)重啟现柠,額。弛矛。够吩。這是什么操作?丈氓?
測(cè)試代碼比較簡單周循,就是在另外一個(gè)進(jìn)程中開啟一個(gè) Service
,然后調(diào)用 killBackgroundProcesses
方法即可万俗,根據(jù)打印的 Service
生命周期可看出湾笛,該 Service
確實(shí)先被殺死然后又走了一次 onCreate
、onStartCommand
生命周期闰歪,代碼和結(jié)果如下所示:
// code 3
// Manifest 文件聲明 Service 在另一個(gè)進(jìn)程中啟動(dòng)
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<service
android:name=".MyService"
android:process="com.secondProcess" />
// 啟動(dòng) Service
startService(Intent(requireContext(), MyService::class.java))
// 殺死后臺(tái)進(jìn)程
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.killBackgroundProcesses(context.packageName)
log 打印結(jié)果:
[圖片上傳失敗...(image-a5f2f4-1697555899201)]
從圖上可知嚎研,在 Android14 的設(shè)備上,調(diào)用 killBackgroundProcesses
方法可以殺死自己 App 的后臺(tái)進(jìn)程库倘,但會(huì)立即重新啟動(dòng)临扮。在源碼中也找到了下面的代碼论矾,雖然已被廢棄:
// code 3
@Deprecated
public void restartPackage(String packageName) {
killBackgroundProcesses(packageName);
}
看來這個(gè) API 就是用來重啟 App 的后臺(tái)進(jìn)程的?
試了下在 Android14 設(shè)備上的 A App 中調(diào)用此 API 去殺死 B App 的后臺(tái)進(jìn)程杆勇,確實(shí)沒有任何作用拇囊;但如果是在 Android14 以下的設(shè)備上調(diào)用,確實(shí)可以殺死 B App 的后臺(tái)進(jìn)程靶橱。感興趣的同學(xué)也可試一試寥袭。
小結(jié):killBackgroundProcesses
API 并沒有什么卵用~(歡迎大佬指點(diǎn))
4. 安全方面
在 Android14 系統(tǒng)手機(jī)上,將無法安裝 targetSdkVersion < 23(低于Android6.0)的 App关霸。
媒體包名稱在 Android14 上可能會(huì)被隱藏传黄。目前媒體庫支持按照 OWNER_PACKAGE_NAME
列查詢某包名下的所有媒體文件,一個(gè)應(yīng)用存儲(chǔ)的媒體文件是帶有它自己的包名信息的队寇。這些信息將在 Android14上被隱藏膘掰,除非滿足以下條件之一:
1)存儲(chǔ)媒體文件的應(yīng)用包名稱始終對(duì)其他應(yīng)用可見(自己開放給所有其他 App);
2)查詢媒體庫的應(yīng)用獲得了 QUERY_ALL_PACKAGES
權(quán)限(其他 App 向用戶申請(qǐng)獲得了權(quán)限)佳遣。
舉個(gè)栗子:
當(dāng)一個(gè)應(yīng)用存儲(chǔ)了一個(gè)媒體文件(例如一張照片或一個(gè)視頻)识埋,它會(huì)在媒體庫中記錄該文件的信息,包括該文件的所有者包名零渐。其他應(yīng)用可以查詢媒體庫以獲取這些信息窒舟,以便在自己的應(yīng)用中顯示該文件或與之交互。
在 Android14 及以后的版本中诵盼,如果存儲(chǔ)媒體文件的應(yīng)用的包名不是始終對(duì)其他應(yīng)用程序可見的惠豺,則在查詢媒體庫時(shí),所有者包名將被隱藏或替換為匿名值风宁。例如洁墙,如果一個(gè)應(yīng)用包名為“com.example.app”,它存儲(chǔ)了一個(gè)媒體文件戒财,但它的包名被隱藏了热监,那么在查詢媒體庫時(shí),所有者包名可能會(huì)被替換為“com.android.providers.media”饮寞。
但是孝扛,如果存儲(chǔ)媒體文件的應(yīng)用具有始終對(duì)其他應(yīng)用可見的包名,或者查詢媒體庫的應(yīng)用程序具有QUERY_ALL_PACKAGES
權(quán)限骂际,則可以看到媒體庫中的完整所有者包名疗琉。例如,一個(gè)應(yīng)用名為“com.example.app”歉铝,它存儲(chǔ)了一個(gè)媒體文件,并且它的包名始終對(duì)其他應(yīng)用程序可見凑耻,那么在查詢媒體庫時(shí)太示,所有者包名將顯示為“com.example.app”柠贤。
5. 用戶體驗(yàn)方面
5.1 可單獨(dú)對(duì)照片和視頻訪問權(quán)限進(jìn)行授權(quán)
如果你的 App 以 Android13 或更高版本為目標(biāo)平臺(tái)(即 targetSdkVersion >= 33),且在 Android14 的設(shè)備上運(yùn)行時(shí)类缤,用戶可以授予對(duì)其照片和視頻的部分訪問權(quán)限臼勉,即單獨(dú)設(shè)置 READ_MEDIA_IMAGES
或 READ_MEDIA_VIDEO
。
即申請(qǐng) READ_MEDIA_IMAGES
權(quán)限時(shí)餐弱,僅會(huì)顯示手機(jī)上所有圖片給用戶進(jìn)行選擇宴霸;申請(qǐng) READ_MEDIA_VIDEO
權(quán)限時(shí),僅會(huì)顯示手機(jī)上所有的視頻給用戶進(jìn)行選擇膏蚓。用戶可以更加細(xì)致地選擇將哪些照片或視頻授權(quán)給 App 讀取使用瓢谢。
新的系統(tǒng)對(duì)話框長這樣:
[圖片上傳失敗...(image-ff46c4-1697555899201)]
1)選擇照片和視頻: Android14 中的新功能。用戶選擇希望提供給應(yīng)用的具體照片和視頻驮瞧。
2)全部允許:用戶授予對(duì)設(shè)備上的所有照片和視頻的完整訪問權(quán)限氓扛。
3)不允許:用戶拒絕授予所有訪問權(quán)限。
注意:
1)當(dāng)應(yīng)用已經(jīng)在使用系統(tǒng)的 照片選擇器论笔,則無需執(zhí)行任何操作即可支持此變更采郎;
2)READ_MEDIA_IMAGES
和 READ_MEDIA_VIDEO
僅在 Android13 或以上的版本才能使用;
新增了一個(gè) READ_MEDIA_VISUAL_USER_SELECTED
權(quán)限狂魔,屬于 Dangerous 級(jí)別蒜埋。用于在用戶點(diǎn)擊自定義的照片選擇器需要申請(qǐng)?jiān)L問照片和視頻的權(quán)限時(shí)使用,這樣就不用去申請(qǐng) READ_MEDIA_IMAGES
和 READ_MEDIA_VIDEO
這兩個(gè)權(quán)限了最楷。
小結(jié):開發(fā)者不用管理茎,新的權(quán)限很雞肋,暫時(shí)用不上管嬉,之前讀取照片和視頻的相關(guān)邏輯也不用改皂林。
5.2 更安全的全屏通知展示
在 Android11(API level 30)上就可以調(diào)用 Notification.Builder.setFullScreenIntent
方法在鎖屏上展示一些全屏的通知了,不過得在 Manifest 文件中申請(qǐng) USE_FULL_SCREEN_INTENT
權(quán)限蚯撩。
全屏通知是為了讓用戶立即注意到的高優(yōu)先級(jí)通知而設(shè)計(jì)的础倍,例如來電或用戶配置的鬧鐘,在展示全全屏通知時(shí)胎挎,用戶只能上滑退出沟启,如下圖所示的系統(tǒng)提示。
[圖片上傳失敗...(image-717c8e-1697555899201)]
從 Android14 開始犹菇,允許使用此權(quán)限的應(yīng)用程序僅限于那些只提供通話和警報(bào)的應(yīng)用德迹。對(duì)于其他應(yīng)用,Google Play 商店會(huì)撤銷它們默認(rèn)的 USE_FULL_SCREEN_INTENT
權(quán)限揭芍。
可以使用新的 API NotificationManager.canUseFullScreenIntent()
檢查應(yīng)用是否有權(quán)限胳搞;如果沒有,可以用新的 ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
來啟動(dòng)用戶可以授予該權(quán)限的設(shè)置頁面。
奇怪的是我在 Android14 官方的虛擬機(jī)上并沒有打開通知成功肌毅,更不用說打開全屏通知了筷转。不過確實(shí)可以打開設(shè)置全屏通知權(quán)限開關(guān)的頁面,如下是全屏通知權(quán)限設(shè)置圖及主要相關(guān)代碼:
[圖片上傳失敗...(image-bf5990-1697555899201)]
// code 4
val notificationBuilder = NotificationCompat.Builder(requireContext())
.setSmallIcon(R.drawable.ic_lock_idle_alarm)
.setContentTitle("Notification Title")
.setContentText("Notification text")
// 創(chuàng)建一個(gè)PendingIntent,點(diǎn)擊Notification時(shí)打開指定頁面
val intent = Intent(context, NotificationFullActivity::class.java)
val fullScreenIntent =
PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_MUTABLE)
notificationBuilder.setFullScreenIntent(fullScreenIntent, true) // 打開全屏通知
// notificationBuilder.setContentIntent(fullScreenIntent) // 打開普通頁面
val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Android 8.0 Oreo以上需要設(shè)置通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "your_channel_id"
val channelName: CharSequence = "Your Channel Name"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(channelId, channelName, importance)
channel.description = "Channel description"
notificationManager.createNotificationChannel(channel)
notificationBuilder.setChannelId(channelId)
}
notificationManager.notify(5, notificationBuilder.build())
if (notificationManager.canUseFullScreenIntent()) {
notificationManager.notify(5, notificationBuilder.build())
} else {
// 打開設(shè)置頁
val intent = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT)
intent.data = Uri.fromParts("package", requireActivity().packageName, null)
startActivity(intent)
}
如果哪位大佬在 Android14 設(shè)備上成功地打開了全屏通知悬而,麻煩交流一下呜舒,謝謝。
小結(jié):大部分 App 用不上這個(gè)全屏通知功能笨奠,個(gè)人覺得并不是很重要袭蝗。。般婆。
5.3 關(guān)于不可關(guān)閉通知用戶體驗(yàn)方式的變更
如果應(yīng)用向用戶顯示不可關(guān)閉的前臺(tái)通知的話需要注意:Android14 中允許用戶關(guān)閉此類通知到腥。即之前通過 Notification.Builder#setOngoing(true)
或 NotificationCompat.Builder#setOngoing(true)
設(shè)置 Notification.FLAG_ONGOING_EVENT
來阻止用戶關(guān)閉前臺(tái)通知的應(yīng)用要小心了。FLAG_ONGOING_EVENT
的行為已發(fā)生變化腺兴,用戶在 Android14 上可以關(guān)閉此類通知左电。
以下情況,此類通知仍不可關(guān)閉:
1)當(dāng)手機(jī)處于鎖定狀態(tài)時(shí)页响;
2)如果用戶選擇全部清除通知操作(有助于防止意外關(guān)閉)篓足;
此外,下列的幾種情況并沒有變更:
1)使用 CallStyle 創(chuàng)建的通知闰蚕,即來電通知的樣式栈拖;
2)設(shè)備策略控制器(DPC)和針對(duì)企業(yè)的支持包;
小結(jié):Android 的通知管理只會(huì)越來越嚴(yán)格没陡,早就應(yīng)該管管了涩哟。其實(shí)就算 Android14 手機(jī)上沒有這個(gè)功能,目前絕大多數(shù)手機(jī)廠商已經(jīng)都可以禁止 App 彈出通知了盼玄,所以這個(gè)也沒啥贴彼。。埃儿。
以上就是本篇的所有內(nèi)容器仗,主要根據(jù)官方文檔自己實(shí)踐操作了一番,可以看出童番,現(xiàn)有的 App 如果直接安裝到 Android14 的手機(jī)上精钮,并不會(huì)有太多的問題,許多東西其實(shí)并不用另外處理剃斧,當(dāng)然建議還是根據(jù)本篇內(nèi)容查漏補(bǔ)缺比較好轨香。如果還想了解 targetSdkVersion 升級(jí)到 34(Android14)還需要注意哪些內(nèi)容,歡迎關(guān)注我幼东,咱們下篇見臂容!
更多內(nèi)容科雳,歡迎關(guān)注工種號(hào):修之竹
或者查看 修之竹的 Android 專輯
贊人玫瑰,手留余香策橘!歡迎點(diǎn)贊炸渡、轉(zhuǎn)發(fā)~ 轉(zhuǎn)發(fā)請(qǐng)注明出處~
參考文獻(xiàn)
- Android 14 官方文檔 https://developer.android.com/about/versions/14
- https://developer.android.google.cn/about/versions/14/behavior-changes-all?hl=zh-cn
- https://developer.android.google.cn/about/versions/14/changes/schedule-exact-alarms?hl=zh-cn
- https://developer.android.google.cn/guide/components/activities/process-lifecycle?hl=zh-cn
- https://developer.android.google.cn/about/versions/14/behavior-changes-14
- Android 14 快速適配要點(diǎn); 戀貓de小郭; https://juejin.cn/post/7231835495557890106?searchId=202307240025039D8229C74EA62159077B