第3章 Android的Activity组件
Activity(活动)组件是Android的四大组件之一,是Android移动应用的基本组件,用
于设计移动应用界面的屏幕显示。Android移动应用往往需要多个界面,因此移动应用中
必须定义一个或多个Activity以实现界面的交互。在Android3.0以后的版本中出现的
Fragment(碎片)可以嵌入Activity的图形用户界面中。目前,采用单个Activity和多个
Fragment结合的方式已成为移动应用界面定义的首选。本章将对Activity组件以及嵌入
Activity组件的Fragment进行介绍。
3.1 Activity的创建
Activity用于创建移动界面的屏幕。它的主要作用就是实现移动界面和用户之间的交
互。Activity类用于创建和管理用户界面,一个应用程序可以有多个Activity,但在同一个
时间内只有一个Activity处于激活状态。在移动应用中,Activity之间的依赖关系低。
创建Activity时,需要完成如下几个步骤。
(1)定义Activity类。
(2)定义Activity对应的布局文件,使得布局文件成为Activity的界面定义。
(3)在配置清单文件AndroidManifest.xml中声明并配置Activity的相应属性。
1.定义Activity类
Activity必须是自己的子类或扩展子类的子类。因为Activity 的一个子类为
AppCompatActivity,它可以定义MaterialDesign风格的界面,所以在当前Android中创建
Activity一般是定义为AppComaptActivity的子类。形式如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
在MainActivity(主活动)中定义了onCreate()函数,该函数是必须定义的回调函数,它
会在系统创建Activity时被调用。在Activity中必须使用setContentView()函数来指定
Activity的界面布局。这个布局通过资源引用R.layout.activity_main来引用资源目录下的
res/layout/activity_main.xml的布局文件。布局文件(如activity_main.xml)用于定义
Activity的外观,Activity可以显示并对布局文件的界面组件进行控制,实现交互行为,如
图3-1所示。
2.创建Activity的布局文件
布局配置文件保存在res/layout目录下,是XML文件。通过配置布局的相关属性和
·44·
图3-1 Activity和布局之间的关系示意
元素可实现布局。布局文件中所有的元素必须配置android:layout_width和android:layout_
height属性,它们分别表示元素在屏幕的宽度和高度,一般取值如下。
(1)match_parent:表示使视图扩展至父元素大小。
(2)wrap_content:表示自适应大小,将组件元素的全部内容进行显示。
当然,也可以设置尺寸大小。Activity类通过setContentView()来引用布局资源,实现
布局在Activity中的渲染和展示,代码如下:
在上述文件中,根元素是TextView,是一个文本标签。如果希望在Activity中对整个
TextView可以进行引用和处理,可以在布局文件中使用android:id="@+id/textView"
来标识资源。其中,"@ +id"表示增加一个资源id。如果希望在布局文件中引用这个
TextView,可以通过"@id/textView"实现。但是在Activity(例如MainActivity代码)中需
要通过这个TextView的资源id来引用,形式如下:
val textView: TextView=findViewById(R.id.textView)
如果在模块的构建文件build.gradle中增加插件kotlin-android-extensions,形如:
apply plugin: 'kotlin-android-extensions'
则可以直接在Activity中通过资源编号引用View(视图)组件:
textView.text="Hello" //直接设置布局资源编号为textView 的文本为Hello
但是由于kotlin-android-extensions插件只能对Kotlin支持,目前已经被弃用。
3.在配置清单文件AndroidManifest.xml中注册Activity
要让创建的Activity能让移动应用识别和调用,就必须在AndroidManifest.xml系统
配置清单文件中进行声明和配置。AndroidManifest.xml文件保存在项目的manifests目
·45·
录中。代码如下:
在application元素(表示移动应用)中增加下级元素activity,并通过android:name属
性指定活动的类名。在manifest元素的package属性中指定了包名,所以在activity元素
的android:name中只需要定义成
android: name=".MainActivity"
即可。在activity元素下定义了intent-filter意图过滤器。intent-filter定义的意图过滤器
非常强大,通过它可以根据显式请求启动Activity,也可以根据隐式请求启动Activity。下
面代码用于指定MainActivity(主活动),它是整个移动应用的入口活动,表示应用程序可以
显示在程序列表中。
action android: name="android.intent.action.MAIN"
category android: name="android.intent.category.LAUNCHER"
注意,在Android12及以上版本中使用intent-filter元素配置时,必须时Activity增加配置
android:exported属性。
3.2 Activity和Intent
一个应用可以定义多个Activity,这些Activity可以相互切换和跳转。要实现这些交
互功能,就必须了解什么是Intent(意图)。Intent表示封装执行操作的意图,是应用程序启
动其他组件的Intent对象启动组件。这些组件可以是Activity,也可以是其他基本组件,如
Service(服务)组件、BroadcastReceiver(广播接收器)组件。从一个基本组件导航到另一个组
件。通过Intent实现组件之间的跳转和关联。Intent分为显式Intent和隐式Intent两种。
3.2.1 显式Intent
显式Intent就是在Intent()函数启动组件时,需要明确指定的激活组件名称,例如:
·46·
Intent(Context packageContext, Class>c)
其中,Intent()函数包含两个参数,packageContext用于提供启动Activity的上下文,c用于
指定想要启动的目标组件类。
例3-1 显式Intent的应用。在本应用中定义3 个Activity 类:MainActivity、
FirstActivity和SecondActivity。其中,MainActivity用于定义菜单,具体的菜单项包括“第
一个活动”“第二个活动”“退出”。根据菜单项,可跳转至不同的活动或退出应用。选中“第一
个活动”菜单项,可跳转到FirstActivity,选中“第二个活动”菜单项,可跳转到SecondActivity,选
中“退出”菜单项,则从移动应用中退出。
在新建移动应用模块时,因为本例涉及菜单的应用,所以需要先定义相关的资源文件。
(1)定义相关资源。
移动应用往往需要大量的字符串来配置相关的组件,因此可以将字符串统一定义在
res/values目录下的strings.xml配置文件中,通过设置string元素配置字符串。string元
素的name属性表示字符串的名字,string元素的内容表示字符串的取值,代码如下:
Ch03_02
第一个活动
第二个活动
退出
在Activity中创建菜单时,必须依赖菜单资源。在res目录下创建资源目录menu,然
后在res/menu目录下创建menu.xml文件,可以直接利用编辑器在menu.xml中编辑菜单
的内容,也可以通过编辑器的Design界面来实现。在这个例题中,res/menu目录下的
menu.xml的内容如下:
在menu.xml文件中,menu元素定义菜单,item 元素定义菜单项。在菜单项中使用
android:id指定菜单的id资源编号,android:icon表示菜单的图标,android:title定义菜
单对应的文字标签。
·47·
如果在移动应用中需要使用具体尺寸大小的相关资源,可以采用类似的方法创建
dimens.xml来保存尺寸资源。具体做法是,选中res/values目录并右击,在弹出的快捷菜
单中选中New|ValuesResourceFile选项,然后指定资源名,如dimens,单击OK 按钮,创
建dimens.xml的尺寸大小的资源文件,代码如下:
40sp
指定的尺寸采用了sp单位,表示比例无关的像素单位,常常表示字体的大小。表示布
局的尺寸往往使用dp单位,dp单位代表密度无关的像素,是基于屏幕的物理密度的抽象或
虚拟单元。
(2)定义MainActivity。MainActivity是当前应用的入口。它对应的布局文件activity
_main.xml文件如下:
在MainActivity中不但利用activity_main.xml渲染生成显示的界面,而且定义菜单,
并根据菜单实现不同活动的跳转。代码如下:
//模块Ch03_02 主活动的定义文件MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}o
verride fun onCreateOptionsMenu(menu: Menu?): Boolean { //定义菜单
menuInflater.inflate(R.menu.menu,menu)
//引用res/menu 目录的menu.xml 生成菜单对象menu
return true
}o
verride fun onOptionsItemSelected(item: MenuItem): Boolean {
//根据选中的菜单项进行处理
when(item.itemId){
R.id.firstItem->turnTo(FirstActivity: : class.java)
R.id.secondItem->turnTo(SecondActivity: : class.java)
R.id.exitItem->{
·48·
AlertDialog.Builder(this).apply{ //定义对话框
title =resources.getString(R.string.app_name)
setMessage("退出应用?")
setPositiveButton("确定") { _, _->
exitProcess(0)
}
setNegativeButton("取消",null)
}.create().show()
}
}
return super.onOptionsItemSelected(item)
}
private fun turnTo(c: Class){ //通过显式意图跳转
val intent =Intent(MainActivity@this,c)
startActivity(intent)
}
}
在MainActivity中定义两个回调函数onCreateOptionsMenu()和onOptionsItemSelected()分
别用于创建菜单和菜单项选择的处理。
onCreateOptionsMenu()函数采用menuInflater.inflate(R.menu.menu,menu)函数引
用菜单资源res/menu/menu.xml文件渲染菜单对象menu。
onOptionsItemSelected()函数对菜单项的id值进行判断并处理,并调用了通用的显式
Intent跳转的turnTo()函数,使得从当前的MainActivity跳转到指定类的Activity中。在
退出菜单项的处理中增加了AlertDialog对话框的显示,如果选中“确定”动作,则执行
exitProcess(0)退出整个移动应用。注意,在setPositiveButton处理部分使用了两个“_”表
示匿名的参数。如果在Lambda表达式中传递的参数没有使用,可以用“_”来替换。
turnTo()函数是自定义的函数,在这个函数中使用了泛型,即定义了类型变量T,将类
型变量传递给Class中,表示各种Class类型。在函数体中,通过创建显式Intent对
象,并启动startActivity()函数实现从MainActivity跳转到指定类的Activity中。如果调
用的是turnTo(FirstActivity::class.java)函数,则等价执行的代码如下:
//创建从当前的MainActivity 跳转到FirstActivity 活动的意图对象
val intent =Intent(MainActivity@this, FirstActivity: : class.java)
//在当前Activity 中启动Intent
MainActivity@this.startActivity(intent)
其中,MainActivity@this表示MainActivity的当前对象。
(3)定义其他的Activity。移动模块中还定义了FirstActivity和SecondActivity,它们
的布局文件分别为activity_first.xml和activity_second.xml,均保存在res/layout目录下。
它们都定义了一个文本标签中显示字符串。布局文件类似activity_main.xml布局文件的
定义。这两个Activity的任务就是引用各自的布局文件显示界面内容。
·49·
//模块Ch03_02 第一个活动的定义文件FirstActivity.kt
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
}
} //模块Ch03_02 第二个活动的定义文件SecondActivity.kt
class SecondActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
}
}
(4)配置清单文件中注册Activity,代码如下:
在配置清单文件中,配置了FirstActivity、SecondActivity 和MainActivity。其中,
MainActivity作为入口,在程序加载时首先启动和显示,如图3-2所示。
运行结果中菜单项对应的图标在下列菜单列表并没有显示。有一个编程技巧,可以将
所有的菜单项向下降一个等级。将res/menu目录下的menu.xml修改成如下形式:
这时,可以出现显示二级菜单,菜单的图标可以显示。
3.2.2 隐式Intent
隐式Intent没有明确地指定要启动哪个Activity,而是通过Android系统分析Intent,
并根据分析结果来启动对应的Activity。执行过程如图3-3所示,即某个ActivityA 通过
startActivity()函数操作某个隐式Intent调用另外的Activity。Android系统先分析应用配
置清单AndroidManifest.xml中声明的IntentFilter内容,再搜索应用中与Intent相匹配的
IntentFilter内容,若匹配成功后,则Android系统调用匹配的Activity,用ActivityB 的
onCreate()函数启动新的Activity,实现从ActivityA 跳转到ActivityB。
一般情况下,隐式Intent需要在配置清单文件AndroidManifest.xml中指定Intent
Filter的action、category和data3个属性。Android系统会对IntentFilter的3个属性进
行分析和匹配,以搜索对应Activity或其他组件。
(1)action:表示该Intent所要完成的一个抽象的Activity。
(2)category:用于为action增加额外的附加类别信息。
(3)data:向action提供操作的数据。
·51·
图3-3 通过隐式Intent通过Android系统启动活动
但是这3个属性并不是必须全部配置,可以根据具体的需要进行组合配置。例如:
例3-2 使用隐式Intent的应用实例。MainActivity可以分别调用自定义的Activity
和拨打电话。
在本应用定义两个活动: 一个是自定义的CustomedActivity;另一个表示
MainActivity,用于移动应用的入口。在MainActivity中提供了两个按钮,单击后都可以实
现调用CustomedActivity和拨打电话的功能。在系统的配置清单文件中,首先配置并注册
这些Activity。
(1)在配置清单AndroidManifest.xml中注册Activity,代码如下:
·52·
(2)定义CustomedActivity,代码如下:
在上述的布局中定义了ImageView 组件,用于显示图片,通过app:srcCompat直接引
用图片res/mipmap目录下的ic_launcher图片资源。
//模块Ch03_03 定义CustomedActivity 的文件CustomedActivity.kt
class CustomedActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_customed)
}
}
(3)定义MainActivity,代码如下:
在MainActivity布局文件中定义了两个按钮,目的是实现不同Activity的跳转的
接口。
//模块Ch03_03 定义MainActivity 的文件MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val firstBtn: Button =findViewById(R.id.firstBtn)
firstBtn.setOnClickListener {
val intent1 =Intent("chenyi.book.android.ch03_03.ACTION")
intent1.addCategory("chenyi.book.android.ch03_03.MyCategory")
MainActivity@this.startActivity(intent1)
}
val secondBtn: Button =findViewById(R.id.secondBtn)
secondBtn.setOnClickListener {
val intent2 =Intent(Intent.ACTION_DIAL)
startActivity(intent2)
}
}
}
运行结果如图3-4所示。
MainActivity是移动应用中第一个调用和加载的界面,在配置清单文件AndroidManifest.
xml中也注册了CustomedActivity,在activity元素定义了下级标签intent-filter,指定了
CustomedActivity的动作名称,并添加了两个类别:android.intent.category.DEFAULT 和
chenyi.book.android.ch03_03.MyCategory。android.intent.category.DEFAULT 是默认的
类别。在调用startActivity()函数时,Android 系统会自动将android.intent.category.
DEFAULT添加到Intent中。chenyi.book.android.ch03_03.MyCategory是自定义的类别,
因此,需要调用addCategory()函数将这个自定义的类别添加到Intent中,即单击第一个按
钮会通过隐式intent1跳转到CustomedActivity。
注意,第二个按钮单击实现拨打电话。Intent.ACTION_DIAL内置的系统动作,对应
android.intent.action.DIAL。Android系统中定义常见的内置动作如表3-1所示。
·54·
动作
ACTION_ANSWER
ACTION_CALL
ACTION_DIAL
ACTION_EDIT
ACTION_VIEW
ACTION_SENDTO
ACTION_SEND
ACTION_SEARCH
图3-
4
隐式Intent实例的运行结果
表3-
1
Intent内置的动作
说明
处理来电
拨打电话,使用Intnt的号码,需要设置adod.rmsin.PHONE
enripeioCALL_
调用拨打电话的程序,使用Intent的号码,没有直接打出
编辑Intent中提供的数据
查看动作,可以浏览网页、短信、地图路肩规划,根据Intent的数据类型和数据值
决定
发送短信、电子邮件
发送信息、电子邮件
搜索,通过Intent的数据类型和数据判断搜索的动作
调用Intnt的stDta()函数可以设置Itnt的数据,用Koln的表达是inetdt
eeanetitn.aa;
调用Intent的setType()函数可以设置Intent数据的类型;调用setDataAndType()函数可
以来设置数据和数据的类型。
3.3
Activity之间的数据传递
Activity之间往往需要传递数据。数据的传递可以借助Intent来实现。Intent提供了
两种实现数据传递的方式:一种是通过Intent的putExtra传递数据;另一种是通过Bundle
传递多类型数据。
·55·
3.3.1 传递常见数据
在这里常见的数据类型包括Int(整型)、Short(短整型)、Long(长整型)、Float(单精度
实型)、Double(双精度实型)、数组、ArrayList(数组列表)等多种类型。在Activity之间,可
以将这些类型的数据进行传递。
首先,Intent提供了一系列的putExtra()函数实现数据传递。在数据的发送方,通过
putExtra()函数种指定键值对,然后启动startActivity将意图从当前的Activity跳转到
intent指定的活动,代码如下:
val intent =Intent(this,OtherActivity: : class.java)
//定义跳转到OtherActivity 的Intent
intent.putExtra("intValue",23) //配置传递数据的键值对
startActivity(intent) //根据Intent 启动Activity
数据接收方(设为OtherActivity)调用getIntent()函数获得启动该Activity的Intent,
代码如下:
val intent=getIntent()
在Kotlin中也可以直接表示成
val intent=intent
然后,根据数据的关键字,调用接收数据类型对应get×××Extra()函数来获得对应的取
值。例如:
val received=intent.getIntExtra("intValue") //根据关键字intValue 获取对应的Int 数据
第二种方式结合Bundle数据包和putExtra()函数实现数据的传递。具体的执行过程
与第一种方式类似。数据的发送方创建Bundle数据包,代码如下:
val bundle: Bundle=Bundle()
然后,调用Bundle对象的对应的put×××函数,设置不同类型的数据,例如:
bundle.putInt("bundleIntValue",1000)
bundle.putString("bundleStringValue","来自MainActivity 的问候!")
在发送方通过调用Intent的putExtra()函数,设置要传递的数据,再调用startActivity
实现Activity的跳转和数据的发送,代码如下:
val intent=Intent(this,OtherActivity: : class.java)
intent.putExtra(bundle)
startActivity(intent)
在数据的接收方,例如OtherActivity有两种方式接收数据:一种是通过Bundle对象
的对应的get×××()函数来获得;另一种是直接通过调用启动该Activity的Intent的get
·56·
×××Extra()函数,根据关键字获得对应的取值,代码如下:
val intent =intent
val bundle: Bundle? =intent.extras
//根据Bundle 来获得数据
val intValue =bundle?.getInt("bundleIntValue",0) //0 为默认值,取值失败时会赋值
val strValue =bundle?.getString ("bundleStringValue")
//或根据Intent 直接获得数据
val intValue =intent.getIntExtra("bundleIntValue")
val strValue =intent.getStringExtra("bundleStringValue")
例3-3 常见数据类型数据的传递的应用实例MainActivity分别使用两种不同的方式
传递数据到其他两个Activity,代码如下:
在MainActivity的布局文件定义了两个按钮,并分别为这两个按钮定义了相应的处理
动作,代码如下:
·57·
//模块Ch03_04 定义数据的发送方的主活动MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val firstBtn: Button =findViewById(R.id.firstBtn)
val secondBtn: Button =findViewById(R.id.secondBtn)
firstBtn.setOnClickListener {
val intent =Intent(MainActivity@this,FirstActivity::class.java)
intent.putExtra("intValue",999)
intent.putExtra("strValue","来自MainActivity 的问候")
val strArr =arrayOf("Kotlin","Java","Scala")
intent.putExtra("arrValue",strArr)
startActivity(intent)
}
secondBtn.setOnClickListener {
val bundle =Bundle()
bundle.putInt("bundleIntValue",1000)
bundle.putString("bundleStringValue","来自MainActivity 的问候!!")
val intArr =IntArray(10)
for(i in intArr.indices)
intArr[i]=i+1
bundle.putIntArray("bundleIntArray",intArr)
val intent =Intent(MainActivity@this,SecondActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
}
}
作为数据的发送方,MainActivity提供了两种数据发送的方法。第一种方法是直接利
用Intent的put×××Extra()函数将数据传递出去;第二种方法是将数据通过键值对放置
在Bundle对象中,然后利用Intent将Bundle数据包整体传递出去。有数据发送,必须有一
个接收方来接收数据,否则发送数据没有任何意义。在此处,定义了FirstActivity 和
SecondActivity分别接收数据,代码如下:
//模块Ch03_04 定义数据接收方FirstActivity.kt
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
val intent =intent
val receivedInt =intent.getIntExtra("intValue",0) //获得整数,0 为默认值
val receivedStr =intent.getStringExtra("strValue") //获得字符串
val receivedStrArr =intent.getStringArrayExtra("arrValue") //获得字符串数组
val result =StringBuilder()
·58·
result.append("接收: ${receivedInt}\n${receivedStr}\n${Arrays.
toString(receivedStrArr)}")
Toast.makeText(this,"$result",Toast.LENGTH_LONG).show()
}
}
FristActivity中接收的数据是利用Intent调用get×××Extra()函数,通过关键字来
获得对应的数据。在代码中,接收了字符串数组,调用java.util.Arrays的toString()函数,
将数组转换成字符串的表达形式。另外,在代码中使用了Toast信息提示框。通过调用
Toast.makeText()函数可以将文本在信息提示中显示出来,代码如下:
//模块Ch03_04 定义数据接收方SecondActivity.kt
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val intent =intent //获得发送数据的意图
val bundle: Bundle? =intent.extras //获得Bundle 对象
val receivedIntValue =bundle?.getInt("bundleIntValue")
//接收整数数据
val receivedStrValue =intent.getStringExtra("bundleStringValue")
//接收字符串数据
val receivedIntArr =bundle?.getIntArray("bundleIntArray")
//接收整型数组数据
val result=StringBuilder()
result.append("接收的数据: ${receivedIntValue}\n${receivedStrValue}\n"
${Arrays.toString(receivedIntArr)}")
Toast.makeText(this,"$result",Toast.LENGTH_LONG).show() //显示数据
}
}
SecondActivity接收数据的方式有两种:一种方式是启动该活动的意图获得Bundle
对象,然后从Bundle对象中获得关键字对应的数据;另一种方式是通过调用Intent的
get×××Extra()函数根据传递数据的关键字获得对应的数据,如图3-5所示。
3.3.2 Serializable对象的传递
在Activity之间传递数据有一种特殊情况,就是传递自定义类的对象或者自定义类的
对象数组或集合类型中包含自定义类的对象。需要对这些自定义类进行可序列化处理。可
序列化处理,就是将对象处理成可以传递或存储的状态,使得对象可以从临时瞬间状态保存
持久状态,即存储到文件、数据库等中。要在Java中实现可序列化,就需要用类实现java.
io.Serializable接口。作为基于JVM 的语言,Kotlin也需要实现java.io.Serializable接口,这
个类的对象就可以进行序列化的处理。形式如下:
class DataType: Serializable{
…
}
·59·
图3-5 活动传递数据实例的运行结果
通过Serializable传递对象的过程,如图3-6所示。发送方将数据序列化成字节流发
送,在接收方,将字节流重组成对应的对象。
图3-6 Serializable方式传递对象的原理
要在Activity之间发送Serializable对象,仍需要通过Intent实现。Intent通过调用
putExtra()函数,设置“关键字”和对象之间的键值对,形式如下:
val intent =Intent(this,OtherActivity…class.java)
val object: Type =…
intent.putExtra("object",object)
startActivity(intent)
Activity的接收方OtherActivity在接收数据时,首先启动该活动的意图,再通过意图
的getSerializableExtra()函数通过关键字来获得对应的Serializable对象,最后利用as操作
符转换成对应的对象。实现了根据接收的状态数据反序列化生成对应的对象。形式如下:
val intent =intent
val object =intent.getSerializableExtra("object") as Type
例3-4 在Activity之间传递Serializable对象应用实例。实现将学生的信息(学号、姓
名、出生日期)从一个Activity传递到另外一个Activity。
·60·
(1)定义实体类Student,代码如下:
//模块Ch03_05 定义数据实体类Student
data class Student(val no: String,val name: String,
val birthday: LocalDate): Serializable
(2)定义数据的发送方MainActivity,代码如下:
MainActivity发送Student对象到FirstActivity。调用Intent的putExtra()函数设置
发送数据的键值对。关键字是student,取值是Student 对象student。然后通过
startActivity(intent)启动Intent封装活动,实现数据通过Intent发送FirstActivity,代码
如下:
//模块Ch03_05 发送方的主活动定义MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sendBtn: Button =findViewById(R.id.sendBtn)
sendBtn.setOnClickListener {
val student: Student=Student("60012322","张三",
LocalDate.of(2008,2,12)) //定义对象
val intent =Intent(this,FirstActivity::class.java) //定义Intent
intent.putExtra("student",student) //Intent 设置键值对
MainActivity@this.startActivity(intent) //启动Activity
}
}
}
(3)定义数据接收方的FirstActivity,代码如下:
//模块Ch03_05 接收方定义FirstActivity.kt
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
val intent =intent
val receivedStu: Student =intent.getSerializableExtra("student") as Student
//将类型强制转换成Student
·61·
Toast.makeText(this,"${receivedStu.toString()}",Toast.LENGTH_LONG).show()
}
}
接收方的FirstActivity 用于接收对象。由于FirstActivity 中Intent 对象的
getSerializableExtra()函数会返回一个Serializable类型的对象,因此需要对接收对象进行
处理,即结合as操作符执行类型转换。形式如下:
val receivedStu: Student =intent.getSerializableExtra("student") as Student
通过这种方式获得接收的对象后,整个实例的运行结果如图3-7所示。
图3-7 活动传递Serializable对象的运行结果
3.3.3 Parcelable对象的传递
通过Java的java.io.Serializable方式传递对象,将对象整体数据序列化处理,将对象转
换可传递状态进行发送。接收方将接收的状态数据重新反序列化重新生成对象。由于这种
处理方式效率低下,因此Android提供了自带的Parcelable(打包)方式实现对象的传递。
以Parcelable方式传递对象的原理是,通过一套机制,将一个完整的对象进行分解,分
解后的每一部分数据都属于Intent支持的数据类型。可以将分解后的可序列化的数据写
入一个共享内存中,其他进程通过Parcel从这块共享内存中读出字节流,并反序列化成对
象,通过这种方式来实现传递对象的功能,如图3-8所示。
要实现Parcelable方式传递对象,需要定义的类必须实现Parcelable接口。必须对其
中的方法进行覆盖重写,具体如下。
(1)writeToParcel():实现将对象的数据进行分解写入。
·62·
图3-8 Parcelable方式传递对象
(2)describeContent():内容接口的描述,默认只需要返回为0。
(3)Parcelable.Creator:需要定义静态内部对象实现Parcelable.Createor接口的匿名
类。该接口提供了两个函数:一个是createFromParcelable()函数实现从Parcel容器中读
取传递数据值,读取数据的顺序与写入数据的顺序必须保持一致,然后封装成Parcel对象
返回逻辑层,另一个是newArray()函数创建一个指定类型指定长度的数组,供外部类反序
列化本类数组使用。下面定义了实现Parcelable接口的Student类,代码如下:
data class Student(val no: String,val name: String,val birthday: LocalDate):
Parcelable {
constructor(parcel: Parcel):
this(parcel.readString()!!,parcel.readString()!!,
parcel.readSerializable() as LocalDate)
override fun writeToParcel(parcel: Parcel, flags: Int) { //分解数据
parcel.writeString(no)
parcel.writeString(name)
parcel.writeSerializable(birthday)
}
override fun describeContents(): Int =0
companion object CREATOR : Parcelable.Creator{ //重组生成对象
override fun createFromParcel(parcel: Parcel): Student {
return Student(parcel)
}
override fun newArray(size: Int): Array{
return arrayOfNulls(size)
}
}
}
但是,这种表示方式非常复杂,在Kotlin中提供了一种更简单的表示方式,即利用@
Parcelize标注实体类。形式如下:
@Parcelize
data class Student(val no: String,val name: String,
val birthday: LocalDate): Parcelable
·63·