典型情况下的生命周期方法, 7个
onCreate(): activity被创建
onStart(): activity已经显示,但在后台, 还不可见.
onResume(): activity由后台切换到前台, 已经可见.
onPause(): activity的显示由前台切换到后台.
onStop(): activity被停止
onDestroy(): activity被销毁
onRestart(): activity A切换到activity B,又从B回退到A,这时, A会先调用onRestart(), 然后调onStart(), 然后调onResume().
有一点要特别注意一下,由MainActivity切换到SecondActivity的回调顺序如下图:
所以不要在MainActivity的onPause()中做耗时操作,尽量把操作移到onStop()中去做,否则会影响SecondActivity启动的速度.
异常情况下的生命周期
- 系统配置发生改变会导致activity的销毁和重建
比如手机横竖屏切换时就会导致activity的销毁和重建,如下图:
在onSaveInstanceState()和onRestoreInstanceState()中,系统自动为我们做了一定的恢复工作.
被保存的数据被保存在参数Bundle对象中, 当因为异常原因创建activity时, 这个bundle对象会作为参数传给onCreate(), 然后再传给onRestoreInstanceState(), 因此在onCreate(Bundle savedInstanceState)中,可以判断bundle参数是否为空,来判断是正常创建activity,还是由于异常原因从新来创建这个acitivity.
//Activity.java
public class Activity {
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
protected void onCreate(Bundle savedInstanceState) {
//activity正常启动时, savedInstanceState为null.
...
}
}
比如文本框的输入, ListView的位置这些信息就被保存在bundle对象中.
实现机制是: activity委托PhoneWindow保存数据,PhoneWindow再委托DecorView保存数据, DecorView再去通知它的子元素去保存数据. 恢复数据也一样,activity通过PhoneWindow, 继而通过DecorView, 通知所有子元素去恢复数据.
以TextView为例:
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
...
//保存数据
SavedState ss = new SavedState(superState);
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
Spannable sp = new SpannableStringBuilder(mText);
ss.text = sp;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
...
Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); //恢复数据
...
}
用onSaveInstanceState(Bundle outState)和onRestoreInstanceState(Bundle savedInstanceState)保存自己特定的数据
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("extra_test","test");
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String str = savedInstanceState.getString("extra_test");
}
当内存不足时, 系统也会杀死低优先级的activity, 保存和恢复数据的机制是一样的.
2.当系统配置发生变化时,禁止activity被重新创建的方法.
在AndroidManifest.xml中,给activity指定configChange属性.
<activity
android:name="com.qihoo.browser.BrowserActivity"
android:configChanges="orientation|screenSize"/>
这样就禁止当手机横竖屏切换时,activity被重新创建.系统不会再调用onSaveInstanceState()和onRestoreInstanceState(),取而代之的是, 系统回调onConfigurationChanged()通知activity系统配置发生了变化.
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
Activity的启动模式 LaunchMode
系统对当前运行的不同app的所有activity的管理,是以任务栈为单位,也就是所谓的task. 所有的activity都在某一个task中存放,每按一次back,正在前台的task中的最顶端activity就会出栈.
4中启动模式: standard, singleTop, singleTask和singleInstance.
standard: 默认模式, 每次启动一个activity都会创建一个新的实例, 不管这个实例是否已经在栈存在. 在这种模式下,谁启动了这个activity,那么这个activity就会运行在启动它的那个activity所在的栈中, 哪怕是启动另一个app的activity也一样. 启动一次, 创建一次, 因此一个任务栈中, 可以有多个相同activity的实例.
singleTop: 栈顶复用模式,也就是如果是栈顶就复用, 不是栈顶就新建. 如果要启动的activity已经位于任务栈的栈顶, 那么不会从新创建此activity的实例. 同时, 它的onNewIntent()被回调. 如果activity的实例不存在或者哪怕存在, 但是不位于栈顶,那么这个activity的实例仍然会被重建,然后把它放在栈顶的位置. 一个任务栈中, 可以有多个相同activity的实例.
singleTask:栈内复用模式, 也就是如果栈内有就复用,没有就新建. 启动一个singleTask模式的activity, 系统首先会检查是否存在声明该activity时, android:taskAffinity="abc"指定名字的任务栈.
如果"abc"任务栈存在, 就在abc中找是否存在要启动的activity的实例,存在的话,不管是不是在栈顶,都不会重建activity实例, 系统回调onNewIntent(),并把该activity实例移到栈顶.如果实例不存在, 就新建实例, 并把它放在栈顶.
如果"abc"任务栈不存在,就新建"abc"任务栈,再新建activity实例,把activity实例放在栈顶.
如果声明该activity时没有指定android:taskAffinity的值, 那么它的android:taskAffinity的默认值是app的包名.
singleTask默认还有clearTop的效果, 例如: acitivity D声明的taskAffinity为S1. S1已存在并且现在的情况是ADBC. 现在要启动D, 因为S1已存在,并且D在S1中也存在,所以D不会被重建,系统调用它的onNewIntent(), 并把D放在S1的栈顶. 因为有clearTop的特性, BC都出栈,所以最终S1的情况是AD.
Affinity的意思是姻亲.
singleInstance: 单实例模式, 具有该模式的activity独享一个任务栈, 也就是说它所在的任务栈永远只有它的实例存在.启动该activity,首先查是否存在它taskAffinity指定的任务栈。存在的话就查该任务栈内是否有它的实例, 有就复用, 调onNewIntent(), 没有就新建实例,放入任务栈. 如果指定的任务栈不存在, 就新建任务栈再新建实例放进去.
app的后退列表由前台后台任务栈构成.
实践经验: 为了尽量少的占用内存, 避免过多的相同的activity存在, 一般app都采用singleTask模式.
设定启动模式:
在AndroidManifest.xml中设定, 也可以通过intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);这种方式设定. 两者同时存在时,以第二种代码设定为准.
两者的对应关系是:
FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP 等同于 "singleTask" 模式
FLAG_ACTIVITY_SINGLE_TOP 等同于 "singleTop" 模式
adb shell dumpsys activity 调试技巧
只看"Running activities (most recent first):" 这部分:
Running activities (most recent first):
TaskRecord{42f36300 #48 A com.qihoo.browser U 0}
Run #5: ActivityRecord{4219adb0 u0 com.qihoo.browser/.activity.WebviewFontActivity}
Run #4: ActivityRecord{4213fb28 u0 com.qihoo.browser/.activity.MainSettingActivity}
Run #3: ActivityRecord{421c6860 u0 com.qihoo.browser/.BrowserActivity}
TaskRecord{424d1028 #3 A com.sonyericsson.home U 0}
Run #2: ActivityRecord{422d46f0 u0 com.sonyericsson.home/.HomeActivity}
TaskRecord{42362b18 #47 A com.maxiosoftware.free.atl U 0}
Run #1: ActivityRecord{42168c68 u0 com.maxiosoftware.free.atl/.TabBarActivity}
TaskRecord{4229e8b8 #44 A com.android.systemui U 0}
Run #0: ActivityRecord{421df108 u0 com.android.systemui/.recent.RecentsActivity}
一个TaskRecord表示一个任务栈, com.qihoo.browser就是这个任务栈的名字, 由taskAffinity指定, 默认为应用的包名.
用这个命令可以快速查找到当前正在显示界面是哪个Activity,对于快速定位问题很有帮助.
IntentFilter的匹配规则
隐式调用Activity, Service等需要intent对象匹配目标组件在<intent-filter>中设置的过滤信息,包括3部门内容, action, category和data. 匹配不上就无法启动目标组件.
一个Activity可以有多个<intent-filter>, 创建的intent对象只要匹配上一组就可以.
<activity
android:name="com.qihoo.browser.BrowserActivity">
<intent-filter android:label="浏览器(推荐)">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="about"/>
<data android:scheme="javascript"/>
</intent-filter>
<intent-filter android:label="浏览器(推荐)">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="inline"/>
<data android:mimeType="application/xhtml+xml"/>
<data android:mimeType="application/vnd.wap.xhtml+xml"/>
</intent-filter>
<intent-filter>
<action android:name="com.qihoo.browserfull.opensearchpage"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<action>: 既可以是系统预置的值, 也可以是app自定义的值.多个<action>存在时,intent只要匹配上一个就行.
<category>: 既可以是系统预置的值, 也可以是app自定义的值.多个<category>存在时,intent要匹配上所有的<category>.并且系统在startActivity()的实现中, 会默认调用intent.addCategory("android.intent.category.DEFAULT")这行代码, 这也就要求我们在AndroidManifest中,必须为<intent-filter>加上<category android:name="android.intent.category.DEFAULT"/>, 这样的activity才能支持隐式调用, 这是需要注意到的.
<data>:多个<data>存在时,intent只要匹配上一个就行. <data>由android:scheme和android:mimeType两部分构成.通过intent.setDataAndType(Uri.parse("file://abc"), "text/plain");设置.
demo:
Intent intent = new Intent("com.ryq.charpter_1.c"); //构造方法就是设置Action值.
intent.addCategory("com.ryq.category.c");
intent.setDataAndType(Uri.parse("file://abc"), "text/plain");
startActivity(intent);
判断是否有Activity能够匹配上隐式intent
PackageManager提供了下面两个方法:
public abstract ResolveInfo resolveActivity(Intent intent, int flags); //返回最佳匹配上的Activity
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags); //返回所有匹配上的Activity
flags都要设置为MATCH_DEFAULT_ONLY去使用, 才能确保查询到的activity一定能被这个隐式intent启动成功.
针对BroadcastReceiver和Service,PackageManger也提供了类似的方法去查询匹配上的组件信息.
Intent也提供了下面的方法, 查询匹配上的组件信息.
public ComponentName resolveActivity(PackageManager pm)
它们的返回结果为null时,表示系统中没有匹配这个隐式intent的组件存在.