Activity生命周期和启动模式探究

目录

  1. Activity的生命周期分析
  2. 处理配置变更
  3. 任务和返回栈
  4. Intent 和 IntentFilter
  5. 参考

最近通过官网重新学习了Activity的相关知识,觉得收获挺大,遂总结一下。

Activity的生命周期分析

Tips:

  1. onCreate()在整个生命周期只出现一次,建议执行Activity的基本设置,例如:声明用户界面(在XML 布局文件中定义),定义成员变量,以及配置某些UI.
  2. onDestroy()建议进行终止在onCreate()期间创建的后台线程或其他如若未正确关闭可能导致内存泄露的长期运行资源.
  3. oncreate()内调用finish()时,系统会立刻调用onDestroy().
  4. onResume()建议初始化在onPause()期间释放的组件并且执行每当Activity进入“继续”状态时必须进行的任何其他初始化操作(比如开始动画和初始化只在Activity具有用户焦点时使用的组件)
  5. onStop()建议释放几乎所有用户不使用时不需要的资源,执行更大、占用更多CPU的关闭操作,比如向数据库写入信息
  6. onRestart()没有适用于一般应用群体的任何方法指导原则。onStart()方法作为onStop对应方法,可以初始化对应关闭的资源。比如:因为用户可能在回到它之前已离开应用很长时间,onStart() 方法是确认所需系统功能已启动的理想选择
  7. onPause()建议进行如下操作:
  • 停止动画或其他可能消耗 CPU 的进行之中的操作;
  • 提交未保存的更改,但仅当用户离开时希望永久性保存此类更改(比如电子邮件草稿);
  • 释放系统资源,比如广播接收器、传感器手柄(比如 GPS)或当您的Activity暂停且用户不需要它们时仍然可能影响电池寿命的任何其他资源

Tips:

  1. Activity创建后, onPause()必定成为最后调用的方法,然后才能终止进程(如果系统在紧急情况下必须恢复内存,则可能不会调用onStop()onDestroy())。因此,您应该使用onPause() 向存储设备写入至关重要的持久性数据。
  2. 在从onPause()返回的时间到onResume()被调用的时间,系统可以终止Activity。
  • Activity A 的onPause()
  • Activity B 的onCreate()onStart()onResume()方法依次执行(Activity B 现在具有用户焦点。)
  • 如果 Activity A 在屏幕上不再可见,则其onStop()方法执行

Tips:

  1. Activity因正常应用行为(用户按下返回或自行完成而被销毁时)不会调用onSaveInstanceState();其他情况(比如:按下电源、Home键;启动新的Activity等)会进行调用,调用时机可能在onPause()onStop()之前。
  2. 为了Android系统恢复Activity中视图的状态,每个视图必须具有android:id属性提供的唯一ID。还可以通过将 android:saveEnabled属性设置为 “false” 或通过调用setSaveEnabled()方法显式阻止布局内的视图保存其状态.
  3. 恢复Activity状态建议在onRestoreInstanceState()中,调用时机在onStart()之后,其参数Bundle savedInstanceState一定不为null

处理配置变更

有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 发生此类变化时,Android 会重建运行中的 Activity(系统调用onDestroy(),然后立即调用onCreate())。此行为旨在通过利用您提供的备用资源(例如适用于不同屏幕方向和屏幕尺寸的不同布局)自动重新加载您的应用来帮助它适应新配置。 如果重启应用需要恢复大量数据时,不仅成本高昂,而且给用户留下糟糕的使用体验。有如下两种解决方案:

1.在配置变更期间保留对象(使用Fragment),官方例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class RetainedFragment extends Fragment {

// data object we want to retain
private MyDataObject data;

// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}

public void setData(MyDataObject data) {
this.data = data;
}

public MyDataObject getData() {
return data;
}
}


public class MyActivity extends Activity {

private RetainedFragment dataFragment;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}

// the data is available in dataFragment.getData()
...
}

@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}

