Android Kotlin 基础知识:生命周期和日志记录

简介

在此 Codelab 中,您将了解 Android 的基本组成部分:activity 和 fragment 生命周期。activity 生命周期是 activity 在其整个存在期间可以处于的一组状态。生命周期涵盖从 activity 最初创建一直到它被销毁以及系统收回相应 activity 资源的过程。当用户在应用中的 activity 之间导航(以及进入或退出应用)时,这些 activity 都会在 activity 生命周期的不同状态之间转换。

fragment 生命周期与 activity 生命周期非常相似。此 Codelab 主要介绍 activity,在最后会简要介绍 fragment。

作为 Android 开发者,您需要了解 activity 生命周期。如果您的 activity 无法正确响应生命周期状态变化,您的应用可能会生成奇怪的错误、出现让您的用户感到困惑的行为,或使用过多的 Android 系统资源。了解 Android 生命周期并正确响应生命周期状态变化,对于成为 Android 优秀公民至关重要。

您应当已掌握的内容

  • 什么是 activity,以及如何在应用中创建 activity。
  • activity 的 onCreate() 方法的用途,以及在该方法中执行的那类操作。
  • 如何为 activity 创建 XML 布局,以及如何在运行时更新布局。

学习内容

  • 如何将日志记录信息输出到 Android Studio Logcat 窗口(有时称为 Android 控制台或 Android Monitor)。
  • ActivityFragment 生命周期的基础知识,以及当 activity 在不同生命周期状态之间切换时调用的回调。
  • 如何替换生命周期回调方法,以便在 activity 生命周期的不同时间执行操作。
  • 如何在应用中使用 Timber 库进行日志记录。

实践内容

  • 修改名为 DessertClicker 的入门应用,以添加在 Logcat 中显示的日志记录信息。
  • 替换生命周期回调方法并记录对 activity 状态的更改。
  • 运行应用,并记下 activity 启动、停止和恢复时显示的日志记录信息。
  • 修改应用以使用 Timber 库。
  • 向 AndroidTrivia 应用添加日志记录信息,并监控 fragment 状态的变更。

在此 Codelab 中,您将使用一款名为 DessertClicker 的初始应用。在此应用中,每当用户点按屏幕上的甜点时,该应用就会为用户“购买”相应甜点。应用会在布局中更新已购甜点的数量值,以及用户消费的总金额。

8216c20f5571fc04.png

此应用存在多个与 Android 生命周期有关的 bug:例如,在某些情况下,此应用会将甜点数量值重置为 0;此外,此应用即使在后台运行时也会继续使用系统资源。了解 Android 生命周期可以帮助您理解这些问题发生的原因以及解决方法。

每个 activity 和每个 fragment 都具有生命周期。这个词源于动物的生命周期,就像这只蝴蝶的生命周期:蝴蝶的不同状态显示了它从出生到完全成年再到死亡的成长过程。

c685f48ff799f0c9.png

同样,activity 生命周期由 activity 可能会经历的不同状态组成:从 activity 首次初始化,到最终销毁,再由系统收回其内存。当用户启动您的应用、在 activity 之间导航、在应用内部和外部导航时以及离开应用时,activity 会切换状态。下图显示了 activity 生命周期的所有状态。顾名思义,这些状态表示 activity 所处的状态。

c803811f4cb4034b.png

在 activity 生命周期状态发生变化时,您通常需要更改某些行为,或者运行一些代码。因此,Activity 类本身以及 Activity 的任何子类(例如 AppCompatActivity)都会实现一组生命周期回调方法。Android 会在 activity 从一种状态切换为另一种状态时调用这些回调,而您可以在自己的 activity 中替换这些方法,通过执行任务来响应这些生命周期状态变化。下图显示了生命周期状态以及可用的可替换回调。

f6b25a71cec4e401.png

