第2章 先从看得到的入手,探究活动

来源:互联网 发布:linux 禁止删除文件夹 编辑:程序博客网 时间:2024/05/29 12:16
第2章 先从看得到的入手,探究活动 


通过上一章的学习,你已经成功创建了你的第一个Android项目。不过仅仅满足于此显
然是不够的,是时候该学点新的东西了。作为你的导师,我有义务帮你制定好后面的学习路
线,那么今天我们应该从哪儿入手呢?现在你可以想象一下,假如你已经写出了一个非常优
秀的应用程序,然后推荐给你的第一个用户,你会从哪里开始介绍呢?毫无疑问,当前是从
界面开始介绍了!因为即使你的程序算法再高效,架构再出色,用户根本不会在乎这些,他
们一开始只会对看得到的东西感兴趣,那么我们今天的主题自然也要从看得到的入手了。 


2.1 活动是什么 


活动(Activity)是最容易吸引到用户的地方了,它是一种可以包含用户界面的组件,
主要用于和用户进行交互。一个应用程序中可以包含零个或多个活动,但不包含任何活动的
应用程序很少见,谁也不想让自己的应用永远无法被用户看到吧? 


其实在上一章中,你已经和活动打过交道了,并且对活动也有了初步的认识。不过上一
章我们的重点是创建你的第一个Android项目,对活动的介绍并不多,在本章中我将对活动
进行详细的介绍。 


2.2 活动的基本用法 


到现在为止,你还没有手动创建过活动呢,因为上一章中的HelloWorldActivity是ADT
帮我们自动创建的。手动创建活动可以加深我们的理解,因此现在是时候应该自己动手了。 


首先,你需要再新建一个Android项目,项目名可以叫做ActivityTest,包名我们就使用
默认值com.example.activitytest。新建项目的步骤你已经在上一章学习过了,不过图1.12中
的那一步需要稍做修改,我们不再勾选Create Activity这个选项,因为这次我们准备手动创
建活动,如图2.1所示。 



 


图 2.1 


点击Finish,项目就创建完成了,这时候你的Eclipse中应该有两个项目,ActivityTest
和HelloWorld。极度建议你将不相干的项目关闭掉,仅打开当前工作所需要的项目,不然我
保证以后你会在这方面吃亏。最好现在就右击HelloWorld项目→Close Project。 


2.2.1 手动创建活动 


目前ActivityTest项目的src目录应该是空的,你应该在src目录下先添加一个包。点击
Eclipse导航栏中的File→New→Package,在弹出窗口中填入我们新建项目时使用的默认包名
com.example.activitytest,点击Finish。添加包之后的目录结构如图2.2所示。 



 


图 2.2 


现在右击com.example.activitytest包→New→Class,会弹出新建类的对话框,我们新建
一个名为FirstActivity的类,并让它继承自Activity,点击Finish完成创建。 


你需要知道,项目中的任何活动都应该重写Activity的onCreate()方法,但目前我们的
FirstActivity内部还什么代码都没有,所以首先你要做的就是在FirstActivity中重写onCreate()
方法,代码如下所示: 


public class FirstActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 } 


 





可以看到,onCreate()方法非常简单,就是调用了父类的onCreate()方法。当然这只是默
认的实现,后面我们还需要在里面加入很多自己的逻辑。 


2.2.2 创建和加载布局 


前面我们说过,Android程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一
个布局,布局就是用来显示界面内容的,因此我们现在就来手动创建一个布局文件。 


右击res/layout目录→New→Android XML File,会弹出创建布局文件的窗口。我们给这
个布局文件命名为first_layout,根元素就默认选择为LinearLayout,如图2.3所示。 



 


图 2.3 


点击Finish完成布局的创建。这时候你会看到如图2.4所示的窗口。 


 


图 2.4 



这是ADT为我们提供的可视化布局编辑器,你可以在屏幕的中央区域预览当前的布局。
在窗口的最下方有两个切换卡,左边是Graphical Layout,右边是first_layout.xml。Graphical 
Layout是当前的可视化布局编辑器,在这里你不仅可以预览当前的布局,还可以通过拖拽的
方式编辑布局。而first_layout.xml则是通过XML文件的方式来编辑布局,现在点击一下
first_layout.xml切换卡,可以看到如下代码: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


</LinearLayout> 


由于我们刚才在创建布局文件时选择了LinearLayout作为根元素,因此现在布局文件中
已经有一个LinearLayout元素了。那我们现在对这个布局稍做编辑,添加一个按钮,如下
所示: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


 


 <Button 


 android:id="@+id/button_1" 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="Button 1" 


 /> 


 


</LinearLayout> 


这里添加了一个Button元素,并在Button元素的内部增加了几个属性。android:id是给
当前的元素定义一个唯一标识符,之后可以在代码中对这个元素进行操作。你可能会对
@+id/button_1这种语法感到陌生,但如果把加号去掉,变成@id/button_1,这你就会觉得有
些熟悉了吧,这不就是在XML中引用资源的语法吗,只不过是把string替换成了id。是的,
如果你需要在XML中引用一个id,就使用@id/id_name这种语法,而如果你需要在XML中
定义一个id,则要使用@+id/id_name这种语法。随后android:layout_width指定了当前元素
的宽度,这里使用match_parent表示让当前元素和父元素一样宽。android:layout_height指定
了当前元素的高度,这里使用wrap_content,表示当前元素的高度只要能刚好包含里面的内
容就行。android:text指定了元素中显示的文字内容。如果你还不能完全看明白,没有关系,
关于编写布局的详细内容我会在下一章中重点讲解,本章只是先简单涉及一些。现在按钮已



经添加完了,你可以点回Graphical Layout切换卡,预览一下当前布局,如图2.5所示。 


 


图 2.5 


可以在中央的预览区域看到,按钮已经成功显示出来了,这样一个简单的布局就编写完
成了。那么接下来我们要做的,就是在活动中加载这个布局。 


重新回到FirstActivity,在onCreate()方法中加入如下代码: 


public class FirstActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 setContentView(R.layout.first_layout); 


 } 


 





可以看到,这里调用了setContentView()方法来给当前的活动加载一个布局,而在
setContentView()方法中,我们一般都会传入一个布局文件的id。在第一章介绍gen目录的时
候我有提到过,项目中添加的任何资源都会在R文件中生成一个相应的资源id,因此我们刚
才创建的first_layout.xml布局的id现在应该是已经添加到R文件中了。在代码中去引用布
局文件的方法你也已经学过了,只需要调用R.layout.first_layout就可以得到first_layout.xml
布局的id,然后将这个值传入setContentView()方法即可。注意这里我们使用的R,是
com.example.activitytest包下的R文件,Android SDK还会自动提供一个android包下的R文
件,千万别使用错了。 



2.2.3 在AndroidManifest文件中注册 


别忘了前面我有说过,所有的活动都要在AndroidManifest.xml中进行注册才能生效,那
么我们现在就打开AndroidManifest.xml来给FirstActivity注册吧,代码如下所示: 


<manifest xmlns:android="http://schemas.android.com/apk/res/android" 


 package="com.example.activitytest" 


 android:versionCode="1" 


 android:versionName="1.0" > 


 <uses-sdk 


 android:minSdkVersion="14" 


 android:targetSdkVersion="19" /> 


 <application 


 android:allowBackup="true" 


 android:icon="@drawable/ic_launcher" 


 android:label="@string/app_name" 


 android:theme="@style/AppTheme" > 


 <activity 


 android:name=".FirstActivity" 


 android:label="This is FirstActivity" > 


 <intent-filter> 


 <action android:name="android.intent.action.MAIN" /> 


 <category android:name="android.intent.category.LAUNCHER" /> 


 </intent-filter> 


 </activity> 


 </application> 


</manifest> 


