Android Activity使用拾遗
一、onWindowFocusChanged
有时我们需要测量一个Activity多长时间才能显示出来,那么在代码中打点计时的时机选在哪儿呢?在onCreate和onResume执行完成后,Activity的界面仍不可见,在onResume之后,framework还会回调一个叫onWindowFocusChanged的函数,它表示用户是否已经可以与Activity的界面进行交互了。onWindowFocusChanged为true意味着Activity的界面已经能够被用户看到了(自然也能和用户交互了)。实际上activity变为visible的时间点出现在onWindowFocusChanged之前,但是这个状态只能在ActivityManagerService中获取,在客户端还是只能通过onWindowFocusChanged作为界面可见或不可见的标志。
在界面变为不可见时,先调用onPause,然后再是onWindowFocusChanged为false。这也容易理解,onPause本来就是Activity被部分遮住时调用的,调用完onPause后才会让界面不能与用户继续交互。
如果想加快Activity的启动时间,把在onCreate里一些耗时操作挪到onResume里,有时会发现并没有什么变化,所以如果要想先让Activity的界面尽快展现给用户还是得把这些逻辑放在onWindowFocusChanged变为true之后执行。
二、onUserLeaveHint
这个回调函数主要用来监听按Home键退出到桌面的动作,发生在onPause之前。在启动一个新的Activity时,ActivityStackSupervisor里会调用startActivityUncheckedLocked,在它里面会给mUserLeaving赋值。mUserLeaving用于指示当前activity退到后台时函数onUserLeaving是否被调用。
可见,只有当设置了FLAG_ACTIVITY_NO_USER_ACTION标志时mUserLeaving才会为false,其他情况下mUserLeaving均为true,也就是onUserLeaveHint会被调用,注释里也说了onUserLeaveHint会在onPause之前被调用。
三、taskAffinity
task是一组用来处理某一任务的Activity的集合,位于一个回退栈内,当新启动一个应用时framework就会创建一个新的task来承载相应的Activity。taskAffinity用来描述Activity的亲和性,即Activity属于哪个task。如果在AndroidManifest.xml的application中没显式定义taskAffinity,那么缺省的affinity名字就是包名。同一affinity的Activity会被放在同一task里,task的affinity名字由根Activity的taskAffinity决定,如果根Activity没设taskAffinity属性,则affinity就由application的affinity决定。
如果taskAffinity设为空字符串,那说明该Activity和其他的task都不存在亲和性。不同应用的Activity也可以设置相同的affinity,这样当启动后它们就会位于同一task中。
四、launchMode
1. standard
默认的launchMode,可以实例化多次。
2.singleTop
使用这种launchMode的情况是,如果要启动的Activity已经在栈的最顶端,那么startActivity时不再会重新调用它的onCreate,而是调用它的onNewIntent。但如果要启动的Activity不在栈顶,比如A –> B à C,这时C再启动B,就会再实例化一个新的B,栈里面变成A à Bà C à B。
如果A中点击一个button来启动B,但系统反应慢,用户在极短的时间内点了两次,如果是B是standard,则会有两个B,如果是singleTop则只会有一个。更典型的场景是用户使用外卖App支付了一笔订单后,从支付页面返回到订单详情页面,如果订单的状态有发生变化,比如商家接单了,这时通知栏会有通知,点击通知会跳入订单详情页面。如果此时订单详情页面是standard的话,那么用户按back键后发现还是在订单详情页面,体验就很差了。如果是singleTop,只需在onNewIntent里更新状态信息,就可以显示出最新的信息,而不必再新创建一个相同的Activity了。
3.singleTask
以这种模式启动的Activity,如果在task中已经存在该Activity的实例,就调用它的onNewIntent方法,进入该task中。如果没有该Activity的实例,就创建一个新的task,把该Activity作为新task的根Activity。所以以singleTask启动的Activity不一定会新创建一个task。需要注意的是,即使Activity位于新的task里,按back键仍然会回到启动它的上一个Activity中,虽然它们不在同一个task里。
如果singleTask的Activity的taskAffinity和现有task的affinity相同,那就直接在现有的task里创建该Activity,所以以singleTask启动的Activity未必就是位于栈底的根Activity。
4.singleInstance
以这种模式启动的Activity自己独占一个task,如果已经有该Activity的实例,再次启动时会调它的onNewIntent。但以singleInstance启动的Activity按back的行为却与singleTask不同。比如 A启动B,B是singleInstance,B再启动C,则B自己单独位于一个新的task中,A和C位于一个task中,则按back键时,从C不会退回到B,而是先退回到A,再按back键再退回到B。
注意,在代码中也可以设置Intent的flag来控制Activity的启动模式,代码中动态设置的比AndroidManifest.xml中静态写的Android:lauchMode优先级高。
五、Intent的flags
1.FLAG_ACTIVITY_NEW_TASK
为Activity新创建一个task,如果startActivity的context不是Activity,而是service或Application,那一定要加上这个flag。以这个flag启动Activity的行为跟launchMode为singleTask的一致。但并不是每次都新创建一个task,把Activity放在里面,而是会先找是否已经有taskAffinity相同的task,如果有就把要启动Activity放在该task里,如果没有taskAffinity相同的task,才会新建一个task。
2.FLAG_ACTIVITY_SINGLE_TOP
作用同singleTop。
3.FLAG_ACTIVITY_CLEAR_TOP
启动Activity时,如果所在task里已经有该Activity的实例,则会清除在它上面所有的Activity。例如,task里的Activity启动关系是Aà Bà Cà D,然后在D启动B时,加上了FLAG_ACTIVITY_CLEAR_TOP标志,在B启动后task里就只有A和B了,C和D被清除了。如果B的launchMode是standard,那么当它收到Intent后,会先销毁掉原来B的实例,然后重新onCreate构建一个新的B;如果B是singleTask类型,那么会保留原有的B的实例,调用它的onNewIntent,传入新的Intent。
4.FLAG_ACTIVITY_CLEAR_TASK
启动Activity时,会清除所在task里其他所有的Activity。例如,task里的Activity启动关系是A à Bà C,在B启动C时,加上FLAG_ACTIVITY_CLEAR_TASK,那么在C启动后,task里就只剩C了,A、B被清除了。如果这个应用中只有这一个task,那么在C中按back键就退回到桌面了。
5.FLAG_ACTIVITY_REORDER_TO_FRONT
以该标志启动的Activity如果已经在task中存在,会被移动到栈顶,其他的Activity顺序不变。如果在task中不存在,则新建一个。例如,task里的Activity启动关系是A ->B ->C->D,在D启动B时,加上这个flag,则B就被移到了栈顶,task里就变成了A ->C ->D -> B了。如果B已经调了finish,但系统还没来得及把它从task中移出,这时以FLAG_ACTIVITY_REORDER_TO_FRONT方式启动B,则无法启动B。只有当B destroy以后才可以启动B。
6.FLAG_ACTIVITY_NO_HISTORY
这个FLAG可以让启动的Activity一旦退出,就finish掉,不再存在于栈中。例如,Activity的启动关系是A-> C->D,在B启动C时加上FLAG_ACTIVITY_NO_HISTORY,在C启动完D后,task里仍然是 A-> B->C->D的栈布局,C并没从栈中清除。当在D中按back键,不会退到C而是退到B,这时dumpsysactivity会发现task中没有C了,只有A和B,在D中按back键时,C就finish掉了。
7.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
以这个flag启动的Activity将不会在最近启动的应用列表中出现。
8.FLAG_ACTIVITY_FORWARD_RESULT
我们用startActivityForResult和setResult在两个Activity之间传递数据,如果中间还隔着另一个Activity,比如A -> B->C,要想在A和C之间用startActivityForResult和setResult的话,就要在B启动C时加上这个参数。也就是A正常的startActivityForResult启动B,B以FLAG_ACTIVITY_FORWARD_RESULT方式启动C,在C中setResult,这样当从C退回B,再退回到A,即C和B都finish后,A的onActivityResult会收到C中setResult传的值。
9.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
这个是从最近任务列表中启动Activity时由系统设置的,第三方应用不会用到这个标志,系统的SystemUI在最近任务列表中才会设这个标志。
10.FLAG_ACTIVITY_NO_ANIMATION
要启动Activity将不执行入场动画。
11.FLAG_ACTIVITY_TASK_ON_HOME
以这个flag启动的Activity所在的task将会位于Home所在的task之上,也就是说在这个Activity中按back键会回到home,而不是启动它的那个Activity。例如,A -> B,B的taskAffinity与A不同,A启动B时同时加上了FLAG_ACTIVITY_TASK_ON_HOME和FLAG_ACTIVITY_NEW_TASK这两个标志,那么A和B位于不同的task中,如果仅是以newtask方式启动B,那么即使B和A不在一个task中,那么在B中按back键还是会退到A的,但加上了FLAG_ACTIVITY_TASK_ON_HOME后,按back键就退回到home了,此时A已经被移动到了后台。
- 嵌入式企鹅圈原创团队由阿里、魅族、nvidia、龙芯、炬力、拓尔思等资深工程师组成。百分百原创,每周两篇,分享嵌入式、Linux、物联网、GPU、Android、自动驾驶等技术。欢迎扫码关注微信公众号:嵌入式企鹅圈,实时推送原创文章!