Activity的生命周期和启动模式

Posted by Cedar7 on March 7, 2019

Activity的生命周期和启动模式

1.1 Activity的生命周期全面分析

  1. 典型情况:是指有用户参与下,Activity所经历的一系列正常的生命周期的改变。

  2. 异常情况:是指Activity被系统回收或者由于当前设备的Configruration发生改变从而导致Activity被销毁重建。

1.1.1 典型情况下的生命周期分析

onCreate(): 生命周期的第一个方法,我们可以做一些初始化工作。如setContentView、或初始化所需数据。

onRestart(): 表示Activity在重新启动,当Activity由不可见变为可见状态时,onRestart就会被调用,一般是由于用户行为所导致的。(在调用onPause、onStop之后再返回Activity时调用)。

onStart(): 表示Activity已经==可见==了,但还没有出现在前台,还无法和用户交互。

onResume(): 表示Activity已经==可见==了,并且已经出现在前台,。可以和用户进行交互了(与onStart区别就是:1️⃣是否出现在前台。2️⃣ 是否可以和用户交互 )

onPause(): 表示Activity正在停止,这时仍然==可见==,可以做一些数据存储、动画停止等操作,但不能太耗时,因为会影响到下一个Activity的显示(onPause()执行完之后,下一个Activity的onResume()才会执行)

onStop(): 表示Activity即将停止,这是已==不可见==,可以做一些重量级的回收工作,同样也不能太耗时。

onDestroy(): 表示Activity即将被销毁,是生命周期最后一个方法,可以做最终的回收与资源释放工作。
image

Questions

  1. onStart()和onResume(), onPause()和onStop()有什么实质的不同?
    onStart()与onStop()是从Activity==是否可见==这个角度来回调的;onResume()和onPause是从==是否位于前台==来回调的。
  2. Activity A打开Activity B,那么B的onResume()和A的onPause()哪个先执行?
    从源码可以看出,在新Activity启动之前,栈顶的Activity需要先==onPause==()后,新Activity才能启动.

    1.1.2 异常情况下的生命周期分析

    A. 资源相关的系统配置发生改变导致Activity杀死并重建
    当系统配置发生改变后,Activity就会被销毁重建,onPause、onStop、onDestroy均会被调用。由于是在异常状况下被终止的,所以系统会调用==onSaveInstanceState==来保存当前Activity的状态。它的调用时机是==onStop==()==之前==,和onPause()没有确切关系。它只有在==异常状况下才会被调用==,正常情况下不会调用这个方法。在重建之后会调用==onRestoreInstanceState==,并且把Bundle对象传递给onRestoreInstanceState和onCreate方法,来进行数据的恢复,onRestoreInstanceState的调用时机在==onStart==之后。

    Question

  3. onRestoreInstanceState与onCreate的区别?
    ==onRestoreInstanceState==一旦被调用,其Bundle参数一定不为空,不需要再判断。但是在正常的启动的流程中,==onCreate==参数的Bundle为null,当重建之后,还需要额外判断。这两个方法都可以进行数据恢复,官方建议我们用onRestoreInstanceState去恢复数据。
  1. onSaveInstanceState在什么时候才会被调用?

    系统只会在Activity==即将被销毁并且有机会重新显示(即异常终止的时候,如系统配置发生变化)==的情况下才会去调用它。

B. 资源内存不足导致==低优先级的Activity==被杀死

  • 前台Activity——正在和用户交互的Activity,==优先级最高==。
  • 可见但非前台Activity——Activity被遮挡住,虽然可见,但处于后台无法与用户交互。
  • 后台Activity——已被暂停的Activity,比如执行了onStop(),==优先级最低==

系统内存不足时,就会按照上述顺序杀死目标Activity所在的进程,在能够再一次展示的前提下,调用onSaveInstanceState与onRestoreInstanceState进行数据的存储与恢复。

