本篇文章主要介绍以下几个知识点:
- 显示、隐式
Intent
的相关内容;- 活动
Activity
的生命周期;- 活动
Activity
的启动模式;- 活动的最佳实践:活动管理类、启动活动的最佳写法。
2.1 活动是什么
活动(activity
)是一种可以包含用户界面的组件,主要用于和用户进行交互。
2.2 使用 Intent 在活动之间穿梭
Intent
大致可分为两种:显示 Intent
和 隐式 Intent
。
2.2.1 使用显示 Intent
显示 Intent
有多个构造函数的重载,其中一个是 Intent(Context packageContext,Class<?>cls)
:
- 第一个参数
Context
要求提供一个启动活动的上下文。 - 第二个参数
Class
则是指定想要启动的目标活动。
代码如下所示:
// 显示Intent
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
2.2.2 使用隐式 Intent
隐式 Intent
并不明确指出想要启动哪一个活动,而是指定了一系列更为抽象的 action
和 category
等信息,然后交由系统去分析这个 Intent
,并找出合适的活动去启动。
用法:打开 AndroidManifest.xml
,添加如下代码:
<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
中的代码修改为:
// 隐式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
startActivity(intent);
只有 <action>
和 <category>
中的内容同时能够匹配上 Intent
中指定的 action
和 category
时,活动才能响应该 Intent
。
上例中 android.intent.category.DEFAULT
是一种默认的 category
,若 Intent
中增加一个 category
:
// 隐式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
intent.addCategory("com.wonderful.MY_CATEGORY");//增加一个category
startActivity(intent);
此时在 <intent-filter>
标签中应再添加一个 category
声明:
<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
,还可以启动其他程序的活动,比如调用系统的浏览器来打开某个网页:
// 隐式Intent:打开网页
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
比如调用系统的拨号界面:
// 隐式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
中的代码为:
String data = "hello SecondActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
SecondActivity
中的代码如下:
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
中的代码如下:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//请求码只要是唯一值就行了,这里传入1
接着在 SecondActivity
中添加返回数据:
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();//销毁当前活动
在 SecondActivity
被销毁后回调上一个活动的 onActivityResult()
方法,因此还需要在 FirstActivity
中重写此方法来得到返回数据:
/**
* 处理得到的返回数据
* @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()
方法:
@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()
方法,其他都是两两相对的,从而又可以将活动分为三种生存期。
- 完整生存期: 活动在
onCreate()
方法和onDestroy()
方法之间所经历的; - 可见生存期: 活动在
onStart()
方法和onStop()
方法之间所经历的; - 前台生存期: 活动在
onResume()
方法和onPause()
方法之间所经历的。
Android 官方提供了一张活动生命周期的示意图,如图所示:
2.3.4 活动被回收了怎么办
Activity 中提供了一个 onSaveInstanceState()
回调方法,这 个方法会保证一定在活动被回收之前调用。
onSaveInstanceState()
方法会携带一个 Bundle
类型的参数,Bundle
提供了一系列的方法用于保存数据,如用 putString()
方法保存字符串,用 putInt()
方法保存整型数据, 以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle
中取值, 第二个参数是真正要保存的内容。
在 Activity
中添加如下代码就可以将临时数据进行保存:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
Activity
被回收后的恢复操作,修改其 onCreate()
方法,如下所示:
@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
中通过给 <activity>
标签指定 android:launchMode
属性来选择启动模式。
2.4.1 standard
standard 是活动默认的启动模式,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard
模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
其原理示意图如下:
2.4.2 singleTop
当活动的启动模式指定为 singleTop
,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
其原理示意图如下:
2.4.3 singleTask
当活动的启动模式指定为 singleTask
,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
其原理示意图如下:
2.4.4 singleInstance
不同于以上三种启动模式,指定为 singleInstance
模式的活动会启用一个新的返回栈来管理这个活动(其实如果 singleTask
模式指定了不同的 taskAffinity
,也会启动一个新的返回栈)。
其原理示意图如下:
2.5 活动的最佳实践
2.5.1 随时随地退出程序
用一个专门的集合类对所有的活动进行管理,新建一个 ActivityCollector
类作为活动管理器,代码如下所示:
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
中的代码如下:
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()
方法就可以了。
当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程代码如下:
// killProcess()方法用于杀掉一个进程,它接收一个进程id参数,可通过myPid()方法获得当前程序的进程id
android.os.Process.killProcess(android.os.Process.myPid());
需注意:killProcess()
方法只能用于杀掉当前程序的进程,不能杀掉其他程序。
2.5.2 启动活动的最佳写法
启动活动的方法:通过 Intent
构建出当前的“意图”,然后调用 startActivity()
或 startActivityForResult()
方法将活动启动起来,如果有数据需要从一个活 动传递到另一个活动,也可以借助 Intent
来完成。
假设 SecondActivity
中需要用到两个非常重要的字符串参数,在启动 SecondActivity
的时候必须要传递过来,那么很容易会写出如下代码:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);
但此时 SecondActivity
并不是由你开发的,但现在你负责的部分需要有启动 SecondActivity
这个功能,而你却不清楚启动这个活动需要传递哪些数据。这时要么你自己去阅读 SecondActivity
中的代码,要么询问负责编写 SecondActivity
的同事。而换一种写法,就可以轻松解决掉上面的窘境。
修改 SecondActivity
中的代码如下:
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.actionStart(FirstActivity.this, "data1", "data2");
好了,今天就到这,下篇文章将进入第三章的学习--UI开发。