fragment 也具有生命周期。fragment 的生命周期与 activity 的生命周期类似,因此您学到的很多知识都适用于这两者。在此 Codelab 中,您将集中了解 activity 生命周期,因为它是 Android 的基本组成部分,并且最易于在简单应用中观察。以下是相应的 fragment 生命周期图:

8494ec955ce0c49d.png

请务必了解要在何时调用这些回调,以及在每个回调方法中应执行的操作。但这两个图都很复杂,可能会令人困惑。在此 Codelab 中,您并不只是要了解每种状态和回调的含义,而是需要进行一些检测工作,逐渐了解发生的情况。

第 1 步:检查 onCreate() 方法并添加日志记录

为了弄清 Android 生命周期的一些情况,了解何时调用各种生命周期方法会很有帮助。这有助于您找出 DessertClicker 中出现问题的地方。

一种简单的方式是使用 Android 日志记录 API。通过日志记录功能,您可以在应用运行期间向控制台写入简短的消息,并利用它们来显示不同回调的触发时间。

  1. 下载 DessertClicker 起始应用,并在 Android Studio 中打开它。
  2. 编译并运行应用,然后在甜点图片上点按几次。请注意 Desserts Sold 的值与美元总金额发生的变化。
  3. 打开 MainActivity.kt 并检查此 activity 的 onCreate() 方法
override fun onCreate(savedInstanceState: Bundle?) {
...
}

在 activity 生命周期图中,您可能认出了 onCreate() 方法,因为您之前使用过此回调。这便是每个 activity 都必须实现的方法。您可使用 onCreate() 方法为 activity 执行所有一次性初始化。例如,在 onCreate() 中,您可以膨胀布局、定义点击监听器或设置数据绑定。

9be2255ff49e0af8.png

系统会在初始化 activity 之后(在内存中创建新的 Activity 对象后)立即调用一次 onCreate() 生命周期方法。执行 onCreate() 后,相应 activity 会被视为已创建

  1. onCreate() 方法中,在对 super.onCreate() 的调用后,紧接着添加下面一行代码。根据需要导入 Log 类。(按 Alt+Enter,或者在 Mac 上按 Option+Enter,然后选择 Import。)
Log.i("MainActivity", "onCreate Called")

Log 类会将消息写入 Logcat。此命令包含三个部分:

  • 日志消息的严重程度,即消息的重要性。在本示例中,Log.i() 方法会写入一条信息性消息。Log 类中的其他方法包括 Log.e()(表示错误)或 Log.w()(表示警告)。
  • 日志标签,在本示例中为 "MainActivity"该标签是一个字符串,可让您更轻松地在 Logcat 中找到自己的日志消息。该标签通常是类的名称。
  • 实际的日志消息,即一个简短的字符串,在本示例中为 "onCreate called"
  1. 编译并运行 DessertClicker 应用。当您点按甜点后,应用行为没有出现任何变化。在 Android Studio 中,点击屏幕底部的 Logcat 标签页。

ff9c50376701877f.png

Logcat 是用于记录消息的控制台。来自 Android 的关于您应用的消息会出现在这里,包括您使用 Log.i() 方法或其他 Log 类方法显式发送到日志的消息。6. 在 Logcat 窗格的搜索字段中输入 I/MainActivityf5c091e2b480edf8.png

Logcat 可能包含很多消息,大多数消息对您而言都无用。您可以通过多种方式过滤 Logcat 条目,但搜索是最简单的方式。由于您在代码中使用了 MainActivity 作为日志标签,因此您可以使用该标签来过滤日志。在开头添加 I/ 表示这是一条信息性消息,由 Log.i() 创建。

您的日志消息包含日期、时间、软件包名称 (com.example.android.dessertclicker)、您的日志标签(开头为 I/)以及实际消息。由于此消息出现在日志中,所以您会知道 onCreate() 已执行。

第 2 步:实现 onStart() 方法

