参考 Google 官方教程

Activity 是什么

Activity 是四大组件之一,每个 Activity 有一个视图,是和用户交互的地方。一个 App 可以有零到多个 Activity,每个 Activity 都必须在 AndroidManifest.xml 清单文件中声明。

Activity 的生命周期

Activity 状态

Activity 基本以三种状态存在:

  • Resumed

    Activity 位于前台,用户可见。

  • Paused

    另一个 Activity(或者其他,比如 Dialog)位于前台获得用户焦点,但是这个 Activity 仍然可见。

  • Stopped

    该 Activity 完全被另一个 Activity 覆盖,用户不可见。

生命周期图

img

结合这张图,Activity的生命周期可分为三个部分:

  • Activity 的整个生命周期onCreate()onDestroy() 之间。

  • Activity 的可见生命周期onStart()onStop() 之间。

  • Activity 的前台申明周期在 onResume()onPause() 之间。**当在 A activity 启动 B activity 时,完整的调用如下:A:onPause() -> B:onCreate() -> B:onStart() -> B:onResume() -> A:onStop() 。**为了尽快的显示 B activity, 所以在这两个方法内尽量少写耗时的代码。在系统熄屏的时候,也会调用 onPause()

可以新建一个 Activity 基类来显示每个子类的生命周期:

 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
abstract class BaseActivity : AppCompatActivity() {
  val TAG = javaClass.simpleName

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    title = javaClass.simpleName
    Log.d(TAG, "onCreate")
  }

  abstract fun initView()
  fun initData() {}

  override fun onStart() {
    super.onStart()
    Log.d(TAG, "onStart")
  }

  override fun onResume() {
    super.onResume()
    Log.d(TAG, "onResume")
  }

  override fun onPause() {
    super.onPause()
    Log.d(TAG, "onPause")
  }

  override fun onStop() {
    super.onStop()
    Log.d(TAG, "onStop")
  }

  override fun onDestroy() {
    super.onDestroy()
    Log.d(TAG, "onDestroy")
  }

  override fun onRestart() {
    super.onRestart()
    Log.d(TAG, "onRestart")
  }
}

名为“是否能事后终止?”的列表示系统是否能在不执行另一行 Activity 代码的情况下,在方法返回后随时终止承载 Activity 的进程。 有三个方法带有“是”标记:(onPause()onStop() 和 onDestroy())。由于 onPause() 是这三个方法中的第一个,因此 Activity 创建后,onPause() 必定成为最后调用的方法,然后才能终止进程 — 如果系统在紧急情况下必须恢复内存,则可能不会调用 onStop() 和 onDestroy()。**因此,您应该使用 onPause() 向存储设备写入至关重要的持久性数据(例如用户编辑)。**不过,您应该对 onPause() 调用期间必须保留的信息有所选择,因为该方法中的任何阻止过程都会妨碍向下一个 Activity 的转变并拖慢用户体验。

这段话可能翻译的有些问题,其实就是说,在 调用 onPause() 之后系统就可以结束这个 Activity。所以应该在 onPause() 里做持久化。

startActivity

启动一个 Activity,同时可以通过 Intenet 向下一个应用传递数据,但是有大小限制(?)

  • 显式启动

    1
    
    startActivity(Intent(this, SecondActivity::class.java)
    
  • 隐式启动

    1
    2
    3
    4
    
    val intent = Intent(Intent.ACTION_SEND).apply {
        putExtra(Intent.EXTRA_EMAIL, recipientArray)
    }
    startActivity(intent)
    

startActivityForResult

startActivityForResult() 方式启动 Activity,可以让这个 Activity 返回结果数据给上一个应用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
override fun initView() {
    first_button.setOnClickListener {
    startActivityForResult(Intent(this, SecondActivity::class.java).apply {
        val text = first_edit.text.toString()
        putExtra("sendToSecond", rot13(
            if (text.isNotBlank()) text else "Hello world")
        )
      }, 1)
    }
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == 1) {
      if (resultCode == Activity.RESULT_OK) {
        val decrypted = data.getStringExtra("fromSecond")
        toast("from SecondActivity: $decrypted")
        Log.d(TAG, "onActivityResult: from SecondActivity: $decrypted")
      } else {
        toast("onActivityResult: no data from SecondActivity")
      }
    }
}

保存 Activity 的状态

pause 和 stop Activity

当 Activity 暂停或停止时,Activity 的状态会得到保留。 确实如此,因为当 Activity 暂停或停止时,Activity 对象仍保留在内存中 — 有关其成员和当前状态的所有信息仍处于活动状态。 因此,用户在 Activity 内所做的任何更改都会得到保留,这样一来,当 Activity 返回前台(当它“继续”)时,这些更改仍然存在。

重建 Activity

不过,当系统为了恢复内存而销毁某项 Activity 时(只有当内存极度不足时才会发生),Activity 对象也会被销毁,因此系统在继续 Activity 时根本无法让其状态保持完好,而是必须在用户返回 Activity 时重建 Activity 对象。但用户并不知道系统销毁 Activity 后又对其进行了重建,因此他们很可能认为 Activity 状态毫无变化。 在这种情况下,您可以实现另一个回调方法对有关 Activity 状态的信息进行保存,以确保有关 Activity 状态的重要信息得到保留:onSaveInstanceState()