Question

  1. 当我们不想因为系统配置发生改变而重建Activity时应该怎么办?

    在Manifest.xml文件中在对应的Activity中配置相关项
    ==android:configChanges=”orientation | keyboardHidden”==
    这样当方向与键盘发生改变时,Activity就不会被重建,这时Activity中的==onConfigurationChanged==方法就会被回调,而onSaveInstanceState与onRestoreInstanceState则不会被回调。

    1.2 Activity的启动模式

    1.2.1 Activity的LaunchMode

  2. standard: 标准模式

    每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否存在。Activity A启动Activity B(标准模式),那么B就会进入A所在的栈中。

  3. singleTop: 栈顶复用模式

    在这种模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时会回调它的onNewIntent方法,我们可以从其中的参数获取请求信息。这时这个Activity的onCreate、onStart不会被系统调用。如果新Activity的实例已存在但不是位于栈顶,那么新的Activity仍然会重建。

  4. singleTask: 栈内复用模式

    这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,而是回调其onNewIntent。例如:Activity A是singleTask模式的,==当被启动时,系统首先会寻找A想要的任务栈,如果不存在,则重新创建一个任务栈,然后创建A的实例把A放到栈中。如果存在A所需的任务栈,这时要看任务栈中时候有A的实例存在,如果存在,那么系统会把A调到栈顶(clearTop: A上面的Activity全部出栈)并调用onNewIntent方法,如果不存在,就创建A的实例并把A压如栈中==。

  5. singleInstance: 单实例模式

    这时一种加强的singleTask,它除了具有singleTask模式的所有特性,还加强了一点,就是它只能==单独地位于一个任务栈==中(==就算它启动一个standard模式的,这个新启动的不能加到singleInstance所在的栈中==)

    Questions

    1. 什么是Activity所需要的任务栈呢
    任务栈即参数:TaskAffinity。属性值必须为字符串,且中间必须含有包名分隔符“==.==” 。

    <activity
            android:name=".Main4Activity"
            android:launchMode="singleTask"
            android:taskAffinity="com.demo.haha"
            />

默认情况下,所有Activity所需的任务栈的名字为应用的==包名==,我们也可以为每个Activity指定taskAffinity,这个属性不能与包名相同,否则相当于没有指定。
任务栈分为==前台任务栈==(正在展示的)和==后台任务栈==(处于暂定状态的)

2. 如何给Activity指定启动模式?

  1. 通过Menifest文件指定
        <activity
            android:name=".Main4Activity"
            android:launchMode="singleTask"
            android:taskAffinity="com.demo.haha"
            />
  1. 通过在intent中设置标志位来指定
    Intent intent = new Intent();
    intent.setClass(MainActivity.class, SecondActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);   

两种方式的区别:
==优先级上==第二种高于第一种,同时存在时以第二种方式为准;
==限定范围==上有所不同,第一种无法直接设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种无法为Activity指定singleInstance模式。

1.2.2 Activity的Flags

常用的一些Flags

  • 设定Activity的启动模式
    FLAG_ACTIVITY_NEW_TASK
    (为Activity指定“singleTask”启动模式,和在xml中指定该模式相同)
    FLAG_ACTIVITY_SINGLE_TOP
    (为Activity指定“singleTop”启动模式,和在xml中指定该模式相同)
  • 影响Activity的运行状态
    FLAG_ACTIVITY_CLEAR_TOP
    如果指定该flag的Activity是singleTask模式,如果栈中存在该Activity实例,则清空它上面所有的,并回调onNewIntent方法;
    如果指定该flag的Activity是standard模式的,如果栈中存在该Activity实例,那么该Activity连同它之上的Activity全部要出栈,并重新创建一个新的实例放入栈顶。
    FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    具有这个标记位的Activity不会出现在历史Activity的列表中(按back键的时候不见了)。等同于xml属性:android:excludeFroRecents=”true”.

    1.3 IntentFilter的匹配规则

    启动Activity分两种,==显式调用====隐式调用==

  • 显式调用
    Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
    startActivity(intent);
  • 隐式调用
    <activity android:name=".SecondActivity">
        <action android:name="com.example.activitytest.ACTION_START"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </activity>
    
    Intent intent = new Intent("com.example.activitytest.ACTION_START");
    startActivity(intent);

如果二者共存的话,以显式调用为主。

IntentFilter的相关属性

