一. Activities 概述
一个 Activities 代表一个与用户交互的屏幕界面,而且 Activities 可以退至后台,当它恢复运行可以恢复到退至后台前的状态。
本章主要讨论以下内容
创建一个 Activities
实现一个用户界面
在 manifest 中声明一个Activities
启动一个 Activities
启动一个 Activities 并让它返回值
结束一个 Activities
管理 Activities 的生命周期
实现 Activities 的生命周期回调方法
保存 Activities 的状态
处理 conffiguration 的改变
Activities 的状态转换
Activities 是应用程序的一种组件,它提供了一个屏幕界面,用于与用户交互,让用户能够进行一些操作,如打电话、照相、发 email 或查看地图。每个
Activities 都会有自己的一个窗口,以用于绘制用户界面。通常这个窗口是填满整个屏幕的,当然也可以比屏幕小,悬浮在别的窗口之上。
一个应用程序通常由多个
Activities 组成,这些
Activities 之间的耦合很小。通常,每个应用程序都会有一个主的
Activities ,当应用程序启动的时候,默认会把这个
Activities 界面显示给用户。每个
Activities 都可以启动别的
Activities 来完成其它的操作。每当一个新的
Activities 被启动时,它之前的那个
Activities 就会被停止,但是系统会把该
Activities 保存到一个回退栈(back stack)中。新的
Activities 启动后,它就会成为回退栈的新栈顶并得到用户焦点。回退栈遵循后进先出的原则。所以当用户完成与当前的活动
Activities 的交互后,并按返回按钮时,当前的
Activities 就会被弹出栈顶并被销毁,而之前的
Activities 则恢复运行。
(The back stack is discussed more in theTasks and Back Stack
document.)
当
Activities 的状态发生改变时,它所声明的生命周期回调方法就会被调用。
Activities 有几个回调方法,当系统创建、停止、恢复、销毁它时,相应的回调方法就会被调用。而在这些回调方法中,你可以执行一些适合于每种特定状态下应该进行的操作。如当
Activities 被停止时,你应该释放掉所有的大对象,如网络或数据库连接,当
Activities 被恢复时,你可以重新申请这些必要的资源,这些状态的变迁也是
Activities 的生命周期的一部分。
本章余下的部分将会详细的讨论如何创建及如何使用一个
Activities,包括
Activities 的整个生命周期的细节。通过这些讨论,你就可以管理好
Activities 的各状态中的所有事务。
二. 创建一个 Activity
要创建一个
Activities ,你必须通过继承
Activities 类或其子类来实现。你需要在你定义的
Activities 类中实现
Activities 的生命周期回调方法,如
Activities 的状态变迁时的回调方法(created、stopped、resumed、destroyed)。其中两种最重要的回调方法为
(1)onCreate():这个回调方法是你必须实现的,当创建一个
Activities 时,系统就会回调这个方法。在你的实现中,你应该在这个方法里初始化这个
Activities 所需要的重要组件。最重要的是,你必须在这里调用 setContentView() 方法来为这个
Activities 设置用户界面(指定 layout)。
(2)onPause():当用户离开当前
Activities 里,系统就会调用
Activities 的 onPause() 方法(注意系统调用
Activities 的 onPause()
方法,并不意味着这个
Activities 就要被销毁
)。所以如果有某些数据的改变应该在当前的用户会话结束后仍然保存下来,那么你就应该在这个方法里提交(commit)了,因为用户可以不再返回这里了。
为了提供良好的流畅的用户体验,你还应该实现
Activities 的
其它的几个生命周期回调方法,并处理
Activities 被异常中断甚至被销毁等情况。稍后将会详细讨论所有的这些回调方法。
1. 实现一个用户界面
一个 Activity 的一个界面就是一个 view 的层级结构图,每个 view 都是 View 及其子类的对象。每个 view 控制着 activity 中一个特定的矩形区域,而且它可以与响应用户的操作。如一个按钮可以响应用户的点击事件。Android 系统自身提供了一些可视地、可响应用户操作的控件(
Widgets
),如 button、text file、checkbox 等。Android 有 4 种已经定义好了的布局管理器:LinearLayout、FrameLayout、TableLayout、RelativeLayout,它们都是继承自 ViewGroup 的。你也可以通过继承 View 和 ViewGroup 来创建你自己的
Widgets 和布局管理器。
通常为一个应用定义布局的方式是在应用的 res 里面定义 xml 布局文件,这样你就可以把用户界面的维护和 activity 的行为逻辑分开管理。通过调用 setContentView(),把资源文件的 ID 当参数传递过去,可以
把一个 layout 布局文件设置为你 activity 的界面。你也可以在你的 activity 代码里面创建一个 View ,然后通过 ViewGroup 创建一个 view 的层级结构,把你的 view 插入到 ViewGroup 中,再通过调用 setContentView() 把根 ViewGroup 设置为你的 layout 就行了。
想要了解更多如何创建用户界面的内容,请查阅 User Interface
。
下面插入两上小 Demo ,通过这两个 Demo 你就知道如何通过继承 View 和 ViewGroup 自定义 activity 的布局了
Demo1 通过继承 ViewGroup ,然后在 ViewGroup 中加入控件的方式来实现
(1)MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyViewGroup(this));
}
}
(2)MyViewGroup.java
public class MyViewGroup extends ViewGroup {
private final static String TAG = "MyViewGroup";
private final static int VIEW_MARGIN = 2;
private static Context mContext;
public MyViewGroup(Context context) {
super(context);
mContext = context;
addMyView();
}
private void addMyView() {
Button button1 = new Button(mContext);
button1.setText("StormShadow");
addView(button1);
Button button2 = new Button(mContext);
button2.setText("There goes the ring");
addView(button2);
Button button3 = new Button(mContext);
button3.setText("He will be will soon");
addView(button3);
Button button4 = new Button(mContext);
button4.setText("My brother will be will soon");
addView(button4);
Button button5 = new Button(mContext);
button5.setText("Nothing can beat me down");
addView(button5);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG, "widthMeasureSpec = " + widthMeasureSpec
+ " heightMeasureSpec" + heightMeasureSpec);
for (int index = 0; index < getChildCount(); index++) {
final View child = getChildAt(index);
// measure
child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
Log.d(TAG, "changed = " + arg0 + " left = " + arg1 + " top = " + arg2
+ " right = " + arg3 + " botom = " + arg4);
final int count = getChildCount();
int row = 0;// which row lay you view relative to parent
int lengthX = arg1; // right position of child relative to parent
int lengthY = arg2; // bottom position of child relative to parent
for (int i = 0; i < count; i++) {
final View child = this.getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
lengthX += width + VIEW_MARGIN;
lengthY = row * (height + VIEW_MARGIN) + VIEW_MARGIN + height
+ arg2;
// if it can't drawing on a same line , skip to next line
if (lengthX > arg3) {
lengthX = width + VIEW_MARGIN + arg1;
row++;
lengthY = row * (height + VIEW_MARGIN) + VIEW_MARGIN + height
+ arg2;
}
child.layout(lengthX - width, lengthY - height, lengthX, lengthY);
}
}
}
Demo2 通过继承 View ,在 View 中进行重绘来实现,我在 DrawMyView 多加了个线程,需要根据 Model 定时刷新 View 的时候你可以用上它
(1)MainActivity.java
public class MainActivity extends Activity {
private DrawMyView mDrawView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawView = new DrawMyView(this);
setContentView(mDrawView);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
}
(2)DrawMyView.java
public class DrawMyView extends View implements Runnable {
private static String LOG_TAG ="lion message";
private static Thread mThread = null;
public DrawMyView(Context context) {
super(context);
mThread = new Thread(this);
mThread.start();
}
public DrawMyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DrawMyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onDraw(Canvas canvas) {
Log.i(LOG_TAG, "onDraw is called()");
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
for (int i = 0; i < 5; i++) {
canvas.drawRect(5, i * 50, 45, (i + 1) * 50,
paint);
}
super.onDraw(canvas);
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
// 刷新屏幕
// postInvalidate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2. 在 manifest 中声明你的 activity
对于 application 中的每个 acitivty ,都要在 manifest 中对它进行声明,这样系统才能找到这个 activity 。声明 activity 的方式是在 <application> 元素中加入 <activity> 节点,并指明相关属性,如
<manifest ... >
<application ... >
<activity android:name=".ExampleActivity" />
...
</application ... >
...
</manifest >
在定义 <activity> 时,android:name 属性是必须的定义的,它定义了该 activity 的类名。一旦你的 appliction 发布出去了,你不应该再修改这个名称,因为如果你这么做了,那么一些功能就没有了,如 application 快捷方式等。其它的一些属性是可选的,它们用来定义 activity 的属性,如 activity 的图标、UI 主题等。
3. 使用 intent filters
一个 <activity> 标签中也可以声明多种类型的 intent filters ,通过 <intent-filter> 元素来声明,声明它的目的是为了声明其它的 application 能够通过怎样的方式来激活该 activity 。
当你通过 Android SDK tools 来创建一个新的 application 里,主 activity 会默认包含一个 intent filter ,用于指明这个 activity 响应 "main" action ,而且它在 “launcher”类别下。如
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
上边这个 <action> 元素声明了该 activity 是该 application 的主入口。而 <category> 元素则声明了这个 activity 应该放到系统的 launcher 列表中(即放到系统的启动列表中),让用户能够启动这个 activity 。
如果你不想让别的 application 激活你的 application 中的 activity ,那么除了定义一个如上边的 intent filter 外,你就不用再定义其它任何的 intent filters 了。如果你不想让别的 application 能够激活一个 activity ,那么你就不要给它定义任何的 intent filters ,这样你要启动它里,需要通过一个显示的 intent 来启动。
至于 activity 怎样能够响应 intents ,请查阅
Intents and Intent Filters
。
4. 启动一个 activity
可以通过在一个 intent 中指定你要启动的 activity ,然后调用 startActivity() 并把这个 intent 作为调用参数的方式来启动另一个 activity 。在这个 intent 中,可以指明一个具体的要启动的 activity ,也可以只描述你想要执行的 action (这样系统会自动从不同的 application 中选择合适的 activity 来执行这个动作)。Intent 中也可以携带一些数据,给它要启动的 activity 使用。
如果只需要在你的 app 范围内工作,那么你只要知道你想启动哪个 activity ,然后通过在 intent 中显示指定的方式来启动它就行了,即在 intent 中指明该 activity 的名称,如
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
但是有时,你的 app 可能要执行一些 action ,如发送一个 email 或 text message 或状态更新等,这些操作会用到你的 activity 中的数据,但是你的 app 中却没有 activity 能够执行这些操作。在这种条件下,你可以利用设备上其它能够执行这些操作的 app 的 activity 来帮你完成这种操作。这就是 intent 的真正强大之处——可以创建一个 intent ,只在其中描述你要执行的 action ,这样系统就能够从别的 app 中启动合适的 activity 来执行这个 action。如果有多个 activities 能够执行这个 action ,那么用户可以选择使用其中一个来使用。如你想发送一个 email ,那么你可以创建这样一个 intent
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
其中的 EXTRA_EMAIL 是一个字符串数组,它中存放了 email 要发送到的地址。当一个 email app 响应这个 intent 时,它会读出这个地址,然后把这个地址填充到自己的发送地址上,再把这个 email 发出去。当 email 发送完成后,又回到之前的 activity 上(即之前的 activity 重新 resumes)。
5. 启动一个 activity 并返回 result
有时候,从 activityA 启动 activityB 进行一些辅助操作,可能需要 activityB 在执行完成后,返回一些结果给 activityA 。那么你就应该通过 startActivityForResult() 来启动 activityB (而不是 startActivity()),并在 activityA 中实现回调方法 onActivityResult() ,这样当从 activityB 返回里,activityB 会返回一个 intent 给 activityA 的 onActivityResult() 方法。
如,也许你想要让用户选择一个联系人信息,这样你的 activity 就能够对这个信息进行一些操作,下面这一段代码就提供了一个实现,通过这个例子你就知道如何创建这样一个 intent 并处理它返回的结果:
private void pickContact() {
// Create an intent to "pick" a contact, as defined by the content provider URI
Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// If the request went well (OK) and the request was PICK_CONTACT_REQUEST
if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
// Perform a query to the contact's content provider for the contact's name
Cursor cursor = getContentResolver().query(data.getData(),
new String[] {Contacts.DISPLAY_NAME}, null, null, null);
if (cursor.moveToFirst()) { // True if the cursor is not empty
int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
String name = cursor.getString(columnIndex);
// Do something with the selected contact's name...
}
}
}
6. 关闭一个 activity
在 activity 完成其任务后,要将其关闭,可以调用 finish() 方法来实现。如果之前你启动了一个独立的 activity ,现在你想关闭它,那么要调用 finishActivity() 来实现。
注意:在大多数情况下,不要显示地去结束一个 activity ,因为 Android 系统会自动管理 activity 的生命周期,所以你不用手动去管理它。除非你确定不让用户再返回这个 activity ,否则这样做会影响用户体验。
7. 管理 activity 的生命周期
要开发一个健壮的,可扩展的 app ,你就要实现好 activities 的生命周期回调方法。activity 的生命周期直接影响到它的行为,它的任务和回退栈。
实际上一个 activity 的状态只有三种:
Resumed
处于 Resumed 状态的 activity ,会处于屏幕的前端,并拥有用户焦点(你也可以把这个状态称为 running 状态)。
Paused
当 activityA 处于前台并拥有用户焦点,而 activityB 仍然部分可见(可能是因为 activityA 的界面是半透明的,或 activityA 的界面并没有占满整个屏幕)。那么此里 activityB 就处于 Paused 状态。一个处于
Paused 状态的 activity 实际还是完全存活着的(即 activity 对象保存着它的内存占用状态,维持着它所有的状态及成员信息,并且还与 window manager 绑定着)。如果内存资源很紧张,那么处于
Paused 状态的 activity 可能会被 kill 掉。
Stopped
处于
Stopped 状态的 activity 是完全不可见的,即这种 activity 处于后台了。实际上一个
Stopped 的 activity 仍然是存活着的(即 activity 对象保持着内存占用,保持着所有状态和成员信息),但是处于
Stopped 状态的 activity 是不会绑定到 window manager 的。而且处于
Stopped 状态的 activity 会在系统需要时(即系统内存不紧张),被 kill 掉。
如果 activity 处于 paused 或 stopped 状态,那么系统在需要时可以让它通过 finish() 来结束自己,也可以只是简单地直接把它的进程 kill 掉,这样当 activity 再次被打开里,它必须重新创建。
8. 实现 activity 的生命周期回调方法
当一个 activity 在上面提到的三种状态间切换时,它的生命周期回调方法应就会被调用。在这些方法里,你可以完全适于那个状态的任务。下面这个例子 activity 包含了基本的生命周期回调方法
public class ExampleActivity extends Activity {
@Override
public void onCreate
(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The activity is being created.
}
@Override
protected void onStart()
{
super.onStart();
// The activity is about to become visible.
}
@Override
protected void onResume()
{
super.onResume();
// The activity has become visible (it is now "resumed").
}
@Override
protected void onPause()
{
super.onPause();
// Another activity is taking focus (this activity is about to be "paused").
}
@Override
protected void onStop()
{
super.onStop();
// The activity is no longer visible (it is now "stopped")
}
@Override
protected void onDestroy()
{
super.onDestroy();
// The activity is about to be destroyed.
}
}
注意:在实现这些方法时,要在每个方法的开始处先调用父类的实现,就像上面写的那样。
所有的这些方法就一起构成了 activity 的整个生命周期,通过实现这些方法,可以监视到 activity 生命周期的三个嵌套小状态周期
(1)entire lifetime:整个完整的生命周期是从 onCreate() 开始,到 onDestroy() 结束的。在 onCreate() 中,要完成一些全局设置地操作(如定义 layout),而到了 onDestroy() ,就应该释放掉它所占有的一些资源。例如,activity 中有一个在后台运行的线程,用于从网络上下载文件,那么你在 onDestory() 时,就应该把这个线程停止掉,否则它就会一直在后台运行,而你可能会把它忘掉。
(2)visible lifetime:可视地生命周期,这个周期是从 onStart() 开始,结束于 onStop() 的。在此期间,activity 对用户可见且可以和用户交互,而且在这个阶段,你可以保持占用 activity 需要让用户使用的资源。如为了监听一些影响到 UI 的任务,你可以在 onStart() 方法中注册 BroadcastReceiver ,然后在 onStop() 方法中取消注册。在整个生命周期中,系统会根据用户的选择(即显示该 activity 或把它放到后台),多次调用 onStart() 和 onStop() 方法。
(3)foreground lifetime:前台生命周期,在此周期期间,activity 处于屏幕的最前端并且拥有用户焦点。该周期是从 onResume() 开始,到 onPause() 结束的。activity 可能会频繁地进入前台或退出前台,如当设备休眠或弹出一个对话框时,就会调用 onPause() 方法。因为这种切换可能是频繁地,所以这两个方法中的任务应该是轻量级的,否则用户就需要等待太长时间。
下图是 activity 的生命周期图
在 onPause()、onStop()、onDestroy() 三个方法中的任一个返回后,系统可以在必要的情况下杀死持有当前 activity 的进程,这样这个 activity 就马上被销毁了,别的方法也没会执行了。而 onPause() 方法是这三个方法中首先会被调用到的,所以你应该在这个方法中把需要持久存储的数据存储起来(如用户正在编辑的内容)。因为从 activity 创建,到执行 onPause() 后,可能会因为发生紧急事件,系统要马上把当前 activity 所属的进程杀死,这样这个 activity 也就被销毁了。但是要注意不要把一些不相关的信息也存储起来,因为存储的数据越多,需要的时间越长,用户等待的时间越久。
在生命周期线上的 onCreate()、onStart()、onResume() 三个方法执行期间,或者说从 onCreate() 开始执行,直到 onResume() 方法返回期间,系统会保证持有该 activity 的进程是不会被 kill 掉的。总的来说,从 onPause() 返回后,到 onResume() 被再次调用前,activity 是可被 kill 掉的;所以在 onPause() 没有被调用并返回前,activity 是不会被 kill 掉的。
9. 保存 activity 的状态
当 activity 被 pause 或 stop 的时候,它还留在内存中的,所以它的状态还是可以恢复的。当 activity 被 destroy 后,再要启动这个 activity 时,就必须重新创建了。但是用户可能希望 activity 被切到后台后,还能在下次打开时恢复到之前的状态。所以在 activity 切至后台前,你应该通过 onSaveInstanceState() 方法来把 activity 的重新信息保存起来,以便后面用于 activity 恢复到之前的状态。
在系统销毁 activity 前,会调用 onSaveInstanceState() ,系统会给这个方法传递一个 Bundle ,你可以把有关这个 activity 的信息以键-值对的形式写到这个 Bundle 中(通过 putString() 或 putInt())来实现。这样当 app 切换到后台后,如果它被系统 kill 掉了,当用户再重新返回到这个 activity 时,系统就会重新创建这个 activity ,并把之前的 Bundle 传递给 onCreate() 和 onRestoreInstanceState() ,在这两个方法中,你可以从 Bundle 中把 activity 恢复到之前的状态。如果没有什么信息要恢复,那么 Bundle 为 null 。下图描述了这些过程
注意:onSaveInstanceState() 方法并不总是会在 activity 被销毁前调用的,因为有时没必要调用它(如当用户点击回退按钮时,表明他想关闭这个 activity ,而不是把它切到后台,那就没必要保存状态了)。系统调用 onSavaInstanceState() 的时机也并不是非常确定的,可能在 onStop() 前调用,也可能是在 onPause() 前调用。
即使你不实现 onSaveInstanceState() ,activity 的一些状态还是会通过其默认实现的 onSaveInstanceState() 来恢复的。尤其是对 layout 中的每个 View ,它们都会有默认实现的 onSaveInstanceState() ,以便当 activity 被切换回来时恢复到它们之前的状态,如 CheckBox 是否被选中、EditText 中是否有用户已编辑的内容等。你所要做的只是为每个 widget 提供唯一的 ID (通过 android:id 来指定),如果你没有为一个 widget 指定 ID ,那么系统是没法保存它的状态的。
你也可以通过 android:saveEnabled 或 setSaveEnabled 显示地指明不让系统保存某个 widget 的状态,但是通常不建议这么做,除非你有特殊需要。
重写 onSaveInstanceState() 时不要忘了先调用 super 的 onSaveInstanceState(),原因你懂的...
注意:由于 onSaveInstanceState() 并不一定会被调用,所以如果要保存某些很重要的数据(如把某些数据写到数据库),你不应该在这个方法里进行,而是在 onPause() 中进行。
测试 app 保存和恢复数据的能力的最好办法是不断地旋转设备,让屏幕不断在横屏和竖屏间切换。因为在横竖屏切换时,系统会重新创建 activity ,以便让 app 给不同的显示方式选择不同的资源(如不同模式下显示不同的图片)等。
10. 处理配置的改变
一些设备的配置可以在运行时改变,如屏幕方向,键盘可见性和语言等。当这些改变发生时,Android 系统会重新创建相应的 activity (即先调用 activity 的 onDestroy() ,然后立即调用 onCreate())。这些功能使得 app 可以根据配置自动加载并根据你的定义选择不同的资源(如屏幕改变时,显示不同的 layout)。所以如果你想让你的 app 看起来更有弹性些,更人性化一些,那么你就要处理好状态改变后的恢复功能了。想了解有关方面的更多信息,请查阅
Handling Runtime Changes
。