系统会在调用 onCreate() 生命周期方法之后立即调用 onStart()onStart() 运行后,您的 activity 会显示在屏幕上。与为初始化 activity 而仅调用一次的 onCreate() 不同,onStart() 可在 activity 的生命周期内多次调用。

385df4ce82ae2de9.png

请注意,onStart() 需要与相应的 onStop() 生命周期方法配对使用。如果用户启动您的应用后又返回设备的主屏幕,相应 activity 会停止,并且不会再在屏幕上显示。

  1. 在 Android Studio 中,打开 MainActivity.kt,依次选择 Code > Override Methods,或按 Control+o。此时,系统会显示一个对话框,其中包含您可以在此类中替换的所有方法的庞大清单。e1f2460242b2ae.png
  2. 开始输入 onStart 以搜索所需的方法。如需滚动到下一个匹配项,请使用向下箭。从列表中选择 onStart(),然后点击 OK 插入样板替换代码。代码如下所示:
override fun onStart() {
   super.onStart()
}
  1. onStart() 方法中,添加一条日志消息:
override fun onStart() {
   super.onStart()

   Log.i("MainActivity", "onStart Called")
}
  1. 编译并运行 DessertClicker 应用,然后打开 Logcat 窗格。在搜索字段中输入 I/MainActivity,以过滤日志。请注意,onCreate()onStart() 方法会依次调用,并且您的 activity 会显示在屏幕上。
  2. 按设备上的主屏幕按钮,然后使用“最近使用的应用”屏幕返回相应 activity。请注意,activity 会从上次停止的位置恢复,同时使用所有相同的值,并且 onStart() 会再次记录到 Logcat 中。另请注意,系统通常不会再次调用 onCreate() 方法。

在此任务中,您将修改应用以使用名为 Timber 的热门日志记录库。与内置的 Android Log 类相比,Timber 具有多项优势。具体而言,Timber 库具有下列优势:

  • 可以根据类名称为您生成日志标签。
  • 有助于避免在您的 Android 应用的发布版本中显示日志。
  • 支持与崩溃报告库集成。

您可以立即见证到第一项优势;其他几项优势会让您在制作和运送更庞大的应用时大获裨益。

第 1 步:将 Timber 添加到 Gradle

  1. 转到 GitHub 上的 Timber 项目,然后复制 Download 标题下以 implementation 一词打头的那行代码。代码行可能如下所示,但版本号可能不同。
implementation 'com.jakewharton.timber:timber:4.7.1'
  1. 在 Android Studio 的“Project: Android”视图中,展开 Gradle Scripts,并打开 build.gradle (Module: app) 文件。
  2. 在依赖项部分中,粘贴您复制的那行代码。
dependencies {
   ...
   implementation 'com.jakewharton.timber:timber:4.7.1'
}
  1. 点击 Android Studio 右上角的 Sync Now 链接重建 Gradle。构建应该能顺利执行,不会出现任何错误。

第 2 步:创建 Application 类并初始化 Timber

在此步骤中,您将创建一个 Application 类。Application 是一个基类,包含整个应用的全局应用状态。它也是操作系统用于与您的应用交互的主要对象。如果您未指定 Application 类,Android 会使用默认的 Application 类,因此,您的应用始终存在 Application 对象,无需您执行任何特殊操作来创建它。

Timber 使用 Application 类,因为整个应用都将使用此日志记录库,并且此库需要先初始化一次,然后才能完成其他一切设置。在此类情况下,您可以子类化 Application 类,并使用您自己的自定义实现来替换默认实现。

创建 Application 类后,您需要在 Android 清单中指定该类。

  1. dessertclicker 软件包中,创建一个名为 ClickerApplication 的新 Kotlin 类。为此,请展开 app > java,然后右键点击 com.example.android.dessertclicker。依次选择 New > Kotlin File/Class
  2. 将该类命名为 ClickerApplication,并将 Kind 设置为 Class。点击 OK

