Android开发基础2 - 探究活动

本文主要介绍以下几个知识点:

  • 显示、隐式 Intent 的相关内容;
  • 活动 Activity 的生命周期;
  • 活动 Activity 的启动模式;
  • 活动的最佳实践:活动管理类、启动活动的最佳写法。

2.1 活动是什么

  活动( activity )是一种可以包含用户界面的组件,主要用于和用户进行交互。

2.2 使用 Intent 在活动之间穿梭

   Intent 大致可分为两种:显示 Intent 和 隐式 Intent

2.2.1 使用显示 Intent

  Intent 有多个构造函数的重载,其中一个是Intent(Context packageContext,Class<?>cls)
  第一个参数Context要求提供一个启动活动的上下文
  第二个参数Class则是指定想要启动的目标活动

  代码如下所示:

1
2
3
// 显示Intent
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);

2.2.2 使用隐式 Intent

  隐式 Intent并不明确指出想要启动哪一个活动,而是指定了一系列更为抽象的 action category 等信息,然后交由系统去分析这个Intent,并找出合适的活动去启动。

  用法:打开 AndroidManifest.xml ,添加如下代码:

1
2
3
4
5
6
7
<activity android:name=".inquiry_activity.SecondActivity">   
<intent-filter>
<!-- 指定活动能响应的action和category -->
<action android:name="com.wonderful.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

  此时在activity中的代码修改为:

1
2
3
// 隐式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
startActivity(intent);

  只有中的内容同时能够匹配上Intent中指定的 action category 时,活动才能响应该Intent。
  上例中android.intent.category.DEFAULT是一种默认的category,若Intent中增加一个category:

1
2
3
4
// 隐式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
intent.addCategory("com.wonderful.MY_CATEGORY");//增加一个category
startActivity(intent);

  此时在 标签中应再添加一个category声明:

1
2
3
4
5
6
7
8
9
<activity android:name=".inquiry_activity.SecondActivity">   
<intent-filter>
<!-- 指定活动能响应的action和category -->
<action android:name="com.wonderful.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- 新添加的category -->
<category android:name="android.intent.category.MY_CATEGORY"/>
</intent-filter>
</activity>

2.2.3 更多隐式 Intent 的用法

  使用隐式Intent,还可以启动其他程序的活动,比如调用系统的浏览器来打开某个网页:

1
2
3
4
// 隐式Intent:打开网页
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);

  比如调用系统的拨号界面:

1
2
3
4
// 隐式Intent:打开系统拨号界面
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

2.2.4 向下一个活动传递数据

  Intent中提供了一系列 putExtra() 方法的重载,可把要传递的数据暂存在Intent中,启动了另一个活动后再从Intent中取出。
  如FirstActivity中传递一个字符串到SecondActivity中,FirstActivity中的代码为:

1
2
3
4
String data = "hello SecondActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);

  SecondActivity中的代码如下:

1
2
3
4
Intent intent = getIntent();
// String:getStringExtra(); int:getIntExtra(); 布尔型:getBooleanExtra() ...以此类推
String data = intent.getStringExtra("extra_data");
Log.d(TAG, data);

2.2.5 返回数据给上一个活动

  Activity 中有个 startActivityForResult() 方法用于启动活动,此方法在活动销毁时能返回一个结果给上个活动。
  startActivityForResult()方法接收两个参数:Intent、请求码(用作回调时判断数据来源)。
  修改FirstActivity中的代码如下:

1
2
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//请求码只要是唯一值就行了,这里传入1

  接着在SecondActivity中添加返回数据:

1
2
3
4
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();//销毁当前活动

  在SecondActivity被销毁后回调上一个活动的 onActivityResult() 方法,因此还需要在FirstActivity中重写此方法来得到返回数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 处理得到的返回数据
* @param requestCode 启动活动时传入的请求码
* @param resultCode 返回数据时传入的处理结果
* @param data 携带返回数据的Intent
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode == RESULT_OK){
String returnedData = data.getStringExtra("data_return");
Log.d(TAG, returnedData);
}
break;
default:
break;
}
}

  若在SecondActivity是通过按下Back键回到FirstActivity,可以在SecondActivity中重写 onBackPressed() 方法:

1
2
3
4
5
6
7
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}

2.3 活动的生命周期

2.3.1 返回栈

  Android 是使用任务 (Task) 来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈 (Back Stack) 。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们 按下 Back 键或调用 finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
如图所示:

2.3.2 活动状态

  每个活动在其生命周期中最多可能会有四种状态。

  1. 运行状态
      活动位于返回栈的栈顶。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。

  2. 暂停状态
      活动不再处于栈顶位置,但仍然可见。(并不是每一个活动都会占满整个屏幕的,如对话框形式的活动只会占用屏幕中间的部分区域)。
      处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。

  3. 停止状态
      活动不再处于栈顶位置,并且完全不可见的时候。系统仍然会为这种活动保存相应的状态和成员变量,但这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。

  4. 销毁状态
      当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。

2.3.3 活动的生存期

Activity 类中定义了七个回调方法,覆盖了活动生命周期的每一个环节

  1. onCreate()
      在活动第一次被创建的时候调用。在这个方法中完成活动的初始化操作,如加载布局、绑定事件等。

  2. onStart()
      在活动由不可见变为可见的时候调用。

  3. onResume()
      在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。

  4. onPause()
      在系统准备去启动或恢复另一个活动的时候调用。(释放一些消耗 CPU 的资源,保存一些关键数据)。

  5. onStop()
      在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,若启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而 onStop() 方法并不会执行。

  6. onDestroy()
      在活动被销毁之前调用,之后活动的状态将变为销毁状态。

  7. onRestart()
      在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

  以上七个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期。
  1. 完整生存期: 活动在 onCreate()方法和 onDestroy()方法之间所经历的;
  2. 可见生存期: 活动在 onStart()方法和 onStop()方法之间所经历的;
  3. 前台生存期: 活动在 onResume()方法和 onPause()方法之间所经历的。

  Android 官方提供了一张活动生命周期的示意图,如图所示:

2.3.4 活动被回收了怎么办

  Activity 中提供了一个 onSaveInstanceState() 回调方法,这 个方法会保证一定在活动被回收之前调用。
  onSaveInstanceState()方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,如用 putString() 方法保存字符串,用 putInt() 方法保存整型数据, 以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值, 第二个参数是真正要保存的内容。

  在Activity 中添加如下代码就可以将临时数据进行保存:

1
2
3
4
5
6
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}

  Activity 被回收后的恢复操作,修改其onCreate()方法,如下所示:

1
2
3
4
5
6
7
8
9
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) { // 若不为空,取出相应的数据
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
}

2.4 活动的启动模式

  启动模式一共有四种,分别是 standard 、singleTop 、singleTask 和 singleInstance,可以在AndroidManifest.xml 中通过给 标签指定 android:launchMode 属性来选择启动模式。

2.4.1 standard

  standard 是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。
  在 standard 模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建 该活动的一个新的实例。
  standard 模式的原理示意图,如图所示:

2.4.2 singleTop

  当活动的启动模式指定为 singleTop ,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
  singleTop模式的原理示意图,如图所示:

2.4.3 singleTask

  当活动的启动模式指定为 singleTask ,每次启动该活动时系统首先 会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
  singleTask模式的原理示意图,如图所示:

2.4.4 singleInstance

  不同于以上三种启动模式,指定为 singleInstance 模式的活动会启用一 个新的返回栈来管理这个活动(其实如果 singleTask 模式指定了不同的 taskAffinity,也会启动一个新的返回栈)。
  singleInstance模式的原理示意图,如图所示:

2.5 活动的最佳实践

2.5.1 随时随地退出程序

  用一个专门的集合类对所有的活动进行管理,新建一个 ActivityCollector 类作为活动管理器,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ActivityCollector {
// 通过一个List来缓存活动
public static List<Activity> activities = new ArrayList<Activity>();
// 用于向List中添加一个活动
public static void addActivity(Activity activity) {
activities.add(activity);
}
// 用于从List中移除活动
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
// 将List中存储的活动全部销毁掉
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}

  在活动管理器中,我们通过一个 List 来暂存活动,然后提供了一个 addActivity() 方法用于向 List 中添加一个活动,提供了一个 removeActivity() 方法用于从 List 中移除活动,最后提供了一个 finishAll() 方法用于将 List 中存储的活动全部都销毁掉。

  接下来修改 BaseActivity 中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());// 知晓当前是在哪一个活动
ActivityCollector.addActivity(this);// 将当前正在创建的活动添加到活动管理期里
}

@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);// 将一个马上要销毁的活动从管理器里移除
}
}

  在 BaseActivity 的 onCreate()方法中调用了 ActivityCollector 的 addActivity() 方法,表明将当前正在创建的活动添加到活动管理器里。然后在 BaseActivity 中重写 onDestroy() 方法, 并调用了 ActivityCollector 的 removeActivity() 方法,表明将一个马上要销毁的活动从活动管理器里移除。

  从此以后,不管你想在什么地方退出程序,只需要调用 ActivityCollector.finishAll() 方法就可以了。
  当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程代码如下:

1
2
// killProcess()方法用于杀掉一个进程,它接收一个进程id参数,可通过myPid()方法获得当前程序的进程id
android.os.Process.killProcess(android.os.Process.myPid());

  需注意: killProcess() 方法只能用于杀掉当前程序的进程,不能杀掉其他程序。

2.5.2 启动活动的最佳写法

  启动活动的方法:通过 Intent 构建出当前的“意图”,然后调用 startActivity() startActivityForResult() 方法将活动启动起来,如果有数据需要从一个活 动传递到另一个活动,也可以借助 Intent 来完成。
  假设 SecondActivity 中需要用到两个非常重要的字符串参数,在启动 SecondActivity 的 时候必须要传递过来,那么我们很容易会写出如下代码:

1
2
3
4
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);

  但此时 SecondActivity 并不是由你开发的,但现在你负责的部分需要有启动SecondActivity 这个功能,而你却不清楚启动这个活动需要传递哪些数据。这时无非就有两种办法,一个是你自己去阅读 SecondActivity 中的代码,二是询问负责编写 SecondActivity的同事。你会不会觉得很麻烦呢?其实只需要换一种写法,就可以轻松解决掉上面的窘境。

  修改 SecondActivity 中的代码,如下所示:

1
2
3
4
5
6
7
8
9
public class SecondActivity extends BaseActivity {
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
……
}

  我们在 SecondActivity 中添加了一个 actionStart() 方法,在这个方法中完成了 Intent 的构建,另外所有 SecondActivity 中需要的数据都是通过 actionStart()方法的参数传递过来的,然后把它们存储到 Intent 中,最后调用 startActivity()方法启动 SecondActivity。

  这样写的好处在哪里呢?最重要的一点就是一目了然,SecondActivity 所需要的数据全 部都在方法参数中体现出来了,这样即使不用阅读 SecondActivity 中的代码,或者询问负责编写 SecondActivity 的同事,你也可以非常清晰地知道启动 SecondActivity 需要传递哪些数据。

  另外,这样写还简化了启动活动的代码,现在只需要一行代码就可以启动 SecondActivity, 如下所示:

1
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");

  好了,今天就到这,下篇文章将进入–UI开发学习。

Jason Xu wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!