Android面試一天一題(Day 19:程序員何苦為難程序員(上))

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)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容