Android Studio 会创建一个新的 ClickerApplication 类,并在代码编辑器中打开它。代码如下所示:

package com.example.android.dessertclicker

class ClickerApplication {
}
  1. 将类定义更改为 Application 的子类,并在必要时导入 Application 类。
class ClickerApplication : Application() {
  1. 如需替换 onCreate() 方法,请依次选择 Code > Override Methods,或按 Control+o
class ClickerApplication : Application() {
   override fun onCreate() {
       super.onCreate()
   }
}
  1. onCreate() 方法中,初始化 Timber 库:
override fun onCreate() {
    super.onCreate()

    Timber.plant(Timber.DebugTree())
}

这行代码会为您的应用初始化 Timber 库,以便您在 activity 中使用该库。

  1. 打开 AndroidManifest.xml
  2. <application> 元素的顶部,为 ClickerApplication 类添加一个新属性,以便 Android 知悉要使用您的 Application 类,而不是默认 Application 类。
<application
   android:name=".ClickerApplication"
...

第 3 步:添加 Timber 日志语句

在此步骤中,您将更改 Log.i() 调用以使用 Timber,然后为其他所有生命周期方法实现日志记录功能。

  1. 打开 MainActivity 并滚动到 onCreate()。将 Log.i() 替换为 Timber.i() 并移除日志标签。
Timber.i("onCreate called")

Log 类一样,Timber 也使用 i() 方法处理信息性消息。请注意,如果使用 Timber,则无需添加日志标签;Timber 会自动将该类的名称用作日志标签。

  1. 同样,更改 onStart() 中的 Log 调用:
override fun onStart() {
   super.onStart()

   Timber.i("onStart Called")
}
  1. 编译并运行 DessertClicker 应用,然后打开 Logcat。请注意,对于 onCreate()onStart(),您仍会看到相同的日志消息,只是现在生成这些消息的是 Timber,而不是 Log 类。
  2. 替换 MainActivity 中其余的生命周期方法,然后为每种方法添加 Timber 日志语句。代码如下:
override fun onResume() {
   super.onResume()
   Timber.i("onResume Called")
}

override fun onPause() {
   super.onPause()
   Timber.i("onPause Called")
}

override fun onStop() {
   super.onStop()
   Timber.i("onStop Called")
}

override fun onDestroy() {
   super.onDestroy()
   Timber.i("onDestroy Called")
}

override fun onRestart() {
   super.onRestart()
   Timber.i("onRestart Called")
}
  1. 再次编译并运行 DessertClicker,然后检查 Logcat。这一次请注意,除了 onCreate()onStart() 之外,还有一条有关 onResume() 生命周期回调的日志消息。

3b61071b3c4334a4.png

当 activity 从头开始启动时,您会看到系统按顺序调用以下三个生命周期回调:

  • onCreate():用于创建应用。
  • onStart():用于启动相应 activity,并让其在屏幕上显示。
  • onResume():用于使相应 activity 成为焦点,并让用户能够与其互动。

onResume() 方法尽管名称是这样,但会在启动时调用,即使没有要恢复的 activity 也是如此。

160054d59f67519.png

现在,DessertClicker 应用已设置好日志记录功能,您可以随时通过各种方式开始使用该应用,并且可以探索如何通过触发生命周期回调来响应这些使用行为。

用例 1:打开和关闭 activity

您可以先从最基本的用例入手,也就是首次启动您的应用,然后完全关闭应用。