使用 Bundle 来暂时保存数据,但是因为系统不保证会调用 ``onSaveInstanceState(),最好不要在这里保存重要的数据,而应该在onPause() 里。向 Bundle里传数据和Intent` 类似。借此,可以恢复一些 UI 的信息(比如在 EditView 里输入的文字)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_first)

    if (savedInstanceState != null) {
      first_edit.setText(savedInstanceState.getString("edit") ?: "")
    }
    initView()
  }
override fun onSaveInstanceState(outState: Bundle?) {
    super.onSaveInstanceState(outState)
    outState?.putString("edit", first_edit.text.toString())
  }

即使不实现 onSaveInstanceState() ,系统也会默认实现:为每个布局恢复状态,比如,EditText 小部件保存用户输入的任何文本,CheckBox 小部件保存复选框的选中或未选中状态。只需为想要保存其状态的每个小部件提供一个唯一的 ID(通过 android:id 属性)。如果小部件没有 ID,则系统无法保存其状态。

这里使用 CheckBox 作为例子,只需要旋转一下屏幕就可以让 Activity 销毁并重建,可以发现有 ID 的 checkbox1 恢复了之前的选中状态,而第二个 CheckBox 则没有。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    <CheckBox
        android:id="@+id/checkbox1"
        android:hint="check1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/home_text" />

    <CheckBox
        android:hint="check2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/check1" />

1526456700052

Screenshot_1526456426

Screenshot_1526456686

TextView 的状态恢复

由于 onSaveInstanceState() 不一定会被调用,TextView里的文本会在屏幕旋转之后初始化,比如下面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class MainActivity : AppCompatActivity() {
    companion object {
        var thisCount = 0;
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun countUp(view: View) {
        thisCount++
        show_count.text = thisCount.toString()
    }
}

在屏幕旋转后,show_count显示0,但是count的值仍然为之前的值。要让 TextView 保存之前的状态,可以参考 stackoverflow上的解答:

1
2
3
<TextView
     ...
     android:freezesText="true" />

任务和返回栈

翻译自 https://developer.android.com/guide/components/activities/tasks-and-back-stack

任务是用户在执行某项工作时与之交互的 Activity 集合。而返回栈是 Activity 的的排列方式,以它被打开的顺序。

Activity 和任务的默认行为概括如下:

  • 当 Activity A 启动 Activity B,Activity A stop 了,但是系统保持他的状态,如果用户在 Activity B 按下返回键,Activity A 会 resume, 并且保持原先的状态。
  • 当用户按下 Home 键 离开一个任务,当前的 Activity stop,并且任务处于后台。系统会保持任务中的所有 Activity 的状态。如果用户返回这个任务,这个任务回到前台,并且 resume 在栈顶的 Activity。
  • 如果用户一直按返回键,当然的 Activity stop 了,这个 Activity 出栈,停止并销毁。前一个 Activity resume。当 Activity 销毁时,系统不会保持它的状态。
  • 一个 Activity 可以被多次实例化,甚至从不同的任务里。

Activity 的启动模式

这里有个 App 帮你理解四种启动模式。

启动模式允许您定义活动的新实例如何与当前任务相关联。您可以通过两种方式定义不同的启动模式:

  • 使用 manifest 文件
  • 使用 Intent flag

使用 manifest 文件

standard

默认的模式,系统创建一个 Activity 的新实例,从它启动的地方,向它传递 Intent。Activity 可以被多次实例化,每个实例可以属于不同的任务,每个任务可以包含多个相同的 Activity。

singleTop

如果一个 Activity 已经在栈顶那么系统不会新建一个实例,而是调用这个 Activity 的 onNewIntent() 方法。当然如果这个 Activity 不再栈顶,那么处理方式和 standard 模式一样。

singleTask

创建一个新的任务来在其中启动这个 Activity,但是如果这个活动的实例已经存在,系统不会创建新的实例,而是调用这个 Activity 的 onNewIntent() 方法。把其他的 Activity 都出栈,让其处于栈顶。

The affinity indicates which task an activity prefers to belong to. By default, all the activities from the same app have an affinity for each other. So, by default, all activities in the same app prefer to be in the same task. However, you can modify the default affinity for an activity. Activities defined in different apps can share an affinity, or activities defined in the same app can be assigned different task affinities.

其实只是在 manifest 这样设置 android:launchMode="singleTask",Activity 也不会在新的 Task 里打开,而只有你改变 ndroid:taskAffinity 这个属性时,Activity 才会在新的 Task 亮丽启动。

无论是在新的任务中启动 Activity 还是在新的 Activity 中启动活动,返回键总是会让界面返回上一个活动。但是如果你以 singleTask 模式启动,如果这个 Activity 已经存在后台的任务中,那么整个任务(栈中的所有 Activity)都会入栈。

singletask_multiactivity

####singleInstance

singleTask 相同,但系统不会将任何其他活动发送到持有该实例的任务中。该活动始终是其任务中唯一且唯一的成员;任何一次模式启动的活动都将在单独的任务中打开。

使用 Intent flag

在向 startActivity()传递树立 FLAG 的 intent。

1
2
3
startActivity(Intent(this, FirstActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
})
  • FLAG_ACTIVITY_NEW_TASK

    sinleTask相同

  • FLAG_ACTIVITY_SINGLE_TOP

    singleTop相同

  • FLAG_ACTIVITY_CLEAR_TOP

    如果要启动的活动已经在当前任务中运行,那么不会创建该活动的新实例,而是将其上的所有其他活动销毁(出栈),并将此意向传递给活动的恢复实例(位于栈顶),调用onNewIntent())。