Android组件:Activity、Service、Broadcast Receiver、Content Provider

Activity

Android中的Activity有四个基本状态:

  1. Actived/Runing 一个新的Activity被启动,处于Activity任务栈栈顶,显示在屏幕最前端,此时它处于可见并可和用户交互的激活状态。
  2. Paused 被另一个透明或者 Dialog 样式的 Activity 覆盖时的状态。仍然可见,但已失去焦点,不能与用户交互
  3. Stoped 被另一个Activity覆盖、不可见、失去焦点的状态。
  4. Killed/Destoryed 被系统回收,Activity实例被销毁

这些状态之间转换都依赖于用户的操作。程序员可以决定一个Activity何时启动,但不能决定它何时被销毁。

Activity生命周期方法

在android.app.Activity类中定义了一系列生命周期相关的方法,在应用自定义的Activity中只要复写了这些方法中所需的,就可以确保被android系统调用到。
Activity生命周期

  • onCreate(Bundle savedInstanceState): 创建Activity时调用,在这里可以做一些初始化操作,如设置用户界面、读取savedInstanceState中的数据
  • onStart(): 开启Activity。在上一个方法之后被调用,或者在stop状态转换成active状态时调用。如用户从前面一个Activity返回时。
  • onResume(): 获取焦点。在上一个方法之后被调用,或在pause状态转换成activ状态时调用。如用户关闭对话框。
  • onPause(): 失去焦点。在active状态转换成pause状态时调用。如用户打开了一个对话框时。
  • onStop(): 停止Activity。从active状态转为stop状态。如用户打开了新的Activity。
  • onDestory(): 结束Activity。被系统回收,一般在这里释放一些占有资源,如服务的连接。

生命周期方法可以分为以下三组:

  • 完整生命周期(Entire lifetime):从onCreate()方法执行直到onDestroy()结束。前者初始化,后者释放资源。
  • 可视生命周期(Visible lifetime):从onStart()方法执行直到onStop()结束。用户可见到Activity,即使它没有获取到焦点不能与用户交互,但可以在onStart()中注册一个广播接 收者来接收用户的操作,在UI上做出相应的改变,然后在onStop()中注销接受者。
    onStart()onStop()可以被多次调用,完全取决于用户操作时Activity是否为用户可见。
  • 前台生命周期(Foreground lifetime):从onResume()方法执行到onPause()结束。Activity位于Activity任务栈顶端,并与用户进行交互。Activity经常会在pausedactived(/running)状态之间转换

保存Activity的状态

当系统内存不足,低优先级的应用进程会被系统Kill(如图所示),这时如果用户按了返回键想回到被回收的Activity,为了提高用户体验,那么系统就会重新启动这个Activity,可是这时的Activity中的数据可能已被销毁,这样,Android采用了一个能临时保存Activity数据的机制——将数据以键值对的形式存在一个Bundle对象中,系统将这个Bundle对象传给onCreate()onRestoreInstanceState(),使用二者之一就可以很方便的从中获取数据展现给用户,如果Bundle对象为空,表明没有进行存放数据或者Activity才第一次被启动。
Activity状态保存
但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。

Activity任务栈(Task Stack)

Android 是通过Activity__任务栈__的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。

处于前台的 Activity 总是在栈的顶端,当新的 Activity 启动入栈时,原 Activity 会被压入到栈的第二层。当前台的 Activity 因为异常或其它原因被销毁时,处于栈第二层的 Activity 将被激活,上浮到栈顶。
一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。
Activity任务栈

创建Activity

编写一个继承自 android.app.Activity的 Java 类并在 清单文件AndroidManifest.xml声明即可。

Activity - MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MainActivity extends Activity {
private static final String LOG_TAG = MainActivity.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d(LOG_TAG, "onCreate");
}
@Override
protected void onStart() {
Log.d(LOG_TAG, "onStart");
super.onStart();
}
@Override
protected void onResume() {
Log.d(LOG_TAG, "onResume");
super.onResume();
}
@Override
protected void onPause() {
Log.d(LOG_TAG, "onPause");
super.onPause();
}
@Override
protected void onStop() {
Log.d(LOG_TAG, "onStop");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(LOG_TAG, "onDestroy ");
super.onDestroy();
}
}

AndroidManifest.xml片段:

Activity -AndroidManifest.xml
1
2
3
4
5
6
7
<activity android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

开启另一个Activity

通过startActivity()方法可以根据传入的Intent*来开启另一个符合Intent的Activity(Activity都必须在清单文件中有定义)。

Activity - MainActivity.java
1
2
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);

*Intent见其详解。

Activity之间的通信

开启另一个Activity

Android用Intent对象来表示一条消息,一个Intent对象不仅包含这个消息的目的地,还可以包含消息的内容。目的地是必须的,而消息则是可选的,如同一封Email。
在MainActivity开启SecondActivity,并加入额外的内容:

Activity - MainActivity.java
1
2
3
4
5
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.putExtra( "data", "String value");//加入String类型的消息
intent.putExtra("isRight", true);// 加入boolean类型的消息
//...
startActivity(intent);

在SecondActivity中的接收这条消息:

Activity - SecondActivity.java
1
2
3
4
Intent intent = getIntent();
Log.i(TAG,intent.getData().toString());
Log.i(TAG,intent.getBooleanExtra("isRight", false));
Log.i(TAG,intent.getStringExtra("data"));

