2010年從韓國回來時(shí),我也是信心爆滿,感覺三星的Android項(xiàng)目都能做下來了,Android的開發(fā)水平那是杠杠的。有一次想跳槽,面試一家公司時(shí)就被問了Activity啟動(dòng)模式的問題,微胖的面試官問我怎么看待四種啟動(dòng)模式,我吧嗒吧嗒后,面試官接著問我Launcher這個(gè)應(yīng)用的Home界面(一般是指Launcher.java)用的是哪種模試。
我自信的回答用singleInstance,要不是面試官早有準(zhǔn)備,估計(jì)他都要被我的自信弄得要開始懷疑人生。我的相法很簡單,認(rèn)為它全局只有一個(gè)實(shí)例而且應(yīng)該只有一個(gè)實(shí)例,用singleInstance最好。
當(dāng)我回來查詢Launcher的源代碼時(shí)發(fā)現(xiàn)使用的是SingleTask模式。之后雖然拿到了offer,但我仍然為這個(gè)問題耿耿于懷。當(dāng)我后來到MTK公司工作時(shí),才對(duì)Android的四種模式有了更深入的理解。
面試題:Activity的啟動(dòng)模式(launchMode)有哪些,有什么區(qū)別?
這應(yīng)該是一道很虐人的面試題,很多人都答不上來,很多人根本就沒有用過。當(dāng)我發(fā)現(xiàn)在被我面試的人中有80%的比例對(duì)它不了解時(shí),我找過一些同事討論是否還有在面試中考查這個(gè)問題的必要,得到的回答是“程序員何苦為難程序員”!
因?yàn)楹芏喑绦騿T都認(rèn)為這個(gè)啟動(dòng)模式?jīng)]有多大用處。好吧,我用一個(gè)實(shí)際中很容易遇到的問題來引出它有多么有用。
很多人在使用startActivityForResult啟動(dòng)一個(gè)Activity時(shí),會(huì)發(fā)現(xiàn)還沒有開始界面跳轉(zhuǎn)本身的onActivityResult馬上就被執(zhí)行了,這是為什么呢?
遇到過吧,我見過很多人為了這個(gè)問題抓耳撓腮的。在Activity.java的startActivityForResult方法上看一下官方的說明吧:
* <p>Note that this method should only be used with Intent protocols
* that are defined to return a result. In other protocols (such as
* {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
* not get the result when you expect. For example, if the activity you
* are launching uses the singleTask launch mode, it will not run in your
* task and thus you will immediately receive a cancel result.
很多人出現(xiàn)這個(gè)問題,確實(shí)是因?yàn)閟tartActivityForResult啟動(dòng)的Activity設(shè)置了singleTask的啟動(dòng)模式。但是,除了這種情況還有可能會(huì)馬上執(zhí)行嗎?
有,而且很多。如下面表格,左邊第1列代表MainActivity的啟動(dòng)模式,第一行代表SecondActivity(即要startActivityForResult啟動(dòng)的Activity)的啟動(dòng)模式,打叉代表在這種組合下onActivityResult會(huì)被馬上調(diào)用。
|stand|singleTop| singleTask | singleInstance
----|:------:|:----:|:----:|:----:
stand|√|√|x| x
singleTop|√|√| x|x
singleTask|√|√| x |x
singleInstance|x|x|x|x
好在幸運(yùn)的是,Android在5.0及以后的版本修改了這個(gè)限制。也就是說上面x的地方全部變成了√。
那么在Android 5.0后,還會(huì)有這個(gè)問題嗎?
還是會(huì)的。如在Intent中設(shè)置了FLAG_ACTIVITY_NEW_TASK再startActivityForResult,即使是標(biāo)準(zhǔn)的啟動(dòng)模式仍然會(huì)有這個(gè)問題。
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Log如下:
07-12 14:21:14.849 20774-20774/net.goeasyway.test I/MainActivity: onCreate
07-12 14:21:14.875 20774-20774/net.goeasyway.test I/MainActivity: onResume
07-12 14:21:19.995 20774-20774/net.goeasyway.test I/MainActivity: onPause
07-12 14:21:19.995 20774-20774/net.goeasyway.test I/MainActivity: onActivityResult requestCode=1 resultCode=0
07-12 14:21:19.996 20774-20774/net.goeasyway.test I/MainActivity: onResume
07-12 14:21:19.996 20774-20774/net.goeasyway.test I/MainActivity: onPause
07-12 14:21:20.005 20774-20774/net.goeasyway.test I/SecondActivity: onCreate
07-12 14:21:20.018 20774-20774/net.goeasyway.test I/SecondActivity: onResume
注意:MainActivity的onResume也會(huì)被觸發(fā)。因?yàn)閛nActivityResult被執(zhí)行時(shí),它會(huì)重新獲得焦點(diǎn)。很多人也會(huì)遇到onResume被無故調(diào)用,也許就是這種情況。
所以,最終我們發(fā)現(xiàn)只要是不和原來的Activity在同一個(gè)Task就會(huì)產(chǎn)生這種立即執(zhí)行onActivityResult的情況,從原代碼也可以得到驗(yàn)證,詳情查看ActivityStackSupervisor.java。
if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0
&& r.resultTo.task.stack != null) {
// For whatever reason this activity is being launched into a new
// task... yet the caller has requested a result back. Well, that
// is pretty messed up, so instead immediately send back a cancel
// and let the new task continue launched as normal without a
// dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
r.resultTo.task.stack.sendActivityResultLocked(-1,
r.resultTo, r.resultWho, r.requestCode,
Activity.RESULT_CANCELED, null);
r.resultTo = null;
}
原因
其實(shí)上面代碼中的英文注解也說得很清楚了,Android認(rèn)為不同的Task之間對(duì)這種要求返回結(jié)果的啟動(dòng)方式會(huì)產(chǎn)生一些依賴(對(duì)Task),所以干脆簡單粗暴在跳轉(zhuǎn)前直接返回RESULT_CANCELED結(jié)果。
我們還是用一個(gè)例子簡單解釋一下,如下圖,有兩個(gè)任務(wù)棧(stack),處于前可視狀態(tài)的是“Back Stack”也叫返回棧,處理后臺(tái)的是“Background Task”。

