Android Kotlin教程:Intent处理与活动间通信
在本教程中,我们将讨论如何在我们的应用程序中使用Kotlin实现Android意图(Intents)。
你将学到什么呢?
- 什么是意图(Intents)?
- 意图的类型有哪些?
- 在活动(Activities)之间使用意图
- 使用Android意图发送数据
- 使用Parcelable和Serializable传递对象
- 创建简写意图
安卓意图(Intent)
如其名所示,意图是用于根据Android应用程序的流程执行某些操作的工具。意图可以用于:
- 启动新活动并传递一些数据。
- 启动片段/在片段之间通信。
- 启动/结束服务。
- 从广播接收器启动活动
在本教程中,我们主要关注使用意图来处理活动。意图定义主要由当前活动的实例组成。我们设置了组件名称,可能是要调用的活动的完全限定类名。这种类型的意图被称为显式意图。例如,URL、电话号码、位置等行为。它将显示所有可用的这些类型的应用程序。这属于隐式意图类别。在Kotlin中,以下是创建活动的方法。
val intent = Intent(this, OtherActivity::class.java)
startActivity(intent)
启动活动将在活动堆栈中添加OtherActivity并启动它。我们的应用程序如何知道要调用哪个活动? 在AndroidManifest.xml中,我们要在应用程序打开时设置带有动作android.intent.action.MAIN和类别android.intent.category.LAUNCHER的意图过滤器到第一个要启动的活动上。使用finish()来销毁活动并将其从堆栈中移除。
意图标记
标志(Flags)就像是可以设置在意图(Intents)上以自定义启动过程的选项。如果每次都启动同一个活动(activity),那么会创建一个新的实例并添加到活动栈中。为了防止这种情况发生,可以使用以下标志:
FLAG_ACTIVITY_SINGLE_TOP – 如果已经在活动栈的顶部运行,则不会启动该活动。
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
如果已经存在的话,同样使用标志位FLAT_ACTIVITY_CLEAR_TOP不会启动另一个活动的实例。此标志位会清除调用它的活动上面的所有活动,并将其置顶。
通过意图传递数据

val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("keyString", "Androidly String data")
这些额外的字段被封装在Bundle对象中,最终该对象包含要传递的所有数据。在另一个活动中检索数据时,我们需要使用bundle对象的extras属性。在新的活动中检索数据。
val bundle: Bundle? = intent.extras
val string: String? = intent.getString("keyString")
val myArray: ArrayList<String>? = intent.getStringArrayList("myArray")
意图和附加项等同于Java中的getIntent()和getExtras()。我们使用了可为空类型的Bundle?来防止数据不存在时出现空指针异常。同样地,对于使用键提取的数据,我们使用了可为空类型来防止发生键不正确时的空指针异常。
使用可序列化和可传递数据
有时候我们需要在一个活动中将完整的对象传递到另一个活动中。如果我们不实现Parcelable或Serializable接口,将无法实现这一点。Parcelable和Serializable之间的区别是什么?
- Parcelable接口是Android SDK的一部分。Serializable是Java的标准接口。
- 在Parcelable中,你需要将所有需要传递的数据设置在Parcel对象中,并且还要重写writeToParcel()等方法。而在serializable中,实现接口就足以传递数据。
- Parcelable比Serializable更快。
发送Parcelable数据
Kotlin 提供了一些方便的注解,以免我们需要重写 writeToParcel() 方法来设置 Parcelable 上的数据。相反,我们可以使用如下所示的 @Parcelize 注解。
@Parcelize
data class Student(
val name: String = "Anupam",
val age: Int = 24
) : Parcelable
注意:目前在您的build.gradle文件中必须添加以下代码才能使@Parcelize注解起作用:
android {
androidExtensions {
experimental = true
}
//..
....
}
在你的活动中,你要做的是:
val student = Student()
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("studentData", student)
startActivity(intent)
发送Serializable数据
data class Blog(val name: String = "Androidly", val year: Int = 2018) : Serializable
val blog = Blog("a", 1)
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("blogData", blog as Serializable)
startActivity(intent)
让我们在我们的Android Studio项目中运用以上知识。
项目结构