获得另一个Activity的返回数据

调用startActivityForResult(intent,requestCode)开启一个新的Activity,新Activity执行完毕,可以根据请求码(requestCode)返回包装数据的Intent对象(intent),并给它指定响应码(responseCode),当前Activity就可以通过onActivityResult(requestCode,responseCode,intent)来获取相应requestCode、responseCode和Intent对象中数据。
eg.
第一个Activity:

Activity - MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
//...
Button button =(Button) this.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){//点击该按钮会打开一个新的Activity
public void onClick(View v) {
//第二个参数为请求码,可以根据业务需求自己编号
startActivityForResult (new Intent(MainActivity.this, NewActivity.class), 1);
}
});
}
//第一个参数为请求码,即调用startActivityForResult()传递过去的值
//第二个参数为结果码,结果码用于标识返回数据来自哪个新Activity
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
String result = data.getExtras().getString(“result”));//得到新Activity 关闭后返回的数据
}
}

第二个Activity:

Activity - NewActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class NewActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
//...
button.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {// 来按钮来模拟处理响应
Intent intent = new Intent();//数据是使用Intent返回
intent.putExtra("result", "我是返回的处理结果");//把返回数据存入Intent
NewActivity.this.setResult(RESULT_OK, intent);//设置返回数据
NewActivity.this.finish();//处理完毕关闭Activity
}
});
}
}

上面的setResult(responseCode,intent)即用来设置返回数据的方法,第一个参数为响应码:RESULT_OK是Activity定义的一个常量,其值为-1

Activity的Intent Filter

在 Android 应用的 AndroidManifest.xml 清单文件中可以通过<intent-filter>节点为一个 Activity 指定其 IntentFilter,以便告诉系统该 Activity 可以响应什么类型的 Intent。

当用startActivity(intent)来启动一个Activity 时,Activity Manager 会在系统中检索与Intent 对象的属性(Action/Data Uri/Category)最为匹配的一个Activity 启动,如果没有找到则会抛出异常。
*具体详见Intent详解

其他

Activity的启动模式

在AndroidMamifest.xml 的<activity>节点android:launchMode属性设置。

默认值为standard:

每一次都会开启一个全新的Activity,加入任务栈。

singleTop的模式:

特点:

  1. 如果栈顶存在的Activity是要被激活的Activity他就不会再创建新的activity,直接在原有Activity上更新界面。
  2. 只有栈顶存在的Activity与要激活的singletop的Activity不相同的时候,才会创建新的activity放在任务栈的栈顶。

主要作用:就是为了避免糟糕的用户体验。避免一个被打开的界面,被重复的多次打开,用户要按多次的后退键才能退出这个应用程序。

singleTask的模式:

特点:在开启activity的时候,会检查任务栈里面是否已经有要激活的Activity的实例存在,如果存在的话,清空这个存在的Activity上面的任务,复用存在的这个Activity
应用场景:eg.浏览器: 初始化webkit内核(耗时耗空间),为了避免多次创建browser Activity的实例,消耗过多系统的内核和cpu,保证在当前应用程序的任务栈里面只有一个浏览器应用的activity的实例存在

singleInstance的模式:

特点:

  1. 这种模式非常的极端,不会把创建的Activity的实例放入到当前应用的任务栈里面。他会自己创建一个任务栈,并且在任务栈里存放自己的一个实例
  2. 在整个手机操作系统里面只有一个单独的任务栈,并且任务栈里面只有一个单独的实例存在

锁定Activity运行时屏幕方向

Android会自动根据内置重力感应器来切换横竖屏,在切换的时候,当前Activity会被destory后再重新启动另一个方向的全新的Activity,这样会极大影响一些应用的用户体验(比如游戏)。
此时,我们需要锁定该Activity运行时的屏幕方向,通过清单文件AndroidManifest.xml 中<activity>节点的android:screenOrientation属性来指定:landscape为横屏,portrait为竖屏。
eg.

Activity -AndroidManifest.xml
1
2
3
4
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape">
</activity>

全屏Activity

  1. 可以在Activity的onCreate()实现方法中添加设置项(必须在setContentView()之前设置)
    全屏模式:

    1
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN , WindowManager.LayoutParams.FLAG_FULLSCREEN );

    无标题栏:

    1
    requestWindowFeature(Window. FEATURE_NO_TITLE);
  2. 或者是在清单文件 AndroidManifest.xml 中的节点添加android:theme属性:
    全屏模式:android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”
    无标题栏:android:theme=”@android:style/Theme.NoTitleBar”

带进度条的标题栏

  • 不明确任务的进度条:

    1
    2
    3
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    setContentView(R.layout.activity_main);
    setProgressBarIndeterminateVisibility(true);

    不明确任务的进度条

  • 明确任务的进度条:

    1
    2
    3
    requestWindowFeature(Window. FEATURE_PROGRESS);
    setContentView(R.layout. activity_main);
    setProgress(5000);// 当前进度为5000,最大值为10000 (默认值)

    明确任务的进度条

Tips:

<application>节点也可以设置应用里所有的Activity的Theme。
更多的Activity样式配置可参见(Android控件主题样式的配置

参考:

[1]https://www.ibm.com/developerworks/cn/opensource/os-cn-android-actvt/
[2]http://developer.android.com/intl/zh-CN/guide/components/activities.html