Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
Activity
1. Activity 的使用
我们新建的工程中带有一个基础 activity。
新建工程中,需要注意3个文件。
- MainActivity.java 在src/main/java里,对应的包名目录下。
- activity_main.xml 在res/layout里。
- AndroidManifest.xml 在src/main里。这里叫做“清单文件”。
这3个文件分布在不同的地方。简单来说,java文件可以控制界面逻辑。 layout文件(这里指的是activity_main.xml)预设了UI如何摆放。 清单文件告诉系统,我这个app有哪些组件,申请了什么权限。
2. layout - 界面布局
新建的 layout 中,as 一般会默认给一个ConstraintLayout
。比如 activity_main.xml。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 省略默认的TextView -->
</androidx.constraintlayout.widget.ConstraintLayout>
这里为了用起来方便,我们把它换成LinearLayout 有的朋友会问,都2021年了,为什么不直接用 ConstraintLayout?
现在不做什么功能,先用LinearLayout,就是为了方便。 换成LinearLayout后,layout文件长这样。
换成LinearLayout后的activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 省略默认的TextView -->
</LinearLayout>
可以看到,标签的开头和结尾都换成了LinearLayout
。其他地方暂时不修改。
as功能强大,非常便利。我们可以用鼠标选中标签开始的androidx...Layout
,然后直接键盘输入LinearLayout
的前几位字母。
as会自动弹出选择框,在里面双击选择LinearLayout
或者回车选择,标签就替换完成了。
3. Java - 控制界面
layout文件设计的是界面的初始布局。它决定了初始界面上放着什么UI组件以及组件是怎么组织安排的。
这里我们说的是「初始界面」或者「初始布局」。也就是说,我们可以控制界面上的UI元素。
先看默认的 MainActivity.java。在onCreate
方法里,R.layout.activity_main
指的就是activity_main.xml。
现在layout中有一个TextView,它可以用来显示文字。我们想在MainActivity
中控制它,该怎么做呢?
现在改一下这个TextView。删掉原来ConstraintLayout用到的那些属性。
给它添加一个id。这个id在这个layout文件中必须是独一无二的。给它分配一个id叫做tv1
,就像下面。
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
现在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
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
从这个默认的清单文件中我们可以得知,activity 是属于 application 的。application 就是我们的应用。
application 标签中也指定了各种元素,例如应用的图标,名字,主题等等。
MainActivity 是应用启动的第一个 activity。可以观察到它设置了 action 和category 属性。
android.intent.action.MAIN
决定应用程序最先启动的Activity。android.intent.category.LAUNCHER
表示可以在手机“桌面”上看到应用图标。
设置了这 2 个标签,决定了这个 activity 是用户点击应用图标时第一个启动的界面。
小结 activity是应用重要的组件之一。纷繁复杂的内容需要activity来承载。
之后我们会在activity中控制各种各样的UI组件,处理用户的操作,申请权限等等。还要了解activity的生命周期,启动方式和跳转方法。
Activity 生命周期
生命周期图示
1. 生命周期变化
执行一些常见的操作,打log看一下生命周期的变化。测试机型:RedMi。
启动然后退出
onCreate
onStart
onResume
onWindowFocusChanged: hasFocus: true
onWindowFocusChanged: 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即将重启。
3. 说下活动的生存期
活动的生存期分为三个:
- 完整生存期
- 可见生存期
- 前台生存期
完整生存期:onCreate()方法与onDestory()都处于完整生存期,一般情况下,Activity会在onCreate()方法中完成各种初始化操作,而在onDestory()方法中完成释放内存的操作。
可见生存期: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
Activity的优先级
- 可见且可以交互(前台Acitivity):正在和用户交互,优先级最高。
- 可见但不可以交互(可见但非前台Activity):比如当前Activity启动了一个对话框Activity,当前Activity就是可见但不可以交互。
- 后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低。 当系统内存不足,会按照优先级顺序从低到高去杀死目标Activity所在的进程。
5. 优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态?
优先级低的 Activity 在内存不足被回收后重新打开(横竖屏切换的过程中)会引发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,都只会调用栈中该实例。
11. 谈谈 singleTop 和 singleTask 的区别以及应用场景
singleTop 模式的含义是(参考上面问题),singleTask 模式的含义是(参考上面问题)
因此二者的差别为:
-
singleTop 模式:该模式下,任务栈中可能有多个相同 Activity 实例,因为它只是判断当前启动的 Activity 是否在栈顶。 该模式的 Activity 会默认进入启动它所属的任务栈,不涉及任务栈的转换。常用于防止快速连续点击而创建多个 Activity 实例。
-
singleTask 模式:该模式向,任务栈中只会有一个Activity实例,因为它会判断当前启动的Activity是否在当前指定的栈中。该模式下Activity可以通过taskAffinity去指定需要的任务栈,可能涉及任务栈的转换,常用于首页或登录页。因为不论我们在进入首页后进入了多少个Activity,当我们返回首页后,还是希望退出首页直接可以退出应用。该模式下会把栈中位于要启动的Activity上面的Activity都出栈。
12. onNewIntent()调用时机?
有两个调用时机,分别是singleTop模式下与singleTask模式下启动Activity。
singleTop模式:当启动的Activity是在任务栈的栈顶时,会回调onNewIntent方法。
singleTask模式:当启动的Activity存在于任务栈中,会回调onNewIntent方法。
13. 了解哪些Activity启动模式的标记位?
- FLAG_ACTIVITY_SINGLE_TOP:对应singleTop启动模式
- FLAG_ACTIVITY_NEW_TASK:对应singleTask模式
更多Android零基础入门教程学习参考: https://mp.weixin.qq.com/s/HQgjJ5vMFvLh_7u1KizHiA