可以看到,活动的注册声明要放在<application>标签内,这里是通过<activity>标签来对
活动进行注册的。首先我们要使用android:name来指定具体注册哪一个活动,那么这里填入
的.FirstActivity是什么意思呢?其实这不过就是com.example.activitytest.FirstActivity的缩写
而已。由于最外层的<manifest>标签中已经通过package属性指定了程序的包名是
com.example.activitytest,因此在注册活动时这一部分就可以省略了,直接使用.FirstActivity
就足够了。然后我们使用了android:label指定活动中标题栏的内容,标题栏是显示在活动最
顶部的,待会儿运行的时候你就会看到。需要注意的是,给主活动指定的label不仅会成为
标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。之后在<activity>标
签的内部我们加入了<intent-filter>标签,并在这个标签里添加了<action android:name= 
"android.intent.action.MAIN" />和<category android:name="android.intent.category.LAUNCHER" />
这两句声明。这个我在前面也已经解释过了,如果你想让FirstActivity作为我们这个程序的



主活动,即点击桌面应用程序图标时首先打开的就是这个活动,那就一定要加入这两句声明。
另外需要注意,如果你的应用程序中没有声明任何一个活动作为主活动,这个程序仍然是可
以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三
方服务供其他的应用在内部进行调用的,如支付宝快捷支付服务。 


好了,现在一切都已准备就绪,让我们来运行一下程序吧,结果如图2.6所示。 


 


图 2.6 


在界面的最顶部是一个标题栏,里面显示着我们刚才在注册活动时指定的内容。标题栏
的下面就是在布局文件first_layout.xml中编写的界面,可以看到我们刚刚定义的按钮。现
在你已经成功掌握了手动创建活动的方法,让我们继续看一看你在活动中还能做哪些更多的
事情。 


2.2.4 隐藏标题栏 


标题栏中可以进行的操作其实还是蛮多的,尤其是在Android 4.0之后加入了Action Bar
的功能。不过有些人会觉得标题栏相当占用屏幕空间,使得内容区域变小,因此也有不少的
应用程序会选择将标题栏隐藏掉。 


隐藏的方法非常简单,打开FirstActivity,在onCreate()方法中添加如下代码: 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 



 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.first_layout); 





其中requestWindowFeature(Window.FEATURE_NO_TITLE)的意思就是不在活动中显示
标题栏,注意这句代码一定要在setContentView()之前执行,不然会报错。再次运行程序,
效果如图2.7所示。 


 


图 2.7 


这样我们的活动中就不会再显示标题栏了,看起来空间大了不少吧! 


2.2.5 在活动中使用Toast 


Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的
信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间,我们现
在就尝试一下如何在活动中使用Toast。 


首先需要定义一个弹出Toast的触发点,正好界面上有个按钮,那我们就让点击这个按
钮的时候弹出一个Toast吧。在onCreate()方法中添加代码: 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.first_layout); 


 Button button1 = (Button) findViewById(R.id.button_1); 



 button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Toast.makeText(FirstActivity.this, "You clicked Button 1", 


 Toast.LENGTH_SHORT).show(); 


 } 


 }); 





在活动中,可以通过findViewById()方法获取到在布局文件中定义的元素,这里我们传
入R.id.button_1,来得到按钮的实例,这个值是刚才在first_layout.xml中通过android:id属性
指定的。findViewById()方法返回的是一个View对象,我们需要向下转型将它转成Button
对象。得到了按钮的实例之后,我们通过调用setOnClickListener()方法为按钮注册一个监听
器,点击按钮时就会执行监听器中的onClick()方法。因此,弹出Toast的功能当然是要在
onClick()方法中编写了。 


Toast的用法非常简单,通过静态方法makeText()创建出一个Toast对象,然后调用show()
将Toast显示出来就可以了。这里需要注意的是,makeText()方法需要传入三个参数。第一
个参数是Context,也就是Toast要求的上下文,由于活动本身就是一个Context对象,因此
这里直接传入FirstActivity.this即可。第二个参数是Toast显示的文本内容,第三个参数是Toast
显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT和Toast.LENGTH_LONG。 


现在重新运行程序,并点击一下按钮,效果如图2.8所示。 


 


图 2.8 



2.2.6 在活动中使用Menu 


不知道你还记不记得,在上一章中创建你的第一个Android项目时,ADT在
HelloWorldActivity中自动创建了一个onCreateOptionsMenu()方法。这个方法是用于在活动
中创建菜单的,由于当时我们的重点不在这里,所以直接先忽略了,现在可以来仔细分析一
下了。 


手机毕竟和电脑不同,它的屏幕空间非常有限,因此充分地利用屏幕空间在手机界面设
计中就显得非常重要了。如果你的活动中有大量的菜单需要显示,这个时候界面设计就会比
较尴尬,因为仅这些菜单就可能占用屏幕将近三分之一的空间,这该怎么办呢?不用担心,
Android给我们提供了一种方式,可以让菜单都能得到展示的同时,还能不占用任何屏幕的
空间。 


首先在res目录下新建一个menu文件夹,右击res目录→New→Folder,输入文件夹名
menu,点击Finish。接着在这个文件夹下再新建一个名叫main的菜单文件,右击menu文件
夹→New→Android XML File,如图2.9所示。 


 


图 2.9 



文件名输入main,点击Finish完成创建。然后在main.xml中添加如下代码: 


<menu xmlns:android="http://schemas.android.com/apk/res/android" > 


 <item 


 android:id="@+id/add_item" 


 android:title="Add"/> 


 <item 


 android:id="@+id/remove_item" 


 android:title="Remove"/> 


</menu> 


这里我们创建了两个菜单项,其中<item>标签就是用来创建具体的某一个菜单项,然后通
过android:id给这个菜单项指定一个唯一标识符,通过android:title给这个菜单项指定一个名称。 


然后打开FirstActivity,重写onCreateOptionsMenu()方法,代码如下所示: 


public boolean onCreateOptionsMenu(Menu menu) { 


 getMenuInflater().inflate(R.menu.main, menu); 


 return true; 





通过getMenuInflater()方法能够得到MenuInflater对象,再调用它的inflate()方法就可以给
当前活动创建菜单了。inflate()方法接收两个参数,第一个参数用于指定我们通过哪一个资源
文件来创建菜单,这里当然传入R.menu.main,第二个参数用于指定我们的菜单项将添加到哪
一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的menu参数。然后给
这个方法返回true,表示允许创建的菜单显示出来,如果返回了false,创建的菜单将无法显示。 


当然,仅仅让菜单显示出来是不够的,我们定义菜单不仅是为了看的,关键是要菜单真
正可用才行,因此还要再定义菜单响应事件。在FirstActivity中重写onOptionsItemSelected()
方法: 


public boolean onOptionsItemSelected(MenuItem item) { 


 switch (item.getItemId()) { 


 case R.id.add_item: 


 Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show(); 


 break; 


 case R.id.remove_item: 


 Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show(); 


 break; 


 default: 


 } 


 return true; 






在onOptionsItemSelected()方法中,通过调用item.getItemId()来判断我们点击的是哪一个菜单
项,然后给每个菜单项加入自己的逻辑处理,这里我们就活学活用,弹出一个刚刚学会的Toast。 


重新运行程序,并按下Menu键,效果如图2.10所示。 


 


图 2.10 


可以看到,菜单默认是不会显示出来的,只有按下了Menu键,菜单才会在底部显示出
来,这样我们就可以放心地使用菜单了,因为它不会占用任何活动的空间。然后点击一下
Add菜单项,效果如图2.11所示。 


 


图 2.11 



2.2.7 销毁一个活动 


通过一整节的学习,你已经掌握了手动创建活动的方法,并学会了如何在活动中创建
Toast和创建菜单。或许你现在心中会有个疑惑,如何销毁一个活动呢? 


其实答案非常简单,只要按一下Back键就可以销毁当前的活动了。不过如果你不想通
过按键的方式,而是希望在程序中通过代码来销毁活动,当然也可以,Activity类提供了一
个finish()方法,我们在活动中调用一下这个方法就可以销毁当前活动了。 


修改按钮监听器中的代码,如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 finish(); 


 } 


}); 


