Android 使用 Intent 传值

在启动 Activity、启动 Service、发送广播等,都需要借助 Intent 来操作,并且还可以在 Intent 中添加一些附加数据,以达到传值的效果。

传递常用数据类型

示例(Kotlin):

显示 Intent:

MainActivity.kt
1
2
3
4
5
6
7
8
// 界面跳转,显式 Intent
val data = "Hello"
val intent = Intent(this, SecondActivity::class.java)
// 传递参数
intent.putExtra("extra_data",data)
intent.putExtra("string_data","hello")
intent.putExtra("int_data",100)
startActivity(intent)
SecondActivity.kt
1
2
3
4
// intent 实际上调用的是父类的 getIntent()
val extraData = intent.getStringExtra("extra_data")
val stringData = intent.getStringExtra("string_data")
val intData = intent.getIntExtra("int_data")

显示 Intent:带返回值的跳转

MainActivity.kt
1
2
val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent,1)
SecondActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 返回数据给上一个页面
override fun onClick(v: View?) {
when (v?.id){
R.id.btn ->{
val intent = Intent()
intent.putExtra("data_return","Hello MainActivity")
setResult(Activity.RESULT_OK,intent)
finish()
}
}
}

override fun onBackPressed() {
val intent = Intent()
intent.putExtra("data_return","Hello MainActivity")
setResult(Activity.RESULT_OK,intent)
finish()
}
MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* requestCode:启动 Activity 时传入的请求码
* resultCode:返回数据时传入的处理结果
* data:携带着返回数据的 Intent
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// 判断数据来源
when(requestCode){
// 判断处理结果是否成功
1 -> if (resultCode == Activity.RESULT_OK){
val returnedData = data?.getStringExtra("data_return")
Log.d("TAG_MainActivity","return data is $returnedData")
}
}
}

隐式 Intent:

MainActivity.kt
1
2
3
4
5
6
// 隐式启动
// 只有 <action> 和 <category> 同时匹配才行
// 但这里的 <category> 因为设置的为 DEFAULT,所以只需匹配 <action> 即可,<category> 会自动添加到 Intent中。
val intent = Intent("com.example.activitytest.ACTION_START")
intent.addCategory("com.example.activitytest.MY_CATEGORY")
startActivity(intent)
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<activity
android:name=".activity.Main3Activity"
// theme 对话框形式主题
android:theme="@style/Theme.AppCompat.Dialog">
// tools:ignore="AppLinkUrlError" 忽略警告
<intent-filter tools:ignore="AppLinkUrlError">

<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />

</intent-filter>
</activity>
<activity android:name=".activity.Main2Activity">
<intent-filter>

<!-- 每个 Intent 中只能指定一个 action,但能指定多个 category。 -->
<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>

更多隐式 Intent 的用法

MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
// 打开网址
val intent = Intent(Intent.ACTION_VIEW)
// 通过 Uri.parse() 将网址字符串解析成一个 Uri 对象,
// 再调用 Intent 的 setData() 将这个 Uri 对象传递进去。
intent.data = Uri.parse("https://www.baidu.com")
startActivity(intent)

// 打开拨号界面
val intent = Intent(Intent.ACTION_DIAL)
intent.data = Uri.parse("tel:1008611")
startActivity(intent)

传递对象

使用 Intent 传递对象通常有两种方式:Serializable 和 Parcelable。

为什么要序列化?

  • 永久性保存对象,保存对象的字节序列到本地文件中;
  • 通过序列化对象在网络中传递对象;
  • 通过序列化在进程间传递对象。

Serializable

它是序列化的意思,表示将一个对象转换成可存储或可运输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。只需要让一个类去实现 Serializable 这个接口即可完成序列化。

1
2
3
4
class Person : Serializable {
var name = ""
var age = 0
}
MainActivity.kt
1
2
3
4
5
6
val person = Person()
person.name = "Tom"
person.age = 20
val intent = Intent(this,SecondActivity::Class.java)
intent.putExtra("person_data",person)
startActivity(intent)
SecondActivity.kt
1
2
// 调用了 Intent 的 getSerializableExtra() 获取通过参数传递过来的序列化对象,然后将它向下转型成 Person 对象。
val person = intent.getSerializableExtra("person_data") as Person

注意:这种传递对象的工作原理是先将一个对象序列化成可存储或可运输的状态,传递给另外一个 Activity 后再将其反序列化成一个新的对象。虽然这两个对象中存储的数据完全一致,但是它们实际上是不同的对象。

Java序列化技术

就是将Java对象序列化为二进制文件

序列化ID问题

虚拟机是否允许反序列化,不仅取决于“类路径“和”功能代码“是否一致,还需要两个类的序列化ID是否一致:

1
2
3
4
5
6
7
// 两种生成策略:
// 1.自定义,比如这里为 1L 。
// 2.不指定的话,会随机生成(实际上是使用JDK工具生成)。
// 一般使用默认的 1L 就可以。
// 对于随机生成的序列化 ID ,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
private static final long serialVersionUID = 1L;

静态变量序列化

因为,序列化保存的是对象的状态,静态变量属于类的状态,
因此,序列化并不保存静态变量。

父类的序列化与 Transient 关键字

要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
如果父类不实现的话的,就 需要有默认的无参的构造函数。

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,
在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

对敏感字段加密

在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,
如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

序列化存储规则

Java 序列化机制为了节省磁盘空间,具有特定的存储规则,
当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。
反序列化时,恢复引用关系,使得这两个对象指向唯一的对象,二者相等。该存储规则极大的节省了存储空间。

Binder 传递数据限制

Intent 传值 Bean 对象时偶尔也会导程序崩溃,因为 Intent 传递数据过大,最终原因是 Android 系统对使用 Binder 传数据进行了限制。通常情况为 1M,但是根据不同版本、不同厂商,这个值会有区别。解决办法:

  • 减少通过 Intent 传递的数据,将非必须字段使用 transient 关键字修饰,避免将其序列化。
  • 将对象转化为 JSON 字符串,减少数据体积。因为 JVM 加载类通常会伴随额外的空间来保存类相关信息,将类中数据转化为 JSON 字符串可以减少数据大小。比如使用 Gson.toJson 方法。(大多时候,将类转化为 JSON 字符串之后,还是会超出 Binder 限制,说明实际需要传递的数据是很大的。这种情况则需要考虑使用本地持久化来实现数据共享,或者使用 EventBus 来实现数据传递。)

Parcelable

Parcelable 的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是 Intent 所支持的数据类型,这样就能实现传递对象的功能了。对比 Serializable,Serializable 的方式较为简单,但由于会把这个对象进行序列化。因此效率会比 Parcelable 方式低一些,所以通常情况下,还是更加推荐使用 Parcelable 的方式来实现 Intent 传递对象的功能。

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
35
36
37
38
39
40
41
42
43
44
class Person : Parcelable {

var name = ""
var age = 0

/**
* 将 Person 类中的字段一一写出
*/
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name) // 写出 name
parcel.writeInt(age) // 写出 age
}

/**
* 直接返回 0 即可
*/
override fun describeContents(): Int {
return 0
}

/**
* 还必须在 Person 类中提供一个名为 CREATOR 的匿名类实现
* 这里创建了 Parcelable.Creator 接口的一个实现,并将泛型指定为 Person。
* 并重写两个方法。
*/
companion object CREATOR : Parcelable.Creator<Person> {
/**
* 要创建出一个 Person 对象进行返回,并读取刚才写出的字段,并且读取的顺序要和写出的顺序一致。
*/
override fun createFromParcel(parcel: Parcel): Person {
val person = Person()
person.name = parcel.readString()?:"" // 读取 name
person.age = parcel.readInt() // 读取 age
return person
}

/**
* 调用 arrayOfNulls 方法,并使用参数中传入的 size 作为数组大小,创建一个空的 Person 数组即可。
*/
override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
}
MainActivity.kt
1
2
3
4
5
6
val person = Person()
person.name = "Tom"
person.age = 20
val intent = Intent(this,SecondActivity::Class.java)
intent.putExtra("person_data",person)
startActivity(intent)
SecondActivity.kt
1
2
// 调用了 Intent 的 getParcelableExtra() 获取通过参数传递过来的序列化对象,然后将它向下转型成 Person 对象。
val person = intent.getParcelableExtra("person_data") as Person

Kotlin 提供了另外一种更加简便的用法实现 Parcelable 序列化,但前提是要传递的所有数据都必须封装在对象的主构造函数中才行。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 如果报错,在 build.gradle 中添加支持。
* ----------------------------------------
* apply plugin: ‘kotlin-android-extensions’
* ....
* androidExtensions {
* experimental = true
* }
* ----------------------------------------
*/
@Parcelize
class Person(var name: String, var age: Int) : Parcelable

启动 Activity 的最佳写法

MainActivity.kt
1
SecondActivity.actionStart(this)
SecondActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 启动 Activity 的最佳写法
* companion object 语法结构:
* Kotlin 规定,所有定义在此结构中的方法都可以使用类似于 Java 静态方法的形式调用。
*/
companion object{
fun actionStart(context: Context,data1:String,data2:String){
// val intent = Intent(context,MainActivity::class.java)
// intent.putExtra("param1",data1)
// intent.putExtra("param2",data2)
// context.startActivity(intent)

val intent = Intent(context, MainActivity::class.java).apply {
putExtra("param1",data1)
putExtra("param2",data2)
}
context.startActivity(intent)
}
}