1. Activity 的生命周期
1.1 分类
在讲解生命周期的方法之前,先放上这张官方的图:
这张图片讲述了 Activity 的回调的方法,下表分类讲解这些方法的作用。
生命周期方法 | 作用 |
---|---|
onCreate | 表示 Activity 正在被创建 |
onRestart | 表示 Activity 正在重新启动 |
onStart | 表示 Activity 正在被启动 |
onResume | 表示 Activity 已经可见 |
onPause | 表示 Activity 正在停止 |
onStop | 表示 Activity 即将停止 |
onDestroy | 表示 Activity 即将被销毁 |
1.2 各种情况下生命周期的回调
以下总结一下各种情况下,生命周期中的回调情况(表中的 A,B 代表的是两个 Activity):
情况 | 回调 |
---|---|
第一次启动 | onCreate() -> onStart() -> onResume() |
从 A 跳转到不透明的 B | A_onPause() -> B_onCreate() -> B_onStart() -> B_onResume() -> A_onStop() |
从 A 跳转到透明的 B | A_onPause() -> B_onCreate() -> B_onStart() -> B_onResume() |
从不透明的 B 再次回到 A | B_onPause() -> A_onRestart() -> A_onStart() -> A_onResume() -> B_onStop() |
从透明的 B 再次回到 A | B_onPause() -> A_onResume() -> B_onStop() -> B_onDestroy() |
用户按 home 键 | onPause() -> onStop() |
按 home 键回后回到应用 | onRestart() -> onStart() -> onResume() |
用户按 back 键回退 | onPause() -> onStop() -> onDestroy() |
1.3 onSaveInstanceState() 与 onRestoreInstanceState()
这两个方法只有在应用遇到意外情况下才会触发。可以用于保存一些临时性的数据。
1.3.1 触发场景
onSaveInstanceState():
- 横竖屏切换
- 按下电源键
- 按下菜单键
- 切换到别的 Activity
- ....
onRestoreInstanceState():
- 横竖屏切换
- 切换语言
- ....
2. Activity 之间的跳转
2.1 相关API
2.1.1 startActivity()
怎么用:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
使用这个方法就可以跳转到 SecondActivity
2.1.2 startActivityforResult()
怎么用:
MainActivity.java:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
这里第二个参数是一个 requestCode,这个参数会在 onActivityResult 回调回来。
SecondActivity.java:
setResult(2);
finish();
当 SecondActivity finish 后会回调 MainActivity 中的 onActivityResult 方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.e("chan", "==================onActivityResult main= " + requestCode + " " + resultCode);
}
打印结果:
E/chan: ==================onActivityResult main= 1 2
当然你也可以不调用 setResult() 方法,这时回调过来的 resultCode 就是 0。
2.2 显式启动
显示启动分类:
- 直接在 Intent 构造方法启动:
Intent intent = new Intent(this, SecondActivity.class);
- setComponent:
ComponentName componentName = new ComponentName(this, SecondActivity.class);
Intent intent = new Intent();
intent.setComponent(componentName);
- setClass / setClassName:
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
intent.setClassName(this, "com.example.administrator.myapplication.SecondActivity");
2.3 隐式启动
隐式启动就是要在该 Activity 中设置 IntentFilter 属性,只要启用的 Intent 匹配 IntentFilter 的条件就可以启动相应的 Activity。
要理解隐式启动就必须要理解 IntentFilter 是如何使用的
2.3.1 IntentFilter 的使用
IntentFilter 有三个标签分别是:
- action
- category
- data
这三个标签都有对应的匹配规则,下面会说到。这里来说下使用 IntentFilter 要注意的地方
- 一个 Activity 中可以有多个 intent-filter
- 一个 intent-filter 同时可以有多个 action,category,data
- 一个 Intent 只要能匹配任何一组 intent-filter 即可启动对应 Activity
- 新建的 Activity 必须加上以下这句:
<category android:name="android.intent.category.DEFAULT"/>
否则就会出现如下错误:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.chan1 }
2.3.2 action 的匹配规则
action 的匹配规则就是只要满足其中一个 action 就可以启动成功。
在 Manifest 定义一个 SecondActivity:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.chan" />
<action android:name="com.chan2" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.chan3" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
MainActivity:
Intent intent = new Intent();
intent.setAction("com.chan2");
startActivity(intent);
这样就可以启动 SecondActivity,要注意的是 action 是区分大小写的。
2.3.3 category 匹配规则
category 在代码设置如下:
intent.addCategory("com.zede");
这句可以添加也可以不添加,因为代码默认会为我们匹配 “android.intent. category.DEFAULT”。
2.3.4 data 匹配规则
data 主要是由 URI 和 mimeType 组成的。URI 的结构如下:
<scheme> :// <host> : <port> [<path>|<pathPrefix>|<pathPattern>]
这些值在 Manifest 文件中可以定义,语法如下:
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
以下用一个例子来说明:
Manifest:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="zede"
android:port="1010"
android:scheme="chan" />
</intent-filter>
</activity>
MainActivity:
Intent intent = new Intent();
intent.setData(Uri.parse("chan://zede:1010"));
startActivity(intent);
通过这个方法就可以跳转到 SecondActivity。
我们也可以创建一个 html 文件,来实现跳转 SecondActivity。
test.html:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<a href="chan://zede:1010">跳转至SecondActivity</a>
</body>
</html>
使用手机浏览器打开这个 html 文件,点击这个超链接也可以跳转到 SecondActivity。
通过这个链接也可以传输数据到 SecondActivity,代码如下:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<a href="chan://zede:1010/mypath?user=admin&psd=123456">跳转至SecondActivity</a>
</body>
</html>
在 SecondActivity 接收数据:
Intent intent = getIntent();
Uri uri = intent.getData();
Log.e("chan", "==================getScheme= " + intent.getScheme());
Log.e("chan", "==================getHost= " + uri.getHost());
Log.e("chan", "==================getPort= " + uri.getPort());
Log.e("chan", "==================getPath= " + uri.getPath());
Log.e("chan", "==================getQuery= " + uri.getQuery());
Set < String > names = uri.getQueryParameterNames();
Iterator < String > iterator = names.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
uri.getQueryParameter(key);
Log.e("chan", "==================getQueryParameter= " + uri.getQueryParameter(key));
}
打印结果:
07-19 10:47:54.969 19201-19201/com.example.administrator.myapplication E/chan: ==================getScheme= chan
07-19 10:47:54.970 19201-19201/com.example.administrator.myapplication E/chan: ==================getHost= zede
==================getPort= 1010
==================getPath= /mypath
==================getQuery= user=admin&psd=123456
==================getQueryParameter= admin
==================getQueryParameter= 123456
另外还需要注意另一个属性:android:mimeType,这个属性就是说要传递什么类型的数据,通常有 text/plain 或 image/jpeg。
可以通过以下代码来启动 Activity:
intent.setType("text/plain");
不过需要注意的是,如果同时设置了 URI 和 mimeType 的话就必须使用如下代码才可以跳转:
intent.setDataAndType(Uri.parse("chan://zede:1010"), "text/plain");
因为如果使用 setData() 或者 setType() 的话,分别会将相应 type 和 data 置为 null。
3. Activity的启动模式
3.1 分类:
启动模式 | 作用 |
---|---|
standard | 每次启动都会重新创建一个 Activity |
singleTop | 如果该栈顶上有所要启动的 Activity,那么就不会重新创建该 Activity,并会回调 onNewIntent() |
singleTask | 如果栈内已经有所要启动的 Activity 就不会被创建,同时也会调用 onNewIntent() |
singleInstance | 创建该 Activity 系统会创建一个新的任务栈 |
这里重点说下 singleTask。
3.2 singleTask 分析
singleTask 叫做栈内复用模式,这个启动模式的启动逻辑如下图:
相信看了上面这个图,大家也清楚 singleTask 的逻辑了,但是这个模式还有几个需要注意的地方。
3.2.1 taskAffinity
前面提到 A 想要的任务栈,那什么是 A 想要的任务栈呢?这就提到一个属性 taskAffinity,以下详细介绍这个属性。
3.2.1.1 作用
标识一个 Activity 所需要的任务栈的名字。如果不设置这个属性值,默认值是应用的包名。
3.2.1.2 taskAffinity 与 singleTask 配对使用
如果启动了设置了这两个属性的 Activity,这个 Activity 就会在 taskAffinity 设置的任务栈中,下面用代码来验证下:
创建 SecondActvitiy,在 Mnifest 文件设置 SecondActvitiy,代码如下:
<activity android:name=".SecondActivity"
android:taskAffinity="com.chan"
android:launchMode="singleTask" />
现在使用 MainActivity 启动 SecondActvitiy,这里的代码就不展示了,我们直接看看结果,在终端输入以下命令:
adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'
这个命令可以查看正在运行的 Activity,结果如下:
Running activities (most recent first):
TaskRecord{762a040 #63 A=com.chan U=0 StackId=1 sz=1}
Run #1: ActivityRecord{3881f68 u0 com.example.activitydemo/.SecondActivity t63}
TaskRecord{351eb79 #62 A=com.example.activitydemo U=0 StackId=1 sz=1}
从打印结果可以看出, MainActivity 和 SecondActivity 运行在不同的任务栈中。
3.2.1.3 taskAffinity 和 allowTaskReparenting 配对使用
allowTaskReparenting 这个属性直接解释的话,可能很多人都会听得懵逼,下面直接使用例子来解释:
现在创建两个应用,一个应用的包名为:com.example.activitydemo,以下成为应用 A。另一个应用的包名为:com.example.hellodemo,以下称为应用 B。
在应用 A 的 MainActivtiy 中添加如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.setClassName("com.example.hellodemo", "com.example.hellodemo.HelloActivity");
startActivity(intent);
}
});
}
应用 B 中创建 HelloActivity,并在 Manifest 文件中设置 HelloActivity 的 allowTaskReparenting 为 true,代码如下:
<activity android:name=".HelloActivity"
android:exported="true"
android:allowTaskReparenting="true" />
然后根据以下步骤操作:
- 现在先运行应用 B,然后退出
- 再运行应用 A,点击按钮启动
- 按 home 键,回到主界面
- 启动应用 B
完成第二步的时候,在终端看下任务栈的情况:
Running activities (most recent first):
TaskRecord{5d54c1c #85 A=com.example.activitydemo U=0 StackId=1 sz=2}
Run #1: ActivityRecord{ff0b8e u0 com.example.hellodemo/.HelloActivity t85}
Run #0: ActivityRecord{95ee35c u0 com.example.activitydemo/.MainActivity t85}
可以看出 HelloActivity 运行在应用 A 的任务栈中。
完成第四步后,再看下任务栈:
Running activities (most recent first):
TaskRecord{74c894d #86 A=com.example.hellodemo U=0 StackId=1 sz=2}
Run #1: ActivityRecord{ff0b8e u0 com.example.hellodemo/.HelloActivity t86}
TaskRecord{5d54c1c #85 A=com.example.activitydemo U=0 StackId=1 sz=1}
Run #0: ActivityRecord{95ee35c u0 com.example.activitydemo/.MainActivity t85}
从结果可以看到,HelloActivity 从应用 A 的任务栈移动到应用 B 的任务栈。
现在再修改下 HelloActivity 的 taskAffinity 属性,代码如下:
<activity android:name=".HelloActivity"
android:exported="true"
android:allowTaskReparenting="true"
android:taskAffinity="com.chan"/>
重新根据以上步骤操作,操作完毕后看下任务栈信息:
Running activities (most recent first):
TaskRecord{50264fe #90 A=com.example.hellodemo U=0 StackId=1 sz=1}
Run #2: ActivityRecord{bc77713 u0 com.example.hellodemo/.MainActivity t90}
TaskRecord{41abf9e #89 A=com.example.activitydemo U=0 StackId=1 sz=2}
Run #1: ActivityRecord{2d0b7bb u0 com.example.hellodemo/.HelloActivity t89}
Run #0: ActivityRecord{8b57551 u0 com.example.activitydemo/.MainActivity t89}
可以看出 HelloActivity 并没有移动到应用 B 的主任务栈中,因为这并不是 HelloActivity 想要的任务栈。
继续修改 HelloActivity 配置属性,增加 singleTask 属性:
<activity android:name=".HelloActivity"
android:exported="true"
android:allowTaskReparenting="true"
android:taskAffinity="com.chan"
android:launchMode="singleTask"/>
继续操作,任务栈结果如下:
Running activities (most recent first):
TaskRecord{775e709 #95 A=com.example.hellodemo U=0 StackId=1 sz=1}
Run #2: ActivityRecord{757bb47 u0 com.example.hellodemo/.MainActivity t95}
TaskRecord{aa75b2 #94 A=com.chan U=0 StackId=1 sz=1}
Run #1: ActivityRecord{76e2133 u0 com.example.hellodemo/.HelloActivity t94}
TaskRecord{21c8903 #93 A=com.example.activitydemo U=0 StackId=1 sz=1}
Run #0: ActivityRecord{be84df4 u0 com.example.activitydemo/.MainActivity t93}
可以看出与没有增加 singleTask 属性的结果是一样的,其实 allowTaskReparenting 这个属性的最主要作用就是将这个 Activity 转移到它所属的任务栈中,例如一个短信应用收到一条带网络链接的短信,点击链接会跳转到浏览器中,这时候如果 allowTaskReparenting 设置为 true 的话,打开浏览器应用就会直接显示刚才打开的网页页面,而打开短信应用后这个浏览器界面就会消失。
3.3 指定启动模式的方式
指定启动模式的方式有两种,一种是在 AndroidMenifest 文件设置 launchMode 属性,另一种就是在 Intent 当中设置标志位。第二种方式的优先级会比第一种的要高,如果两种都设置了会以第二种方式为准。我们来验证一下:
在 MainActivity 设置如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
}
SecondActivity 在 AndroidMenifest 设置如下:
<activity android:name=".SecondActivity"
android:taskAffinity="com.chan" />
这里我并没有设置 SecondActivity 为 sigleTask,来验证下启动 SecondActivity 是否会开启一个新的任务栈。
运行后,任务栈的结果为:
Running activities (most recent first):
TaskRecord{148d7c5 #143 A=com.chan U=0 StackId=1 sz=1}
Run #2: ActivityRecord{de59b2d u0 com.example.activitydemo/.SecondActivity t143}
TaskRecord{520151a #142 A=com.example.activitydemo U=0 StackId=1 sz=1}
Run #1: ActivityRecord{d80bfc1 u0 com.example.activitydemo/.MainActivity t142}
从结果可以看出,是开启了一个新的任务栈的,也证明了第二种方式的优先级比较高
3.4 onNewIntent 回调时机
启动 singleTask 的 Activity 的时候会回调 onNewIntent() 方法,但是并不是所有情况都这样,总结如下图:
以下使用代码来验证一下这四种情况:
3.4.1 不存在 A 所需的任务栈
代码如下:
MainActivity.java:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e("chan", "MainActivity=======================onNewIntent");
}
}
SecondActivity.java:
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.start2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SecondActivity.this, ThirdActivity.class));
}
});
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e("chan", "SecondActivity=======================onNewIntent");
}
}
清单文件中将 SecondActivity 设置为 singleTask,taskAffinity 属性设置一个非该程序包名的值,代码如下:
<activity android:name=".SecondActivity"
android:taskAffinity="com.onnewintent"
android:launchMode="singleTask" />
以上代码的结果并没有打印任何东西,证明这样并不会回调 onNewIntent()。
3.4.2 存在 A 所需的任务栈
这种情况还要分两种子情况,一种就是 A 不在栈中,另一种 A 不在栈中。
3.4.2.1 A 不在栈中
还是用回上面的例子的代码,添加一个 ThridActivity,ThridActivity 在清单文件描述如下:
<activity android:name=".ThirdActivity"
android:taskAffinity="com.onnewintent"
android:launchMode="singleTask" />
点击 SecondActivity 跳转按钮后同样也不会有任何打印,证明并不会回调 onNewIntent()。
3.4.2.2 A 在栈中
这种情况也会分两种子情况,一种就是 A 在栈顶,另一种就是 A 不在栈顶。
3.4.2.2.1 A 在栈顶
同样也是使用上面的例子,修改 ThirdActivity,代码如下:
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
findViewById(R.id.start3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ThirdActivity.this, ThirdActivity.class));
}
});
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e("chan", "ThirdActivity=======================onNewIntent");
}
}
到 ThirdActivity 时,ThirdActivity 已经是在栈顶了,这时候点击按钮再次启动 ThirdActivity,打印结果如下:
chan : ThirdActivity=======================onNewIntent
可以发现这种情况会回调 ThirdActivity 的 onNewIntent。
3.4.2.2.2 A 不在在栈顶
同样改动 ThirdActivity,这次启动的是 SecondActivity。具体代码就不写了,打印结果如下:
chan : SecondActivity=======================onNewIntent
可以发现这种情况会回调 SecondActivity 的 onNewIntent。
3.5 Activity 中的 Flags
3.5.1 FLAG_ACTIVITY_NEW_TASK
与启动模式 singleTask 的作用一样,必须设置 taskAffinity
3.5.2 FLAG_ACTIVITY_SINGLE_TOP
与启动模式 singleTop 的作用一样
3.5.3 FLAG_ACTIVITY_CLEAR_TOP
从名字就可以看出这个标志的作用就是如果这个 Activity 顶部有别的 Activity 的话,那么就会它顶部的 Activity 全部出栈,如果这个 Activity 的启动模式为 standard 的话,就会将之前的 Activity 出栈,并且创建一个新的 Activity,如果不是的话就会调用 onNewIntent 方法。
3.5.4 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
不能在查看历史 Activity 中查看到此 Activity