2.自行处理配置变更
声明Activity将自行处理配置变更,这样可以阻止系统重启Activity。在清单文件中编辑相应的<activity>元素,以包含 android:configChanges属性以及代表要处理的配置值.最常用的是android:configChanges="orientation|screenSize|keyboardHidden|",避免因屏幕方向和可用键盘改变而导致重启.当其中一个配置发生变化时,onConfigurationChanged()会被调用,此时Activity的Resources对象会相应地进行更新,以根据新配置返回资源。

任务和返回栈

  1. 任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即“返回栈”)中。
  2. 设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷键)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。
  3. android:taskAffinity 用于指定Activity属于哪个任务,默认情况下同一个application的Activities都属于一个任务,如果不指定则默认值是<application>中的taskAffinity值,该值默认是<manifest>中的包名。一个Activity任务的Affinity取决于根Activity。
  4. android:launchMode 详解
    • standard 默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
    • singleTop 如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。
    • singleTask 使用singleTask启动模式的Activity在系统中只会存在一个实例。如果这个实例已经存在,Intent就会通过onNewIntent传递到这个Activity。否则新的Activity实例被创建。(官网说的不明确,以下参照相关实验结果和源码分析)
      • taskAffinity相同
        • 如果系统中不存在singleTask Activity的实例,那么就需要创建这个Activity的实例,并且将这个实例放入和调用者相同的Task中并位于栈顶
        • 如果singleTask Activity实例已然存在,那么在Activity回退栈中,所有位于该Activity上面的Activity实例都将被销毁掉(销毁过程会调用Activity生命周期回调),这样使得singleTask Activity实例位于栈顶。与此同时,Intent会通过onNewIntent传递到这个SingleTask Activity实例。
      • taskAffinity不同
        • 如果singleTask Activity所在的应用进程存在,但是singleTask Activity实例不存在,那么从别的应用启动这个Activity,新的Activity实例会被创建,并放入到所属进程所在的Task中,并位于栈顶位置。
        • 如果singleTask Activity所在的应用进程存在,但是singleTask Activity实例也存在,参照官网的图理解:
    • singleInstancesingleTask相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。
  5. android:allowTaskReparenting 设为true时,Activity可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。该值默认为false,只对standardsingleTop启动的Activity起作用。

  6. 常用的Intent标志

  7. 清理返回栈
    如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根任务除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。可以使用下列几个 Activity 属性修改此行为(实验发现这些参数通过overview screen返回app时无效)
  8. activity-alias使用举例
    重复使用Activity而设计,典型应用场景是实现Launcher中多个Icon进入同一个app,示意图如下:

Intent 和 IntentFilter

