Android入门教程之Activity(生命周期,启动...)
Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
Activity
1. Activity 的使用
我们新建的工程中带有一个基础 activity。
新建工程中,需要注意3个文件。
- MainActivity.java 在src/main/java里,对应的包名目录下。
- activity_main.xml 在res/layout里。
- AndroidManifest.xml 在src/main里。这里叫做“清单文件”。
layout文件(这里指的是activity_main.xml)预设了UI如何摆放。
清单文件告诉系统,我这个app有哪些组件,申请了什么权限。
2. layout - 界面布局 新建的 layout 中,as 一般会默认给一个
ConstraintLayout
。比如 activity_main.xml。activity_main.xml
这里为了用起来方便,我们把它换成LinearLayout
有的朋友会问,都2021年了,为什么不直接用 ConstraintLayout?
现在不做什么功能,先用LinearLayout,就是为了方便。 换成LinearLayout后,layout文件长这样。
换成LinearLayout后的activity_main.xml
可以看到,标签的开头和结尾都换成了
LinearLayout
。其他地方暂时不修改。as功能强大,非常便利。我们可以用鼠标选中标签开始的
androidx...Layout
,然后直接键盘输入LinearLayout
的前几位字母。as会自动弹出选择框,在里面双击选择
LinearLayout
或者回车选择,标签就替换完成了。3. Java - 控制界面 layout文件设计的是界面的初始布局。它决定了初始界面上放着什么UI组件以及组件是怎么组织安排的。
这里我们说的是「初始界面」或者「初始布局」。也就是说,我们可以控制界面上的UI元素。
【Android入门教程之Activity(生命周期,启动...)】先看默认的 MainActivity.java。在
onCreate
方法里,R.layout.activity_main
指的就是activity_main.xml。现在layout中有一个TextView,它可以用来显示文字。我们想在
MainActivity
中控制它,该怎么做呢?现在改一下这个TextView。删掉原来ConstraintLayout用到的那些属性。
给它添加一个id。这个id在这个layout文件中必须是独一无二的。给它分配一个id叫做
tv1
,就像下面。
现在TextView有了身份证,我们在activity中就可以找到它。用的是
findViewById
方法。TextView tv1 = findViewById(R.id.tv1);
现在我们就拿到了界面上的这个TextView对象。可以对它进行操作了。
比如改变它显示的文字。
TextView tv1 = findViewById(R.id.tv1);
// 拿到textView的对象
tv1.setText("Today is a good day.");
// 改变文字
4. AndroidManifest.xml - 清单文件 也可以简称为「manifest文件」。清单文件非常重要,它告诉系统我们的app有哪些activity,用到了什么权限等等信息。
如果要新建activity,需要在清单中注册。
AndroidManifest.xml
从这个默认的清单文件中我们可以得知,activity 是属于 application 的。application 就是我们的应用。
application 标签中也指定了各种元素,例如应用的图标,名字,主题等等。
MainActivity 是应用启动的第一个 activity。可以观察到它设置了 action 和category 属性。
android.intent.action.MAIN
决定应用程序最先启动的Activity。android.intent.category.LAUNCHER
表示可以在手机“桌面”上看到应用图标。
小结
activity是应用重要的组件之一。纷繁复杂的内容需要activity来承载。
之后我们会在activity中控制各种各样的UI组件,处理用户的操作,申请权限等等。还要了解activity的生命周期,启动方式和跳转方法。
Activity 生命周期
生命周期图示
文章图片
1. 生命周期变化 执行一些常见的操作,打log看一下生命周期的变化。测试机型:RedMi。
启动然后退出
onCreate
onStart
onResume
onWindowFocusChanged: hasFocus: trueonWindowFocusChanged: hasFocus: false
onPause
onStop
onDestroy
启动后按home键
Act1: onCreate
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true// 按home键
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop// 再回来
Act1: onRestart
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true// 按返回键退出act
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
Act1: onDestroy
旋转手机
activity 在切换横竖屏的时候的生命周期。
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true// 横屏
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true// 竖屏
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true// 返回
[Life]: onWindowFocusChanged: hasFocus: false
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
来回切换的生命周期变化
以2个Activity启动为例。
Act1: onCreate
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
Act1: onPause
Act1: onWindowFocusChanged: hasFocus: false
Act2: onCreate
Act2: onStart
Act2: onResume
Act2: onWindowFocusChanged: hasFocus: true
Act1: onStop
Act2: onWindowFocusChanged: hasFocus: false
Act2: onPause
Act1: onRestart
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
Act2: onStop
Act2: onDestroy
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
Act1: onDestroy
弹出 AlertDialog
点击按钮弹出一个
AlertDialog
。观察发现调用 onWindowFocusChanged
。onWindowFocusChanged: hasFocus: false
onWindowFocusChanged: hasFocus: true
这里也可以用 DialogFragment 来做测试。
recreate
调用 recreate() 方法
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
[Life]: click [recreate]
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
可以看到,调用
recreate()
方法后并没有走onWindowFocusChanged
回调。2. onCreate 和 onStart 的区别 activity的状态区别
onCreate
在系统首次创建 Activity 时触发。Activity会在创建后进入已创建状态。- 当 Activity 进入“已开始”状态时,系统会调用此回调。
onStart()
调用使 Activity 对用户可见,因为应用会为 Activity 进入前台并支持交互做准备。
onStart()
方法会非常快速地完成,并且与“已创建”状态一样,Activity 不会一直处于“已开始”状态。一旦此回调结束,Activity 便会进入已恢复状态,系统将调用 onResume()
方法。3. onPause 和 onStop 的区别
onPause()
执行非常简单,而且不一定要有足够的时间来执行保存操作。 因此,您不应使用 onPause()
来保存应用或用户数据、进行网络调用,或执行数据库事务。因为在该方法完成之前,此类工作可能无法完成。已进入已停止状态,因此系统将调用
onStop()
回调。举例而言,如果新启动的 Activity 覆盖整个屏幕,就可能会发生这种情况。在
onStop()
方法中,应用应释放或调整应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从细粒度位置更新切换到粗粒度位置更新。 使用 onStop()
而非 onPause()
可确保与界面相关的工作继续进行,即使用户在多窗口模式下查看您的 Activity 也能如此。 您还应该使用 onStop()
执行 CPU 相对密集的关闭操作。Activity 启动,携带参数启动
前面大致了解了Activity是一个应用组件,能为用户提供一个界面。以及如何新增activity。 一个App中,通常有多个界面。假设每一个界面对应一个activity,不同界面之间怎么跳转呢?
1. Intent 通常activity之间的跳转离不开Intent这个类。 Intent,直译为“意图”。我们把信息包裹在intent对象中,然后执行。 比如启动
RelativeLayoutGuideAct
这个activity。startActivity(new Intent(getApplicationContext(), RelativeLayoutGuideAct.class));
这里用到一个很常见的方法
startActivity (Intent intent)
。 startActivity
属于Context类,Activity是Context的子类。java.lang.Object
android.content.Context
android.content.ContextWrapper
android.view.ContextThemeWrapper
android.app.Activity
现在我们知道了,启动activity需要使用Intent,调用
startActivity
方法。2. 带参数的跳转 在跳转去下一个页面时,我们可能会想携带一些信息到下一个界面去。例如携带一些文本,数字等等。 或者是一个对象。 这些信息我们可以交给Intent,传递到下一个activity去。下一个activity中拿到我们传入的Intent。
携带基本类型和String
我们直接看intent的方法。
Intent intent = new Intent(getApplicationContext(), SendParamsDemo.class);
intent.putExtra(SendParamsDemo.K_INT, 100);
intent.putExtra(SendParamsDemo.K_BOOL, true);
intent.putExtra(SendParamsDemo.K_STR, "Input string");
startActivity(intent);
intent的
putExtra
方法,可以传入参数。它接受1个String作为key,然后是具体参数。 例子中我们跳转去了SendParamsDemo。public class SendParamsDemo extends AbsActivity {public static final String K_INT = "k_int";
public static final String K_BOOL = "k_bool";
public static final String K_STR = "k_str";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gotInput();
}private void gotInput() {
Intent intent = getIntent();
if (intent != null) {
int i = intent.getIntExtra(K_INT, -1);
boolean b = intent.getBooleanExtra(K_BOOL, false);
String str = intent.getStringExtra(K_STR);
Log.d(TAG, "gotInput: i:" + i + ", b: " + b + ", str: " + str);
} else {
Log.d(TAG, "gotInput: input null.");
}
}
}
// log:
// com.rustfisher.tutorial2020 D/rustAppSendParamsDemo: gotInput: i:100, b: true, str: Input string
在这个activity中我们接收到了传入的参数。
观察intent的
putExtra
方法,我们发现它支持传入很多种参数。int,byte, char, float, double, long, boolean,string,CharSequence或是它们的数组。 也可以传入Parcelable,Serializable对象或是对象数组。
传入Serializable对象
除了基本类型和String,可以传送对象吗? 答案是肯定的。Intent可以携带
Serializable
对象。 Serializable
本身是一个接口,自定义的对象实现这个接口后,就可以被Intent携带。 比如我们改造一下DataTest类,让它实现Serializable
接口。public class DataTest implements Serializable { // 实现接口
然后将对象送给intent,再启动activity。
Intent intent = new Intent(getApplicationContext(), RecyclerViewDemo2Act.class);
DataTest out = new DataTest("input time", 233, 666, 999);
Log.d(TAG, "startInputData: sending object: " + out);
intent.putExtra(RecyclerViewDemo2Act.K_INPUT_DATA, out);
startActivity(intent);
被启动的activity接受传入的intent并取出对象。
Intent intent = getIntent();
if (intent != null) {
DataTest d = (DataTest) intent.getSerializableExtra(K_INPUT_DATA);
// 取出了对象,拿去显示
}
Serializable
接口不含任何方法。实现了这个接口的类,系统会自动将其序列化。我们打印出发送和接收到的对象。
startInputData: sending object: com.rustfisher.tutorial2020.recycler.data.DataTest@fb43df5
getInputData: input data object: com.rustfisher.tutorial2020.recycler.data.DataTest@a588b5c
可以发现这2个对象并不是同一个引用。但它们的“内容”是一样的。对象经历了序列化和反序列化的过程。
值得注意的是,Intent 能携带的对象大小并不是无限制的。实际开发中,需要开发者自己预估传输的数据大小。
传送 Parcelable 对象和传送 Serializable 对象类似,用同样的存入和取出操作。
Activity 相关面试题
1. 谈一下返回栈 首先理解android是使用Task来管理活动,一个Task就是一组存放在栈里的活动的集合,这个栈就叫做返回栈,每启动一个新的活动,就会将其放入栈顶,当我们点击back回退或调用activity的finish函数处于栈顶的活动就会出栈,前一个入栈的活动就会到栈顶,系统总是显示处于栈顶的活动。
2. 说下Activity的生命周期?
- onCreate()方法:活动第一次创建的时候被调用,常做初始化的操作,比如加载布局(setContentView),绑定事件(findViewById)。表示Activity正在创建。
- onStart()方法:活动由不可见到可见的时候被调用,表示Activity正在启动,此时Activity可见但不在前台。
- onResume()方法:活动准备好和用户进行交互时调用。表示Acitivity获得焦点,此时Activity可见且在前台。
- onPause()方法:系统准备去启动或恢复另一个活动时调用。表示Activity正在停止,此时可做存储数据,停止动画等操作。
- onStop()方法:在活动完全不可见的时候调用。表示Activity即将停止。
- onDestory()方法:在活动被销毁之前调用,表示Activity即将销毁,常做回收工作、资源释放。
- onRestart()方法:在活动由停止状态变为运行状态之前调用。表示Activity即将重启。
- 完整生存期
- 可见生存期
- 前台生存期
可见生存期:onStart()方法与onStop()方法就是可见生存期,Activity对于用户是可见的,但无法与用户交互。onStart()方法中对资源进行加载,onStop()方法中对资源进行释放。
前台生存期:onResume方法与onPause方法就是前台生存期,在前台生存期内,活动处于运行状态,此时可以与用户交互。
4. 说下Activity处于onPasue()下可以执行那些操作?
- 用户返回该Activity,调用onResume()方法,重新running
- 用户打开了其他Activity,就会调用onStop()方法
- 系统内存不足,拥有更高权限的应用需要内存,该Activity就会被系统回收
- 如果用户返回到onStop()的Activity又显示在前台了,系统会调用
onRestart() -> onStart() -> onResume() 然后重新running
当Activity结束(调用finish()方法)就会调用onDestory()方法释放所有占用的资源。
- 启动一个Activity onCreate->onStart->onResume
- 当一个Activity打开另一个Activity都会回调哪些方法,如果ActivityB是完全透明的呢,如果启动的是一个对话框Activity呢? A:onPause->B:onCreate->B:onStart->B:onResume->A:onStop 如果ActivityB是完全透明的或对话框Activity则不会调用onStop。
- 启动新Activity后,又返回到旧的Activity B:onPause->A:onRestart->A:onStart->A:onResume->B:onStop->B:onDestory
- 关闭屏幕/按Home键: onPause->onStop
- 当一个Activity按Home键切换到桌面后又回到该Activity回调哪些方法。 onPause->onStop->onRestart->onStart->onResume
- 当一个Activity按back键回退时回调哪些方法 onPause->onStop->onDestory
- 可见且可以交互(前台Acitivity):正在和用户交互,优先级最高。
- 可见但不可以交互(可见但非前台Activity):比如当前Activity启动了一个对话框Activity,当前Activity就是可见但不可以交互。
- 后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低。 当系统内存不足,会按照优先级顺序从低到高去杀死目标Activity所在的进程。
在 Activity 由于异常情况被终止时,系统会调用
onSaveInstanceState
方法来保存当前 Activity 的状态,该方法调用于 onStop 之前,与 onPause 方法没有时序关系。当异常终止的 Activity 被重建时,会调用
onRestoreInstanceState
方法(该方法在 onStart 之后),并且把 Activity 销毁时 onSaveInstanceState
保存的 Bundle 对象参数同时传递给 onCreate
方法和onRestoreInstanceState
方法。该方法的调用是在 onStart
之前。因此可通过
onRestoreInstanceState(Bundle savedInstanceState)
和 onCreate((Bundle savedInstanceState)
来判断 Activity
是否被重建,并取出数据进行恢复。但需要注意的是,在 onCreate
取出数据时一定要先判断savedInstanceState
是否为空。补充:其中 onCreate 和
onRestoreInstanceState
方法来恢复 Activity 的状态的区别: onRestoreInstanceState
方法回调则说明 bundle 对象非空,不需要加非空判断,而 onCreate 需要非空判断。6. 谈谈
onSaveInstanceState()
与 onRestoreIntanceState()
onSaveInstanceState() 这两个方法并不是生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用,该方法的调用在onStop之前,与onPause没有时序关系。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。
onSaveInstanceState()
时机: (1)用户按下Home键
(2)横竖屏切换
(3)按下电源按钮(关闭屏幕显示)
(4)内存不足导致优先级的Activity被杀死
onRestoreIntanceState()
当被系统异常销毁的 Activity 被重建时,会调用 onRestoreIntanceState 或 onCreate 方法来恢复,而 onRestoreInstance 与 Oncreate 方法中传入的 Bundle 对象是销毁时 onSaveInstanceState 保存的,onRestoreIntanceState 在 onStart之后。
7. onSaveInstanceState()与onPause()的区别?
onSaveInstanceState()
只适合用于保存一些临时性的状态,而onPause()
适合用于数据的持久化保存。8. 谈谈横竖屏切换过程中调用的函数 要切记这里活动已经被销毁了。
onPause->onSaveInstanceState->onStop->onDestory()->onCreate->onStart->onRestoreIntanceState->onResume
9. 如何防止横竖屏切换(配置改变)时Activity销毁并切换 通过对AndroidManifest文件的Activity中指定(configChanges)属性:
android:configChanges = “orientation| screensize”
来避免横竖屏切换时,Activity的销毁和重建,而是回调了onCofigurationChanged()方法
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
这里附上android configChanges 的所有属性解释
“mcc“ 移动国家号码,由三位数字组成,每个国家都有自己独立的MCC,可以识别手机用户所属国家。
“mnc“ 移动网号,在一个国家或者地区中,用于区分手机用户的服务商。
“locale“ 所在地区发生变化。
“touchscreen“ 触摸屏已经改变。(这不应该常发生。)
“keyboard“ 键盘模式发生变化,例如:用户接入外部键盘输入。
“keyboardHidden“ 用户打开手机硬件键盘
“navigation“ 导航型发生了变化。(这不应该常发生。)
“orientation“ 设备旋转,横向显示和竖向显示模式切换。
“fontScale“ 全局字体大小缩放发生改变
10. 说下Activity的四种启动模式?
- standard模式(标准模式):普通启动模式,每次启动Activity时,就会创建一个实例。
- singletop模式(栈顶模式):当启动Activity时,会判断任务栈的栈顶是否为该Activity,如果是该Activity则不会创建实例,去回调onNewIntent(intent)方法,否则会创建实例
- singletask模式(栈内模式):当启动Activity时,只要该Activity在指定的栈中,就不会创建实例,去回调onNewIntent(intent)**方法。如果不存在,会判断是否指定的栈不存在,就创建一个栈并将Activity的实例压入,如果指定的栈存在,就直接压入该栈中。
- singleInstance模式(单实例模式):该模式下,创建Activity实例时,直接创建一个栈,栈中只有该Activity实例。之后无论哪个应用程序启动该Activity,都只会调用栈中该实例。
因此二者的差别为:
- singleTop 模式:该模式下,任务栈中可能有多个相同 Activity 实例,因为它只是判断当前启动的 Activity 是否在栈顶。 该模式的 Activity 会默认进入启动它所属的任务栈,不涉及任务栈的转换。常用于防止快速连续点击而创建多个 Activity 实例。
- singleTask 模式:该模式向,任务栈中只会有一个Activity实例,因为它会判断当前启动的Activity是否在当前指定的栈中。该模式下Activity可以通过taskAffinity去指定需要的任务栈,可能涉及任务栈的转换,常用于首页或登录页。因为不论我们在进入首页后进入了多少个Activity,当我们返回首页后,还是希望退出首页直接可以退出应用。该模式下会把栈中位于要启动的Activity上面的Activity都出栈。
singleTop模式:当启动的Activity是在任务栈的栈顶时,会回调onNewIntent方法。
singleTask模式:当启动的Activity存在于任务栈中,会回调onNewIntent方法。
13. 了解哪些Activity启动模式的标记位?
- FLAG_ACTIVITY_SINGLE_TOP:对应singleTop启动模式
- FLAG_ACTIVITY_NEW_TASK:对应singleTask模式
推荐阅读
- PMSJ寻平面设计师之现代(Hyundai)
- android第三方框架(五)ButterKnife
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 深入理解Go之generate
- 由浅入深理解AOP
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天