重新运行程序,这时点击一下按钮,当前的活动就被成功销毁了,效果和按下Back键
是一样的。 


 


经验值:+500 目前经验值:1005 


级别:小菜鸟 


赢得宝物:战胜Android界面砖家。拾取砖家掉落的宝物,大屏幕Android手机一个、
修罗界APP界面设计速成班听课证一张、九成新羊皮Android战袍一套、小型信心增强大力
丸1颗、抗抑郁冲剂2袋。换上羊皮战袍。在“有部Android手机”客栈将速成班听课证、
抗抑郁冲剂,还有之前换下的两套低战斗力战袍等没啥用的东西统统卖掉,换了些盘缠。信
心目前足够用,小型大力丸留着备用。在跟店家交易时,恍悟间仿佛又看到屋顶房檐上有什
么东西在看着我。我选择无视它。继续前进。 



2.3 使用Intent在活动之间穿梭 


只有一个活动的应用也太简单了吧?没错,你的追求应该更高一点。不管你想创建多少
个活动,方法都和上一节中介绍的是一样的。唯一的问题在于,你在启动器中点击应用的图
标只会进入到该应用的主活动,那么怎样才能由主活动跳转到其他活动呢?我们现在就来一
起看一看。 


2.3.1 使用显式Intent 


你应该已经对创建活动的流程比较熟悉了,那我们现在快速地在ActivityTest项目中再
创建一个活动。新建一个second_layout.xml布局文件,代码如下: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


 


 <Button 


 android:id="@+id/button_2" 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="Button 2" 


 /> 


 


</LinearLayout> 


我们还是定义了一个按钮,按钮上显示Button 2。然后新建活动SecondActivity继承自
Activity,代码如下: 


public class SecondActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.second_layout); 


 } 


 





最后在AndroidManifest.xml中为SecondActivity进行注册。 



<application 


 android:allowBackup="true" 


 android:icon="@drawable/ic_launcher" 


 android:label="@string/app_name" 


 android:theme="@style/AppTheme" > 


 <activity 


 android:name=".FirstActivity" 


 android:label="This is FirstActivity" > 


 <intent-filter> 


 <action android:name="android.intent.action.MAIN" /> 


 <category android:name="android.intent.category.LAUNCHER" /> 


 </intent-filter> 


 </activity> 


 <activity android:name=".SecondActivity" > 


 </activity> 


</application> 


由于SecondActivity不是主活动,因此不需要配置<intent-filter>标签里的内容,注册活
动的代码也是简单了许多。现在第二个活动已经创建完成,剩下的问题就是如何去启动这第
二个活动了,这里我们需要引入一个新的概念,Intent。 


Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组
件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动
服务、以及发送广播等场景,由于服务、广播等概念你暂时还未涉及,那么本章我们的目光
无疑就锁定在了启动活动上面。 


Intent的用法大致可以分为两种,显式Intent和隐式Intent,我们先来看一下显式Intent
如何使用。 


Intent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls)。
这个构造函数接收两个参数,第一个参数Context要求提供一个启动活动的上下文,第二个
参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent的“意图”。
然后我们应该怎么使用这个Intent呢?Activity类中提供了一个startActivity()方法,这个方法
是专门用于启动活动的,它接收一个Intent参数,这里我们将构建好的Intent传入startActivity()
方法就可以启动目标活动了。 


修改FirstActivity中按钮的点击事件,代码如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 



 startActivity(intent); 


 } 


}); 


我们首先构建出了一个Intent,传入FirstActivity.this作为上下文,传入SecondActivity.class
作为目标活动,这样我们的“意图”就非常明显了,即在FirstActivity这个活动的基础上打
开SecondActivity这个活动。然后通过startActivity()方法来执行这个Intent。 


重新运行程序,在FirstActivity的界面点击一下按钮,结果如图2.12所示。 


 


图 2.12 


可以看到,我们已经成功启动SecondActivity这个活动了。如果你想要回到上一个活动
怎么办呢?很简单,按下Back键就可以销毁当前活动,从而回到上一个活动了。 


使用这种方式来启动活动,Intent的“意图”非常明显,因此我们称之为显式Intent。 


2.3.2 使用隐式Intent 


相比于显式Intent,隐式Intent则含蓄了许多,它并不明确指出我们想要启动哪一个活
动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,
并帮我们找出合适的活动去启动。 


什么叫做合适的活动呢?简单来说就是可以响应我们这个隐式Intent的活动,那么目前
SecondActivity可以响应什么样的隐式Intent呢?额,现在好像还什么都响应不了,不过很



快就会有了。 


通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的action
和category,打开AndroidManifest.xml,添加如下代码: 


<activity android:name=".SecondActivity" > 


 <intent-filter> 


 <action android:name="com.example.activitytest.ACTION_START" /> 


 <category android:name="android.intent.category.DEFAULT" /> 


 </intent-filter> 


</activity> 


在<action>标签中我们指明了当前活动可以响应com.example.activitytest.ACTION_ 
START这个action,而<category>标签则包含了一些附加信息,更精确地指明了当前的活动
能够响应的Intent中还可能带有的category。只有<action>和<category>中的内容同时能够匹
配上Intent中指定的action和category时,这个活动才能响应该Intent。 


修改FirstActivity中按钮的点击事件,代码如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent("com.example.activitytest.ACTION_START"); 


 startActivity(intent); 


 } 


}); 


可以看到,我们使用了Intent的另一个构造函数,直接将action的字符串传了进去,表
明我们想要启动能够响应com.example.activitytest.ACTION_START这个action的活动。那前
面不是说要<action>和<category>同时匹配上才能响应的吗?怎么没看到哪里有指定
category呢?这是因为android.intent.category.DEFAULT是一种默认的category,在调用
startActivity()方法的时候会自动将这个category添加到Intent中。 


重新运行程序,在FirstActivity的界面点击一下按钮,你同样成功启动SecondActivity
了。不同的是,这次你是使用了隐式Intent的方式来启动的,说明我们在<activity>标签下配
置的action和category的内容已经生效了! 


每个Intent中只能指定一个action,但却能指定多个category。目前我们的Intent中只有
一个默认的category,那么现在再来增加一个吧。 


修改FirstActivity中按钮的点击事件,代码如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 



 Intent intent = new Intent("com.example.activitytest.ACTION_START"); 


 intent.addCategory("com.example.activitytest.MY_CATEGORY"); 


 startActivity(intent); 


 } 


}); 


可以调用Intent中的addCategory()方法来添加一个category,这里我们指定了一个自定
义的category,值为com.example.activitytest.MY_CATEGORY。 


现在重新运行程序,在FirstActivity的界面点击一下按钮,你会发现,程序崩溃了!这
是你第一次遇到程序崩溃,可能会有些束手无策。别紧张,其实大多数的崩溃问题都是很
好解决的,只要你善于分析。在LogCat界面查看错误日志,你会看到如图2.13所示的错误
信息。 


 


图 2.13 


错误信息中提醒我们,没有任何一个活动可以响应我们的Intent,为什么呢?这是因为
我们刚刚在Intent中新增了一个category,而SecondActivity的<intent-filter>标签中并没有声
明可以响应这个category,所以就出现了没有任何活动可以响应该Intent的情况。现在我们
在<intent-filter>中再添加一个category的声明,如下所示: 


<activity android:name=".SecondActivity" > 


 <intent-filter> 


 <action android:name="com.example.activitytest.ACTION_START" /> 


 <category android:name="android.intent.category.DEFAULT" /> 


 <category android:name="com.example.activitytest.MY_CATEGORY"/> 


 </intent-filter> 


</activity> 


再次重新运行程序,你就会发现一切都正常了。 


2.3.3 更多隐式Intent的用法 


上一节中,你掌握了通过隐式Intent来启动活动的方法,但实际上隐式Intent还有更多
的内容需要你去了解,本节我们就来展开介绍一下。 


使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,
这使得Android多个应用程序之间的功能共享成为了可能。比如说你的应用程序中需要展示