显式调用较为简单,这里主要讲一下隐式调用。它要求Intent能够==同时==匹配目标组件的IntentFilter中所设置的过滤信息(actioncategorydata),如果不匹配则无法启动Activity。
一个Activity可以有==多个==intent-filter,一个Intent只要能匹配==任何一组==intent-filter,即可成功启动对应的Activity。

  1. action的匹配规则
    • 是一个字符串,系统预定义了一些,我们也可以自定义,用“.”分隔符进行分割。(类似”com.demo.category”)
    • action必须存在(不能没有)且必须与过滤规则中的其中一个action相同(可以定义多个,也可以传多个,只要一个对上就行)。
    • action区分大小写
  2. category的匹配规则
    • 是一个字符串,系统预定义了一些,我们也可以自定义,用“.”分隔符进行分割。(类似”com.demo.category”)
    • category可以传多个(<= category的个数,但必须要都对上),但要保证传递过来的category都是与action是同一级别下的(只能是同一个intent-filter下的,跨intent-filter是不行的);
      可以不传,但在intent-filter中要加上,因为调用startActivity或startActivityForResult时,会默认携带上这个category,所以一般来说,只要想被隐式调用打开的Activity,都会加上这个默认的category。
  3. data的匹配规则
    <!--data结构-->
    <data android:scheme="string"
      android:host="string"
      android:port="80"
      android:path="/string"
      android:pathPattern="string"
      android:pathPrefix="/string"
      android:mimeType="text/plain"/>

data总体来说包括两部分:mimeTypeURI

  • mimeType表示image/ipeg,video/*等媒体类型
  • URI信息量相对大一点,其结构一般为:
    <scheme>://<host>:<port>/[<path>|<pathPrefix|<pathPattern>>] 

下边讲分别来介绍下各个节点数据的含义

  1. scheme:整个URI的模式,如常见的http,file等,注意如果URI中没有指定的scheme,那么整个uri无效
  2. host:URI的域名,比如我们常见的www.mi.com,www.baidu.com,与scheme一样,一旦没有host那么整个URI也毫无意义
  3. port:端口号,比如80,很容易理解,只有在URI中指定了scheme和host之后端口号才是有意义的;
  4. path,pathPattern,pathPrefix包含路径信息,path表示完整的路径,pathPattern在此基础上可以包含通配符,pathPrefix表示路径的前缀信息;

    Intent中必须有data数据;

Intent中的data必须和过滤规则中的某一个data完全匹配;

过滤规则中可以有多个data存在,但是Intent中的data只需匹配其中的任意一个data即可;

过滤规则中可以没有指定URI,但是系统会赋予其默认值:content和file,这一点在Intent中需要注意;

为Intent设定data的时候必须要调用setDataAndType()方法,而不能先setData再setType,因为这两个方法是互斥的,都会清除对方的值,这个有兴趣可以参见源码

在匹配规则中,data的scheme,host,port,path等属性可以写在同一个< />中,也可以分开单独写,其功效是一样的;

    <activity android:name=".MyActivity">
    <intent-filter>
        <action android:name="com.demo.a"/>
        <action android:name="com.demo.b"/>

        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="com.demo.c"/>
        <category android:name="com.demo.d"/>

        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>
    Intent intent = new Intent("com.demo.a");
    intent.addCategory("com.demo.c");
    intent.setDataAndType(Uri.parse("file://xxx"), "text/plain");
    startActivity(intent);

PS

  • 所有intent-filter匹配规则同样适用于ServiceBroadcastReceiver,但是在Android5.0以后需要显式调用来启动Service,否则会报异常:java.lang.IllegalArgumentException: Service Intent must be explicit。
  • 检测一下即将要打开的Activity是否存在?
    1. Intent中的resolveActivity(context.getPackageManager()), 如果返回null,则证明没有找到符合的Activity。
    public ComponentName resolveActivity(@NonNull PackageManager pm) {
        if (mComponent != null) {
            return mComponent;
        }

        ResolveInfo info = pm.resolveActivity(
            this, PackageManager.MATCH_DEFAULT_ONLY);
        if (info != null) {
            return new ComponentName(
                    info.activityInfo.applicationInfo.packageName,
                    info.activityInfo.name);
        }

        return null;
    }
  1. PackageManager的resolveActivity()和queryIntentActivities()
     public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags);
     
     public abstract List<ResolveInfo> queryIntentActivities(Intent intent, @ResolveInfoFlags int flags);

第一个参数当然是intent了,第二个参数我们要使用MATCH_DEFAULT_ONLY这个标记位,这个标记位会帮助我们,当这两个方法返回非null值时,返回的Activity一定是能够跳转的。如果不设置这个标记位,返回非null值时,可能跳转失败,因为是隐式调用,intent会默认带上,但不设置这个标志位的,返回的数据中可能是含有没设置这个默认category的,所以最好加上这个标记位。

  1. 比较重要的action和category
    <action android:name="android.intent.action.MAIN"/>

    <category android:name="android.intent.category.LAUNCHER"/>

这是一个App的入口。