0.写在前面的话
本篇文章最初找工作时期准备面试时看《Android开发艺术探索》一书的总结的笔记,好记性不如烂键盘,再综合一些网上关于Activity必知必会的知识点加以总结。不断更新中……
1.Activity的生命周期
1.1 生命周期图:
(1) onCreate():当 Activity 第一次创建时会被调用。
(2) onRestart():表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为导致的,比如用户按Home键切换到桌面或打开了另一个新的Activity,接着用户又回到了这个Actvity。
(3) onStart(): 表示Activity正在被启动,即将开始,这时Activity已经出现了,但是还没有出现在前台,无法与用户交互。这个时候可以理解为Activity已经显示出来,但是我们还看不到。
(4) onResume():表示Activity已经可见了,并且出现在前台并开始活动。需要和onStart()对比,onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
(5) onPause():表示 Activity正在停止,仍可见,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地回到当前Activity,那么onResume就会被调用(极端情况)。onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行。
(6) onStop():表示Activity即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。
(7) onDestory():表示Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。
注意:
① 如果新Activity采用了透明主题,那么当前Activity不会回调onStop。
② onPause必须执行完,新Activity的onResume才会执行。
③ onStart和onStop对应,onStart时Activity可见,onStop时Activity不可见;onResume和onPause对应,onResume时Activity位于前台,onPause时Activity不在前台。
1.2 异常情况下的生命周期
(1)系统配置发生改变,Activity被杀死并重建
onSaveInstanceState:在Activity由于异常情况下终止时,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用是在onStop之前,它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。
onRestoreInstanceState:当异常终止的Activity被重建以后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象参数同时传递给onRestoreInstanceState和onCreate方法。因此,可以通过onRestoreInstanceState方法来恢复Activity的状态,该方法的调用时机是在onStart之后。
其中onCreate和onRestoreInstanceState方法来恢复Activity的状态的区别:onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断。onCreate需要非空判断。建议使用onRestoreInstanceState。
当系统配置的某些内容发生改变时,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。几个常见的configChanges的项目和含义如下:
- local:设备的本地位置发生改变,一般指切换了系统语言。
- keyboardHidden:键盘的访问性发生改变,比如用户调出了键盘。
- orientation:屏幕方向发生了改变。
- screenSize:当屏幕的尺寸信息发生了改变,旋转屏幕时尺寸会发生变化。(当minSdkVersion和targetSdkVersion都低于13时,此项不会导致Activity重启)
例如,不想屏幕旋转时Activity重启:
android:configChanges = "orientation| screenSize"
此时旋转屏幕会调用onConfigurationChanged方法。
(2)资源内存不足,低优先级Activity被杀死
Activity优先级的划分和下面的Activity的三种运行状态是对应的。
(1) 前台Activity——正在和用户交互的Activity,优先级最高。
(2) 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户交互。
(3) 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,会按照上述优先级从低到高去杀死目标Activity所在的进程。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,比较好的方法是将后台工作放到Service中从而保证进程有一定的优先级。
2.Activity的启动模式
2.1 四种启动模式
(1)标准模式(standard)
系统的默认模式。每启动一次Activity,就会创建一个新的Activity实例并置于栈顶。谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。
特殊情况,如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。**
(2)栈顶复用模式(singleTop)
如果新Activity已经位于任务栈栈顶,那么此Activity就不会重建,而是重用栈顶的实例。并回调如下方法:
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
由于不会重建一个Activity实例,则不会回调其他生命周期方法。
如果栈顶不是新的Activity,就会创建该Activity新的实例,并放入栈顶。
(3)栈内复用模式(singleTask)
该模式是一种单例模式,即一个栈内只有一个该Activity实例。如果Activity指定的栈不存在,则创建一个栈,然后创建Activity压入栈内。如果Activity指定的栈存在,但是其中没有该Activity实例,则会创建Activity并压入栈顶,如果其中有该Activity实例,则把该Activity实例之上的所有Activity杀死清除出栈,让该Activity实例处在栈顶,然后调用onNewIntent()方法。
该模式可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中,即singleTask的Activity可以指定想要加载的目标栈。singleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中。
<activity android:name=".Activity1"
android:launchMode="singleTask"
android:taskAffinity="com.lvr.task"
android:label="@string/app_name">
</activity>
关于taskAffinity的值:每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。taskAffinity的值为字符串,中间必须包含包名分隔符“.”
(4)单实例模式(singleInstance)
加强版的栈内复用模式(singleTask),具有此种模式的Activity只能单独地位于一个任务栈中。
打开该Activity时,直接创建一个新的任务栈,并创建该Activity实例放入新栈中。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。
2.2 特殊情况:
① 前台任务栈调用启动模式为SingleTask的后台任务栈中的Activity,整个后台任务栈都会被切换到前台。singleTask具有clearTop特性,把之上的栈内Activity清除。
② 当TaskAffinity和allowTaskReparenting结合时产生特殊效果。应用A启动应用B的Activity C后,如果Activity C的allowTaskReparenting为true,那么当应用B启动后,Activity C会从应用A的任务栈转移到任务B的任务栈中,此时B应用不是启动主Activity,而是重新显示已经被应用A启动的Activity C。
2.3 给Activity指定启动模式
① 通过AndroidManifest指定
<activity android:name=".Activity1"
android:launchMode="singleTask"
android:label="@string/app_name">
</activity>
② 通过Intent设置标志位指定
Intent intent = new Intent();
intent.setClass(this,SecondActivity,class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
第二种方式的优先级要高于第一种方式。
3.Activity的Flags
(1)FLAG_ACTIVITY_NEW_TASK
其效果与指定Activity为singleTask模式一致。
(2)FLAG_ACTIVITY_SINGLE_TOP
其效果与指定Activity为singleTop模式一致。
(3)FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。如果和singleTask模式一起出现,若被启动的Activity已经存在栈中,则清除其之上的Activity,并调用该Activity的onNewIntent方法。如果被启动的Activity采用standard模式,那么该Activity连同之上的所有Activity出栈,然后创建新的Activity实例并压入栈中。
(4)FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记的Activity不会出现在历史Activity列表中,等同于在XML中指定Activity的属性android:excludeFromRecents=”true”。
4.IntentFilter的匹配规则
匹配过滤列表,同时匹配过滤列表中的action/category/data信息,才能隐式调用启动Activity。
(1)action匹配规则
Intent必须存在action,且和过滤规则的其中任意一个action的字符串值完全相同(区分大小写)。如果Intent中没有指定action,那匹配失败。
(2)category匹配规则
Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。Intent可以没有category,仍然可以匹配成功。因为系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFUALT”这个cate。为了Activity能够接受隐式调用,必须在intent-filter中指定android.intent.category.DEFUALT这个category。
(3)data的匹配规则
如果过滤规则中定义了data,那Intent中必须也要定义可匹配的data。data包括mimeType和URI两部分。mimeType指媒体类型。URI的结构为:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
例如:
http://www.baidu.com:80/search/info
如下过滤规则:
<intent-filter>
<data android:mimeType="image/*">
.....
</intent-filter>
虽然没有指定URI,但是URI有默认值,为content和file。所以Intent的URI的schema必须为content或者file才能匹配。匹配上述过滤规则的Intent如下:
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
示例
Activity中的过滤规则:
<intent-filter>
<action androd:name="com.hunst.action_1">
<action androd:name="com.hunst.action_2">
<category androd:name="com.hunst.category_1">
<category androd:name="com.hunst.category_2">
<category androd:name="android.intent.category.DEFAULT">
<data android:mimeType="text/plain">
</intent-filter>
完全匹配上面过滤规则的Intent:
Intent intent = new Intent("com.hunst.action_1");
intent.addCategory("com.hunst.category_1");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);