美文网首页Android面试题面试题Android 开发经验集
Android面试一天一题(Day 19:程序员何苦为难程序员(

Android面试一天一题(Day 19:程序员何苦为难程序员(

作者: goeasyway | 来源:发表于2016-07-12 20:46 被阅读5799次

    2010年从韩国回来时,我也是信心爆满,感觉三星的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会被马上调用。

    |stand|singleTop| singleTask | singleInstance
    ----|:------:|:----:|:----:|:----:
    stand|√|√|x| x
    singleTop|√|√| x|x
    singleTask|√|√| x |x
    singleInstance|x|x|x|x

    好在幸运的是,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

            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”应该获得焦点了,会产生一些混乱或是违反的原有的一些约定。

    小结

    关于启动模式的问题,其实我开始写这个系统的文章时就想介绍它的,不过发现它的水实现太深了,需要用比较长的篇幅才能说明清楚。今天也只是通过一个实际中容易碰到的问题引起大家的关注,也同时引出了“任务”和“返回栈”。

    所以,就让程序员多为难程序员一次,进一步的说明请听下回分解。

    Even 原创
    简书账号goeasyway:http://www.jianshu.com/users/f9fbc7a39b36/latest_articles
    转载请注明出处。

    相关文章

      网友评论

      • 望北8261:最简单粗暴的理解:
        standard 默认
        singleTop 在顶部就直接用,不在就新建
        singleTask 在task中有了就直接用,没有就新建
        singleInstance 整个app中只有一个实例,而且在一个独立的task中
      • 郭某人1:FLAG_ACTIVITY_NEW_TASK

        作用是为Activity指定 “SingleTask”启动模式。跟在AndroidMainfest.xml指定效果同样。
      • uuid1234:看着你的简书成长
      • PeterHe888:没有理解,上面的ActivityY被描述为SingleTop和SingleTask有什么区别呢?
      • 9b4f2400ea72:LauncherActivity我在as里面搜只有LauncherActivity.class。
        在androidXref里面搜到好几个LauncherActivity,但是看了下好像都不是我要找的。
        有些地方有import android.app.LauncherActivity;但是点击之后是搜索不到。
        dalao你在哪里看的LauncherActivity的launchMode啊,我搜manifest文件也是找不到= =。
        dalao指点下我吧,拜谢dalao。
        Little_Mango:@中二菜鸡 xref: /packages/apps/Launcher3/AndroidManifest.xml
      • Fritz_Xu:最近发现在Android4.3版本里面,两个Activity启动模式都为stand时,一样会提前执行onActivityResult
      • lishiwei:确实遇到过。解决办法就是 判断不为空 :joy:
      • poplanchong:哈哈,之前正好碰到这个问题,我的手机版本5.0,同事4.1,就会出现onActivityResult立即执行,今天终于知道原因了,好好
        poplanchong:我是通过Aactivity->Bacitity,然后B提交数据,返回A,在onActivityResult重新得到最新数据,如果有这个问题,逻辑就有问题了
        MrTT:@武械 空指针异常
        Fritz_Xu:@poplanchong 能请教下这个提前执行onActivityResult会带来的什么困扰?本人太菜想不到会发生什么不好的后果 :joy:
      • 一杯茶一本书:不错,学习学习
      • chandarlee:上面也说到了这种onActivityResult立即返回的情况是因为目标Act和当前Act不在同一个Task里面!如果去请求一个singletask 的Act返回结果,目标Act不一定会在新的Task里面,这个要看task affinity 的!所以有可能single task启动的Act还在当前task中,这个时候应该能拿到result的吧,请问博主你实际测试了
        吗?确定不能拿到结果吗
        goeasyway:@chandarlee 5.0以上可以正常,以下的会立即执行onActivityResult
      • 魏魏魏魏:比第一行代码详细多了,谢谢
        ITIan:@檀木丁 我是零基础学安卓,所以我觉得第一行代码对新手帮助还是很大,虽然里面的话有点直白,感觉不是很专业,但确实让我明白了很多
        goeasyway:@檀木丁 嗯,各自的側重點不同,能幫到某部份人就是好的。
        檀木丁:@魏魏魏魏
        详细不一定是好事情,第一行代码是给初学者看的,楼主也是后来深刻认知的哦。
      • 浮华染流年:最后一个例子没看明白
        ITIan:@浮华染流年
        新人愚见:1.通过startActivityForResult启动这种情况下,谁被finish,就需要返回result给它的启动者,例子中2启动Y,Y被finish了就返回result给启动者2,通知2显示得到的数据等;
        2.Y和X是一起在后台栈中,我们都知道,一个栈中,栈顶finish后,就显示底下第二个,所以Y被finish了,照理就是显示X;
        3.然后事情发生了,上面两点都是Y被用户Back后被finish掉,但却有两个结果,系统这时候就懵逼了,该显示谁呢?
        4.所以在2启动Y之前提前给2返回一个取消信号,屏蔽掉这个情况
        5.个人理解,用startActivityForResult,只要压入返回栈的是一个独立栈就会出现上述这种矛盾情况,所以谷歌设计一个屏蔽信号,确保Back后只出现一种情况
        goeasyway:@浮华染流年 一种假设吧,图片是借用官网讲任务和返回栈的图。如果不太明白,可以写一个简单的startActivityForResult试试,把每个Activity生命周期的回调打印出来。

      本文标题:Android面试一天一题(Day 19:程序员何苦为难程序员(

      本文链接:https://www.haomeiwen.com/subject/xnrvrttx.html