Android面試,程式設計師何苦為難程式設計師!
Android的開發水平那是槓槓的。有一次想跳槽,面試一家公司時就被問了Activity啟動模式的問題,微胖的面試官問我怎麼看待四種啟動模式,我吧嗒吧嗒後,面試官接著問我Launcher這個應用的Home介面(一般是指Launcher.java)用的是哪種模試。
我自信的回答用singleInstance,要不是面試官早有準備,估計他都要被我的自信弄得要開始懷疑人生。我的相法很簡單,認為它全域性只有一個例項而且應該只有一個例項,用singleInstance最好。
當我回來查詢Launcher的原始碼時發現使用的是SingleTask模式。之後雖然拿到了offer,但我仍然為這個問題耿耿於懷。當我後來到MTK公司工作時,才對Android的四種模式有了更深入的理解。
面試題:Activity的啟動模式(launchMode)有哪些,有什麼區別?
這應該是一道很虐人的面試題,很多人都答不上來,很多人根本就沒有用過。當我發現在被我面試的人中有80%的比例對它不瞭解時,我找過一些同事討論是否還有在面試中考查這個問題的必要,得到的回答是“程式員何苦為難程式設計師”!
因為很多程式員都認為這個啟動模式沒有多大用處。好吧,我用一個實際中很容易遇到的問題來引出它有多麼有用。
很多人在使用startActivityForResult啟動一個Activity時,會發現還沒有開始介面跳轉本身的onActivityResult馬上就被執行了,這是為什麼呢?
遇到過吧,我見過很多人為了這個問題抓耳撓腮的。在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.
很多人出現這個問題,確實是因為startActivityForResult啟動的Activity設定了singleTask的啟動模式。但是,除了這種情況還有可能會馬上執行嗎?
有,而且很多。如下面表格,左邊第1列代表MainActivity的啟動模式,第一行代表SecondActivity(即要startActivityForResult啟動的Activity)的啟動模式,打叉代表在這種組合下onActivityResult會被馬上呼叫。
standsingleTopsingleTasksingleInstance
stand√√xx
singleTop√√xx
singleTask√√xx
singleInstancexxxx
好在幸運的是,Android在5.0及以後的版本修改了這個限制。也就是說上面x的地方全部變成了√。
那麼在Android 5.0後,還會有這個問題嗎?
還是會的。如在Intent中設定了FLAG_ACTIVITY_NEW_TASK再startActivityForResult,即使是標準的啟動模式仍然會有這個問題。
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也會被觸發。因為onActivityResult被執行時,它會重新獲得焦點。很多人也會遇到onResume被無故呼叫,也許就是這種情況。
所以,最終我們發現只要是不和原來的Activity在同一個Task就會產生這種立即執行onActivityResult的情況,從原始碼也可以得到驗證,詳情檢視ActivityStackSupervisor.java(http://androidxref.com/6.0.1_r10/xref/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java#1882)。
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;
}
原因
其實上面程式碼中的英文註解也說得很清楚了,Android認為不同的Task之間對這種要求返回結果的啟動方式會產生一些依賴(對Task),所以乾脆簡單粗暴在跳轉前直接返回RESULT_CANCELED結果。
我們還是用一個例子簡單解釋一下,如下圖,有兩個任務棧(stack),處於前可視狀態的是“Back Stack”也叫返回棧,處理後臺的是“Background Task”。

當“Activity 2”通過startActivityForResult啟動“Activity Y”時,“Background Task”中的Activity會被壓入返回棧的棧頂。這種情況下,如果沒有在跳轉前直接返回RESULT_CANCELED給“Activity 2”,那麼按Back鍵,應該要跳轉到“Activity X”,而按Back鍵“Activity Y”就會呼叫finish會發送Result給啟動它的“Activity 2”。這時就很難搞清楚,到底是“Activity 2”還是“Activity X”應該獲得焦點了,會產生一些混亂或是違反的原有的一些約定。
小結
關於啟動模式的問題,其實我開始寫這個系統的文章時就想介紹它的,不過發現它的水實現太深了,需要用比較長的篇幅才能說明清楚。今天也只是通過一個實際中容易碰到的問題引起大家的關注,也同時引出了“任務”和“返回棧”。
所以,就讓程式設計師多為難程式設計師一次,進一步的說明請聽下回分解