  1. 编译并运行 DessertClicker 应用(如果该应用尚未运行)。正如您所看到的,当 activity 首次启动时,系统会调用 onCreate()onStart()onResume() 回调。3b61071b3c4334a4.png
  2. 多次点按纸杯蛋糕。
  3. 点按设备上的返回按钮。请注意,在 Logcat 中,系统将按上述顺序调用 onPause()onStop()onDestroy()

524b1d0c9a584f37.png

在本示例中,使用返回按钮会导致 activity(和应用)完全关闭。执行 onDestroy() 方法意味着相应 activity 已完全关闭,可以进行垃圾回收。垃圾回收是指自动清理您不再使用的对象。调用 onDestroy() 后,操作系统会知道这些资源是可丢弃的,然后开始清理这部分内存。

2dcc4d9c6478a9f4.png

如果您的代码手动调用该 activity 的 finish() 方法,或者用户强行退出该应用(例如,用户通过在“最近”屏幕点击窗口一角的 X 强行退出),您的 activity 也可能会完全关闭。如果您的应用长时间没有在屏幕上显示,Android 系统也可能会自行关闭您的 activity。Android 这样做是为了节省电量,同时允许其他应用使用您应用的资源。

  1. 使用“最近”屏幕返回该应用。Logcat 如下所示:fb7d1239f699fba4.png

相应 activity 在上一步中已被销毁,因此当您返回该应用时,Android 会启动一个新的 activity 并调用 onCreate()onStart()onResume() 方法。请注意,您在上一个 activity 中订购的甜点的数量和总价尚未保留。它们已重置为零。您可能不希望自己的应用出现此行为。我们会在后续的 Codelab 中解决此问题。

此处的要点是,在单个 activity 实例的生命周期内,onCreate()onDestroy() 仅调用了一次:onCreate() 用于首次初始化应用,onDestroy() 则用于清理您的应用所用的资源。

onCreate() 方法是重要的一步;在此方法中,您会执行所有的首次初始化,通过膨胀首次设置其布局,以及对变量进行初始化。

用例 2:离开和返回 activity

现在,您已经启动了应用并将其完全关闭,在此过程中,您看到了 activity 首次创建时经历的大部分生命周期状态。此外,您还看到了 activity 在完全关闭和销毁过程中经历的所有生命周期状态。但当用户与其 Android 设备交互时,他们会执行各种操作,例如在应用之间切换、返回主屏幕、启动新应用以及处理由通话等其他外部 activity 导致的中断。

每次用户离开您的 activity 时,它都不会关闭:

  • 当 activity 不再在屏幕上可见时,就说明该 activity 已置于后台(与之相反的是 activity 位于前台或屏幕上)。
  • 当用户返回您的应用时,相应 activity 会重启并再次可见。这部分生命周期称为应用的可见生命周期。

当您的应用位于后台时,为保留系统资源和延长电池续航时间,应用不应活跃运行。您可以使用 Activity 生命周期及其回调来了解应用何时切换到后台,以便您暂停任何正在进行的操作。然后,在您的应用进入前台时重启这些操作。

例如,假设某个应用需要大量使用计算资源。此应用可能会使用设备的 CPU 进行许多计算。移动设备的处理能力和电池续航时间通常受到限制,因此 Android 运行时系统需要均衡资源。由于后台进程可能会降低性能或过早耗尽手机电量,因此 Android 可能会阻止未在前台运行的应用使用资源。

在下一步中,您将查看当应用进入后台以及返回前台时的 activity 生命周期。

  1. 在 DessertClicker 应用运行时,点击几次纸杯蛋糕。
  2. 按设备上的主屏幕按钮,然后在 Android Studio 中观察 Logcat。请注意,系统会调用 onPause() 方法和 onStop() 方法,但不会调用 onDestroy()。返回主屏幕的操作会将您的应用置于后台,而不是完全关闭应用。38710e0d2c4d1910.png

在调用 onPause() 后,该应用不会再获得焦点。在 onStop() 之后,该应用将不再显示在屏幕上。虽然该 activity 已停止,但 Activity 对象仍位于内存中(在后台)。该 activity 尚未销毁。用户可能会返回该应用,因此 Android 会保留您的 activity 资源。

b488b32801220b79.png