一个网页,这时你没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用
系统的浏览器来打开这个网页就行了。 


修改FirstActivity中按钮点击事件的代码,如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(Intent.ACTION_VIEW); 


 intent.setData(Uri.parse("http://www.baidu.com")); 


 startActivity(intent); 


 } 


}); 


这里我们首先指定了Intent的action是Intent.ACTION_VIEW,这是一个Android系统内
置的动作,其常量值为android.intent.action.VIEW。然后通过Uri.parse()方法,将一个网址字
符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。 


重新运行程序,在FirstActivity界面点击按钮就可以看到打开了系统浏览器,如图2.14
所示。 


 



图 2.14 


上述的代码中,可能你会对setData()部分感觉到陌生,这是我们前面没有讲到过的。这
个方法其实并不复杂,它接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而
这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。 


与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指
定当前活动能够响应什么类型的数据。<data>标签中主要可以配置以下内容。 


1. android:scheme 




用于指定数据的协议部分,如上例中的http部分。 


2. android:host 




用于指定数据的主机名部分,如上例中的www.baidu.com部分。 


3. android:port 




用于指定数据的端口部分,一般紧随在主机名之后。 


4. android:path 




用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。 


5. android:mimeType 




用于指定可以处理的数据类型,允许使用通配符的方式进行指定。 


只有<data>标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应
该Intent。不过一般在<data>标签中都不会指定过多的内容,如上面浏览器示例中,其实只
需要指定android:scheme为http,就可以响应所有的http协议的Intent了。 


为了让你能够更加直观地理解,我们来自己建立一个活动,让它也能响应打开网页的
Intent。 


新建third_layout.xml布局文件,代码如下: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


 


 <Button 


 android:id="@+id/button_3" 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="Button 3" 


 /> 


 


</LinearLayout> 



然后新建活动ThirdActivity继承自Activity,代码如下: 


public class ThirdActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.third_layout); 


 } 


 





最后在AndroidManifest.xml中为ThirdActivity进行注册。 


<activity android:name=".ThirdActivity" > 


 <intent-filter> 


 <action android:name="android.intent.action.VIEW" /> 


 <category android:name="android.intent.category.DEFAULT" /> 


 <data android:scheme="http" /> 


 </intent-filter> 


</activity> 


我们在ThirdActivity的<intent-filter>中配置了当前活动能够响应的action是
Intent.ACTION_VIEW的常量值,而category则毫无疑问指定了默认的category值,另外在
<data>标签中我们通过android:scheme指定了数据的协议必须是http协议,这样ThirdActivity
应该就和浏览器一样,能够响应一个打开网页的Intent了。让我们运行一下程序试试吧,在
FirstActivity的界面点击一下按钮,结果如图2.15所示。 



 


图 2.15 


可以看到,系统自动弹出了一个列表,显示了目前能够响应这个Intent的所有程序。点
击Browser还会像之前一样打开浏览器,并显示百度的主页,而如果点击了ActivityTest,则
会启动ThirdActivity。需要注意的是,虽然我们声明了ThirdActivity是可以响应打开网页的
Intent的,但实际上这个活动并没有加载并显示网页的功能,所以在真正的项目中尽量不要
去做这种有可能误导用户的行为,不然会让用户对我们的应用产生负面的印象。 


除了http协议外,我们还可以指定很多其他协议,比如geo表示显示地理位置、tel表示
拨打电话。下面的代码展示了如何在我们的程序中调用系统拨号界面。 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(Intent.ACTION_DIAL); 


 intent.setData(Uri.parse("tel:10086")); 


 startActivity(intent); 


 } 


}); 


首先指定了Intent的action是Intent.ACTION_DIAL,这又是一个Android系统的内置动
作。然后在data部分指定了协议是tel,号码是10086。重新运行一下程序,在FirstActivity
的界面点击一下按钮,结果如图2.16所示。 



 


图 2.16 


2.3.4 向下一个活动传递数据 


经过前面几节的学习,你已经对Intent有了一定的了解。不过到目前为止,我们都只是
简单地使用Intent来启动一个活动,其实Intent还可以在启动活动的时候传递数据的,我们
来一起看一下。 


在启动活动时传递数据的思路很简单,Intent中提供了一系列putExtra()方法的重载,可
以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从
Intent中取出就可以了。比如说FirstActivity中有一个字符串,现在想把这个字符串传递到
SecondActivity中,你就可以这样编写: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 String data = "Hello SecondActivity"; 


 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 


 intent.putExtra("extra_data", data); 


 startActivity(intent); 


 } 


}); 



这里我们还是使用显式Intent的方式来启动SecondActivity,并通过putExtra()方法传递
了一个字符串。注意这里putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent
中取值,第二个参数才是真正要传递的数据。 


然后我们在SecondActivity中将传递的数据取出,并打印出来,代码如下所示: 


public class SecondActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.second_layout); 


 Intent intent = getIntent(); 


 String data = intent.getStringExtra("extra_data"); 


 Log.d("SecondActivity", data); 


 } 


 





首先可以通过getIntent()方法获取到用于启动SecondActivity的Intent,然后调用
getStringExtra()方法,传入相应的键值,就可以得到传递的数据了。这里由于我们传递的是
字符串,所以使用getStringExtra()方法来获取传递的数据,如果传递的是整型数据,则使用
getIntExtra()方法,传递的是布尔型数据,则使用getBooleanExtra()方法,以此类推。 


重新运行程序,在FirstActivity的界面点击一下按钮会跳转到SecondActivity,查看
LogCat打印信息,如图2.17所示。 


 


图 2.17 


可以看到,我们在SecondActivity中成功得到了从FirstActivity传递过来的数据。 


2.3.5 返回数据给上一个活动 


既然可以传递数据给下一个活动,那么能不能够返回数据给上一个活动呢?答案是肯定
的。不过不同的是,返回上一个活动只需要按一下Back键就可以了,并没有一个用于启动
活动Intent来传递数据。通过查阅文档你会发现,Activity中还有一个startActivityForResult()



方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活
动。毫无疑问,这就是我们所需要的。 


startActivityForResult()方法接收两个参数,第一个参数还是Intent,第二个参数是请求
码,用于在之后的回调中判断数据的来源。我们还是来实战一下,修改FirstActivity中按钮
的点击事件,代码如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 


 startActivityForResult(intent, 1); 


 } 


}); 


这里我们使用了startActivityForResult()方法来启动SecondActivity,请求码只要是一个
唯一值就可以了,这里传入了1。接下来我们在SecondActivity中给按钮注册点击事件,并
在点击事件中添加返回数据的逻辑,代码如下所示: 


public class SecondActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.second_layout); 


 Button button2 = (Button) findViewById(R.id.button_2); 


 button2.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(); 


 intent.putExtra("data_return", "Hello FirstActivity"); 


 setResult(RESULT_OK, intent); 


 finish(); 


 } 


 }); 


 } 


 





可以看到,我们还是构建了一个Intent,只不过这个Intent仅仅是用于传递数据而已,
它没有指定任何的“意图”。紧接着把要传递的数据存放在Intent中,然后调用了setResult()



方法。这个方法非常重要,是专门用于向上一个活动返回数据的。setResult()方法接收两个
参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或
RESULT_CANCELED这两个值,第二个参数则是把带有数据的Intent传递回去,然后调用
了finish()方法来销毁当前活动。 


由于我们是使用startActivityForResult()方法来启动SecondActivity的,在SecondActivity
被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity中重
写这个方法来得到返回的数据,如下所示: 


@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("FirstActivity", returnedData); 


 } 


 break; 


 default: 


 } 





onActivityResult()方法带有三个参数,第一个参数requestCode,即我们在启动活动时传
入的请求码。第二个参数resultCode,即我们在返回数据时传入的处理结果。第三个参数data,
即携带着返回数据的Intent。由于在一个活动中有可能调用startActivityForResult()方法去启
动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult()这个方法中,因此
我们首先要做的就是通过检查requestCode的值来判断数据来源。确定数据是从
SecondActivity返回的之后,我们再通过resultCode的值来判断处理结果是否成功。最后从
data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。 


