作者:王三端
Activity作為android四大組件之一,地位就不用多說了吧,該組件看起來是比較簡單的,但是也涉及到很多知識點(diǎn),要想全部理解并在合適的業(yè)務(wù)場景下使用,也是需要一定的技術(shù)沉淀,本文主要是對activity一些重要知識點(diǎn)進(jìn)行總結(jié)整理,可能平時(shí)不一定用到,但是一定要有所了解。
當(dāng)然這些知識點(diǎn)并沒有設(shè)計(jì)過多源碼部分,比如activity的啟動(dòng)流程什么的,主要是零散的知識點(diǎn),對于activity的啟動(dòng)流程網(wǎng)上文章太多了,后面自己也準(zhǔn)備重新梳理下,好記性不如爛筆頭,在不斷學(xué)習(xí)整理的過程中,一定會(huì)因?yàn)槟硞€(gè)知識點(diǎn)而豁然開朗。

1.生命周期
①.兩個(gè)頁面跳轉(zhuǎn)
從MainActivity跳轉(zhuǎn)到SecordActivity的生命周期,重點(diǎn)關(guān)注Main的onPause和onStop與Secord幾個(gè)關(guān)鍵生命周期的順序,以及從Secord返回時(shí)與Main的生命周期的交叉:

可以發(fā)現(xiàn)Main頁面的onPause生命周期之后直接執(zhí)行Secord的onCreate,onStart,onResume,所以onPause生命周期內(nèi)不要執(zhí)行耗時(shí)操作,以免影響新頁面的展示,造成卡頓感。
②.彈出Dialog
- 單純的彈出
Dialog是不會(huì)影響Activity的生命周期的; - 啟動(dòng)
dialog theme的Activity的時(shí)候,啟動(dòng)的activity只會(huì)執(zhí)行onPause方法,onStop不會(huì)執(zhí)行,被啟動(dòng)的activity會(huì)正常走生命周期,back的時(shí)候,啟動(dòng)的Activity會(huì)對應(yīng)執(zhí)行onResume方法;