  1. 使用“最近”屏幕返回该应用。请注意,在 Logcat 中,该 activity 使用 onRestart()onStart() 重启,然后使用 onResume() 恢复。

f6275abeaa53abe4.png

当该 activity 返回前台时,系统不会再次调用 onCreate() 方法。相应 activity 对象未被销毁,因此不需要重新创建。系统会调用 onRestart() 方法,而不是 onCreate()。请注意,这一次该 activity 返回前台时,系统会保留 Desserts Sold 数值。

  1. 除 DessertClicker 以外,至少启动一个应用,这样设备的“最近使用的应用”屏幕中就会有一些应用。
  2. 启动“最近使用的应用”屏幕,然后打开另外一个近期 activity。然后,返回最近用过的应用并让 DessertClicker 返回前台。

请注意,您在 Logcat 这里看到的回调与按主屏幕按钮后看到的相同。当应用进入后台时,系统会调用 onPause()onStop(),并在应用返回时调用 onRestart()onStart()onResume()

此处的要点是,当用户导航到该 activity 或离开该 activity 时,系统会多次调用 onStart()onStop()。您应替换这些方法,使应用在进入后台后停止运行,或者在返回前台时重新启动。

onRestart() 又是什么情况呢?onRestart() 方法与 onCreate() 非常相似。无论是 onCreate() 还是 onRestart(),都会在相应 activity 变得可见之前调用。onCreate() 方法只在第一次被调用,之后会调用 onRestart()onRestart() 方法用于放置仅在 activity 是首次启动时才需要调用的代码。

用例 3:隐藏部分 activity

您已了解,在应用启动并且系统调用 onStart() 后,该应用将在屏幕上变得可见。在应用恢复并且系统调用 onResume() 后,该应用可获得用户焦点。应用在屏幕上完全显示并且具有用户焦点时的这部分生命周期称为“交互式”生命周期。

当应用进入后台后,焦点在 onPause() 后便会丢失,并且在 onStop() 后,该应用将不再可见。

焦点与可见性之间的区别非常重要,因为 activity 有可能是在屏幕上部分可见,但没有用户焦点。在这一步,您将查看 activity 处于部分可见状态,但没有用户焦点的情况。

  1. 在 DessertClicker 应用运行时,点击屏幕右上角的共享按钮。

e2319779260eb5ee.png

9ddc8b1dc79b1bff.png

共享 activity 出现在屏幕的下半部分,但相应 activity 在上半部分仍然可见。

  1. 检查 Logcat,您会注意到,系统仅调用了 onPause()cf96ef14999a9a3c.png

在此用例中,系统没有调用 onStop(),因为相应 activity 仍然部分可见。但是,该 activity 没有用户焦点,并且用户无法与之交互。位于前台的“分享”activity 具有用户焦点。

为什么这种区别至关重要?以之前的计算密集型应用为例。您可能希望该应用在转入后台后停止运行,但在其被部分遮挡时继续运行。在这种情况下,您可以在 onStop() 中终止它。如果您希望该应用在相应 activity 被部分遮挡时也停止运行,则需要在 onPause() 中放置用于终止应用的代码。

onPause() 中运行的任何代码都会阻止其他内容显示,因此请使 onPause() 中的代码保持轻量级。例如,当有来电时,onPause() 中的代码可能会延迟来电通知。

  1. 在共享对话框之外点击一下,返回应用,您会注意到系统调用了 onResume()

onResume()onPause() 都必须处理焦点。当相应 activity 具有焦点时,系统会调用 onResume() 方法;当该 activity 失去焦点时,系统会调用 onPause()

Android fragment 生命周期类似于 activity 生命周期,还具有几个特定于 fragment 的方法。

8494ec955ce0c49d.png

在此任务中,您将查看在之前的 Codelab 中构建的 AndroidTrivia 应用,并添加一些日志记录条目,以探索 fragment 生命周期。您可以在 AndroidTrivia 中回答与 Android 开发相关的问题;如果您连续答对三题,就会赢得游戏。

AndroidTrivia 应用中的每个屏幕都是一个 Fragment

为简单起见,您在此任务中使用 Android 日志记录 API,而不是 Timber 库。