重新运行程序,在FirstActivity的界面点击按钮会打开SecondActivity,然后在
SecondActivity界面点击Button 2按钮会回到FirstActivity,这时查看LogCat的打印信息,如
图2.18所示。 


 


图 2.18 



可以看到,SecondActivity已经成功返回数据给FirstActivity了。 


这时候你可能会问,如果用户在SecondActivity中并不是通过点击按钮,而是通过按下
Back键回到FirstActivity,这样数据不就没法返回了吗?没错,不过这种情况还是很好处理
的,我们可以通过重写onBackPressed()方法来解决这个问题,代码如下所示: 


@Override 


public void onBackPressed() { 


 Intent intent = new Intent(); 


 intent.putExtra("data_return", "Hello FirstActivity"); 


 setResult(RESULT_OK, intent); 


 finish(); 





这样的话,当用户按下Back键,就会去执行onBackPressed()方法中的代码,我们在这
里添加返回数据的逻辑就行了。 


 


经验值:+600 目前经验值:1605 


级别:小菜鸟 


捡到宝物:在路过一个神界废弃的射击靶场的时候,捡到带锈点的代码银弹两枚。代码
银弹是神界程序员的一种消费类日用品,一颗银弹可自动修复一处bug(修复成功率接近
100%)。神界最大的编程组织TNND曾多次呼吁程序员们尽量少用或不用银弹,因为随着十
年前代码银弹被一个名叫King Dom(中文译为坑爹)的顶级黑客呕心沥血二十年发明出来后,
十年间代码银弹的销费量逐年增加,而神界程序员的编程水平也似乎有所下降。神界发行量
最大的IT报纸《每日加班报》的记者日前就代码银弹所引发的问题采访King Dom时,King Dom
只回答了四个字“你懂个球!”代码银弹可以用配有银弹发射接口的任何枪支来发射。 



2.4 活动的生命周期 


掌握活动的生命周期对任何Android开发者来说都非常重要,当你深入理解活动的生命
周期之后,就可以写出更加连贯流畅的程序,并在如何合理管理应用资源方面,你会发挥的
游刃有余。你的应用程序将会拥有更好的用户体验。 


2.4.1 返回栈 


经过前面几节的学习,我相信你已经发现了这一点,Android中的活动是可以层叠的。
我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back键会销毁最上面的活动,
下面的一个活动就会重新显示出来。 


其实Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动
的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况
下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们
按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入
栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。 


示意图2.19展示了返回栈是如何管理活动入栈出栈操作的。 


 


图 2.19 


2.4.2 活动状态 


每个活动在其生命周期中最多可能会有四种状态。 


1. 运行状态 





当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的
就是处于运行状态的活动,因为这会带来非常差的用户体验。 


2. 暂停状态 




当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可
能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会
占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在
后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这
种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只
有在内存极低的情况下,系统才会去考虑回收这种活动。 


3. 停止状态 




当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统
仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方
需要内存时,处于停止状态的活动有可能会被系统回收。 


4. 销毁状态 




当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状
态的活动,从而保证手机的内存充足。 


2.4.3 活动的生存期 


Activity类中定义了七个回调方法,覆盖了活动生命周期的每一个环节,下面我来一一
介绍下这七个方法。 


1. onCreate() 




这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动
第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布
局、绑定事件等。 


2. onStart() 




这个方法在活动由不可见变为可见的时候调用。 


3. onResume() 




这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的
栈顶,并且处于运行状态。 


4. onPause() 




这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方
法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度
一定要快,不然会影响到新的栈顶活动的使用。 


5. onStop() 





这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如
果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()
方法并不会执行。 


6. onDestroy() 




这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。 


7. onRestart() 




这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。 


以上七个方法中除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为三
种生存期。 


1. 完整生存期 




活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期。一般情
况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完
成释放内存的操作。 


2. 可见生存期 




活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。在可见生存
期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两
个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,
而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。 


3. 前台生存期 




活动在onResume()方法和onPause()方法之间所经历的,就是前台生存期。在前台
生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行相互的,我们平时
看到和接触最多的也这个状态下的活动。 


为了帮助你能够更好的理解,Android官方提供了一张活动生命周期的示意图,如图2.20
所示。 



 


图 2.20 


2.4.4 体验活动的生命周期 


讲了这么多理论知识,也是时候该实战一下了,下面我们将通过一个实例,让你可以更
加直观地体验活动的生命周期。 



这次我们不准备在ActivityTest这个项目的基础上修改了,而是新建一个项目。因此,
首先关闭ActivityTest项目,然后新建一个ActivityLifeCycleTest项目。新建项目的过程你应
该已经非常清楚了,不需要我再进行赘述,这次我们允许ADT帮我们自动创建活动,这样
可以省去不少工作,创建的活动名和布局名都使用默认值。 


这样主活动就创建完成了,我们还需要分别再创建两个子活动,NormalActivity和
DialogActivity,下面一步步来实现。 


新建normal_layout.xml文件,代码如下所示: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


 


 <TextView 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="This is a normal activity" 


 /> 


 


</LinearLayout> 


这个布局中我们就非常简单地使用了一个TextView,用于显示一行文字,在下一章中你
将会学到更多关于TextView的用法。 


然后同样的方法,我们再新建一个dialog_layout.xml文件,代码如下所示: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


 


 <TextView 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="This is a dialog activity" 


 /> 


 


</LinearLayout> 


两个布局文件的代码几乎没有区别,只是显示的文字不同而已。 


然后新建NormalActivity继承自Activity,代码如下所示: 



public class NormalActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.normal_layout); 


 } 


 





我们在NormalActivity中加载了normal_layout这个布局。 


同样的方法,再新建DialogActivity继承自Activity,代码如下所示: 


public class DialogActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.dialog_layout); 


 } 


 





我们在DialogActivity中加载了dialog_layout这个布局。 


其实从名字上你就可以看出,这两个活动一个是普通的活动,一个是对话框式的活动。
可是现在不管怎么看,这两个活动的代码都几乎都是一模一样的,在哪里有体现出将活动设
成对话框式的呢?别着急,下面我们马上开始设置。在AndroidManifest.xml的<activity>标
签中添加如下代码: 


<activity android:name=".NormalActivity" > 


</activity> 


<activity android:name=".DialogActivity" android:theme="@android:style/ 
Theme.Dialog" > 


</activity> 


这里分别为两个活动进行注册,但是DialogActivity的注册代码有些不同,它使用了一
个android:theme属性,这是用于给当前活动指定主题的,Android系统内置有很多主题可以
选择,当然我们也可以定制自己的主题,而这里@android:style/Theme.Dialog则毫无疑问是
让DialogActivity使用对话框式的主题。 



接下来我们修改activity_main.xml,重新定制我们主活动的布局: 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 


 android:layout_width="match_parent" 


 android:layout_height="match_parent" 


 android:orientation="vertical" > 


 


 <Button 


 android:id="@+id/start_normal_activity" 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="Start NormalActivity" /> 


 


 <Button 


 android:id="@+id/start_dialog_activity" 


 android:layout_width="match_parent" 


 android:layout_height="wrap_content" 


 android:text="Start DialogActivity" /> 


 


</LinearLayout> 


自动生成的布局代码有些复杂,这里我们完全替换掉,仍然还是使用最熟悉的LinearLayout,
然后加入了两个按钮,一个用于启动NormalActivity,一个用于启动DialogActivity。 


最后修改MainActivity中的代码,如下所示: 


public class MainActivity extends Activity { 


 


