1. Activity生命周期全面分析
- 生命周期
- onCreate:表示Activity正在被创建
- onRestart:表示Activity正在重新启动
- onStart:表示Activity正在启动,Activity已经可见,但是还没有出现在前台
- onResume:表示Activity已经可见,并且出现在前台开始活动
- onPause:表示Activity正在停止,可以存储数据、停止动画等,不能太耗时
- onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,不能太耗时
- onDestroy:表示Activity即将被销毁,可以做一些回收工作和最终的资源释放
- Activity生命周期切换过程
- 针对一个特定的Activity,第一次启动,回调如下:onCreate-onStart-onResume
- 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause-onStop。如果新的Activity采用了透明主题,那么当前Activity不会回调onStop。
- 当用户再次回到原Activity,回调如下:onRestart-onStart-onResume。
- 当用户按back回退键时,回调如下:onPause-onStop-onDestroy
- onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度来回调的。
- 从Activity A跳转到Activity B,Activity A的onPause先执行,Activity B的onResume在执行。
- onActivityResult() 和onResume()的调用顺序问题,回到调用activity时会马上执行onActivityResult方法,然后才是onResume()方法。
异常情况下的生命周期分析
- 资源相关的系统配置发生改变导致Activity被杀死并重新创建
在Activity异常终止的情况下,Activity被重新创建后,系统会调用 onSaveInstanceState ,并且把Activity销毁时onSaveInstanceState 方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate()方法,我们可以通过上述两个方法判断Activity是否被重建了,如果重建了,我们可以取出数据并恢复,从时序上来讲,onRestoreInstanceState的调用时机在onStart之后。
在onSaveInstanceState 和onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据,比如文本框输入的数据、ListView滚动的位置等。
onRestoreInstanceState和onCreate()方法的区别是,onRestoreInstanceState一旦被调用,其参数一定是有值的,官方建议使用这种方法去恢复数据,onCreate()`不行。 资源内存不足导致低优先级的Activity被杀死
Activity的优先级从高到低:
1. 前台Activity — 正在和用户交互的Activity,优先级最高
2. 可见但非前台的Activity — 比如Activity中弹出一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
3. 后台Activity — 已经被暂停的Activity,比如执行了onStop,优先级最低。当系统内存不足的时候,系统会按照上述优先级杀死Activity所在的进程,并通过
onSaveInstanceState
和onRestoreInstanceState
方法来存储和恢复数据。如果一个进程没有在4大组件执行,进程很快会被系统杀死。
当系统配置发生改变后,Activity会被重新创建,我们可以给Activity指定configChanges属性,Activity不会被重新创建,没有调用onSaveInstanceState
和onRestoreInstanceState
方法,取而代之的是调用onConfigurationChanges
方法,如下所示:
android:configChanges="orientation | keyboardHidden
主要有如下三个重要属性:
local 设备的本地位置发生改变,一般指切换了系统语言
orientation屏幕方向发生了改变,比如旋转了屏幕
keyboardHidden键盘的可访问性发生了改变,比如用户调出键盘
- 资源相关的系统配置发生改变导致Activity被杀死并重新创建
补充:
横竖屏切换时候Activity的生命周期:
不设置Activity的android:configChanges时,切屏会重新回掉各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
设置Activity的android:configChanges=”orientation”时,切屏还是会调用各个生命周期,切换横竖屏只会执行一次
设置Activity的android:configChanges=”orientation |keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
2. Activity的启动模式
- standard:标准模式
系统的默认模式,每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的生命周期符合典型情况下的生命周期,它的onCreate、onStart、onResume都会调用。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity栈中。比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的栈中。使用场景大多数Activity。 - singleTop:栈顶复用模式
如果新Activity已经位于任务栈的栈顶,那么次Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法我们可以取出当前请求的信息。这个Activity的onCreate、onStart不会被调用,因为它并没有发生改变。使用场景如新闻类或者阅读类App的内容页面。 - singleTask:栈内复用模式
一种单实例模式,只要Activity在一个栈中存在,多次启动Activity都不会重新创建实例,系统会回调onNewIntent方法。当设置此模式后,比如Activity A会首先寻找是否存在A想要的任务栈,如果不存在就重新创建一个任务栈,如果存在,就看A是否在栈中有实例存在,如果有实例存在那么系统就会把A调到栈顶并调用onNewIntent方法,同时由于singleTask默认具有clearTop的效果,就会导致栈内所有在A上面的Activity全部出栈,如果不存在,就创建A的实例并把A压入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。 - singleInstance:单实例模式
这是一种加强的singleTask模式,除了具有singleTask所有特性外,还具有此种模式的Activity A只能单独的位于一个新的任务栈,然后A单独在这个栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。 - Activity的Flags
- 设置启动模式的两种方式
通过AndroidMenifest为Activity指定启动模式
android:launchMode="singleTask"
Intent中设置标记为来为activity指定启动模式
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- 常用标记位
FLAG_ACTIVITY_NEW_TASK
为Activity指定singleTask启动模式
FLAG_ACTIVITY_SINGLE_TOP
为Activity指定singleTop启动模式
FLAG_ACTIVITY_CLEAN_TOP
具有此标记位的Activity,当它启动时,同一个任务栈中所有位于它上面的Activity都要出栈。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用,等同于在XML中指定Activity的属性android:excludFromRecents="true"
.
- 设置启动模式的两种方式
3. IntentFilter的匹配规则
为了匹配过滤列表,需要同时匹配过滤列表中的action、category、data信息,否则匹配失效。一个过滤列表中的action、category和data可以有多个。一个Intetn同时匹配action类别、category类别、data类别才算完全匹配,只有完全匹配才能启动目标Activity。一个Activity中可以有多个intent-filter,一个Intent只要匹配其中任何一组intent-filter即可成功启动对应的Activity。
1. action的匹配规则
action是一个字符串,系统预定义了一些action,我们也可以自定义action,匹配规则是Intent中的action必须能够和过滤规则中的action匹配,一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功,Intent中的action存在且必须和过滤规则中的其中一个action相同。
2. category的匹配规则
category是一个字符串,系统预定义了一些category,我们也可以在应用中定义自己的category。category和action的匹配规则不同,要求Intent中如果含有category,那么所有的category都必须和过滤规则中(即intent filter中设置的category)的其中一个相同。Intent也可以没有category,如果没有category的话,在startActivity或者startActivityForResult的时候会默认为Intent加上"android.intent.category.DEFAULT"
。
3. data的匹配规则
如果过滤规则中添加了data,那么Intent中必须也要定义可匹配的data。data的语法如下所示:
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
data由两部分组成,mineType和URI。mineType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同媒体格式,而URI包含的数比较多,一个URI结构如下所示
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
下面举个实际的例子大家就明白了:
content://com.example.project:200/folder/subfolder/etc
其中:
- Scheme:URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效,即URI也是无效的;
- Host:URI的主机名,如com.example.project,如果host未指定,那么整个URI中的其他参数无效,即URI也是无效的;
- Port:URI中的端口号,如200,只有URI中指定了scheme和host参数的时候port参数才有意义;
- Path、PathPattern和PathPrefix:这三个参数表示路径信息,如folder/subfolder/etc,其中path表示完整的路径信息;PathPattern也表示完整的路径信息,但是它里面可包含通符“*”,表示0个或多个任意字符;PathPrefix表示路径的前缀信息。
data匹配规则:
<intent-filter>
<data android:mimeType="image/*"/>
...
</intent-filter>
这种规则制定了媒体类型为所有类型的图片,那么Intent中mimeType属性必须为”image/*”才能匹配,这种情况下没有指定URI,但是Intent中的URI部分的schema必须为content或者file才能匹配,我们需要在Intent中这样指定:
intent.setDataAndType(Uri.parse("file://abc"), "image/png")
如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值。
怎么判断是否有Activity是否能够匹配我们的隐式Intent?
1. 采用PackageManager
的resolveActivity
方法或者Intent的resolveActivity
方法,如果找不到就返回null。
2. PackageManager
方法还提供了queryIntentActivities
方法,这个方法和resolveActivity
方法最大的不同是,它不是返回最佳匹配的Activity的信息,而是返回的是所有成功匹配的Activity信息。我们可以看一下方法的原型:
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,@ResolveInfoFlags int flags);
public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags);
上述两个方法的第二个参数注意,我们要使用MATCH_DEFAULT_ONLY
这个标记位,只能匹配intent-filter
中声明了<category android:name="android.intent.category.DEFAULT"/>
这个category的Activity。使用这个标记位最大的意义是上述两个方法不返回null,那么startActivity一定可以成功。如果不使用这个标记位,就可以吧intent-filter
中category不含DEFAULT的那些Activity匹配出来,startActivity有可能失败,不含有DEFAULT这个category的Activity是无法接受隐式Intent的。
3. 在action和category中有一类比较重要,用来标注一个入口:
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
针对Service和BroadcastReceiver等组件,PackageManager同样提供了类似的方法去获取成功匹配的组件信息。
关于隐式Intent更多信息可以参考这两篇官方翻译的博文,也可以直接去看官方的文档:
Android Intent最全面的解析
史上最全的隐式Intent解析