Intent是一个消息传递对象,可以使用它从其他应用组件请求操作。主要场景就是:启动Activity、启动服务和传递广播。其中如果希望Activity完成后收到结果,需要调用startActivityForResult(),在Activity的onActivityResult()回调中接收Intent对象。

  1. Intent类型

    • 显示Intent 按名称(完全限定类名)指定要启动的组件。创建显式Intent启动Activity或Service时,系统将立即启动Intent对象中指定的应用组件。为了确保应用的安全性,启动Service时,请始终使用显式Intent,且不要为服务声明Intent过滤器。
    • 隐式Intent 如果Activity没有申明Intent过滤器,则只能显示启动。

  2. Intent构成:

    • ComponentName
      使用显示Intent时必须指定ComponentName

    • Action
      系统定义的Action参考 https://developer.android.com/guide/components/intents-common.html
    • Data
      由URI和MIME类型组成,若要同时设置URI和MIME类型,请勿调用setData()和setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType()同时设置URI和MIME类型。
    • Category
      使用addCategory()指定,完整列表参考Intent类。
    • Extras
      key-value 或者 Bundle
    • Flags
  3. 隐式Intent注意事项

    1. 验证Activity是否会接收Intent,调用Intent对象resolveActivity()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // Create the text message with a string
      Intent sendIntent = new Intent();
      sendIntent.setAction(Intent.ACTION_SEND);
      sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
      sendIntent.setType("text/plain");

      // Verify that the intent will resolve to an activity
      if (sendIntent.resolveActivity(getPackageManager()) != null) {
      startActivity(sendIntent);
      }
    2. 强制使用应用选择器
      如果有多个应用响应隐式 Intent,则用户可以选择要使用的应用,并将其设置为该操作的默认选项。如果多个应用可以响应 Intent,且用户可能希望每次使用不同的应用,则应采用显式方式显示选择器对话框。选择器对话框要求用户选择每次操作要使用的应用(用户无法为该操作选择默认应用)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      Intent sendIntent = new Intent(Intent.ACTION_SEND);
      ...

      // Always use string resources for UI text.
      // This says something like "Share this photo with"
      String title = getResources().getString(R.string.chooser_title);
      // Create intent to show the chooser dialog
      Intent chooser = Intent.createChooser(sendIntent, title);

      // Verify the original intent will resolve to at least one activity
      if (sendIntent.resolveActivity(getPackageManager()) != null) {
      startActivity(chooser);
      }
    3. 隐式Intent的匹配规则
      要公布应用可以接收哪些隐式Intent,需要在清单文件中使用<intent-filter>元素为每个应用组件声明一个或多个Intent过滤器。一个过滤器包括多个<action><data><category>标签。为了接收隐式Intent,必须将CATEGORY_DEFAULT类别包括在Intent过滤器中。

      • <action> : 传入Intent中指定的Action必须与<IntentFilter>中的Action某一项匹配;如果<IntentFilter>中Action为空,则所有Intent都无法匹配;但如果Intent未指定Action,则会通过测试(只要<IntentFilter>至少包含一个Action)。
      • Category : Intent中的每个Category均必须与过滤器中的Category匹配;如果Intent没有声明Category,则始终会通过测试。Android会自动将CATEGORY_DEFAULT应用于传递给startActivity()和startActivityForResult()的所有隐式Intent。因此,如需Activity接收隐式Intent,则必须将 “android.intent.category.DEFAULT”包括在其<IntentFilter>中。
      • Data : 由URI和MIME类型组成,URI格式是<scheme>://<host>:<port>/<path>;MIME类型在Android中区分大小写,官方建议全部小写。其匹配规则总结如下:
        • Intent中的URI仅与<IntentFilter>中包含的部分URI进行比较;
        • <IntentFilter>中未指定URI或MIME时,不含URI和MIME类型的Intent才会通过测试
        • 对于包含URI、但不含MIME类型(既未显式声明,也无法通过URI推断得出)的Intent,仅当其URI与<IntentFilter>的URI格式匹配、且同样未指定MIME类型时,才会通过测试
        • 仅当<IntentFilter>列出相同的MIME类型且未指定URI格式时,包含MIME类型、但不含URI的Intent才会通过测试
        • 注意: <IntentFilter>默认支持content:file:数据
        • PackageManager提供了一整套query…()方法来返回所有能够接受特定Intent的组件。此外,它还提供了一系列类似的resolve…()方法来确定响应Intent的最佳组件
  4. PendingIntent
    Intent对象的包装器,主要目的是授权外部应用使用包含的Intent,就像是它从当前应用本身的进程中执行的一样。

  5. Overview Screen
    概览屏幕(也称为最新动态屏幕、最近任务列表或最近使用的应用)是一个系统级别UI,其中列出了最近访问过的Activity和任务。 用户可以浏览该列表并选择要恢复的任务,也可以通过滑动清除任务将其从列表中删除。 对于Android 5.0版本(API级别21),包含多个文档的同一Activity的多个实例可能会以任务的形式显示在概览屏幕中。需要定制的场景不多,具体使用参考(https://developer.android.com/guide/components/recents.html)。

参考

[1] https://developer.android.com/guide/components/activities.html
[2] https://developer.android.com/guide/components/intents-filters.html