③.橫豎屏切換
- AndroidManifest不配置
configChanges時(shí),橫豎屏切換,會(huì)銷毀重建Activity,生命周期會(huì)重新走一遍; - 當(dāng)Activity
configChanges="orientation|screenSize"時(shí),橫豎屏切換不會(huì)重新走Activity生命周期方法,只會(huì)執(zhí)行onConfigurationChanged方法,如需要可以在此方法中進(jìn)行相應(yīng)業(yè)務(wù)處理;
如橫豎屏切換時(shí)需要對布局進(jìn)行適配,可在res下新建
layout-port、layout-land目錄,并提供相同的xml布局文件,橫豎屏切換時(shí)即可自動(dòng)加載相應(yīng)布局。(前提是未配置configChanges忽略橫豎屏影響,否則不會(huì)重新加載布局)
④.啟動(dòng)模式對生命周期的影響
1.A(singleTask)啟動(dòng)(startActivity)B(standard),再從B啟動(dòng)A,生命周期如下:
A啟動(dòng)B:A_onPause、B_onCreate、B_onStart、B_onResume、A_onStop
第二步:B_onPause、A_onNewIntent、A_onRestart、A_onStart、A_onResume、B_onStop、B_onDestory
2.A(singleTask)啟動(dòng)A,或者A(singleTop)啟動(dòng)A
A_onPause、A_onNewIntent、A_Resume
3.singleInstance模式的activity
多次啟動(dòng)A(singleInstance),只有第一次會(huì)創(chuàng)建一個(gè)單獨(dú)的任務(wù)棧(全局唯一),再次啟動(dòng)會(huì)調(diào)用A_onPause、A_onNewIntent、A_Resume。
2.啟動(dòng)模式
Activity的啟動(dòng)模式一直是standard、singleTop、singleTask、singleInstance四種,Android 12新增了singleInstancePerTask啟動(dòng)模式,在這里不一一介紹,僅介紹重要知識點(diǎn)。
①.singleTask
1.Activity是一個(gè)可以跨進(jìn)程、跨應(yīng)用的組件,當(dāng)你在 A App里打開 B App的Activity的時(shí)候,這個(gè)Activity會(huì)直接被放進(jìn)A的Task里,而對于B的Task,是沒有任何影響的。
從A應(yīng)用啟動(dòng)B應(yīng)用,默認(rèn)情況下啟動(dòng)的B應(yīng)用的Activity會(huì)進(jìn)入A應(yīng)用當(dāng)前頁面所在的任務(wù)棧中,此時(shí)按home建,再次啟動(dòng)B應(yīng)用,會(huì)發(fā)現(xiàn)B應(yīng)用并不會(huì)出現(xiàn)A啟動(dòng)的頁面(前提是A應(yīng)用啟動(dòng)的不是B應(yīng)用主activity,如果是必然一樣),而是如第一次啟動(dòng)一般.
如果想要啟動(dòng)B應(yīng)用的時(shí)候出現(xiàn)被A應(yīng)用啟動(dòng)的頁面,需要設(shè)置B應(yīng)用被啟動(dòng)頁的launchmode為singleTask,此時(shí)從A應(yīng)用的ActivityA頁面啟動(dòng)B應(yīng)用的頁面ActivityB(launchmode為singleTask),發(fā)現(xiàn)動(dòng)畫切換方式是應(yīng)用間切換,此時(shí)ActivityB和ActivityA分別處于各自的任務(wù)棧中,并沒有在一個(gè)task中,此時(shí)按Home鍵后,再次點(diǎn)擊啟動(dòng)B應(yīng)用,發(fā)現(xiàn)B應(yīng)用停留在ActivityB頁面。
如果想要實(shí)現(xiàn)上述效果,除了設(shè)置launchmode之外,還可以通過設(shè)置allowTaskReparenting屬性達(dá)到同樣的效果,Activity 默認(rèn)情況下只會(huì)歸屬于一個(gè) Task,不會(huì)在多個(gè)Task之間跳來跳去,但你可以通過設(shè)置來改變這個(gè)邏輯,如果你不設(shè)置singleTask,而是設(shè)置allowTaskReparenting為true,此時(shí)從A應(yīng)用的ActivityA頁面啟動(dòng)B應(yīng)用的頁面ActivityB(設(shè)置了allowTaskReparenting為true),ActivityB會(huì)進(jìn)入ActivityA的任務(wù)棧,此時(shí)按Home鍵,點(diǎn)擊啟動(dòng)B應(yīng)用,會(huì)進(jìn)入ActivityB頁面,也就是說ActivityB從ActivityA的任務(wù)棧移動(dòng)到了自己的任務(wù)棧中,此時(shí)點(diǎn)擊返回,會(huì)依次退出ActivityB所在任務(wù)棧的各個(gè)頁面,直到B應(yīng)用退出。
注意:allowTaskReparenting在不同Android版本上表現(xiàn)有所不同,Android9以下是生效的,Android9,10又是失效的,但Android11又修復(fù)好了,在使用時(shí)一定要好好測試,避免一些因版本差異產(chǎn)生的問題。
②.singleInstance
singleInstance具備singleTask模式的所有特性外,與它的區(qū)別就是,這種模式下的Activity會(huì)單獨(dú)占用一個(gè)Task棧,具有全局唯一性,即整個(gè)系統(tǒng)中就這么一個(gè)實(shí)例,由于棧內(nèi)復(fù)用的特性,后續(xù)的請求均不會(huì)創(chuàng)建新的Activity實(shí)例,除非這個(gè)特殊的任務(wù)棧被銷毀了。以singleInstance模式啟動(dòng)的Activity在整個(gè)系統(tǒng)中是單例的,如果在啟動(dòng)這樣的Activity時(shí),已經(jīng)存在了一個(gè)實(shí)例,那么會(huì)把它所在的任務(wù)調(diào)度到前臺,重用這個(gè)實(shí)例。
③.singleInstancePerTask
釋義:singleInstancePerTask的作用和singleTask幾乎一模一樣,不過singleInstancePerTask不需要為啟動(dòng)的Activity設(shè)置一個(gè)特殊的taskAffinity就可以創(chuàng)建新的task,換句話講就是設(shè)置singleInstancePerTask模式的activity可以存在于多個(gè)task任務(wù)棧中,并且在每個(gè)任務(wù)棧中是單例的。
多次啟動(dòng)設(shè)置singleInstancePerTask模式的Activity并不會(huì)多次創(chuàng)建新的任務(wù)棧,而是如singleInstance模式一樣,把當(dāng)前Activity所在的任務(wù)棧置于前臺展示,如果想每次以新的任務(wù)棧啟動(dòng)需要設(shè)置FLAG_ACTIVITY_MULTIPLE_TASK和FLAG_ACTIVITY_NEW_DOCUMENT,使用方式如下:
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
此時(shí),每次啟動(dòng)Activity就會(huì)單獨(dú)創(chuàng)建新的任務(wù)棧。
注意:測試需要在Android12的真機(jī)或者模擬器上,否則默認(rèn)為Standard模式
3.taskAffinity
taskAffinity可以指定任務(wù)棧的名字,默認(rèn)任務(wù)棧是應(yīng)用的包名,前提是要和singleTask,singleInstance模式配合使用,standard,singleTop模式無效,當(dāng)app存在多個(gè)任務(wù)棧時(shí),如果taskAffinity相同,則在最近任務(wù)列表中只會(huì)出現(xiàn)處于前臺任務(wù)棧的頁面,后臺任務(wù)棧會(huì)“隱藏”在某處,如果taskAffinity不同,最近任務(wù)列表會(huì)出現(xiàn)多個(gè)任務(wù)頁面,點(diǎn)擊某個(gè)就會(huì)把該任務(wù)棧至于前臺。
4.清空任務(wù)棧
activity跳轉(zhuǎn)后設(shè)置FLAG_ACTIVITY_CLEAR_TASK即可清空任務(wù)棧,并不是新建一個(gè)任務(wù)棧,而是清空并把當(dāng)前要啟動(dòng)的activity置于棧底,使用場景比如:退出登錄跳轉(zhuǎn)到登錄頁面,可以以此情況activity任務(wù)棧。
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
注意:FLAG_ACTIVITY_CLEAR_TASK必須與FLAG_ACTIVITY_NEW_TASK一起使用.
5.Activity.FLAG
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_NEW_TASK并不像起名字一樣,每次都會(huì)創(chuàng)建新的task任務(wù)棧,而是有一套復(fù)雜的規(guī)則來判斷:
- 通過
activity類型的context啟動(dòng),如果要啟動(dòng)的Activity的taskAffinity與當(dāng)前Activity不一致,則會(huì)創(chuàng)建新的任務(wù)棧,并將要啟動(dòng)的Activity置于棧底,taskAffinity一致的話,就會(huì)存放于當(dāng)前activity所在的任務(wù)棧(注意啟動(dòng)模式章節(jié)第三點(diǎn)taskAffinity的知識點(diǎn)); -
taskAffinity一致的情況下,如果要啟動(dòng)的activity已經(jīng)存在,并且是棧根activity,那么將沒有任何反應(yīng)(啟動(dòng)不了要啟動(dòng)的activity)或者把要啟動(dòng)的activity所在的任務(wù)棧置于前臺;否則如果要啟動(dòng)的activity不存在,將會(huì)在當(dāng)前任務(wù)棧創(chuàng)建要啟動(dòng)的activity實(shí)例,并入棧; -
taskAffinity一致的情況下,如果要啟動(dòng)的activity已經(jīng)存在,但不是棧根activity,依然會(huì)重新創(chuàng)建activity示例,并入棧(前提是:要啟動(dòng)的activity的launchMode為standard,意思就是是否會(huì)創(chuàng)建新實(shí)例會(huì)受到launchMode的影響); - 非
activity的context啟動(dòng)activity時(shí)(比如在service或者broadcast中啟動(dòng)activity),在android7.0之前和9.0之后必須添加FLAG_ACTIVITY_NEW_TASK,否則會(huì)報(bào)錯(cuò)(基于android-32的源碼,不同版本可能不同):
//以下代碼基于android 12
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
//檢測FLAG_ACTIVITY_NEW_TASK
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
//未設(shè)置FLAG_ACTIVITY_NEW_TASK,直接拋出異常
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
//正常啟動(dòng)activity
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
注意:FLAG_ACTIVITY_NEW_TASK的設(shè)置效果受到taskAffinity以及其他一些配置的影響,實(shí)際使用過程中一定要進(jìn)行充分測試,并且不同的android版本也會(huì)表現(xiàn)不同,極端場景下要仔細(xì)分析測試,選擇最優(yōu)方案;
提示:通過
adb shell dumpsys activity activities命令可以查看activity任務(wù)棧;
6.多進(jìn)程
正常情況下,app運(yùn)行在以包名為進(jìn)程名的進(jìn)程中,其實(shí)android四大組件支持多進(jìn)程,通過manifest配置process屬性,可以指定與包名不同的進(jìn)程名,即可運(yùn)行在指定的進(jìn)程中,從而開啟多進(jìn)程,那么,開啟多進(jìn)程有什么優(yōu)缺點(diǎn)呢?
多進(jìn)程下,可以分散內(nèi)存占用,可以隔離進(jìn)程,對于比較重的并且與其他模塊關(guān)聯(lián)不多的模塊可以放在單獨(dú)的進(jìn)程中,從而分擔(dān)主進(jìn)程的壓力,另外主進(jìn)程和子進(jìn)程不會(huì)相互影響,各自做各自的事,但開啟了多進(jìn)程后,也會(huì)帶來一些麻煩事,比如會(huì)引起Application的多次創(chuàng)建,靜態(tài)成員失效,文件共享等問題。
所以是否選擇使用多進(jìn)程要看實(shí)際需要,我們都知道app進(jìn)程分配的內(nèi)存是有限的,超過系統(tǒng)上限就會(huì)導(dǎo)致內(nèi)存溢出,如果想要分配到更多的內(nèi)存,多進(jìn)程不失為一種解決方案,但是要注意規(guī)避或處理一些多進(jìn)程引起的問題;
設(shè)置多進(jìn)程的方式:
android:process=":childProcess" //實(shí)際上完整的進(jìn)程名為:包名:childProcess,這種方式聲明的屬于私有進(jìn)程。
android:process="com.child.process" //完整的進(jìn)程名即為聲明的名字:com.child.process,這種方式聲明的屬于全局進(jìn)程。
7.excludeFromRecents
excludeFromRecents如果設(shè)置為true,那么設(shè)置的Activity將不會(huì)出現(xiàn)在最近任務(wù)列表中,如果這個(gè)Activity是整個(gè)Task的根Activity,整個(gè)Task將不會(huì)出現(xiàn)在最近任務(wù)列表中.
8.startActivityForResult被棄用
使用Activity Result Api代替,使用方式如下:
private val launcherActivity = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) {
Log.e("code","resultCode = "+it.resultCode)
}
findViewById<Button>(R.id.btn_jump).setOnClickListener {
launcherActivity.launch(Intent(this@MainActivity,SecordActivity::class.java))
}
//要跳轉(zhuǎn)的Activity設(shè)置回調(diào)數(shù)據(jù):
val resultIntent = Intent()
resultIntent.putExtra("dataKey","data value")
setResult(1001,resultIntent)
finish()
9.Deep link
簡單理解,所謂Deep Link就是可以通過外部鏈接來啟動(dòng)app或者到達(dá)app指定頁面的一想技術(shù),比如可以通過點(diǎn)擊短信或者網(wǎng)頁中的鏈接來拉起app到指定頁面,以達(dá)到提供日活或者其他目的,一般流程是可以通過在manifest的activity標(biāo)簽中配置固定的schema來實(shí)現(xiàn)這種效果,形如:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="jumptest"
android:host="work"
android:port="8801"
android:path="/main"
/>
</intent-filter>
然后在網(wǎng)頁中就可以通過如下方式來啟動(dòng)當(dāng)前activity:
<a href="jumptest://work:8801/main?data=123456">你好</a>
格式
<scheme>://<host>:<port>/<path>?<query>
被啟動(dòng)的app可以通過如下方式拿到傳遞的參數(shù)以及schmea配置項(xiàng):
val host = schemaIntent.data?.host
val path = schemaIntent.data?.path
val schema = schemaIntent.data?.scheme
val query = schemaIntent.data?.query
Log.e("schema","host = $host, path = $path, schema = $schema, query = $query")
結(jié)果:

注意:
1.
intent-filter與Main主Activity搭配使用時(shí),要單獨(dú)開啟一個(gè)intent-filter,否則匹配不到。
2.從android12開始,設(shè)置了intent-filter標(biāo)簽后,activity的exported必須設(shè)置成true,這個(gè)要注意(android12之前,其實(shí)添加了intent-filter,系統(tǒng)也會(huì)默認(rèn)設(shè)置exported為true)。
①.app link
App link是一種特殊的Deep link,它的作用就是可以使通過網(wǎng)站地址打開app的時(shí)候,不需要用戶選擇使用哪個(gè)應(yīng)用來打開,換種說法就是,我可以設(shè)置默認(rèn)打開次地址的應(yīng)用,這樣一來,就可以直接引導(dǎo)到自己的app。
10.setResult和finish的順序關(guān)系
通過startActivityForResult啟動(dòng)activity,通常會(huì)在被啟動(dòng)的activity的合適時(shí)機(jī)調(diào)用setResult來回調(diào)數(shù)據(jù)給上一個(gè)頁面,然后當(dāng)前頁面返回的時(shí)候就會(huì)回調(diào)onActivityResult,這里要注意setResult的調(diào)用時(shí)機(jī),請一定要在activity的finish()方法之前調(diào)用,否則可能不會(huì)生效(不會(huì)回調(diào)onActivityResult)。
原因如下:
private void finish(int finishTask) {
if (mParent == null) {
int resultCode;
Intent resultData;
//會(huì)在finish的時(shí)候把回調(diào)數(shù)據(jù)賦值
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
···
if (ActivityClient.getInstance().finishActivity(mToken, resultCode, resultData,
finishTask)) {
mFinished = true;
}
} else {
mParent.finishFromChild(this);
}
···
}
//setResult對mResultCode,mResultData賦值
public final void setResult(int resultCode) {
synchronized (this) {
mResultCode = resultCode;
mResultData = null;
}
}
由上述代碼可以看出,setResult必須在finish之前賦值,才能夠在finish的時(shí)候拿到需要callback的數(shù)據(jù),以便在合適的時(shí)機(jī)回調(diào)onActivityResult;
11.onSaveInstanceState()和onRestoreInstanceState()
activity在非正常情況被銷毀的時(shí)候(非正常情況:橫豎屏切換,系統(tǒng)配置發(fā)生變化,內(nèi)存不足后臺activity被回收等),當(dāng)重新回到該activity,系統(tǒng)會(huì)重新實(shí)例化該對象,如果沒有對頁面輸入的內(nèi)容進(jìn)行保存,就會(huì)存在內(nèi)容丟失的情況,此時(shí)可以通過onSaveInstanceState來保存頁面數(shù)據(jù),在onCreate或者onRestoreInstanceState中對數(shù)據(jù)進(jìn)行恢復(fù),形如:
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("SAVE_KEY","SAVE_DATA")
outState.putString("SAVE_KEY","SAVE_DATA2")
super.onSaveInstanceState(outState)
}
//需要判空,savedInstanceState不一定有值
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(null != savedInstanceState){
saveData = savedInstanceState.getString("SAVE_KEY") ?: ""
saveData2 = savedInstanceState.getString("SAVE_KEY2") ?: ""
}
setContentView(R.layout.activity_main)
}
//或者在onRestoreInstanceState恢復(fù)數(shù)據(jù),無需判空,回調(diào)此方法一定有值
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
saveData = savedInstanceState.getString("SAVE_KEY") ?: ""
saveData2 = savedInstanceState.getString("SAVE_KEY2") ?: ""
super.onRestoreInstanceState(savedInstanceState)
}
注意:請使用
onSaveInstanceState(outState: Bundle)一個(gè)參數(shù)的方法,兩個(gè)參數(shù)的方法和Activity的persistableMode有關(guān)。
本文主要對Activity重難點(diǎn)知識進(jìn)行整理和解釋,希望對大家有所幫助,當(dāng)然難免存在錯(cuò)誤,如有發(fā)現(xiàn),希望指正,如果感覺不錯(cuò),麻煩點(diǎn)個(gè)贊,這將給我持續(xù)更文以更大的動(dòng)力,后續(xù)如有其他知識點(diǎn),也會(huì)持續(xù)更新。