  1. 从之前的一个 Codelab 中打开 AndroidTriviaNavigation 应用,或者从 GitHub 下载 AndroidTriviaNavigation 解决方案代码。
  2. 打开 TitleFragment.kt 文件。请注意,在您重新构建该应用之前,Android Studio 可能会显示绑定错误和未解析的引用错误。
  3. 向下滚动到 onCreateView() 方法。请注意,这里是膨胀 fragment 的布局和发生数据绑定的位置。
  4. 将日志记录语句添加到 onCreateView() 方法中,具体位置在 setHasOptionsMenu() 代码行和要返回的最终调用之间:
setHasOptionsMenu(true)

Log.i("TitleFragment", "onCreateView called")

return binding.root
  1. onCreateView() 方法的正下方,为每个剩余的 fragment 生命周期方法添加日志记录语句。代码如下:
override fun onAttach(context: Context) {
   super.onAttach(context)
   Log.i("TitleFragment", "onAttach called")
}
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   Log.i("TitleFragment", "onCreate called")
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    Log.i("TitleFragment", "onViewCreated called")
}

override fun onStart() {
   super.onStart()
   Log.i("TitleFragment", "onStart called")
}
override fun onResume() {
   super.onResume()
   Log.i("TitleFragment", "onResume called")
}
override fun onPause() {
   super.onPause()
   Log.i("TitleFragment", "onPause called")
}
override fun onStop() {
   super.onStop()
   Log.i("TitleFragment", "onStop called")
}
override fun onDestroyView() {
   super.onDestroyView()
   Log.i("TitleFragment", "onDestroyView called")
}
override fun onDetach() {
   super.onDetach()
   Log.i("TitleFragment", "onDetach called")
}
  1. 编译并运行应用,然后打开 Logcat。
  2. 在搜索字段中输入 I/TitleFragment,以过滤日志。该应用启动后,Logcat 将如下所示:
21933-21933/com.example.android.navigation I/TitleFragment: onAttach called
21933-21933/com.example.android.navigation I/TitleFragment: onCreate called
21933-21933/com.example.android.navigation I/TitleFragment: onCreateView called
21933-21933/com.example.android.navigation I/TitleFragment: onViewCreated called
21933-21933/com.example.android.navigation I/TitleFragment: onStart called
21933-21933/com.example.android.navigation I/TitleFragment: onResume called

您可以在此处查看 fragment 的整个启动生命周期,包括以下回调:

  • onAttach():在 fragment 与其所有者 activity 相关联时调用。
  • onCreate():与 activity 的 onCreate() 类似,系统会调用 fragment 的 onCreate() 来执行初始 fragment 创建(布局除外)。
  • onCreateView():调用的目的是膨胀 fragment 的布局。
  • onViewCreated():在 onCreateView() 返回后立即调用,但要在任何已保存状态恢复到视图中之前。
  • onStart():在 fragment 变为可见时调用;与 activity 的 onStart() 并行。
  • onResume():在 fragment 获得用户焦点时调用;与 activity 的 onResume() 并行。
  1. 点按 Play 按钮继续玩知识问答游戏,观察现在的 Logcat。
21933-21933/com.example.android.navigation I/TitleFragment: onAttach called
21933-21933/com.example.android.navigation I/TitleFragment: onCreate called
21933-21933/com.example.android.navigation I/TitleFragment: onCreateView called
21933-21933/com.example.android.navigation I/TitleFragment: onViewCreated called
21933-21933/com.example.android.navigation I/TitleFragment: onStart called
21933-21933/com.example.android.navigation I/TitleFragment: onResume called
21933-21933/com.example.android.navigation I/TitleFragment: onPause called
21933-21933/com.example.android.navigation I/TitleFragment: onStop called
21933-21933/com.example.android.navigation I/TitleFragment: onDestroyView called