當(dāng)“Activity 2”通過startActivityForResult啟動(dòng)“Activity Y”時(shí),“Background Task”中的Activity會(huì)被壓入返回棧的棧頂。這種情況下,如果沒有在跳轉(zhuǎn)前直接返回RESULT_CANCELED給“Activity 2”,那么按Back鍵,應(yīng)該要跳轉(zhuǎn)到“Activity X”,而按Back鍵“Activity Y”就會(huì)調(diào)用finish會(huì)發(fā)送Result給啟動(dòng)它的“Activity 2”。這時(shí)就很難搞清楚,到底是“Activity 2”還是“Activity X”應(yīng)該獲得焦點(diǎn)了,會(huì)產(chǎn)生一些混亂或是違反的原有的一些約定。
小結(jié)
關(guān)于啟動(dòng)模式的問題,其實(shí)我開始寫這個(gè)系統(tǒng)的文章時(shí)就想介紹它的,不過發(fā)現(xiàn)它的水實(shí)現(xiàn)太深了,需要用比較長的篇幅才能說明清楚。今天也只是通過一個(gè)實(shí)際中容易碰到的問題引起大家的關(guān)注,也同時(shí)引出了“任務(wù)”和“返回?!?。
所以,就讓程序員多為難程序員一次,進(jìn)一步的說明請(qǐng)聽下回分解。
Even 原創(chuàng)
簡書賬號(hào)goeasyway:http://m.itdecent.cn/users/f9fbc7a39b36/latest_articles
轉(zhuǎn)載請(qǐng)注明出處。