布局代码
以下是activity_main.xml布局的代码:
这是文章《使用Kotlin在Android活动之间处理意图》的第2部分(共4部分)。
内容片段:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btnSimpleIntent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="简单意图" />
<Button
android:id="@+id/btnSimpleIntentAndData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="带数据的简单意图" />
<Button
android:id="@+id/btnParcelableIntent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="可打包意图" />
<Button
android:id="@+id/btnSerializableIntent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="可序列化意图" />
<Button
android:id="@+id/btnBrowserIntent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="浏览器意图" />
<Button
android:id="@+id/btnMapsIntent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="地图意图" />
<Button
android:id="@+id/btnGenericIntent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="通用意图" />
</LinearLayout>
以下是activity_other.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="意图数据显示在这里" />
</LinearLayout>
活动代码
下面是MainActivity.kt类的代码:
package net.androidly.androidlyintents
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_main.*
import java.io.Serializable
@Parcelize
data class Student(
val name: String = "阿努潘",
val age: Int = 24
) : Parcelable
data class Blog(val name: String = "Androidly", val year: Int = 2018) : Serializable
class MainActivity : AppCompatActivity(), View.OnClickListener {
fun Context.gotoClass(targetType: Class<*>) =
ComponentName(this, targetType)
fun Context.startActivity(f: Intent.() -> Unit): Unit =
Intent().apply(f).run(this::startActivity)
inline fun Context.start(
noinline createIntent: Intent.() -> Unit = {}
) = startActivity {
component = gotoClass(T::class.java)
createIntent(this)
}
var arrayList = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnSimpleIntent.setOnClickListener(this)
btnSimpleIntentAndData.setOnClickListener(this)
btnParcelableIntent.setOnClickListener(this)
btnSerializableIntent.setOnClickListener(this)
btnBrowserIntent.setOnClickListener(this)
btnMapsIntent.setOnClickListener(this)
btnGenericIntent.setOnClickListener(this)
arrayList.add("Androidly")
arrayList.add("Android")
arrayList.add("Intents")
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.btnSimpleIntent -> {
val intent = Intent(this, OtherActivity::class.java)
startActivity(intent)
}
R.id.btnSimpleIntentAndData -> {
val intent = Intent(this, OtherActivity::class.java)
with(intent)
{
putExtra("keyString", "Androidly 字符串数据")
putStringArrayListExtra("arrayList", arrayList)
putExtra("keyBoolean", true)
putExtra("keyFloat", 1.2f)
}
startActivity(intent)
}
R.id.btnParcelableIntent -> {
val student = Student()
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("studentData", student)
startActivity(intent)
}
R.id.btnSerializableIntent -> {
val blog = Blog("a", 1)
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("blogData", blog as Serializable)
startActivity(intent)
}
R.id.btnBrowserIntent -> {
val url = "https://www.androidly.net"
val uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
Toast.makeText(applicationContext, "未找到应用程序", LENGTH_LONG).show()
}
}
R.id.btnMapsIntent -> {
val loc = "12.9538477,77.3507442"
val addressUri = Uri.parse("geo:0,0?q=" + loc)
val intent = Intent(Intent.ACTION_VIEW, addressUri)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
Toast.makeText(applicationContext, "未找到应用程序", LENGTH_LONG).show()
}
}
else -> start {
putExtra("keyString", "Androidly 通用意图")
}
}
}
}
在上面的代码中,我们使用了不同类型的意图按钮。我们使用了Kotlin的with
表达式,以避免每次设置数据到意图对象上。此外,我们除了上面讨论过的那些之外,还创建了三个不同的意图。浏览器意图用于在浏览器应用中打开意图中的URL。它使用了Intent(Intent.ACTION_VIEW, uri)
。位置意图用于在地图应用中打开经纬度位置。这两者都是隐式意图。最后,我们使用了通用意图,其中我们使用了Kotlin的扩展函数和Lambda表达式来创建一个简写函数以启动意图。为此,我们使用了以下函数:
fun Context.gotoClass(targetType: Class<*>) =
ComponentName(this, targetType)
fun Context.startActivity(createIntent: Intent.() -> Unit): Unit =
Intent().apply(createIntent).run(this::startActivity)
inline fun <reified T : Activity> Context.start(
noinline createIntent: Intent.() -> Unit = {}
) = startActivity {
component = gotoClass(T::class.java)
createIntent(this)
}
startActivity是一个扩展函数,它接收一个高阶函数作为参数。得益于这个特性,我们现在可以用更少的代码行数来启动意图,例如:start<OtherActivity>。OtherActivity.kt类的代码如下所示。
package net.androidly.androidlyintents
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_other.*
class OtherActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_other)
val bundle: Bundle? = intent.extras
bundle?.let {
bundle.apply {
//包含数据的意图
val string: String? = getString("keyString")
textView.text = string
val myArray: ArrayList<String>? = getStringArrayList("myArray")
showToast(message = "MyArrayList大小:${myArray?.size}")
val arrayList: ArrayList<String>? = getStringArrayList("arrayList")
showToast(message = "ArrayList大小:${arrayList?.size}")
val float: Float? = bundle.get("keyFloat") as Float?
var boolean = bundle.get("boolean") as? Boolean
showToast(message = "浮点数据是:$float")
showToast(message = "布尔数据是:$boolean")
boolean = bundle.get("keyBoolean") as? Boolean
showToast(message = "正确键的布尔数据是:$boolean")
}
bundle.apply {
//可序列化数据
val blog = getSerializable("blogData") as Blog?
if (blog != null) {
textView.text = "博客名称是${blog?.name}。创建年份:${blog?.year}"
}
}
bundle.apply {
//可打包数据
val student: Student? = getParcelable("studentData")
if (student != null) {
textView.text = "姓名是${student?.name}。年龄:${student?.age}"
}
}
}
}
private fun showToast(context: Context = applicationContext, message: String, duration: Int = Toast.LENGTH_SHORT) {
if (!message.contains("null"))
Toast.makeText(context, message, duration).show()
}
}

安卓意图输出示例