任何应用都不会只存在一个界面,但是点击应用图标只会进入该应用的主Activity,因此不同的Activity之间就需要转换。
先再创建一个Activity,并命名为SecondActivity,并勾选Generate Layout File,给布局文件命名为second_layout,不勾选Launcher Activity选项。
修改second_layout的代码为:
上面的代码同样定义了一个按钮,并显示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自动注册了:
该Activity不是主Activity,因此无需额外处理,剩下的就是考虑两个Activity之间转换的问题了。
Intent是Android程序中各个组件之间进行交互的重要方式,其不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity,启动Service,以及发送广播等场景。
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并不明确指出想要启动哪一个Activity,而是指定了一系列更为抽象的action和category等信息,然后交给系统去分析该Intent,并找到合适的Activity去启动。
合适的Activity简单说就是可以响应隐式Intent的Activity。
这里在AndroidManifest.xml中为SecondActivity添加内容:
在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,不仅可以启动自己程序内的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,并修改布局文件内容:
在AndroidManifest.xml中修改ThirdActivity的注册信息:
上面的代码在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时传递数据的思路很简单,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只需要按一下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()
}
可以看出,只是将代码重写了一遍而已。