 public static final String TAG = "MainActivity"; 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d(TAG, "onCreate"); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.activity_main); 


 Button startNormalActivity = (Button) findViewById(R.id.start_ 
normal_activity); 


 Button startDialogActivity = (Button) findViewById(R.id.start_ 
dialog_activity); 


 startNormalActivity.setOnClickListener(new OnClickListener() { 


 @Override 



 public void onClick(View v) { 


 Intent intent = new Intent(MainActivity.this, 
NormalActivity.class); 


 startActivity(intent); 


 } 


 }); 


 startDialogActivity.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(MainActivity.this, 
DialogActivity.class); 


 startActivity(intent); 


 } 


 }); 


 } 


 


 @Override 


 protected void onStart() { 


 super.onStart(); 


 Log.d(TAG, "onStart"); 


 } 


 


 @Override 


 protected void onResume() { 


 super.onResume(); 


 Log.d(TAG, "onResume"); 


 } 


 


 @Override 


 protected void onPause() { 


 super.onPause(); 


 Log.d(TAG, "onPause"); 


 } 


 


 @Override 


 protected void onStop() { 


 super.onStop(); 


 Log.d(TAG, "onStop"); 


 } 


 



 @Override 


 protected void onDestroy() { 


 super.onDestroy(); 


 Log.d(TAG, "onDestroy"); 


 } 


 


 @Override 


 protected void onRestart() { 


 super.onRestart(); 


 Log.d(TAG, "onRestart"); 


 } 


 





在onCreate()方法中,我们分别为两个按钮注册了点击事件,点击第一个按钮会启动
NormalActivity,点击第二个按钮会启动DialogActivity。然后在Activity的七个回调方法中
分别打印了一句话,这样就可以通过观察日志的方式来更直观地理解活动的生命周期。 


现在运行程序,效果如图2.21所示。 


 


图 2.21 


 



这时观察LogCat中的打印日志,如图2.22所示。 


 


图 2.22 


可以看到,当MainActivity第一次被创建时会依次执行onCreate()、onStart()和onResume()
方法。然后点击第一个按钮,启动NormalActivity,如图2.23所示。 


 


图 2.23 


此时的打印信息如图2.24所示。 


 


图 2.24 



由于NormalActivity已经把MainActivity完全遮挡住,因此onPause()和onStop()方法都
会得到执行。然后按下Back键返回MainActivity,打印信息如图2.25所示。 


 


图 2.25 


由于之前MainActivity已经进入了停止状态,所以onRestart()方法会得到执行,之后又
会依次执行onStart()和onResume()方法。注意此时onCreate()方法不会执行,因为MainActivity
并没有重新创建。 


然后再点击第二个按钮,启动DialogActivity,如图2.26所示。 


 


图 2.26 


此时观察打印信息,如图2.27所示。 


 


图 2.27 



可以看到,只有onPause()方法得到了执行,onStop()方法并没有执行,这是因为
DialogActivity并没有完全遮挡住MainActivity,此时MainActivity只是进入了暂停状态,并
没有进入停止状态。相应地,按下Back键返回MainActivity也应该只有onResume()方法会
得到执行,如图2.28所示。 


 


图 2.28 


最后在MainActivity按下Back键退出程序,打印信息如图2.29所示。 


 


图 2.29 


依次会执行onPause()、onStop()和onDestroy()方法,最终销毁MainActivity。 


这样活动完整的生命周期你已经体验了一遍,是不是理解得更加深刻了? 


2.4.5 活动被回收了怎么办 


前面我们已经说过,当一个活动进入到了停止状态,是有可能被系统回收的。那么想象
以下场景,应用中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了
停止状态,这个时候由于系统内存不足,将活动A回收掉了,然后用户按下Back键返回活
动A,会出现什么情况呢?其实还是会正常显示活动A的,只不过这时并不会执行onRestart()
方法,而是会执行活动A的onCreate()方法,因为活动A在这种情况下会被重新创建一次。 


这样看上去好像一切正常,可是别忽略了一个重要问题,活动A中是可能存在临时数据
和状态的。打个比方,MainActivity中有一个文本输入框,现在你输入了一段文字,然后
启动NormalActivity,这时MainActivity由于系统内存不足被回收掉,过了一会你又点击了
Back键回到MainActivity,你会发现刚刚输入的文字全部都没了,因为MainActivity被重新
创建了。 


如果我们的应用出现了这种情况,是会严重影响用户体验的,所以必须要想想办法解决
这个问题。查阅文档可以看出,Activity中还提供了一个onSaveInstanceState()回调方法,这
个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时



临时数据得不到保存的问题。 


onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法
用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,
以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,
第二个参数是真正要保存的内容。 


在MainActivity中添加如下代码就可以将临时数据进行保存: 


@Override 


protected void onSaveInstanceState(Bundle outState) { 


 super.onSaveInstanceState(outState); 


 String tempData = "Something you just typed"; 


 outState.putString("data_key", tempData); 





数据是已经保存下来了,那么我们应该在哪里进行恢复呢?细心的你也许早就发现,我
们一直使用的onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是
null,但是当活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,这个参
数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。 


修改MainActivity的onCreate()方法,如下所示: 


@Override 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d(TAG, "onCreate"); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.activity_main); 


 if (savedInstanceState != null) { 


 String tempData = savedInstanceState.getString("data_key"); 


 Log.d(TAG, tempData); 


 } 


 …… 





取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框
上,这里我们只是简单地打印一下。 


不知道你有没有察觉,使用Bundle来保存和取出数据是不是有些似曾相识呢?没错!
我们在使用Intent传递数据时也是用的类似的方法。这里跟你提醒一点,Intent还可以结合
Bundle一起用于传递数据的,首先可以把需要传递的数据都保存在Bundle对象中,然后再
将Bundle对象存放在Intent里。到了目标活动之后先从Intent中取出Bundle,再从Bundle
中一一取出数据。具体的代码我就不写了,要学会举一反三哦。 



经验值:+700 目前经验值:2305 


级别:小菜鸟 


赢得宝物:战胜活动周期兽。拾取周期兽掉落的宝物,全新周期兽皮Android战袍一套、
体力恢复剂两瓶、周期兽怒吼丸三颗。体力恢复剂可用于迅速补充体力,常被神界程序员加
班时使用。周期兽是食草类巨兽,身长超过5米,肩高可达3米,体重超过8吨,最喜欢吃
咆哮树结的咆哮果,除了强有力的犄角和粗大的蹄子外,周期兽的怒吼也是其有效的攻击性
武器,一旦开嚎,常常令敌人肝胆俱裂。周期兽怒吼丸是用周期兽的粪便熬制而成,用法是
一次连服三颗,1分钟后起效,药力强劲,可令服用者的肺活量短时间增大200倍,声带也
相应增强至猛犸级,常被程序员用于辞职前向老板说byebye,或是与产品经理探讨问题。 


2.5 活动的启动模式 


活动的启动模式对你来说应该是个全新的概念,在实际项目中我们应该根据特定的需求
为每个活动指定恰当的启动模式。启动模式一共有四种,分别是standard、singleTop、
singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定
android:launchMode属性来选择启动模式。下面我们来逐个进行学习。 


2.5.1 standard 


standard是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用
这种启动模式。因此,到目前为止我们写过的所有活动都是使用的standard模式。经过上一
节的学习,你已经知道了Android是使用返回栈来管理活动的,在standard模式(即默认情
况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用
standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建
该活动的一个新的实例。 


我们现在通过实践来体会一下standard模式,这次还是准备在ActivityTest项目的基础



上修改,首先关闭ActivityLifeCycleTest项目,打开ActivityTest项目。 


修改FirstActivity中onCreate()方法的代码,如下所示: 


@Override 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("FirstActivity", this.toString()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.first_layout); 


 Button button1 = (Button) findViewById(R.id.button_1); 


 button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(FirstActivity.this, FirstActivity.class); 


 startActivity(intent); 


 } 


 }); 





代码看起来有些奇怪吧,在FirstActivity的基础上启动FirstActivity。从逻辑上来讲这确
实没什么意义,不过我们的重点在于研究standard模式,因此不必在意这段代码有什么实际
用途。另外我们还在onCreate()方法中添加了一行打印信息,用于打印当前活动的实例。 


现在重新运行程序,然后在FirstActivity界面连续点击两次按钮,可以看到LogCat中打
印信息如图2.30所示。 


 


