Activity的生命周期/启动模式/过滤规则

Activity的生命周期/启动模式/过滤规则

1.1 Activity 的生命周期

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

  • onCreate:该阶段可以做一些初始化工作:加载布局资源(setContentView)、初始化所需数据等。

  • onRestart:Activity重新启动

  • onStart:Activity正在启动,即将开始。Activity已经显示了,但是我们还看不到,不能交互。

  • onResume:Activity可见,能交互。

  • onPause:Activity正在停止。可以做一些不太耗时的操作:存储数据、停止动画等。(特殊情况下快速切换会从onPause->onResume,几乎不能重现。)

  • onStop:Activity即将停止。可以做一些稍微重量级的回收,但不能太耗时。

  • onDestroy:Activity即将销毁。做回收工作和最终的资源释放。

    Activity生命周期

  • Activity生命周期说明

    • 特定Activity第一次启动:onCreate->onStart->onResume。
    • 用户打开新Activity或返回桌面:onPause->onStop。若新Activity采用透明主题,当前Activity不会回调onStop。
    • 用户再次回到原Activity时:onRestart->onStart->onResume。
    • 用户back键回退:onPause->onStop->onDestroy。
  • (onStart和onStop)与(onResume和onPause)的区别

    • onStart和onStop是从Activity是否可见这个角度来回调的。
    • onResume和onPause是从Activity是都位于前台(交互与否)这个角度来回调的。
  • 当前Activity为A,新Activity为B;发生的生命周期调用顺序

    • A:onPause->B:onCreate->B:onStart->B:onResume->A:onStop
    • 所以不能在onPause里做耗时操作。应使新Activity尽快到前台。

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

  • 注意:1.1.2部分在新版本(测试API>28)时会有一定差异,差异参考1.1.3部分补充内容。

  • 1.资源相关的系统配置发生改变导致Activity被杀死并重新创建:(手机横竖屏切换)

    异常情况下Activity重建

    • onSaveInstanceState调用是在onStop之前,与onPause无时序关系。Activity周期正常的情况下不会调用该回调。onRestoreInstanceState调用是在onStart之后。
    • Activity重建是,系统会调用onRestoreInstanceState,将Activity销毁时onSaveInstanceState方法保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。(可通过onRestoreInstanceState和onCreate方法判断Activity是否被重建。)
  • 2.内存不足导致低优先级的Activity被杀死

    • 优先级:前台Activity>可见的非前台Activity>后台Activity。

    • 当系统内存不足时,系统会按照优先级从低到高杀死进程,后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果进程中无四大组件会很快被系统杀死,所以后台程序不适合脱离四大组件独自运行在后台,最好的方式是放在Service中,从而保证进程有一定的优先级,不会轻易被系统杀死。

    • 如果不希望Activity在屏幕横竖切换是被重建,可以指定configChange属性:

      <!--可以使用'|'符号连接-->
      android:configChanges="orientation|screenSize"
      

      注意:不会被重建不是被销毁;是不在通过保存状态->销毁->恢复状态这一流程;上述例子可能会导致不会根据屏幕横竖屏切换加载适合的资源文件(可能横屏竖时采用的是不同资源,hdpi<->xhdpi)。不重建情况下,系统会调用onConfigurationChanged方法。

    • configChanges项目及含义

      项目 含义
      mcc SIM卡唯一标识IMSI ( 国际移动用户识别码)中的国家代码,由三位数字组成,中国为460。
      mnc SIM卡唯-标识IMSI (国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。
      locale 设备的本地位置发生了改变,一般指切换了系统语言
      keyboard 键盘类型发生了改变,比如用户使用了外插键盘
      touchscreen 触摸屏发生了改变。
      keyboardHidden 键盘的可访问性发生了改变,比如用户调出了键盘。
      navigation 系统导航方式发生了改变,比如采用了轨迹球导航。
      screenLayout 屏幕布局发生了改变,很可能是用户激活了另外一个显示设备。
      fontScale 系统字体缩放比例发生了改变,比如用户选择了一个新字号。
      uiMode 用户界面模式发生了改变,比如是否开启了夜间模式。
      orientation 屏幕方向发生了改变,比如旋转了手机屏幕。
      screenSize 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化。当API大于13时会导致Activity重启。
      smallestScreenSize 设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,比如用户切换到了外部的显示设备。当API大于13时会导致Activity重启。
      layoutDirection 当布局方向发生变化。

      常用localeorientationkeyboardHidden。注意screenSize和smallestScreenSize,这两个项目与编译有关,与运行环境无关,当API大于13时会导致Activity重启,所以在例子中也要连接上它。

1.1.3 关于新版情况下的onSaveInstanceState补充

  • 新版本(测试环境API>28)onSaveInstanceState被调用的情况分析

    • 首先onSaveInstanceState的调用的时间有所改变:改为onStop之后调用。(旧版本在onStop之前调用)

    • onSaveInstanceState的触发情况有所改变:在Activity有可能被恢复的情况下调用

    • 情况一:Activity A启动Activity B,那么A会在调用onStop后调用onSaveInstanceState;之后销毁B返回到A时:

      • 如果A因内存不足销毁了就会调用onRestoreInstanceState重建A。
      • 如果A没有被销毁就不调用onRestoreInstanceState。
    • 情况二:Activity A启动Activity B,之后销毁B返回到A时,B调用onStop后不会调用onSaveInstanceState,这是因为销毁B的行为表示B不会被恢复。

    • 情况三:对情况一的补充。如果Activity A启动Activity B,如果A仍在页面上有显示(透明模式的情况),那么A不会调用onSaveInstanceState。

  • 新版旧版onRestoreInstanceState对比

    • 1:在 资源相关的系统配置发生改变导致Activity被杀死并重新创建 情况下并无明显差异。调用周期依然为:onResume->onPause->onStop->onSaveInstanceState->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume。这个不同于Activity A启动Activity B的生命周期,A调用onPause后,B开始onCreate,B在onResume后,A开始onStop。

    • 2:在 内存不足导致低优先级的Activity被杀死 情况下有所差异。

      • 新版本调用onSaveInstanceState的根据是Activity有没有被恢复的可能,如果有可能被恢复就会在调用onStop后直接调用onSaveInstanceState。并且,在A被恢复时,会跟据A是否被销毁选择性调用onRestoreInstanceState。
      • 旧版本是在Activity在内存不足情况下,因优先级低要被销毁时,调用onSaveInstanceState。

1.2 启动模式

1.2.1 四种启动模式

  • 1.standard:标准模式。该模式下,谁启动新Activity,那么新Activity就运行在启动它的那个Activity所在的栈中。

    • 可能在低版本情况下可能会出现(Android9以上不会):

      使用ApplicationContext启动standard模式的Activity时会报错;因为非Activity类型的Context并无任务栈,需要添加FLAG_ACTIVITY_NEW_TASK标记位。

  • 2.singleTop:栈顶复用模式。会调用onNewIntent回调,通过该回调可以获得再请求当前Activity的信息。

  • 3.singleTask:栈内复用模式。

    • 系统会先寻找Activity A想要的任务栈,如果栈不存在,创建栈->创建实例A->压入栈。
    • 如果栈存在,在栈中寻找是否存在A的实例,不存在,创建实例A->压入栈;存在,将A调到栈顶并调用onNewIntent回调。
    • singleTask具有clearTop效果,会将栈内所有在A上的实例出栈。
    • 栈顶为A,再启动A时会经历onPause->onNewIntent->onResume。
  • 4.singleInstance:单实例模式。

1.2.2 TaskAffinity

  • TaskAffinity:任务相关性,标识一个Activity所需要的任务栈的名字。

  • 默认情况下,所有Activity所需的任务栈的名字为应用的包名。

  • 我们可以为每个Activity都单独指定TaskAffinity属性,要与包名不一样。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,其他情况无意义。任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。

  • 当TaskAffinity和singleTask启动模式配对使用时,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。

  • 当TaskAffinity和allowTaskReparenting结合使用的时候,比如应用A启动应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。

1.2.3 指定启动模式

  • 方式一:在Menifest文件中指定,android:launchMode="singleTask"
  • 方式二:在Intent中设置标志位,intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  • 优先级上,方式二>方式一。限定范围有所不同:方式一无法设定FLAG_ACTIVITY_CLEAR_TOP标识;方式二无法指定singleInstance模式。

1.2.4 Activity的标识

  • FLAG_ACTIVITY_NEW_TASK:当启动该标识的Activity A时,先查找是否存在A想要的任务栈(TaskAffinity判断),如果有,将这个栈移动到前台,并保持栈中的Activity原有顺序,然后创建实例A->压入栈;如果没有,创建栈->创建实例A->压入栈。
  • FLAG_ACTIVITY_SINGLE_TOP:指定singleTop启动模式。
  • FLAG_ACTIVITY_CLEAR_TOP:具有此标记的Activity启动时,同一个任务栈中所有位于它上面的Activity都要出栈。通常要配合FLAG_ACTIVITY_NEW_TASK使用。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的Activity不会出现在历史Activity的列表中,如果我们不希望用户通过历史列表回到我们的Activity时使用。等同于Menifest指定:android:excluedeFromRecents="true"

1.3 IntentFilter 的过滤规则(intent-filter)

  • 启动Activity时隐式调用需要Intent匹配目标组件的IntentFilter中的过滤信息,过滤信息有actioncategorydata。每种过滤信息可包含多个,当action、category、data完全匹配时才启动Activity。
  • 一个Activity可以有多个intent-filter,匹配任意一个即可启动Activity。
  • 补充:原则上一个Activity不应该既是隐式启动又是显式启动,但两种启动方式并存时,以显式启动为主。

1.3.1action的匹配规则

  • action是一个字符串,系统预定义了一部分,也可自定义。action区分大小写。
  • 一个过滤规则中可以有多个action,只要Intent中的action能够匹配过滤规则中的任何一个即视为匹配成功。
  • 通过intent.setAction("String")方法设置Intent的action。
  • Intent中必须有action。

1.3.2 category的匹配规则

  • category是一个字符串,系统预定义了一部分,也可自定义。category区分大小写。
  • Intent中如果有category,那么Intent中的所有category都必须和过滤规则中的某个或某几个category匹配。
  • Intent中如果没有category,视为通过category匹配。系统调用startAcivity或startActivityForResult时会默认为Intent加上"android.intent.category.DEFAULT"这个category,而我们设置intent-filter时必须指定"android.intent.category.DEFAULT"这个category。
  • 通过intent.addCategory("String")方法设置Intent的category。

1.3.3 data的匹配规则

  • 与action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。

  • data在过滤规则中存在两种写法:

    • 写法一:

      <intent-filter ...>
           <android:scheme="file" android:host="www.baidu.com" />
      	...
      </intent-filter>
      
    • 写法二:

      <intent-filter ...>
           <android:scheme="file" />
           <android:host="www.baidu.com" />
      	...
      </intent-filter>
      
    • 补充:scheme默认值为contentfile

  • 通过intent.setDataAndType(Uri.parse("URI"),"mimeType")方法设置Intent的data。

    • 注意intent.setData(Uri.parse("URI"))intent.setType("mimeType")方法会互相覆盖(源码中会将另一个属性设为null),如果要为Intent指定完整的data,要调用intent.setDataAndType(Uri.parse("URI"),"mimeType")方法。
  • 补充:data结构:

    • data语法:

      <data android:scheme="string"
            android:host="string"
            android:port="string"
            android:path="string"
            android:pathPattern="string"
            android:pathPrefix="string"
            android:mimeType="string" />
      
    • data由两部分组成,mimeType和URI。

    • mimeType指媒体类型,比如image/jpegaudio/mpeg4-genericvideo/*等。

    • URI:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

    • Scheme:URI的模式,比如httpfilecontent等。如果URI没有指定scheme,那么URI无效。

    • Host:URI的主机名,比如www.baidu.com。如果URI没有指定host,那么URI无效。

    • Port:URI的端口号,比如80。URI指定scheme和host时,port才有意义。

    • Path、pathPattern、pathPrefix:路径信息。path表示完整的路径信息;pathPattern表示完整的路径信息,但是可以包含通配符*;pathPrefix表示路径的前缀信息。

  • Intent-filter的匹配规则对于Service和BroadcastReceiver相同,不过对Service的建议是尽量使用显示方式启动服务。

1.3.4 判断隐式启动Activity是否匹配成功

  • 方法一PackageManagerresolveActivity方法:

    /**
      * 1.MATCH_DEFAULT_ONLY标识位表示匹配在intent-filter中声明了<category android:name="android.intent.category.DEFAULT" />的Activity。
      * 2.如果不使用该标识位,会返回不含DEFAULT的那些Activity,这些Activity不支持隐式启动,从而导致startActivity或startActivityForResult方法失败。
    */
    if(null!=getPackageManager().resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY)){
       try {
           startActivity(intent);
       }catch (ActivityNotFoundException e){
           e.printStackTrace();
       }
    }
    
    • 补充:PackageManager还提供queryIntentActivities方法,该方法会返回所有匹配成功的Activity的信息;而resolveActivityf方法返回最佳匹配成功的Activity的信息。
  • 方法二IntentresolveActivity方法:

    if(null!=intent.resolveActivity(getPackageManager())){
       try {
           startActivity(intent);
       }catch (ActivityNotFoundException e){
           e.printStackTrace();
       }
    }
    
  • 对于Service和BroadcastReceiver,PackageManager提供类似的方法去获取成功匹配的组件信息。

1.3.5 其他

  • 有一类重要的action和category:

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

    二者共同表明这是一个入口Activity(二者缺一不可),并且会出现在系统的应用列表中。

1.4 参考资料

  • Android 开发艺术探索
发布了64 篇原创文章 · 获赞 65 · 访问量 8819

猜你喜欢

转载自blog.csdn.net/qq_33334951/article/details/102901098