学习目标
了解Activity的七个生命周期回调方法
理解Activity回调方法什么时候被触发
会使用Log类的方法输出各类调试信息
知道Activity有四种启动模式
一、Activity生命周期
1、了解Activity生命周期
当用户浏览、退出和返回到您的应用时,您应用中的 Activity 实例会在其生命周期的不同状态间转换。Activity 类会提供许多回调,这些回调会让 Activity 知晓某个状态已经更改:系统正在创建、停止或恢复某个 Activity,或者正在销毁该 Activity 所在的进程。
在生命周期回调方法中,您可以声明用户离开和再次进入 Activity 时 Activity 的行为方式。例如,如果您正构建流媒体视频播放器,当用户切换至另一应用时,您可能要暂停视频或终止网络连接。当用户返回时,您可以重新连接网络并允许用户从同一位置继续播放视频。换言之,每个回调都支持您执行适合给定状态变更的特定作业。在合适的时间执行正确的作业,并妥善处理转换,这将提升应用的稳健性和性能。例如,良好的生命周期回调实现有助于防止应用出现以下问题:
当用户在使用应用时接听来电,或切换至另一应用时崩溃。
当用户未主动使用它时,消耗宝贵的系统资源。
当用户离开应用并在稍后返回时,丢失用户的进度。
当屏幕在横向和纵向之间旋转时,崩溃或丢失用户的进度。
2、Activity生命周期简化图
为了在Activity生命周期各个阶段之间导航转换,Activity类提供六个核心回调方法:onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy()。当 Activity 进入新状态时,系统会调用其中每个回调方法。
(1)Activity存在与否
Activity整个生命周期从onCreate()方法开始,到最终调用onDestroy()方法结束。对于Activity的全局资源在onCreate()方法中设置,最终在onDestroy()方法中进行资源回收。
(2)Activity可见与否
Activity触发onStart()方法后处于可见状态,但未必能与用户进行交互。当Activity完全不可见时,触发onStop()方法。重新可见时,先触发onRestart()方法,再触发onStart()方法。onStart()方法和onStop()方法可以被多次调用。
(3)Activity可交互与否
Activity在onResume()方法和onPause()方法之间时处于活动状态,此时可以与用户进行交互。
3、Android进程优先级
应用程序的生命周期是在Android系统中进程从启动到终止的所有阶段,也就是Android从启动到停止的全过程。Android应用程序的生命周期的终结这个动作并非由应用程序进程本身执行,而是取决于Android系统。那么,系统是根据一个怎样的重要性标准来终止Android应用程序呢?Android根据应用程序的组件以及组件当前运行状态将所有的进程按重要性程度从高到低划分为五个,如下图所示:
二、Activity生命周期回调方法
(一)回调方法概述
1、onCreate()回调方法
系统首次创建 Activity 时触发,同时还以Bundle形式存储以前的状态。
2、onRestart()回调方法
重新启动Activity调用,该Activity在堆栈中,而不是创建一个新Activity。
3、onStart()回调方法
当 Activity 进入“已开始”状态时,系统会调用此回调。调用onStart()方法使Activity对用户可见,因为应用会为 Activity 进入前台并支持互动做准备。
4、onResume()回调方法
Activity 会在进入“已恢复”状态时来到前台,然后系统调用 onResume() 回调。这是应用与用户互动的状态。应用会一直保持这种状态,直到某些事件发生,让焦点远离应用。
5、onPause()回调方法
Activity被暂停。该方法是用来保存活动状态的地方,以便Activity被重新启动时具有与其退出相同的状态。
6、onStop()回调方法
当Activity不再对用户可见,说明其已进入“已停止”状态,因此系统将调用onStop()回调。
7、onDestroy()回调方法
销毁Ativity之前,系统会先调用onDestroy()。onDestroy()回调应释放先前的回调(例如onStop())尚未释放的所有资源。
(二)回调方法案例演示
1、创建安卓应用【LifeCycle】
选择项目模板 - Empty Activity
配置项目信息
单击【Finish】按钮,完成项目初始化
2、修改主布局文件activity_main.xml
设置标签控件相关属性
3、修改主界面MainActivity
定义标记常量TAG
在每个回调方法里输出一条调试信息
4、添加消息过滤器
为了更好地查看本应用的调试信息,需要添加消息过滤器
创建消息过滤器life_cycle
5、启动应用,切换状态,查看LogCat里的消息
启动应用,查看LogCat里的消息
单击模拟器上的【Home】键
此时,应用窗口被桌面遮住,查看LogCat里的消息
单击【查看最近应用】按钮
单击在后台运行的【LifeCycle】应用,其窗口又重回前台,查看LogCat里的信息
单击模拟器上的【Back键】,关闭应用,查看LogCat里的信息
三层架构
最外层(onCreate - onDestory)——(存在 - 不存在)
中间层(onStart - onStop)—— (可见 - 不可见)
最内层(onResume - onPause)—— (可交互 - 不可交互)
三、利用Log类输出信息
在调试代码时,需要查看调试信息,就需要用安卓的android.util.Log类,它有5个常用方法:
1、Log.v()
输出颜色为黑色的,任何消息都会输出,这里的v代表verbose啰嗦的意思,平时使用就是Log.v(“”,“”);
2、Log.d()
输出颜色是蓝色的,仅输出debug调试信息,但会输出上层的信息,过滤起来可以通过DDMS的Logcat标签来选择。
3、Log.i()
输出颜色为绿色,输出一般提示性消息,不会输出Log.v和Log.d的信息,但会显示i、w和e的信息。
4、Log.w()
输出颜色为橙色,输出警告信息,一般需要我们注意优化安卓代码,同时选择它后还会输出Log.e的信息。
5、Log.e()
输出颜色为红色,仅显示红色的错误信息,需要我们认真分析,查找出错原因。
注意:不同打印方法使用时,都带上(String tag, String msg)参数,tag表示打印信息的标签,msg表示需要打印的信息。
四、Android启动模式
(一)任务与返回栈
在当前 Activity 启动另一个 Activity 时,新的 Activity 将被推送到堆栈顶部并获得焦点。上一个 Activity 仍保留在堆栈中,但会停止。当 Activity 停止时,系统会保留其界面的当前状态。当用户按返回按钮时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。堆栈中的 Activity 永远不会重新排列,只会被送入和退出,在当前 Activity 启动时被送入堆栈,在用户使用返回按钮离开时从堆栈中退出。因此,返回堆栈按照“后进先出”的对象结构运作。下图借助一个时间轴直观地显示了这种行为。该时间轴显示了 Activity 之间的进展以及每个时间点的当前返回堆栈。
(二)启动模式概述
启动模式Activity启动时的策略,在AndroidManifest.xml中元素的android:launchMode属性设置;Android有4种启动模式:standard、singleTop、singleTask、singleInstance。
(三)启动模式案例演示
1、standard启动模式
每次激活Activity时,都创建Activity实例,并放入任务栈
创建安卓应用【StandardLaunchMode】
编辑主布局资源文件activity_main.xml
代码说明:android:onClick=“doStartMainActivity” - 按钮绑定了事件处理方法doStartMainActivity(View view)
编辑字符串资源文件strings.xml
在项目清单文件里设置MainActivity的启动模式
Activity默认启动模式就是standard模式,不用设置也行
主界面类MainActivity
package net.hw.standard;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tvTaskId;
private TextView tvHashCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
// 通过资源标识符获取控件实例
tvTaskId = findViewById(R.id.tvTaskId);
tvHashCode = findViewById(R.id.tvHashCode);
// 显示任务标识符与主窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 显示任务标识符与主窗口哈希码
*/
private void displayTaskIdAndHashCode() {
// 显示任务标识符
tvTaskId.setText(String.format("TaskId: %d", getTaskId()));
// 显示主窗口哈希码
tvHashCode.setText(String.format("MainActivityHashCode: %d", hashCode()));
}
/**
* 启动主窗口
* @param view
*/
public void doStartMainActivity(View view) {
// 启动主窗口
startActivity(new Intent(MainActivity.this, MainActivity.class));
// 显示任务标识符与主窗口哈希码
displayTaskIdAndHashCode();
}
}
启动应用,查看效果
单击【启动MAINACTIVITY】按钮
TaskId值没有变,但是MainActivityHashCode值变了,也就是说,标准模式启动Activity,在同一个任务栈里,但是每次启动都会创建一个新的实例。
2、singleTop启动模式
如果某个Activity自己激活自己,即任务栈栈顶就是该Activity,则不需要创建,其余情况都要创建Activity实例
创建安卓应用【SingleTopLaunchMode】
基于Activity模板创建SecondActivity
主窗口布局文件activity_main.xml
<TextView
android:id="@+id/tvTaskId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<TextView
android:id="@+id/tvHashCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<Button
android:id="@+id/tvStartMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartMainActivity"
android:text="@string/start_main_activity"
android:textSize="20dp" />
<Button
android:id="@+id/tvStartSecondActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartSecondActivity"
android:text="@string/start_second_activity"
android:textSize="20dp" />
第二个窗口布局文件activity_second.xml
<TextView
android:id="@+id/tvTaskId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<TextView
android:id="@+id/tvHashCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<Button
android:id="@+id/tvStartMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartMainActivity"
android:text="@string/start_main_activity"
android:textSize="20dp" />
<Button
android:id="@+id/tvStartSecondActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartSecondActivity"
android:text="@string/start_second_activity"
android:textSize="20dp" />
字符串资源文件strings.xml
项目清单文件AndroidManifest.xml
主窗口MainActivity
package net.hw.singletop;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tvTaskId;
private TextView tvHashCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
// 通过资源标识符获取控件实例
tvTaskId = findViewById(R.id.tvTaskId);
tvHashCode = findViewById(R.id.tvHashCode);
// 显示任务标识符与主窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 显示任务标识符与主窗口哈希码
*/
private void displayTaskIdAndHashCode() {
// 显示任务标识符
tvTaskId.setText(String.format("TaskId: %d", getTaskId()));
// 显示主窗口哈希码
tvHashCode.setText(String.format("MainActivityHashCode: %d", hashCode()));
}
/**
* 启动主窗口
* @param view
*/
public void doStartMainActivity(View view) {
// 启动主窗口
startActivity(new Intent(MainActivity.this, MainActivity.class));
// 显示任务标识符与主窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 启动第二个窗口
* @param view
*/
public void doStartSecondActivity(View view) {
// 启动第二个窗口
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
}
第二个窗口SecondActivity
package net.hw.singletop;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class SecondActivity extends AppCompatActivity {
private TextView tvTaskId;
private TextView tvHashCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
// 通过资源标识符获取控件实例
tvTaskId = findViewById(R.id.tvTaskId);
tvHashCode = findViewById(R.id.tvHashCode);
// 显示任务标识符与第二个窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 显示任务标识符与主窗口哈希码
*/
private void displayTaskIdAndHashCode() {
// 显示任务标识符
tvTaskId.setText(String.format("TaskId: %d", getTaskId()));
// 显示第二个窗口哈希码
tvHashCode.setText(String.format("SecondActivityHashCode: %d", hashCode()));
}
/**
* 启动主窗口
* @param view
*/
public void doStartMainActivity(View view) {
// 启动主窗口
startActivity(new Intent(SecondActivity.this, MainActivity.class));
}
/**
* 启动第二个窗口
* @param view
*/
public void doStartSecondActivity(View view) {
// 启动第二个窗口
startActivity(new Intent(SecondActivity.this, SecondActivity.class));
// 显示任务标识符与第二个窗口哈希码
displayTaskIdAndHashCode();
}
}
启动应用,查看效果
单击【启动MAINACTIVITY】按钮, TaskId与MainActivityHashCod都没有变,因此此时MainActivity处于栈顶,不会创建新的实例。
单击【启动SECONDACTIVITY】按钮
TaskId没有变,表明第二个窗口跟第一个窗口处于同一个任务栈
此时第二个窗口在栈顶,将第一个窗口遮住,单击【启动MAINACTIVITY】按钮
大家可以发现,MainActivityHashCode已发生变化,由206253123变成186207819,表明创建了MainActivity的新实例。
3、singleTask启动模式
如果要激活的那个Activity在任务栈中存在该实例,则不需要创建,只需要把此Activity放入栈顶,并把该Activity以上的Activity实例都弹出销毁。
创建安卓应用【SingleTaskLaunchMode】
基于Activity模板创建SecondActivity
主窗口布局文件activity_main.xml
<TextView
android:id="@+id/tvTaskId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<TextView
android:id="@+id/tvHashCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<Button
android:id="@+id/tvStartMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartMainActivity"
android:text="@string/start_main_activity"
android:textSize="20dp" />
<Button
android:id="@+id/tvStartSecondActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartSecondActivity"
android:text="@string/start_second_activity"
android:textSize="20dp" />
第二个窗口布局文件activity_second.xml
<TextView
android:id="@+id/tvTaskId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<TextView
android:id="@+id/tvHashCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="20dp" />
<Button
android:id="@+id/tvStartMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartMainActivity"
android:text="@string/start_main_activity"
android:textSize="20dp" />
<Button
android:id="@+id/tvStartSecondActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="doStartSecondActivity"
android:text="@string/start_second_activity"
android:textSize="20dp" />
字符串资源文件strings.xml
项目清单文件AndroidManifest.xml
主窗口MainActivity
package net.hw.singletask;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import net.hw.singletask.R;
import net.hw.singletask.SecondActivity;
public class MainActivity extends AppCompatActivity {
private TextView tvTaskId;
private TextView tvHashCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
// 通过资源标识符获取控件实例
tvTaskId = findViewById(R.id.tvTaskId);
tvHashCode = findViewById(R.id.tvHashCode);
// 显示任务标识符与主窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 显示任务标识符与主窗口哈希码
*/
private void displayTaskIdAndHashCode() {
// 显示任务标识符
tvTaskId.setText(String.format("TaskId: %d", getTaskId()));
// 显示主窗口哈希码
tvHashCode.setText(String.format("MainActivityHashCode: %d", hashCode()));
}
/**
* 启动主窗口
* @param view
*/
public void doStartMainActivity(View view) {
// 启动主窗口
startActivity(new Intent(MainActivity.this, MainActivity.class));
// 显示任务标识符与主窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 启动第二个窗口
* @param view
*/
public void doStartSecondActivity(View view) {
// 启动第二个窗口
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
}
第二个窗口SecondActivity
package net.hw.singletask;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class SecondActivity extends AppCompatActivity {
private TextView tvTaskId;
private TextView tvHashCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
// 通过资源标识符获取控件实例
tvTaskId = findViewById(R.id.tvTaskId);
tvHashCode = findViewById(R.id.tvHashCode);
// 显示任务标识符与第二个窗口哈希码
displayTaskIdAndHashCode();
}
/**
* 显示任务标识符与主窗口哈希码
*/
private void displayTaskIdAndHashCode() {
// 显示任务标识符
tvTaskId.setText(String.format("TaskId: %d", getTaskId()));
// 显示第二个窗口哈希码
tvHashCode.setText(String.format("SecondActivityHashCode: %d", hashCode()));
}
/**
* 启动主窗口
*
* @param view
*/
public void doStartMainActivity(View view) {
// 启动主窗口
startActivity(new Intent(SecondActivity.this, MainActivity.class));
}
/**
* 启动第二个窗口
*
* @param view
*/
public void doStartSecondActivity(View view) {
// 启动第二个窗口
startActivity(new Intent(SecondActivity.this, SecondActivity.class));
// 显示任务标识符与第二个窗口哈希码
displayTaskIdAndHashCode();
}
}
启动应用,查看效果
单击【启动MAINACTIVITY】按钮
可以看到,TaskId与MainActivityHashCode都没有变化
单击【启动SECONDACTIVITY】按钮
此时主窗口MainActivity被第二个窗口SecondActivity遮住,即SecondActivity处于任务栈顶
单击【启动MAINACTIVITY】按钮
可以看到,主窗口MainActivity再次处于任务栈顶,但是MainActivityHashCode依然还是206253123,说明并没有创建MainActivity的新实例,这一点跟SingleTop启动模式不同。
SingTask的应用:可以用来退出整个应用。将主Activity设为SingTask模式,然后在要退出的Activity中转到主Activity,然后重写主Activity的onNewIntent方法,并在函数中加上一句finish()。
启动应用,测试效果
在主窗口单击【启动SECONDACTIVITY】按钮,跳转到第二个窗口,在第二个窗口单击【启动MAINACTIVITY】按钮,跳转到主窗口,立即执行onNewIntent()方法里的finish()方法,关闭当前窗口,结束整个应用。
4、singleInstance启动模式
如果应用1的任务栈中创建了MainActivity实例,如果应用2也要激活MainActivity,则不需要创建,两应用共享该Activity实例。