【Android练级之路】Activity
大纲
1. 生命周期
Activity的生命周期可以分为两部分内容,一部分是典型情况下的生命周期,另一部分是异常情况下的生命周期。所谓典型情况下的生命周期,是指在有用户参与的情况下,Activity所经过的生命周期的改变;而异常情况下的生命周期是指Activity被系统回收或者由于当前设备的Configuration发生改变从而导致Activity被销毁重建,异常情况下的生命周期的关注点和典型情况下略有不同。
1.1 典型情况下的生命周期
- onCreate():表示Activity正在被创建,这个方法可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。
- onRestart():表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会暂停,也就是onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况。
- onStart():表示Activity正在被启动,即将开始,这时Activity 已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候其实可以理解为Activity已经显示出来了,但是我们还看不到。
- onResume():表示Activity已经可见了,并且出现在前台并开始活动。要注意这个和onStart的对比,onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台
- onPause():表示Activity正在停止,正常情况下,紧接着onStop就会被调用。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity的onResume才会执行。
- onStop():表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。
- onDestroy():表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。
此外,有几种特殊情况需要补充说明:
- 针对一个特定的Activity,第一次启动,回调如下:onCreate -> onStart ->onResume
- 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause ->onStop。这里有一种特殊情况,如果新Activity采用了透明主题,那么当前Activity不会回调onStop
- 当用户再次回到原Activity时,回调如下:onRestart -> onStart ->onResume
- 当用户按back键回退时,回调如下:onPause -> onStop -> onDestroy
- 当Activity被系统回收后再次打开,生命周期方法回调过程和(1)一样,注意只是生命周期方法一样,不代表所有过程都一样
1.2 异常情况下的生命周期
1.2.1 资源相关的系统配置发生改变导致Activity被杀死并重新创建
当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity。在默认情况下,如果我们的Activity不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并重新创建,其生命周期下图所示。
由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState()来保存当前Activity的状态。需要强调的一点是, 这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。 当Activity被重新创建后,系统会调用onRestoreInstanceState(),并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用时机在onStart之后。
1.2.2 资源内存不足导致低优先级的Activity被杀死
Activity按照优先级从高到低,可以分为如下三种:
- 前台Activity——正在和用户交互的Activity,优先级最高。
- 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
- 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数数据。
1.3 onSaveInstanceState()方法
当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用。用来保存状态,调用在onStop之前,但和onPause没有时序关系。onSaveInstanceState()适用于对临时性状态的保存,而onPause()适用于对数据的持久化保存
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// 保存数据
savedInstanceState.putString("data", "这是保存的数据");
super.onSaveInstanceState(savedInstanceState);
}
- 什么时候调用
- 当用户按下HOME键时。
- 长按HOME键,选择运行其他的程序时。
- 按下电源按键(关闭屏幕显示)时。
- 从activity A中启动一个新的activity时
- 屏幕方向切换时,例如从竖屏切换到横屏时(与android:configChanges属性相关联)
configChanges中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。比如不想让Activity在屏幕旋转的时候重新创建,就可以在AndroidManifest种给Activity添加configChanges属性添加orientation这个值.
如果我们想指定多个值,可以用“|”连接起来,比如android:configChanges="orientation|keyboardHidden"
。系统配置中所含的项目是非常多的,下面介绍每个项目的含义,如下表所示。
如果我们没有在Activity的configChanges属性中指定该选项的话,当配置发生改变后就会导致Activity重新创建。
1.4 onRestoreInstanceState()方法
可以简单地理解,系统只在Activity异常终止的时候才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发这个过程。onSaveInstanceState 和 onRestoreInstanceState 并不是一定成双出现的,终于当 Activity 真正的被销毁的时候才会执行 onRestoreInstanceState()。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 恢复数据
if (savedInstanceState != null) {
String data = savedInstanceState.getString("data");
}
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 也可以在 onRestoreInstanceState() 方法中恢复数据
if (savedInstanceState != null) {
String data = savedInstanceState.getString("data");
}
}
2. 启动模式
2.1 Activity的LaunchMode
- Standard:系统默认,每次启动都会创建一个新的Activity实例
- SingleTop:栈顶复用,如果处于栈顶,直接调用,非栈顶则创建新的Activity实例
- SingleTask:栈内复用,如果存在栈内,则在其上所有Activity全部出栈,使得其位于栈顶,生命周期和SingleTop一样,app首页基本是用这
- SingleInstance:这个是SingleTask加强本,系统会为要启动的Activity单独开一个栈,这个栈里只有它
2.2 Activity的Flag
Activity的Flags有很多,这里主要分析一些比较常用的标记位。标记位的作用很多,有的标记位可以设定Activity的启动模式,比如FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_SINGLE_TOP等;还有的标记位可以影响Activity的运行状态,比如FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS等。下面主要介绍几个比较常用的标记位,剩下的标记位读者可以查看官方文档去了解,大部分情况下,我们不需要为Activity指定标记位,因此,对于标记位理解即可。在使用标记位的时候,要注意有些标记位是系统内部使用的,应用程序不需要去手动设置这些标记位以防出现问题。
- FLAG_ACTIVITY_NEW_TASK
这个标记位的作用是为Activity指定“singleTask”启动模式,其效果和在XML中指定该启动模式相同。 - FLAG_ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定该启动模式相同。 - FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般需要和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。singleTask启动模式默认就具有此标记位的效果。 - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents=“true”。
3.页面跳转
3.1 startActivity()
3.2 startActivityForResult()
AActivity ➡ BActivity
AActivity:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
int resultCode = 1;
startActivityForResult(new Intent(this, BActivity.class), resultCode);
}
/**
* 接收 OtherActivity 返回的数据
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
String result = data.getExtras().getString("result");
}
BActivity:
public void feekBack(){
Intent intent = new Intent();
// 把返回数据存入 Intent
intent.putExtra("result", "我是返回数据");
// 设置返回数据
setResult(RESULT_OK, intent);
// 关闭当前 Activity
finish();
}
3.3 URL Scheme
Android 中的 Scheme 是一种页面内跳转协议,是一种非常好的实现机制。通过定义自己的 Scheme 协议,可以非常方便跳转 App 中的各个页面。
使用场景:
- 通过小程序,利用 Scheme 协议打开原生 App。
- H5 页面点击锚点,根据锚点具体跳转路径 App 端跳转具体的页面。
- App 端收到服务器端下发的 Push 通知栏消息,根据消息的点击跳转路径跳转相关页面。
- App 根据URL跳转到另外一个 App 指定页面。
- 通过短信息中的 URL 打开原生 App。
Scheme 路径的规则:
<scheme> :// <host> : <port> [<path>|<pathPrefix>|<pathPattern>]
设置 Scheme
在 AndroidManifest.xml 中对标签增加设置 Scheme。
<activity
android:name=".ui.activity.SchemeActivity"
android:screenOrientation="portrait">
<!--Android 接收外部跳转过滤器-->
<!--要想在别的 App 上能成功调起 App,必须添加 intent 过滤器-->
<intent-filter>
<!--协议部分配置,注意需要跟 web 配置相同-->
<!--协议部分,随便设置 aa://bb:1024/from?type=meetliang-->
<data android:scheme="aa"
android:host="bb"
android:port="1024"
android:path="/from"/>
<!--下面这几行也必须得设置-->
<category android:name="android.intent.category.DEFAULT" />
<!--表示 Activity 允许通过网络浏览器启动,以显示链接方式引用,如图像或电子邮件-->
<category android:name="android.intent.category.BROWSABLE" />
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
原生调用:
Uri uri = Uri.parse("aa://bb:1024/from?type=meetliang");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
网页调用:
<a href="aa://bb:1024/from?type=meetliang">打开 App</a>
在 SchemeActivity 中可以处理 Scheme 跳转的参数:
public class SchemeActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
if (uri != null) {
//获取指定参数值
String type = uri.getQueryParameter("type");
Log.e("SchemeActivity", "type:" + type);
if(type.equals("meetliang")){
ActivityUtils.startActivity(XXXActivity.class);
}else if(type.equals("main")){
ActivityUtils.startActivity(MainActivity.class);
}
}
finish();
}
}
如何判断一个 Scheme 是否有效:
PackageManager packageManager = getPackageManager();
Uri uri = Uri.parse("aa://bb:1024/from?type=meetliang");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isValid = !activities.isEmpty();
if (isValid) {
startActivity(intent);
}
4. Activity、Windows、View
- Activity是安卓四大组件之一,负责界面展示、用户交互与业务逻辑处理,但是无法处理显示什么控件(view)的,是通过PhoneWindow进行显示的;
- Window就是负责界面展示以及交互的职能部门,就相当于Activity的下属,Activity的生命周期方法负责业务的处理;
- View就是放在Window容器的元素,Window是View的载体,View是Window的具体展示。