android activity 生命周期总结

典型情况下的生命周期方法, 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的回调顺序如下图:

activity切换.png

所以不要在MainActivity的onPause()中做耗时操作,尽量把操作移到onStop()中去做,否则会影响SecondActivity启动的速度.

异常情况下的生命周期
  1. 系统配置发生改变会导致activity的销毁和重建
    比如手机横竖屏切换时就会导致activity的销毁和重建,如下图:
activity异常.png

在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的组件存在.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容