图 2.30 


从打印信息中我们就可以看出,每点击一次按钮就会创建出一个新的FirstActivity实例。
此时返回栈中也会存在三个FirstActivity的实例,因此你需要连按三次Back键才能退出程序。 


standard模式的原理示意图,如图2.31所示。 



 


图 2.31 


2.5.2 singleTop 


可能在有些情况下,你会觉得standard模式不太合理。活动明明已经在栈顶了,为什么
再次启动的时候还要创建一个新的活动实例呢?别着急,这只是系统默认的一种启动模式而
已,你完全可以根据自己的需要进行修改,比如说使用singleTop模式。当活动的启动模式
指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用
它,不会再创建新的活动实例。 


我们还是通过实践来体会一下,修改AndroidManifest.xml中FirstActivity的启动模式,
如下所示: 


<activity 


 android:name=".FirstActivity" 


 android:launchMode="singleTop" 


 android:label="This is FirstActivity" > 


 <intent-filter> 


 <action android:name="android.intent.action.MAIN" /> 


 <category android:name="android.intent.category.LAUNCHER" /> 


 </intent-filter> 


</activity> 


然后重新运行程序,查看LogCat会看到已经创建了一个FirstActivity的实例,如图2.32
所示。 


 



图 2.32 


但是之后不管你点击多少次按钮都不会再有新的打印信息出现,因为目前FirstActivity
已经处于返回栈的栈顶,每当想要再启动一个FirstActivity时都会直接使用栈顶的活动,因
此FirstActivity也只会有一个实例,仅按一次Back键就可以退出程序。 


不过当FirstActivity并未处于栈顶位置时,这时再启动FirstActivity,还是会创建新的实
例的。下面我们来实验一下,修改FirstActivity中onCreate()方法的代码,如下所示: 


@Override 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("FirstActivity", this.toString()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.first_layout); 


 Button button1 = (Button) findViewById(R.id.button_1); 


 button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(FirstActivity.this, 
SecondActivity.class); 


 startActivity(intent); 


 } 


 }); 





这次我们点击按钮后启动的是SecondActivity。然后修改SecondActivity中onCreate()方
法的代码,如下所示: 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("SecondActivity", this.toString()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.second_layout); 


 Button button2 = (Button) findViewById(R.id.button_2); 


 button2.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(SecondActivity.this, 
FirstActivity.class); 


 startActivity(intent); 


 } 



 }); 





我们在SecondActivity中的按钮点击事件里又加入了启动FirstActivity的代码。现在重
新运行程序,在FirstActivity界面点击按钮进入到SecondActivity,然后在SecondActivity界
面点击按钮,又会重新进入到FirstActivity。 


查看LogCat中的打印信息,如图2.33所示。 


 


图 2.33 


可以看到系统创建了两个不同的FirstActivity实例,这是由于在SecondActivity中再次
启动FirstActivity时,栈顶活动已经变成了SecondActivity,因此会创建一个新的FirstActivity
实例。现在按下Back键会返回到SecondActivity,再次按下Back键又会回到FirstActivity,
再按一次Back键才会退出程序。 


singleTop模式的原理示意图,如图2.34所示。 


 


图 2.34 


2.5.3 singleTask 


使用singleTop模式可以很好地解决重复创建栈顶活动的问题,但是正如你在上一节所



看到的,如果该活动并没有处于栈顶的位置,还是可能会创建多个活动实例的。那么有没有
什么办法可以让某个活动在整个应用程序的上下文中只存在一个实例呢?这就要借助
singleTask模式来实现了。当活动的启动模式指定为singleTask,每次启动该活动时系统首先
会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这
个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。 


我们还是通过代码来更加直观地理解一下。修改AndroidManifest.xml中FirstActivity的
启动模式: 


<activity 


 android:name=".FirstActivity" 


 android:launchMode="singleTask" 


 android:label="This is FirstActivity" > 


 <intent-filter> 


 <action android:name="android.intent.action.MAIN" /> 


 <category android:name="android.intent.category.LAUNCHER" /> 


 </intent-filter> 


</activity> 


然后在FirstActivity中添加onRestart()方法,并打印日志: 


@Override 


protected void onRestart() { 


 super.onRestart(); 


 Log.d("FirstActivity", "onRestart"); 





最后在SecondActivity中添加onDestroy()方法,并打印日志: 


@Override 


protected void onDestroy() { 


 super.onDestroy(); 


 Log.d("SecondActivity", "onDestroy"); 





现在重新运行程序,在FirstActivity界面点击按钮进入到SecondActivity,然后在
SecondActivity界面点击按钮,又会重新进入到FirstActivity。 


查看LogCat中的打印信息,如图2.35所示。 



 


图 2.35 


其实从打印信息中就可以明显看出了,在SecondActivity中启动FirstActivity时,会发
现返回栈中已经存在一个FirstActivity的实例,并且是在SecondActivity的下面,于是
SecondActivity会从返回栈中出栈,而FirstActivity重新成为了栈顶活动,因此FirstActivity
的onRestart()方法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中应该只剩
下一个FirstActivity的实例了,按一下Back键就可以退出程序。 


singleTask模式的原理示意图,如图2.36所示。 


 


图 2.36 


2.5.4 singleInstance 


singleInstance模式应该算是四种启动模式中最特殊也最复杂的一个了,你也需要多花点
功夫来理解这个模式。不同于以上三种启动模式,指定为singleInstance模式的活动会启用一
个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启



动一个新的返回栈)。那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个
活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实
例,应该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自
己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用
singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活
动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实
例的问题。 


为了帮助你可以更好地理解这种启动模式,我们还是来实践一下。修改AndroidManifest. 
xml中SecondActivity的启动模式: 


<activity 


 android:name=".SecondActivity" 


 android:launchMode="singleInstance" > 


 <intent-filter> 


 <action android:name="com.example.activitytest.ACTION_START" /> 


 <category android:name="android.intent.category.DEFAULT" /> 


 <category android:name="com.example.activitytest.MY_CATEGORY" /> 


 </intent-filter> 


</activity> 


我们先将SecondActivity的启动模式指定为singleInstance,然后修改FirstActivity中
onCreate()方法的代码: 


@Override 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("FirstActivity", "Task id is " + getTaskId()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.first_layout); 


 Button button1 = (Button) findViewById(R.id.button_1); 


 button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(FirstActivity.this, 
SecondActivity.class); 


 startActivity(intent); 


 } 


 }); 






在onCreate()方法中打印了当前返回栈的id。然后修改SecondActivity中onCreate()方法
的代码: 


@Override 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("SecondActivity", "Task id is " + getTaskId()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.second_layout); 


 Button button2 = (Button) findViewById(R.id.button_2); 


 button2.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 Intent intent = new Intent(SecondActivity.this, 
ThirdActivity.class); 


 startActivity(intent); 


 } 


 }); 





同样在onCreate()方法中打印了当前返回栈的id,然后又修改了按钮点击事件的代码,
用于启动ThirdActivity。最后修改ThirdActivity中onCreate()方法的代码: 


@Override 


protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("ThirdActivity", "Task id is " + getTaskId()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.third_layout); 





仍然是在onCreate()方法中打印了当前返回栈的id。现在重新运行程序,在FirstActivity
界面点击按钮进入到SecondActivity,然后在SecondActivity界面点击按钮进入到
ThirdActivity。 


查看LogCat中的打印信息,如图2.37所示。 


 



图 2.37 


可以看到,SecondActivity的Task id不同于FirstActivity和ThirdActivity,这说明
SecondActivity确实是存放在一个单独的返回栈里的,而且这个栈中只有SecondActivity这一
个活动。 


