一,安卓的七大生命周期
onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy | onRestart
onCreate:Activity正在被创建
onStart:已经可见但是还不在前台,无法交互
onResume:已经可以开始交互了
onPause:Activity正在停止,onStop会被紧接着调用。不应该在这个阶段做耗时操作,只有这个方法执行完,新的Activity的onResume才能被执行
onStop:Activity即将停止,可以做一些重量级回收工作,也不能太耗时
onDestroy:Activity即将被销毁,可以做一些资源释放和回收工作
onRestart:表示Activity重新启动,一般由不可见变为可见时被调用
注意事项:
- 用户打开的新的Activity是透明主题,原来的Activity的onStop不会被调用
- 回到上一个Activity,该Activity生命周期回调:onRestart -> onStart -> onResume
- 当用户按下返回键,当前Activity生命周期回调:onPause -> onStop -> onDestroy
- Activity A启动Activity B,生命周期:A onPause -> B onCreate -> B onStart -> B onResume -> A onStop
- Activity被系统回收后重新启动生命周期从头走一遍
- onStart和onStop是关于Activity可见不可见而调用的,onResume和onPause是关于Activity是否位于前台来调用的,因此这四个方法都会随着用户熄灭屏幕、亮起屏幕而频繁调用
二,安卓异常情况下的生命周期
- 当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,它和onPause没有既定的时序关系,它既可能在onPause之前调用,也可能在onPause之后调用。需要强调的一点是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。总结如下:
(onPause/onSaveInstanceState)->(onPause/onSaveInstanceState)-> onStop -> onDestroy - 异常情况终止下,onSaveInstanceState保存的数据可以通过onCreate和onRestoreInstanceState的形参Bundle传入。因此在onCreate中对Bundle进行判空可以判断是否是异常终止然后重建的,而onRestoreInstanceState在异常情况下重建必然会被调用,所以传入的Bundle一定不为空。官方推荐在onRestoreInstanceState方法中进行数据恢复。onRestoreInstanceState方法调用时机在onStart之后。
- 注意:onSaveInstanceState和onRestoreInstanceState方法只有在Activity被销毁并且有机会重新显示的情况下才会被调用。例如屏幕旋转,Activity被销毁但是又要立即展示出来;资源不足,低优先级被回收,但是用户可能还会回到那个界面,还有机会展示。所以正常的销毁是不会调用这两个方法的!
- 关于Activity的优先级:
4.1 前台Activity,优先级最高
4.2 可见但非前台Activity,例如这个Activity弹出一个Dialog,这时候Activity不能交互,但是可见,优先级其次
4.3 后台Activity,已经位于后台被暂停,例如执行了onStop,优先级最低 - 对于系统配置变更后Activity其实是有办法不进行重建的,在清单文件中对应的Activity加上
android:configChanges="orientation"
,但是当minSdkVersion或者targetSdkVersion大于13时,为了防止重建还得加上screenSize
,也就是这样了:“android:configChanges="orientation|screenSize”
三,安卓启动模式
standard:默认启动模式,每次启动就在启动它的那个Activity所在的任务栈的栈顶添加一个Activity实例。所以我们在使用ApplicationContext启动Activity的时候就会报错,因为非Activity类的Context没有任务栈。解决办法就是添加一个FLAG_ACTIVITY_NEW_TASK
的FLAG
singleTop:栈顶复用模式。每次启动前看看栈顶是否有当前需要启动的Activity的实例,如果有则不会创建新的实例,而且Activity的onCreate和onStart都不会被调用,会在onResume回调之前回调一个onNewIntent的方法,并且可以从这个方法的形参中可以取出请求信息。
singleTask:栈内复用模式。只要即将启动的Activity实例在任何一个栈内存在,那么该栈会置于前台,并且该Activity实例上所有的Activity会被移出栈,同样会在onResume之前调用onNewIntent。流程如下(假如需要启动Activity A):
singleTask:单实例模式。就相当于加强的singleTask,但是这个模式下,这种Activity会独占一个任务栈。
注意事项
- 什么是Activity所需的任务栈?这个跟taskAffinity属性有关,默认情况Activity所需的任务栈名字和应用包名一致。我们可以给每个Activity单独制定这个参数,而且属性值不能与包名相同,不然就相当于没有指定。这个属性一般和singleTask启动模式或者allowTaskReparenting一起使用。
-
如何理解taskAffinity为什么需要和singleTask启动模式或者allowTaskReparenting成对使用?因为指定了taskAffinity就相当于指定了任务栈名字,所以只有启动的Activity需要一个新的任务栈(启动模式为singleTask或者singleInstance这种就是需要一个新的任务栈)才会起作用。但是其实对于singleInstance,taskAffinity是不起作用的,加入taskAffinity指定同样的名称,多次启动不同的Activity,这些Activity的启动模式是singleInstance的话,他们还是会在不同的任务栈。因为singleInstance会使一个Activity独占一个任务栈。同样,allowTaskReparenting这个属性的作用是指定出现符合taskAffinity属性值的任务栈是否将Activity从启动它的任务栈中移至符合的任务栈中,true代表移动,false代表不移动。例子:A启动B应用中的C这个Activity:
四,如何给Activity指定启动模式
一是通过AndroidMenifest种Activity标签下添加launchMode属性为Activity指定启动模式
二是在实例化Intent的时候给它设置Flag,例如:
Intent intent = new Intent(this, SecondActivity.class);
intent.addFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
这两种方法,第二种优先级更高。
Flags
FLAG_ACTIVITY_NEW_TASK:为Activity指定“singleTask”启动模式。
FLAG_ACTIVITY_SINGLE_TOP:为Activity指定“singleTop”启动模式。
FLAG_ACTIVITY_CLEAR_TOP:添加此标记,启动目标Activity时在同一个任务栈中所有位于这个Activity实例之上的Activity都要出栈。这个一般需要配合FLAG_ACTIVITY_NEW_TASK
一起使用,在这个情况下如果目标Activity已经有实例,那么会调用它的onNewIntent。如果目标Activity的启动方式是standard,那么它连同它之上的Activity都会出栈,然后系统会创建一个它的新的实例放入栈顶。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的Activity不会出现在历史Activity的列表中,等同于在清单文件中Activity标签下指定android:excludeFromRecents="true"
。
五,IntentFilter的匹配规则
启动Activity的方式有两种,分别是显式调用和隐式调用。
注意:显式调用需要明确地指定被启动对象的组件信息,包括包名和类名,而隐式调用则不需要明确指定组件信息。原则上一个Intent不应该既是显式调用又是隐式调用,如果二者共存的话以显式调用为主。
隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中的过滤信息有action、category、data,他们分别可以有多个,分别构成不同类别的匹配,只有三者同时匹配上了,才算匹配上了对应的Activity!另外,一个Activity可以有多个IntentFilter,但是一个Intent只要能匹配上其中一组就可以了。
- action的匹配规则:Intent中的action必须能够和过滤规则中的action的字符串值完全一样(区分大小写),过滤规则中action可以有多个,只要Intent能匹配上一个就算匹配成功,但是Intent没有action就算匹配失败。
- category的匹配规则:Intent中只要有category,那么Intent中所有的category都必须在过滤规则中有就算匹配成功。如果Intent没有category则也算匹配成功。
注意:系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category,所以如果过滤规则中添加了“android.intent.category.DEFAULT”这个category,我们的Intent不添加category也是可以匹配的
- data匹配规则:
3.1 data的数据分为以下几种:
android:scheme="string":URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效,这也意味着URI是无效的。
android:host="string":URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。
android:port="string":URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
android:path="string":表示完整的路径信息。
android:pathPattern="string":表示路径的完整信息,但是可以使用正则表达式进行匹配。
android:pathPrefix="string":表示路径的前缀信息
android:mimeType="string”:指定媒体类型,例如:“image/*”
主要其实就是两大部分,一是mimeType,二是uri。uri的组成:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]。例如:http://www.baidu.com:80/search/info
3.2 data匹配规则与action类似,它要求Intent中必须含有data数据,并且data数据能够完全匹配Intent-filter中的某一个data。
特殊情况说明:例如过滤规则中指明了mimeType="image/*",虽然没有指定uri,但是uri是有默认值的,默认值的schema为content和file。所以也就是说Intent需要额为加上uri,schema必须为content或者file才能匹配。另外,Intent如果需要同时指定data和mimeType的话必须调用setDataAndType方法,不能单独调用setData之后又调用setType方法,这会使得这两个属性相互覆盖。
过滤规则(隐式调用)总结
- 隐式调用下,如果无法匹配对应的Activity会报错。同理,隐式调用BroadcastReceiver和Service也会出现类似的错误。为了避免这种错误,可以采用PackageManager的resolveActivity方法或者Intent的resolveActivity方法,如果它们找不到匹配的Activity就会返回null。
- 另外,PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity方法不同的是:它不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息。
- queryIntentActivities方法对比resolveActivity方法,这两个方法原型如下:
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);
public abstract ResolveInfo resolveActivity(Intent intent,int flags);
上面方法中第二个参数要使用MATCH_DEFAULT_ONLY这个标志,这个标志的含义是仅仅匹配在过滤规则中声明了<category android:name="android.intent.category.DEFAULT"/>
这个category的Activity。使用这个标记位的意义在于,只要上述两个方法不返回null,那么startActivity一定可以成功。如果不用这个标记位,就可以把intent-filter中category不含DEFAULT的那些Activity给匹配出来,从而导致startActivity可能失败。因为不含有DEFAULT这个category的Activity是无法接收隐式Intent的。
- 在action和category中,有一类action和category比较重要,它们是:
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
这二者共同作用是用来标明这是一个入口Activity并且会出现在系统的应用列表中,少了任何一个都没有实际意义,也无法出现在系统的应用列表中,也就是二者缺一不可。另外,针对Service和BroadcastReceiver,PackageManager同样提供了类似的方法去获取成功匹配的组件信息。