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的回调顺序如下图:

android activity 生命周期总结_第1张图片
activity切换.png

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

异常情况下的生命周期
  1. 系统配置发生改变会导致activity的销毁和重建
    比如手机横竖屏切换时就会导致activity的销毁和重建,如下图:
android activity 生命周期总结_第2张图片
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被重新创建.系统不会再调用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对象匹配目标组件在中设置的过滤信息,包括3部门内容, action, category和data. 匹配不上就无法启动目标组件.
一个Activity可以有多个, 创建的intent对象只要匹配上一组就可以.


            
                
                
                
                
                
                
                
            
            
                
                
                
                
                
                
                
                
            
            
                
                
            

: 既可以是系统预置的值, 也可以是app自定义的值.多个存在时,intent只要匹配上一个就行.
: 既可以是系统预置的值, 也可以是app自定义的值.多个存在时,intent要匹配上所有的.并且系统在startActivity()的实现中, 会默认调用intent.addCategory("android.intent.category.DEFAULT")这行代码, 这也就要求我们在AndroidManifest中,必须为加上, 这样的activity才能支持隐式调用, 这是需要注意到的.
:多个存在时,intent只要匹配上一个就行. 由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 queryIntentActivities(Intent intent, int flags); //返回所有匹配上的Activity

flags都要设置为MATCH_DEFAULT_ONLY去使用, 才能确保查询到的activity一定能被这个隐式intent启动成功.
针对BroadcastReceiver和Service,PackageManger也提供了类似的方法去查询匹配上的组件信息.
Intent也提供了下面的方法, 查询匹配上的组件信息.

public ComponentName resolveActivity(PackageManager pm)

它们的返回结果为null时,表示系统中没有匹配这个隐式intent的组件存在.

你可能感兴趣的:(android activity 生命周期总结)