第5章 Activity Activity是与用户交互的接口,提供了图形界面供用户操作。前文中每个应用程序 都用到了Activity:通过调用setContentView() 方法,为Activity生成一个图形界面。因 此,Acrtivity是Android系统最重要的功能之一。 5.1 生命周期 在一个Android应用中可以有多个Activity,这些Activity组成了Activity栈 (Stack), 当前活动的Activity位于栈顶,之前的Activity被压入下面成为非活动 Activity,等待是否可能被恢复为活动状态。Activity的4个重要状态见表5-1。 表5- 1 Activity的4个重要状态 序号状态说明 1 运行状态当前Activity,用户可见,可以获得焦点 2 暂停状态失去焦点的Activity,仍然可见 3 停止状态该Activity被其他Activity覆盖,不可见,会保存所有状态和信息 4 销毁状态该Activity结束 一个Activity从创建到消亡叫作一个生命周期,其中有很多有用的回调函数,方便截 获添加有价值的处理功能。Activity常用的回调函数见表5-2。 表5- 2 Activity常用的回调函数 序号函数名称说明 1 onCreate() 创建Activity时被回调,执行更多的初始化功能,如添加消息响应 1 72 Android 简明程序设计 续表 序号函数名称说 明 2 onStart() 启动Activity时被回调,也就是当Activity变为可见时被回调 3 onResume() Activity由暂停态变为运行态时被调用 4 onPause() 暂停Activity时被调用,通常用于持久保存数据 5 onRestart() 重新启动Activity时被调用,在onStop()后运行 6 onStop() 停止Activity时被回调 7 onDestroy() 销毁Activity时被回调 对Activity来说,onCreate()与onDestroy()函数都运行一次,其他回调函数可运行 多次,主要有4个流程,如下所示。 流程1:若焦点一直在Activity上,则流程为onCreate()->onStart()->onResume()-> 正常运行界面的其他功能->onDestroy()正常销毁。 流程2:焦点有切换,假设有两个Activity,名称为a、b。初始时焦点在a上,则流程 为onCreate()->onStart()->onResume()->正常运行界面的其他功能;当切换到名称 为b的Activity时,对a来说,运行onPause->onStop();当焦点再切回到名称为a的 Activity时,对a来说,运行onRestart()->onStart()->onResume->正常运行界面的其 他功能。 流程3:从onPause()->onResume()函数,例如,当在某Activity中弹出某模态对话 框后,Activity运行onPause()函数,当关闭模态对话框后,Activity运行onResume() 函数。流 程4:由App进程管理器直接销毁该Activity及对应的应用程序。 【例5-1】 验证Active各回调函数的执行顺序。 本示例仅能验证上文所述的流程1、流程2的情况。许多Android书都用日志监测 Activity各回调函数的执行流程,在集成开发环境中看日志较方便,但将该应用安装在真 实手机上看日志却并不方便。本示例利用图形用户界面显示Activity各回调函数响应的 各种情况,在集成开发平台与在真实手机中均非常方便。思路是:将Activity响应函数 名称显示在TextView控件中。各关键代码如下所示。 ① 主布局文件:main.xml。 <? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" 第5 章 Activity 1 73 android:layout_height="match_parent"> <TextView android:id="@+id/mytext" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="30sp"/> </LinearLayout> 以上代码采用线性布局,定义了id为mytext的TextView 控件,用于显示Activity 各回调函数的响应名称。 ② MainActivity.java。 public class MainActivity extends AppCompatActivity { void show(String name){ TextView tv = (TextView)findViewById(R.id.mytext); String s = tv.getText().toString(); tv.setText(s + "\n" +name); } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); show("onCreate"); } protected void onStart() {super.onStart();show("onStart");} protected void onDestroy() {super.onDestroy();show("onDestroy");} protected void onStop() {super.onStop();show("onStop");} protected void onResume() {super.onResume();show("onResume");} protected void onPause() {super.onPause();show("onPause");} protected void onRestart() {super.onRestart();show("onRestart");} } 关键理解show(Stringname)函数,name即回调函数字符串名称。该函数首先获得 TextView组件上已有的内容,将其与当前name字符串相加,再重新设置回TextView。 5.2 建立Activity 5.2.1 入口Activity类 一个应用可以包含多个Activity活动页面,其入口Activity类对应应用的第一个界 1 74 Android 简明程序设计 面,之前经常用的MainActivity.java就是入口Activity类,如何区分入口Activity类与其 他普通Activity类? 这涉及应用配置文件Androidmanifest.xml(在res目录下),打开该 文件,可以看到与Activity类MainActivity对应的配置信息在<activity>标签内,如下 所示。 <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity> 属性name= “.MainActivity”,标识了该Active 类的名称为 MainActivity,在工程默认包下。 <activity>子标签<intent-filter>定义了过滤器信息,对初学者而言,只要知道 <intent-filter>子标签<action>属性name=“android.intent.action.MAIN”,<category>属 性name=“android.intent.category.LAUNCHER”,那么它对应的Activity类就位于优先 级最高级,最先执行。 5.2.2 普通Activity类 从入口Active类类推,普通Activity类也应有如下内容:ActivityJava类、界面 XML布局文件、Androidmanifest.xml配置文件中增加的<activity>子标签内容。 【例5-2】 最简单的Activity启动程序。一般来说,已有工程已经包含入口Activity 类MainActivity,现在要求再建立一个普通Activity类SecondActivity。MainActivity对 应的主界面(main.xml)有一按钮OK,当单击OK 按钮时,启动SecondActivity对应的界 面(main2.xml);SecondActivity对应的界面显示“Thisissecondactivity”,详细内容如下 所示。① Activity类界面布局文件。 //main.xml <? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/myok" 第5 章 Activity 1 75 android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ok"/> </LinearLayout> //main2.xml <? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="This is second activity"/> </LinearLayout> ② 配置文件Androidmanifest.xml,以下仅列出与Activity有关的<activity>节点 内容。 <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity"></activity> 可以看出,每增加一个普通Activity类,在配置文件中就增加一个<activity>节点, 而且内容较简单,设定其name属性等于Activity类名称即可。 ③ 对应的两个Activity类。 //SecondActivity.java public class SecondActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second); } } //MainActivity.java public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { 1 76 Android 简明程序设计 super.onCreate(savedInstanceState); setContentView(R.layout.main); Button b = (Button)findViewById(R.id.myok); b.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent in = new Intent(MainActivity.this,SecondActivity.class); startActivity(in); } }); } } 可以看出,启动另一个Activity的关键函数是startActivity(Intentintent),因此必须 先产生Intent对象。Intent一个常用的构造方法是Intent(Contextctx,Class<?>cls),ctx 是上下文对象,cls可以是待启动的Activity类的类名。 5.3 Activity 通信 Activity类间数据通信要应用Intent对象,Intent保存数据的本质是map映射(键- 值),键一般是字符串,值可以是多种类型,Intent可读、可写,常用的函数如下所示。 . voidputExtra(Stringkey,XXXvalue),写单值函数,XXX 可以是8个基本数据 类型:byte、short、int、long、float、double、char、boolean。 . voidputExtra(Stringkey,XXXvalue[]),写数组函数,XXX 可以是8个基本数 据类型:byte、short、int、long、float、double、char、boolean。 . voidputExtra(Stringkey,CharSequence value),写字符串函数。 . voidputExtra(Stringkey,CharSequence value[]),写字符串数组函数。 . voidputExtra(Stringkey,Serializablevalue),写实现序列化对象函数。 . voidputExtras(Bundleb),写捆绑对象。 . xxxgetXxxExtra(Stringkey,xxxdefault),读基本数据类型,3个小写的“xxx” 表示8种基本数据类型之一;首字符大写其余小写的“Xxx”,表示8种基本数据类 型名称首字符大写,其余小写。该函数的含义是:若读成功,则返回读的值,若读 不成功,则返回default值。 . xxx[]getXxxArrayExtra(Stringkey),读基本数据类型数组,若不成功,则返回null。 . StringgetStringExtra(Stringkey),读字符串数据。 . String[]getStringArrayExtra(Stringkey),读字符串数组数据。 第5 章 Activity 1 77 . SerializablegetSerializableExtra(Stringkey),读序列化对象数据。 . BundlegetExtras(Stringkey),读捆绑对象。 假设有两个Activity类A、B,A 启动B,A、B间传参有以下情况:A 向B传送基本数 据类型;A 向B传送类对象类型;A 接收B销毁返回给A 的数据。下面以关键代码的形 式一一加以说明。 ① A 向B传送基本数据类型,A 发送数据,B解析数据,见表5-3。 表5-3 基本数据类型发送解析关键代码 A发送数据B解析数据 方法1 Intentout=newIntent(A.this,B.class); intno=1000;Stringname="zhang"; out.putExtra("no",no); out.putExtra("name",name); startActivity(out); Intentin=B.this.getIntent(); intno=in.getIntExtra("no",0); Stringname=in.getStringExtra("name"); 方法2 Bundleb=newBundle(); intno=1000;Stringname="zhang"; b.putInt("no",no); b.putString("name",name); Intentout= newIntent(A.this,B.class); out.putExtras(b); startActivity(out); Intentin=B.this.getIntent(); Bundleb=in.getExtras(); intno= b.getInt("no"); Stringname= b.getString("name"); ② A 向B传送类对象类型,A 发送数据、B解析数据。 假设自定义Data类如下所示。 class Data implements Serializable{ int no;String name; Data(int no,String name){this.no=no; this.name=name;} } 之所以实现Serializable接口,是因为要用到putExtra(Stringkey,Serializablevalue)函 数,value对应的类要求必须实现Serializable接口,其相应的发送、接收关键代码见表5-4。 表5-4 类对象数据发送、接收关键代码 A发送数据B解析数据 Datad=newData(1000,"zhang"); Intentout= newIntent(A.this,B.class); out.putExtra("data",d); startActivity(out); Intentin=this.getIntent(); Datad= (Data)in.getSerializableExtra("data"); 178Android简明程序设计 利用Bundle捆绑数据也能实现类对象数据在Activity间的发送和解析,本题中的自 定义类也无须实现Serializable接口。请读者参照表5-3中的方法2实现相应的发送和接 收数据。 ③A接收B销毁返回给A的数据。 为了更好地理解此部分知识,还需要掌握系统Activity类的3个基本函数,如下 所示。 .vitrtvtoRslItnnet,itrqetoe),与strciiy()函 odsatAciiyFreut(netitnneuscdatAtvt 数不同,本函数是具有接收返回结果的Activity启动函数,requestcode是请求识 别码。 .voidfinish(),结束当前Activity对象,返回到前一个Active对象。 t(e,Ia),e是结果识别码,a是准 备返回前一级Activity对象的数据。 另外,还要知道:若某Activity对象接收下一级Activity对象的返回数据,则必须重 写如下函数 oe 。 tdvitvteut(neetoe,itrslCdnetdt .voidsetResulintresultcodntentdatresultcoddat .prtceodonAciiyRslitrqusCdneutoe,Itnaa); 很明显,dtrequstCdrsulCd aa是返回的数据,eoe是请求码,etoe是结果码 。 实现A接收B销毁返回给A的数据的关键代码见表5-5 。 表5- 5 实现 A 接收B销毁返回给 A 的数据的关键代码表 A中的关键代码 B中的关键代码 //A启动B代码 //B主动销毁时,直接调用以下函数 Itntout=newIntent(MainActivity.this,SecondActivity. BkPesed()//(o) 重(n) 写(a) o(r) (c) nBackPresed()函数clas);(e) (n) t(t,1); ttAtiityFRlou pblicvoidonBackPrsed(){//(s) A(a) 接收(c) B(v) 的返(o) 回值,(u) (s) (e) (r) (r) 必须重载下面函数 //(u) suproBcrsed();(e) e.nakPeprotectedvoidonAtivityReslintrequestCod t(e, Intentout=newItnt(); intresultCode,I(c) tntdata){(u) ou.uEra(o,10(n) 00);(e) tptt" " uprociiyRslrqetoe,etoe,dta) ; e.nAtvteut((e) (n) eusCdrlCda setRult(2(x) ,out);(n) if(r(s) equestCode==1&&resultCde==2){(u) (s) finish();(s) (e) in}(o) =aagtnEta("o,0);(o) } tndt.eItxrn" } B销毁有两种情况:一是响应B中的界面子组件事件,如按钮事件等;二是响应手机固 有的“返回”事件。这两种情况的响应函数本质上功能是一致的,因此本例重写了“返回” 事件函数onBackPresed(),完成了向上一级Activity对象设置返回值及销毁本级 第 5 章 Activity 179 Aciiy对象功能。在onakPed()函数内,注释了一行语句suproBcrsed(),若 tvtBcrsee.nakPe 把注释打开,运行程序后会发现A中的onActivityResult()函数不会接收B中传过来的 数据。这是因为B中的superonBackPresed()函数已经完成了销毁B的功能,在该行代 码下再设置向A发送的数据当然(.) 是无效了。 通过此例,再加深理解startActivityForResult(intent,requestCode)及setResult (e,t)中申请码re、结果码re的作用,这两个值最终都 resultCodintenequestCodesultCod 体现在onActivityResult(requestCode,resultCode,data)函数参数内:根据requestCode 可知道是从哪个Activity对象返回的;根据resultCode,可以知道该如何解析data,因为 不同的resultCode可能代表不同的、有用的数据类型。 5.4 隐式启动Activity 前文所述启动Activity都是显示方式,主要体现在建立Intent对象时明确体现了启 动的ActvtItnn=nwItnticas), tvt iiy对象。例如,netienet(A.hs,B.l则A是源Aciiy 类,B是待启动的Activity类。隐式启动Activity的含义是:在程序中看不出启动的 Actvty类,它是通过匹配查找配置文件Andodmaietxml中与Aciiy有关的内 iirinfs.tvt 容,找出应该启动哪个Activity类。 5.1 itn-itr 4.netfle 在Androdmaietxml中,与Aciiy有关的主要是<aciiy>标签的子标签 inds.tvttvt <intent-filter>的内容,对隐式启动的Activity来说,必须配置<intent-filter>的内容。 它主要包含<an> 、<cy>、<da>3个子标签的内容设置,下面一一加以 说明。 ctioategorat ①<action>标签。 action属性的值为一个字符串,它代表系统中已经定义了一系列常用的动作,形如 <actionandroid:name=“XXX”>,XXX可以自定义,也可以应用系统定义的常量,如下 所示。 .自定义动作字符串。 .ACTION_MAIN:AndroidApplication的入口,每个Android应用必须且只能包 含一个此类型的Action声明。 .ACTION_VIEW:系统根据不同的Data类型,通过已注册的对应Application显 示数据。 1 80 Android 简明程序设计 . ACTION_EDIT:系统根据不同的Data类型,通过已注册的对应Application编 辑数据。 . ACTION_DIAL:打开系统默认的拨号程序,如果Data中设置了电话号码,则自 动在拨号程序中输入此号码。 . ACTION_CALL:直接呼叫Data中所带的号码。 . ACTION_ANSWER:接听来电。 . ACTION_SEND:由用户指定发送方式进行数据发送操作。 . ACTION_SENDTO:系统根据不同的Data类型,通过已注册的对应Application 进行数据发送操作。 所有系统定义的常量都要加前缀“android.intent.action.”,形如<actionandroid: name=“android.intent.action.ACTION_MAIN”>。 ② <category>标签 <category>标签用于指定当前intent被处理的环境,一个intent-filter可以包含多 个category属性。 . 自定义种类字符串。 . CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity 的执行方式执行。 . CATEGORY_HOME:设置该组件为HomeActivity。 . CATEGORY_PREFERENCE:设置该组件为Preference。 . CATEGORY_LAUNCHER:设置该组件为在当前应用程序启动器中优先级最 高的Activity,通常与入口ACTION_MAIN 配合使用。 . CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。 . CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。 所有系统定义的常量都要加前缀“android.intent.category.”,形如<category android:name=“android.intent.category.CATEGORY_DEFAULT”>。 ③ <data>标签。 每个<data>标签都可以指定一个URI结构以及data的MIME类型。一个完整的 URI由scheme、host、port和path组成,其结构如下所示。 <scheme>://<host>:<port>/<path> 其中,scheme既可以是Android中常见的协议,也可以是自定义的协议。Android中常 见的协议包括content协议、http协议、file协议等,自定义协议可以使用自定义字符串。 <data>标签格式形如:<dataandroid:scheme="XXX"android:host="XXX">。