打开下一个 fragment 会使标题 fragment 关闭,并调用以下生命周期方法:

  • onPause():在 fragment 失去用户焦点时调用;与 activity 的 onPause() 并行。
  • onStop():在 fragment 不再在屏幕上可见时调用;与 activity 的 onStop() 并行。
  • onDestroyView():在不再需要 fragment 的视图时调用,用于清理与该视图关联的资源。
  1. 在应用中,点按向上按钮(屏幕左上角的箭头),返回标题 fragment。
21933-21933/com.example.android.navigation I/TitleFragment: onPause called
21933-21933/com.example.android.navigation I/TitleFragment: onStop called
21933-21933/com.example.android.navigation I/TitleFragment: onDestroyView called
21933-21933/com.example.android.navigation I/TitleFragment: onCreateView called
21933-21933/com.example.android.navigation I/TitleFragment: onViewCreated called
21933-21933/com.example.android.navigation I/TitleFragment: onStart called
21933-21933/com.example.android.navigation I/TitleFragment: onResume called

这次,系统可能不会通过调用 onAttach()onCreate() 来启动 fragment。fragment 对象仍然存在并仍与其所有者 activity 相关联,因此生命周期以 onCreateView() 开头。

  1. 按设备的主屏幕按钮。请注意,在 Logcat 中,系统仅会调用 onPause()onStop()。这与 activity 的行为相同:返回主屏幕会使 activity 和 fragment 置于后台。
  2. 使用“最近”屏幕返回该应用。与 activity 发生的情况一样,系统会调用 onStart()onResume() 方法,以将 fragment 返回到前台。

Android Studio 项目:DessertClickerLogs

activity 生命周期

  • activity 生命周期是 activity 会切换的一组状态。 activity 生命周期在 activity 首次创建时开始,到 activity 被销毁时结束。
  • 当用户在 activity 之间以及应用内外导航时,每个 activity 会在 activity 生命周期中的状态之间切换。
  • activity 生命周期中的每种状态都有一个对应的回调方法,您可以在 Activity 类中替换此类方法。生命周期方法有七种:onCreate()onStart()onPause()onRestart()onResume()onStop()onDestroy()
  • 如需添加要在 activity 转换为某种生命周期状态时发生的行为,请替换相应状态的回调方法。
  • 如需在 Android Studio 中为类添加框架替换方法,请依次选择 Code > Override Methods,或按 Control+o

使用 Log 记录日志

  • 借助 Android 日志记录 API(具体而言是 Log 类),您可以写入要在 Android Studio 内的 Logcat 中显示的简短消息。
  • 使用 Log.i() 可写入信息性消息。此方法采用两个参数:日志标签(通常是类的名称)和日志消息(一个简短的字符串)。
  • 使用 Android Studio 中的 Logcat 窗格可查看系统日志,包括您写入的消息。

使用 Timber 记录日志

Timber 是一个日志记录库,与 Android 日志记录 API 相比具有多项优势。具体而言,Timber 库具有下列优势:

  • 可以根据类名称为您生成日志标签。
  • 有助于避免在您的 Android 应用的发布版本中显示日志。
  • 支持与崩溃报告库集成。

如需使用 Timber,请将其依赖项添加到您的 Gradle 文件中,然后扩展 Application 类,以对其进行初始化:

  • Application 是一个基类,包含整个应用的全局应用状态。如果您未指定 Application 类,Android 会使用默认的 Application 类。您可以创建自己的 Application 子类来初始化 Timber 等应用级库。
  • 通过将 android:name 属性添加到 Android 清单中的 <application> 元素,可以将自定义 Application 类添加到您的应用。请不要忘记执行此操作!
  • 使用 Timber.i() 可通过 Timber 写入日志消息。此方法仅接受一个参数:要写入的消息。系统会自动添加日志标签(类名称)。

Android 开发者文档:

其他: