Activity的生命周期和启动模式

1.1 Activity的生命周期全面分析

用户正常使用情况下的生命周期 & 由于Activity被系统回收或者设备配置改变导致Activity被销毁重建情况下的生命周期。

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

Activity生命周期的切换过程

  1. Activity第一次启动:onCreate->onStart->onResume
  2. Activity切换到后台(用户打开新的Activity或者切换到桌面),onPause->onStop
  3. Activity从后台到前台,重新可见,onRestart->onStart->onResume
  4. 用户退出Activity,onPause->onStop->onDestroy
  5. onStart开始到onStop之前,Activity可见。onResume到onPause之前,Activity可以接受用户交互。
  6. 在新Activity启动之前,栈顶的Activity需要先onPause后,新Activity才能启动。所以不能在onPause执行耗时操作

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

1 系统配置变化导致Activity销毁重建

例如Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,Activity就会被销毁并重新创建。

  • 在异常情况下系统会在onStop之前调用onSaveInstanceState来保存状态。Activity重新创建后,会在onStart之后调用onRestoreInstanceState来恢复之前保存的数据
  • 保存数据的流程: Activity被意外终止,调用onSaveIntanceState保存数据-> Activity委托Window,Window委托它上面的顶级容器一个ViewGroup(书上说很可能就是DecorView)。然后顶层容器在通知所有子元素来保存数据。 每个View都有onSaveInstanceStateonRestoreInstanceState方法。查看TextView源码可以发现保存了文本选中状态和文本内容。
  • 系统只在Activity异常终止的时候才会调用onSaveInstanceStateonRestoreInstanceState方法。其他情况不会触发。

2 资源内存不足导致低优先级的Activity被回收

  1. 前台- 可见非前台(被对话框遮挡的Activity)-后台,这三种Activity优先级从高到低。
  2. android:configChanges="orientation"在manifest中指定configChanges在系统配置变化后不重新创建Activity,也不会执行onSaveInstanceStateonRestoreInstanceState方法,而是调用onConfigurationChnaged方法。
  3. configChanges一般常用三个选项:
    • locale 系统语言变化
    • keyborardHidden 键盘的可访问性发生了变化,比如用户调出了键盘
    • orientation 屏幕方向变化

1.2 Activity的启动模式

1.2.1 Activity的LaunchMode

Android使用栈来管理Activity。

1 standard

  • 每次启动都会重新创建一个实例,不管这个Activity在栈中是否已经存在。
  • 谁启动了这个Activity,那么Activity就运行在启动它的那个Activity所在的栈中。
  • 用Application去启动Activity时会报错,提示非Activity的Context没有所谓的任务栈。解决办法是为待启动Activity制定FLAG_ACTIVITY_NEW_TASH标志位,这样就会为它创建一个新的任务栈。

2 singleTop

  • 如果新Activity位于任务栈的栈顶,那么此Activity不会被重新创建,同时回调onNewIntent方法。
  • 如果新Activity已经存在但不是位于栈顶,那么新Activity仍然会被创建。

3 singleTask

  • 这是一种单实例模式
  • 只要Activity在栈中存在,那么多次启动这个Activity都不会重新创建实例,同时也会回调onNewIntent方法。
  • 同时会导致在Activity之上的栈内Activity出栈。

4 singleIntance

  • 具有singleTask模式的所有特性,同时具有此模式的Activity只能单独的位于一个任务栈中

TaskAffinity属性

TaskAffinity参数标识了一个Activity所需要的任务栈的名字。为字符串,且中间必须包含包名分隔符“.”。默认情况下,所有Activity所需的任务栈名字为应用包名。TashAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,其他情况下没有意义。 应用A启动了应用B的某个Activity后,如果Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。 打个比方就是,应用A启动了应用B的ActivityX,然后按Home回到桌面,单击应用B的图标,这时并不会启动B的主Activity,而是重新显示已经被应用A启动的ActivityX。这是因为ActivityX的TaskAffinity值肯定不和应用A的任务栈相同(因为包名不同)。所以当应用B被启动以后,发现ActivityX原本所需的任务栈已经被创建了,所以把ActivityX从A的任务栈中转移过来了。

设置启动模式

  1. manifest中 设置下的android:launchMode属性。
  2. 启动Activity的intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  3. 两种同时存在时,以第二种为准。第一种方式无法直接为Activity添加FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法指定singleInstance模式。
  4. 可以通过命令行adb shell dumpsys activity命令查看栈中的Activity信息。

1.2.2 Activity的Flags

这些FLAG可以设定启动模式、可以影响Activity的运行状态。

  • FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的Activity启动时,同一个任务栈中位于它上面的Activity都要出栈,一般和FLAG_ACTIVITY_NEW_TASK配合使用。效果和singleTask一样。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 如果设置,新的Activity不会在最近启动的Activity的列表(就是安卓手机里显示最近打开的Activity那个系统级的UI)中保存。

1.3 IntentFilter的匹配规则

启动Activity分为两种:

  1. 显示调用 明确指定被启动对象的组件信息,包括包名和类名
  2. 隐式调用 不需要明确指定组件信息,需要Intent能够匹配目标组件中的IntentFilter中所设置的过滤信息。
  3. IntentFilter中的过滤信息有action、category、data。
  4. 只有一个Intent同时匹配action类别、category类别、data类别才能成功启动目标Activity。
  5. 一个Activity可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。

1 action

  1. action是一个字符串。
  2. 一个intent-filter可以有多个aciton,只要Intent中的action能够和任何一个action相同即可成功匹配。匹配是指与action的字符串完全一样。
  3. Intent中如果没有指定action,那么匹配失败。

2 category

  1. category是一个字符串。
  2. Intent可以没有category,但是如果你一旦有category,不管有几个,每个都能够与intent-filter中的其中一个category相同。
  3. 系统在startActivitystartActivityForResult的时候,会默认为Intent加上android.intent.category.DEFAULT这个category,所以为了我们的activity能够接收隐式调用,就必须在intent-filter中加上android.intent.category.DEFAULT这个category。

3 data

  1. data的匹配规则与action一样,如果intent-filter中定义了data,那么Intent中必须要定义可匹配的data。
  2. intent-filter中data的语法:

     <data android:scheme="string"
             android:host="string"
             android:port="string"
             android:path="string"
             android:pathPattern="string"
             android:pathPrefix="string"
             android:mimeType="string"/>
    
  3. Intent中的data有两部分组成:mimeType和URI。mimeType是指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/等,可以表示图片、文本、视频等不同的媒体格式。

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

      //实际例子
      content://com.example.project:200/folder/subfolder/etc
      http://www.baidu.com:80/search/info
      
      • scheme:URI的模式,比如http、file、content等,默认值是file
      • host:URI的主机名
      • port:URI的端口号
      • path、pathPattern和pathPrefix:这三个参数描述路径信息。
        • path、pathPattern可以表示完整的路径信息,其中pathPattern可以包含通配符*,表示0个或者多个任意字符。
        • pathPrefix只表示路径的前缀信息。
    • Intent指定data时,必须调用setDataAndType方法,setDatasetType会清除另一方的值。

隐式调用需注意

  1. 当通过隐式调用启动Activity时,没找到对应的Activity系统就会抛出android.content.ActivityNotFoundException异常,所以需要判断是否有Activity能够匹配我们的隐式Intent。

    1. 采用PackageManagerresloveActivity方法

       public abstract List<ResolveInfo> queryIntentActivityies(Intent intent,int flags);
       public abstract ResolveInfo resloveActivity(Intent intent,int flags);
      

      以上的第二个参数使用MATCH_DEFAULT_ONLY,这个标志位的含义是仅仅匹配那些在intent-filter中声明了android.intent.category.DEFAULT这个category的Activity。因为如果把不含这个category的Activity匹配出来了,由于不含DEFAULT这个category的Activity是无法接受隐式Intent的从而导致startActivity失败。

    2. 采用IntentresloveActivity方法
  2. 下面的action和category用来表明这是一个入口Activity并且会出现在系统的应用列表中,二者缺一不可。
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    

results matching ""

    No results matching ""