然后我们按下Back键进行返回,你会发现ThirdActivity竟然直接返回到了FirstActivity,
再按下Back键又会返回到SecondActivity,再按下Back键才会退出程序,这是为什么呢?
其实原理很简单,由于FirstActivity和ThirdActivity是存放在同一个返回栈里的,当在
ThirdActivity的界面按下Back键,ThirdActivity会从返回栈中出栈,那么FirstActivity就成
为了栈顶活动显示在界面上,因此也就出现了从ThirdActivity直接返回到FirstActivity的情
况。然后在FirstActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了
另一个返回栈的栈顶活动,即SecondActivity。最后再次按下Back键,这时所有返回栈都已
经空了,也就自然退出了程序。 


 


singleInstance模式的原理示意图,如图2.38所示。 


 


图 2.38 


2.6 活动的最佳实践 


你已经掌握了关于活动非常多的知识,不过恐怕离能够完全灵活运用还有一段距离。虽
然知识点只有这么多,但运用的技巧却是多种多样。所以,在这里我准备教你几种关于活动
的最佳实践技巧,这些技巧在你以后的开发工作当中将会非常受用。 



2.6.1 知晓当前是在哪一个活动 


这个技巧将教会你,如何根据程序当前的界面就能判断出这是哪一个活动。可能你会觉
得挺纳闷的,我自己写的代码怎么会不知道这是哪一个活动呢?很不幸的是,在你真正进入
到企业之后,更有可能的是接手一份别人写的代码,因为你刚进公司就正好有一个新项目启
动的概率并不高。阅读别人的代码时有一个很头疼的问题,就是你需要在某个界面上修改一
些非常简单的东西,但是你半天找不到这个界面对应的活动是哪一个。学会了本节的技巧之
后,这对你来说就再也不是难题了。 


我们还是在ActivityTest项目的基础上修改。首先需要新建一个BaseActivity继承自
Activity,然后在BaseActivity中重写onCreate()方法,如下所示: 


public class BaseActivity extends Activity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("BaseActivity", getClass().getSimpleName()); 


 } 


 





我们在onCreate()方法中获取了当前实例的类名,并通过Log打印了出来。 


接下来我们需要让BaseActivity成为ActivityTest项目中所有活动的父类。修改
FirstActivity、SecondActivity和ThirdActivity的继承结构,让它们不再继承自Activity,而是
继承自BaseActivity。虽然项目中的活动不再直接继承自Activity了,但是它们仍然完全继
承了Activity中的所有特性。 


现在重新运行程序,然后通过点击按钮分别进入到FirstActivity、SecondActivity和
ThirdActivity的界面,这时观察LogCat中的打印信息,如图2.39所示。 


 


图 2.39 


现在每当我们进入到一个活动的界面,该活动的类名就会被打印出来,这样我们就可以
时时刻刻知晓当前界面对应的是哪一个活动了。 



2.6.2 随时随地退出程序 


如果目前你手机的界面还停留在ThirdActivity,你会发现当前想退出程序是非常不方便
的,需要连按三次Back键才行。按Home键只是把程序挂起,并没有退出程序。其实这个
问题就足以引起你的思考,如果我们的程序需要一个注销或者退出的功能该怎么办呢?必须
要有一个随时随地都能退出程序的方案才行。 


其实解决思路也很简单,只需要用一个专门的集合类对所有的活动进行管理就可以了,
下面我们就来实现一下。 


新建一个ActivityCollector类作为活动管理器,代码如下所示: 


public class ActivityCollector { 


 


 public static List<Activity> activities = new ArrayList<Activity>(); 


 


 public static void addActivity(Activity activity) { 


 activities.add(activity); 


 } 


 


 public static void removeActivity(Activity activity) { 


 activities.remove(activity); 


 } 


 


 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 Activity { 


 


 @Override 



 protected void onCreate(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()方法
就可以了。例如在ThirdActivity界面想通过点击按钮直接退出程序,只需将代码改成如下
所示: 


public class ThirdActivity extends BaseActivity { 


 


 @Override 


 protected void onCreate(Bundle savedInstanceState) { 


 super.onCreate(savedInstanceState); 


 Log.d("ThirdActivity", "Task id is " + getTaskId()); 


 requestWindowFeature(Window.FEATURE_NO_TITLE); 


 setContentView(R.layout.third_layout); 


 Button button3 = (Button) findViewById(R.id.button_3); 


 button3.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 ActivityCollector.finishAll(); 


 } 


 }); 


 } 


 






当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全
退出。 


2.6.3 启动活动的最佳写法 


启动活动的方法相信你已经非常熟悉了,首先通过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,
如下所示: 


button1.setOnClickListener(new OnClickListener() { 


 @Override 


 public void onClick(View v) { 


 SecondActivity.actionStart(FirstActivity.this, "data1", "data2"); 


 } 


}); 


养成一个良好的习惯,给你编写的每个活动都添加类似的启动方法,这样不仅可以让启
动活动变得非常简单,还可以节省不少你同事过来询问你的时间。 


2.7 小结与点评 


真是好疲惫啊!没错,学习了这么多的东西不疲惫才怪呢。但是,你内心那种掌握了知
识的喜悦感相信也是无法掩盖的。本章的收获非常多啊,不管是理论型还是实践型的东西都
涉及了,从活动的基本用法,到启动活动和传递数据的方式,再到活动的生命周期,以及活
动的启动模式,你几乎已经学会了关于活动所有重要的知识点。另外在本章的最后,还学习
了几种可以应用在活动中的最佳实践技巧,毫不夸张地说,你在Android活动方面已经算是
一个小高手了。 


不过你的Android旅途才刚刚开始呢,后面需要学习的东西还很多,也许会比现在还累,
一定要做好心理准备哦。总体来说,我给你现在的状态打满分,毕竟你已经学会了那么多的
东西,也是时候该放松一下了。自己适当控制一下休息的时间,然后我们继续前进吧! 


 


经验值:+600 目前经验值:2905 


级别:小菜鸟 


赢得宝物:战胜活动周期兽猎人。拾取周期兽猎人掉落的宝物,九成新周期兽皮Android
战袍一套、体力恢复剂5瓶、周期兽怒吼丸24颗(颤抖吧,小老板们)、半自动老款卡宾枪
一支(配有可拆卸代码银弹接口)、九成新代码银弹弹夹一个(含20枚代码银弹)。在野外
用带锈点的代码银弹试射了一下,成功修复了一只bug鸟的bug,原本少一只翅膀的它现在



扑腾着两只翅膀飞走了。向当地老乡打听后得知,周期兽猎人虽然叫周期兽猎人,但这称呼
完全是个误会,实际上他和周期兽是好基友,平日里两个人喜欢在林中散步,一起听听歌什
么的,两人最大的乐趣是用代码银弹修复bug鸟、bug猪,以及森林中的其他有bug的小动
物。我向被我战胜的正蹲在地上气喘吁吁的周期兽猎人道别。树上那只鬼鬼祟祟的松鼠再次
向我露出神秘的笑容。无视它。继续前进。 



第3章 软件也要拼脸蛋,UI开
发的点点滴滴 


我一直都认为程序员在软件的审美方面普遍都比较差,至少我个人就是如此。如果说要
追究其根本原因,我觉得这是由于程序员的工作性质所导致的。每当我们看到一个软件时,
不会像普通用户一样仅仅是关注一下它的界面以及有哪些功能。我们总是会不自觉地思考这
些功能是如何实现的,很多在普通用户看来理所应当的功能,背后可能却需要非常复杂的算
法来完成。以至于当别人唾骂一句,这软件做得真丑的时候,我们还可能赞叹一句,这功能
做得好牛逼啊! 


不过缺乏审美观毕竟不是一件值得炫耀的事情,在软件开发过程中,界面设计和功能开
发同样重要。界面美观的应用程序不仅可以大大增加用户粘性,还能帮我们吸引到更多的新
用户。而Android也是给我们提供了大量的UI开发工具,只要合理地使用它们,就可以编
写出各种各样漂亮的界面。 


在这里,我无法教会你如何提升自己的审美观,但我可以教会你怎样使用Android提供
的UI开发工具来编写程序界面。想必你在上一章中反反复复地使用那几个按钮都快要吐了
吧,本章我们就来学习更多的UI开发方面的知识。
0 0
原创粉丝点击