Android开发应用中Activity详解
Android应用开发中会经常遇到Activity组件的使用,下面就来讲解下Activity组件。Activity的生命周期、通信方式和IntentFilter等内容,并提供了一些日常开发中经常用到的关于Activity的技巧和方法。通过本文,你可以进一步了接Android中Activity的运作方式。
Activity的生命周期
和J2ME的MIDlet一样,在android中,Activity的生命周期交给系统统一管理。与MIDlet不同的是安装在android中的所有的Activity都是平等的。
Activity的状态及状态间的转换
在android中,Activity拥有四种基本状态:
Active/Runing一个新Activity启动入栈后,它在屏幕最前端,处于栈的最顶端,此时它处于可见并可和用户交互的激活状态。
Paused当Activity被另一个透明或者Dialog样式的Activity覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍然可见,但它已经失去了焦点故不可与用户交互。
Stoped当Activity被另外一个Activity覆盖、失去焦点并不可见时处于Stoped状态。
KilledActivity被系统杀死回收或者没有被启动时处于Killed状态。
当一个Activity实例被创建、销毁或者启动另外一个Activity时,它在这四种状态之间进行转换,这种转换的发生依赖于用户程序的动作。下图说明了Activity在不同状态间转换的时机和条件:
图1.Activity的状态转换
如上所示,Android程序员可以决定一个Activity的“生”,但不能决定它的“死”,也就时说程序员可以启动一个Activity,但是却不能手动的“结束”一个Activity。当你调用Activity.finish()方法时,结果和用户按下BACK键一样:告诉ActivityManager该Activity实例完成了相应的工作,可以被“回收”。随后ActivityManager激活处于栈第二层的Activity并重新入栈,同时原Activity被压入到栈的第二层,从Active状态转到Paused状态。例如:从Activity1中启动了Activity2,则当前处于栈顶端的是Activity2,第二层是Activity1,当我们调用Activity2.finish()方法时,ActivityManager重新激活Activity1并入栈,Activity2从Active状态转换Stoped状态,Activity1.onActivityResult(intrequestCode,intresultCode,Intentdata)方法被执行,Activity2返回的数据通过data参数返回给Activity1。
Activity栈
Android是通过一种Activity栈的方式来管理Activity的,一个Activity的实例的状态决定它在栈中的位置。处于前台的Activity总是在栈的顶端,当前台的Activity因为异常或其它原因被销毁时,处于栈第二层的Activity将被激活,上浮到栈顶。当新的Activity启动入栈时,原Activity会被压入到栈的第二层。一个Activity在栈中的位置变化反映了它在不同状态间的转换。Activity的状态与它在栈中的位置关系如下图所示:
图2.Activity的状态与它在栈中的位置关系
如上所示,除了最顶层即处在Active状态的Activity外,其它的Activity都有可能在系统内存不足时被回收,一个Activity的实例越是处在栈的底层,它被系统回收的可能性越大。系统负责管理栈中Activity的实例,它根据Activity所处的状态来改变其在栈中的位置。
Activity生命周期
在android.app.Activity类中,Android定义了一系列与生命周期相关的方法,在我们自己的Activity中,只是根据需要复写需要的方法,Java的多态性会保证我们自己的方法被虚拟机调用,这一点与J2ME中的MIDlet类似。
publicclassOurActivityextendsActivity{
protectedvoidonCreate(BundlesavedInstanceState);
protectedvoidonStart();
protectedvoidonResume();
protectedvoidonPause();
protectedvoidonStop();
protectedvoidonDestroy();
}
这些方法的说明如下:
protectedvoidonCreate(BundlesavedInstanceState)一个Activity的实例被启动时调用的第一个方法。一般情况下,我们都覆盖该方法作为应用程序的一个入口点,在这里做一些初始化数据、设置用户界面等工作。大多数情况下,我们都要在这里从xml中加载设计好的用户界面。例如:
setContentView(R.layout.main);
当然,也可从savedInstanceState中读我们保存到存储设备中的数据,但是需要判断savedInstanceState是否为null,因为Activity第一次启动时并没有数据被存贮在设备中:
if(savedInstanceState!=null){
savedInstanceState.get("Key");
}
protectedvoidonStart()该方法在onCreate()方法之后被调用,或者在Activity从Stop状态转换为Active状态时被调用。
protectedvoidonResume()在Activity从Pause状态转换到Active状态时被调用。
protectedvoidonResume()在Activity从Active状态转换到Pause状态时被调用。
protectedvoidonStop()在Activity从Active状态转换到Stop状态时被调用。一般我们在这里保存Activity的状态信息。
protectedvoidonDestroy()在Active被结束时调用,它是被结束时调用的最后一个方法,在这里一般做些释放资源,清理内存等工作。
图3.这些方法的调用时机
此外,Android还定义了一些不常用的与生命周期相关的方法可用:
protectedvoidonPostCreate(BundlesavedInstanceState);
protectedvoidonRestart();
protectedvoidonPostResume();
Android提供的文档详细的说明了它们的调用规则。
创建一个Activity
在android中创建一个Activity是很简单的事情,编写一个继承自android.app.Activity的Java类并在AndroidManifest.xml声明即可。下面是一个为了研究Activity生命周期的一个Activity实例(工程源码见下载):
Activity文件:
publicclassEX01extendsActivity{
privatestaticfinalStringLOG_TAG=EX01.class.getSimpleName();
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.e(LOG_TAG,"onCreate");
}
@Override
protectedvoidonStart(){
Log.e(LOG_TAG,"onStart");
super.onStart();
}
@Override
protectedvoidonResume(){
Log.e(LOG_TAG,"onResume");
super.onResume();
}
@Override
protectedvoidonPause(){
Log.e(LOG_TAG,"onPause");
super.onPause();
}
@Override
protectedvoidonStop(){
Log.e(LOG_TAG,"onStop");
super.onStop();
}
@Override
protectedvoidonDestroy(){
Log.e(LOG_TAG,"onDestroy");
super.onDestroy();
}
}
AndroidManifest.xml中通过<activity>节点说明Activity,将apk文件安装后,系统根据这里的说明来查找读取Activity,本例中的说明如下:
<activityandroid:name=".EX01"android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
启动另外一个Activity
Activity.startActivity()方法可以根据传入的参数启动另外一个Activity:
Intentintent=newIntent(CurrentActivity.this,OtherActivity.class);
startActivity(intent);
当然,OtherActivity同样需要在AndroidManifest.xml中定义。
Activity之间通信
使用Intent通信
在Android中,不同的Activity实例可能运行在一个进程中,也可能运行在不同的进程中。因此我们需要一种特别的机制帮助我们在Activity之间传递消息。Android中通过Intent对象来表示一条消息,一个Intent对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个Intent对象,消息“目的地”是必须的,而内容则是可选项。
在上面的实例中通过Activity.startActivity(intent)启动另外一个Activity的时候,我们在Intent类的构造器中指定了“收件人地址”。
如果我们想要给“收件人”Activity说点什么的话,那么可以通过下面这封“e-mail”来将我们消息传递出去:
Intentintent=newIntent(CurrentActivity.this,OtherActivity.class);
//创建一个带“收件人地址”的email
Bundlebundle=newBundle();//创建email内容
bundle.putBoolean("boolean_key",true);//编写内容
bundle.putString("string_key","string_value");
intent.putExtra("key",bundle);//封装email
startActivity(intent);//启动新的Activity
那么“收件人”该如何收信呢?在OtherActivity类的onCreate()或者其它任何地方使用下面的代码就可以打开这封“e-mail”阅读其中的信息:
Intentintent=getIntent();//收取email
Bundlebundle=intent.getBundleExtra("key");//打开email
bundle.getBoolean("boolean_key");//读取内容
bundle.getString("string_key");
上面我们通过bundle对象来传递信息,bundle维护了一个HashMap<String,Object>对象,将我们的数据存贮在这个HashMap中来进行传递。但是像上面这样的代码稍显复杂,因为Intent内部为我们准备好了一个bundle,所以我们也可以使用这种更为简便的方法:
Intentintent=newIntent(EX06.this,OtherActivity.class);
intent.putExtra("boolean_key",true);
intent.putExtra("string_key","string_value");
startActivity(intent);
接收:
Intentintent=getIntent();
intent.getBooleanExtra("boolean_key",false);
intent.getStringExtra("string_key");
使用SharedPreferences
SharedPreferences使用xml格式为Android应用提供一种永久的数据存贮方式。对于一个Android应用,它存贮在文件系统的/data/data/your_app_package_name/shared_prefs/目录下,可以被处在同一个应用中的所有Activity访问。Android提供了相关的API来处理这些数据而不需要程序员直接操作这些文件或者考虑数据同步问题。
//写入SharedPreferences
SharedPreferencespreferences=getSharedPreferences("name",MODE_PRIVATE);
Editoreditor=preferences.edit();
editor.putBoolean("boolean_key",true);
editor.putString("string_key","string_value");
editor.commit();
//读取SharedPreferences
SharedPreferencespreferences=getSharedPreferences("name",MODE_PRIVATE);
preferences.getBoolean("boolean_key",false);
preferences.getString("string_key","default_value");
其它方式
Android提供了包括SharedPreferences在内的很多种数据存贮方式,比如SQLite,文件等,程序员可以通过这些API实现Activity之间的数据交换。如果必要,我们还可以使用IPC方式。
Activity的IntentFilter
IntentFilter描述了一个组件愿意接收什么样的Intent对象,Android将其抽象为android.content.IntentFilter类。在Android的AndroidManifest.xml配置文件中可以通过<intent-filter>节点为一个Activity指定其IntentFilter,以便告诉系统该Activity可以响应什么类型的Intent。
当程序员使用startActivity(intent)来启动另外一个Activity时,如果直接指定intent了对象的Component属性,那么ActivityManager将试图启动其Component属性指定的Activity。否则Android将通过Intent的其它属性从安装在系统中的所有Activity中查找与之最匹配的一个启动,如果没有找到合适的Activity,应用程序会得到一个系统抛出的异常。这个匹配的过程如下:
图4.Activity种IntentFilter的匹配过程
Action匹配
Action是一个用户定义的字符串,用于描述一个Android应用程序组件,一个IntentFilter可以包含多个Action。在AndroidManifest.xml的Activity定义时可以在其<intent-filter>节点指定一个Action列表用于标示Activity所能接受的“动作”,例如:
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<actionandroid:name="com.zy.myaction"/>
……
</intent-filter>
如果我们在启动一个Activity时使用这样的Intent对象:
Intentintent=newIntent();
intent.setAction("com.zy.myaction");
那么所有的Action列表中包含了“com.zy.myaction”的Activity都将会匹配成功。
Android预定义了一系列的Action分别表示特定的系统动作。这些Action通过常量的方式定义在android.content.Intent中,以“ACTION_”开头。我们可以在Android提供的文档中找到它们的详细说明。
URI数据匹配
一个Intent可以通过URI携带外部数据给目标组件。在<intent-filter>节点中,通过<data/>节点匹配外部数据。
mimeType属性指定携带外部数据的数据类型,scheme指定协议,host、port、path指定数据的位置、端口、和路径。如下:
<dataandroid:mimeType="mimeType"android:scheme="scheme"
android:host="host"android:port="port"android:path="path"/>
如果在IntentFilter中指定了这些属性,那么只有所有的属性都匹配成功时URI数据匹配才会成功。
Category类别匹配
<intent-filter>节点中可以为组件定义一个Category类别列表,当Intent中包含这个列表的所有项目时Category类别匹配才会成功。
一些关于Activity的技巧
锁定Activity运行时的屏幕方向
Android内置了方向感应器的支持。在G1中,Android会根据G1所处的方向自动在竖屏和横屏间切换。但是有时我们的应用程序仅能在横屏/竖屏时运行,比如某些游戏,此时我们需要锁定该Activity运行时的屏幕方向,<activity>节点的android:screenOrientation属性可以完成该项任务,示例代码如下:
<activityandroid:name=".EX01"
android:label="@string/app_name"
android:screenOrientation="portrait">//竖屏,值为landscape时为横屏
…………
</activity>
全屏的Activity
要使一个Activity全屏运行,可以在其onCreate()方法中添加如下代码实现:
//设置全屏模式
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
//去除标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
在Activity的Title中加入进度条
为了更友好的用户体验,在处理一些需要花费较长时间的任务时可以使用一个进度条来提示用户“不要着急,我们正在努力的完成你交给的任务”。如下图:
在Activity的标题栏中显示进度条不失为一个好办法,下面是实现代码:
//不明确进度条
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
setProgressBarIndeterminateVisibility(true);
//明确进度条
requestWindowFeature(Window.FEATURE_PROGRESS);
setContentView(R.layout.main);
setProgress(5000);