任何应用都不会只存在一个界面,但是点击应用图标只会进入该应用的主Activity,因此不同的Activity之间就需要转换。
先再创建一个Activity,并命名为SecondActivity,并勾选Generate Layout File,给布局文件命名为second_layout,不勾选Launcher Activity选项。
修改second_layout的代码为:
- "1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android" - android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
-
- android:id="@+id/button2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Button 2"
- />
-
上面的代码同样定义了一个按钮,并显示Button 2。
SecondActivity的代码为:
- package com.example.activitytest
-
- import androidx.appcompat.app.AppCompatActivity
- import android.os.Bundle
-
- class SecondActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.second_layout)
- }
- }
同样,该Activity也由Android Studio自动注册了:
- "1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.activitytest">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.ActivityTest">
- <activity
- android:name=".SecondActivity"
- android:exported="true" />
- <activity
- android:name=".FirstActivity"
- android:exported="true"
- 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>
该Activity不是主Activity,因此无需额外处理,剩下的就是考虑两个Activity之间转换的问题了。
Intent是Android程序中各个组件之间进行交互的重要方式,其不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity,启动Service,以及发送广播等场景。
Intent大致可以分为两种:
- 显式Intent
- 隐式Intent
Intent存在多个构造函数的重载,其中一个是下面的形式,该函数第一个参数Context要求提供一个启动Activity的上下文,第二个参数用于指定想要启动的目标Activity。
Activity类中提供了startActivity方法,专门用于启动Activity,其接收一个Intent参数,将构建好的Intent传入该方法就可以启动目标Activity了。
-
- /**
- * Create an intent for a specific component. All other fields (action, data,
- * type, class) are null, though they can be modified later with explicit
- * calls. This provides a convenient way to create an intent that is
- * intended to execute a hard-coded class name, rather than relying on the
- * system to find an appropriate class for you; see {@link #setComponent}
- * for more information on the repercussions of this.
- *
- * @param packageContext A Context of the application package implementing
- * this class.
- * @param cls The component class that is to be used for the intent.
- *
- * @see #setClass
- * @see #setComponent
- * @see #Intent(String, android.net.Uri , Context, Class)
- */
- public Intent(Context packageContext, Class> cls)
这里修改FirstActivity中按钮的点击事件:
- button1.setOnClickListener {
- val intent = Intent(this, SecondActivity::class.java)
- startActivity(intent)
- }
上面的代码首先构建了Intent对象,第一个参数this也就是FirstActivity作为上下文,第二个参数传入SecondActivity::class.java作为目标Activity。之后通过startActivity方法传入该Intent。
Kotlin中的SecondActivity::class.java的写法就相当于Java中SecondActivity.class的写法。
代码运行后,点击按钮后的结果为:

在这个界面下,按back键就可以销毁当前Activity,返回到上一个Activity。
使用这种方式启动Activity,称为显式Intent。
使用隐式Intent
隐式Intent并不明确指出想要启动哪一个Activity,而是指定了一系列更为抽象的action和category等信息,然后交给系统去分析该Intent,并找到合适的Activity去启动。
合适的Activity简单说就是可以响应隐式Intent的Activity。
这里在AndroidManifest.xml中为SecondActivity添加内容:
- <activity
- android:name=".SecondActivity"
- android:exported="true" >
- <intent-filter>
- <action android:name="com.example.activityTest.ACTION_START" />
- <category android:name="android.intent.category.DEFAULT" />
- intent-filter>
- activity>
在action标签中指明了当前Activity可以响应com.example.activityTest.ACTION_START,而category标签则包含了一些附加信息,更精确地指明了当前Activity能够响应的Intent中还可能带有的category。只有action和category中的内容同时匹配Intent中指定的action和category时,该Activity才能够响应该Intent。
修改FirstActivity中按钮的点击事件,代码为:
- button1.setOnClickListener {
- val intent = Intent("com.example.activityTest.ACTION_START")
- startActivity(intent)
- }
这里可以看到使用了Intent的另一个构造函数,直接传入了action的字符串,表示要启动能够响应com.example.activityTest.ACTION_START这个action的Activity。而之前提到要action和category同时匹配才能够响应,而这里并没有指定category,这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity方法时会自动将该category添加到Intent中。
重新运行程序,结果是一样的。
每个Intent中只能指定一个action,但能够指定多个category,这里修改FirstActivity中按钮的点击事件:
- button1.setOnClickListener {
- val intent = Intent("com.example.activityTest.ACTION_START")
- intent.addCategory("com.example.activityTest.MY_CATEGORY")
- startActivity(intent)
- }
上面使用Intent的addCategory方法添加了一个category,这里指定了一个自定义的category。
重新运行程序,点击按钮,程序会崩溃,错误日志为:
- 2022-09-12 10:27:08.792 8588-8588/com.example.activitytest E/AndroidRuntime: FATAL EXCEPTION: main
- Process: com.example.activitytest, PID: 8588
- android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.activityTest.ACTION_START cat=[com.example.activityTest.MY_CATEGORY] }
可以看到,此时没有任何一个Activity可以响应该Intent,这是因为Intent中新增了一个category,而SecondActivity的intent-filter标签中并没有声明可以响应该category,也就没有Activity可以响应该Intent,而在intent-filter中再添加一个声明:
- <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>
此时重新执行代码,就正常了。
更多隐式Intent的用法
使用隐式Intent,不仅可以启动自己程序内的Activity,还可以启动其它程序的Activity,也就使多个应用程序之间可以进行功能共享。比如应用程序中需要展示一个网页,只需要调用系统的浏览器打开该网页即可。
修改FirstActivity中按钮点击事件的代码:
- button1.setOnClickListener {
- val intent = Intent(Intent.ACTION_VIEW)
- intent.data = Uri.parse("https://www.baidu.com")
- startActivity(intent)
- }
上面代码中首先指定了Intent的action是Intent.ACTION_VIEW,这是Android系统内置的动作,其常量值为android.intent.action.VIEW,然后通过Uri.parse方法将地址字符串解析成一个Uri对象,再调用Intent的setData方法将该Uri对象传递进去。
运行程序,点击按钮后的结果为:

这里再构建一个Activity,命名为ThirdActivity,同时生成布局文件,命名为third_layout,并修改布局文件内容:
- "1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <Button
- android:id="@+id/button3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Button 3"
- />
-
- LinearLayout>
在AndroidManifest.xml中修改ThirdActivity的注册信息:
- <activity
- android:name=".ThirdActivity"
- android:exported="true">
- <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>
上面的代码在intent-filter中配置了当前Activity能够响应的ACTION_VIEW,而category则指定了默认的category值,data则通过android:scheme指定了数据协议必须是https协议,这样ThirdActivity应该就和浏览器一样,能够响应一个打开网页的Intent了。
不过由于Android Studio认为所有能够响应ACTION_VIEW的Activity都应该加上BROWSABLE的category,否则就会给出一段警告提醒,而加上BROWSABLE的category是为了实现deep link功能,目前用不到,因此使用tools:ignore属性忽略该警告。
运行程序,点击按钮后的结果为:

从上面看来,系统弹出了一个列表,显示了目前能够响应该Intent的所有程序。选择Chrome会和之前一样打开浏览器,而选择ActivityTest,则会启动ThirdActivity。
不过虽然声明了ThirdActivity是可以响应打开网页的Intent,但实际上该Activity并没有加载并显式网页的功能,实际开发中应避免此类问题。
而除了https协议外,还可以指定其它协议,比如geo表示显式地理位置,tel表示拨打电话:
- button1.setOnClickListener {
- val intent = Intent(Intent.ACTION_DIAL)
- intent.data = Uri.parse("tel:10086")
- startActivity(intent)
- }
上面的代码指定了Intent的action为Intent.ACTION_DIAL,然后在data部分指定了协议为tel,号码为10086。运行程序,点击按钮后的结果为:

向下一个Activity传递数据
在启动Activity时传递数据的思路很简单,Intent中提供了一系列putExtra方法的重载,可以将数据暂存在Intent中,在启动另一个Activity后,只需要将这些数据从Intent中取出即可。
- button1.setOnClickListener {
- val data = "Hello SecondActivity"
- val intent = Intent(this, SecondActivity::class.java)
- intent.putExtra("extra_data", data)
- startActivity(intent)
- }
比如上面的代码,使用显式Intent方式启动SecondActivity,并通过putExtra方法传递字符串,putExtra方法的第一个参数是键,用于之后从Intent中取值,第二个参数才是真正要传递的数据。
然后修改SecondActivity的代码读取该字符串:
- package com.example.activitytest
-
- import android.content.Intent
- import androidx.appcompat.app.AppCompatActivity
- import android.os.Bundle
- import android.util.Log
-
- class SecondActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.second_layout)
- val extraData = intent.getStringExtra("extra_data")
- Log.d("SecondActivity", "extra data is $extraData")
- }
- }
上面代码中的intent实际上调用的是父类的getIntent方法,该方法会获取用于启动SecondActivity的Intent,然后调用getStringExtra方法并传入相应的键值,就可获取到对应的数据。由于传递的是字符串,所以要使用getStringExtra方法。
运行程序后,可看到如下的打印信息:
2022-09-12 11:24:23.078 10125-10125/com.example.activitytest D/SecondActivity: extra data is Hello SecondActivity
返回数据给上一个Activity
返回上一个Activity只需要按一下back键盘,并没有一个用于启动Activity的Intent来传递数据,但Activity类中还有一个用于启动Activity的startActivityForResult方法,该方法期望在Activity销毁时能够返回一个结果到上一个Activity。
- /**
- * Modifies the standard behavior to allow results to be delivered to fragments.
- * This imposes a restriction that requestCode be <= 0xffff.
- */
- @Override
- public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
- int requestCode)
该方法接收两个参数,第一个参数为Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
- button1.setOnClickListener {
- val intent = Intent(this, SecondActivity::class.java)
- startActivityForResult(intent, 1)
- }
这里使用了startActivityForResult方法来启动SecondActivity,请求码只要是一个唯一值即可,这里为1。SecondActivity对应的逻辑为:
- button2.setOnClickListener {
- val intent = Intent()
- intent.putExtra("data_reurn", "Hello FirstActiviry")
- setResult(RESULT_OK, intent)
- finish()
- }
上面的代码中,构建了一个Intent用于传递数据,然后调用了setResult方法,该方法专门用于向上一个Activity返回数据。setResult方法接收两个参数,第一个参数用于向上一个Activity返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED,第二个参数则把带有数据的Intent传递回去,最后调用finish销毁当前Activity。
而由于使用startActivityForResult方法启动SecondActivity,在SecondActivity被销毁后会回调上一个Activity的onActivityResult方法,因此要重写该方法:
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- when (requestCode) {
- 1 -> if (resultCode == RESULT_OK) {
- val returnedData = data?.getStringExtra("data_return")
- Log.d("FirstActivity", "returned data is $returnedData")
- }
- }
- }
方法onActivityResult中有三个参数,第一个参数requestCode即启动Activity时传入的请求码,第二个参数是resultCode,即返回数据时传入的处理结果,第三个参数data,即携带着返回数据的Intent。由于一个Activity可能调用startActivityForResult方法启动不同的Activity,而每一个Activity返回的数据都会回调到onActivityResult,因此需要检查requestCode判断数据来源,然后判断resultCode是否成功,最后进行数据处理,这样就完成了向上一个Activity返回数据。
运行程序后,可能会看到如下的打印信息:
2022-09-12 11:50:29.015 10658-10658/com.example.activitytest D/FirstActivity: returned data is Hello FirstActiviry
上面的代码是通过finish方法销毁当前进程的,而如果该进程是通过back键销毁的,此时数据便无法返回,需要另外重写onBackPressed方法解决该问题。
- override fun onBackPressed() {
- val intent = Intent()
- intent.putExtra("data_return", "Hello FirstActivity")
- setResult(RESULT_OK, intent)
- finish()
- }
可以看出,只是将代码重写了一遍而已。