• Android开发基础——Activity和Intent


    使用Intent在Activity之间穿梭

    任何应用都不会只存在一个界面,但是点击应用图标只会进入该应用的主Activity,因此不同的Activity之间就需要转换。

    使用显式Intent

    先再创建一个Activity,并命名为SecondActivity,并勾选Generate Layout File,给布局文件命名为second_layout,不勾选Launcher Activity选项。

    修改second_layout的代码为:

    1. "1.0" encoding="utf-8"?>
    2. "http://schemas.android.com/apk/res/android"
    3. android:orientation="vertical"
    4. android:layout_width="match_parent"
    5. android:layout_height="match_parent">
    6. android:id="@+id/button2"
    7. android:layout_width="match_parent"
    8. android:layout_height="wrap_content"
    9. android:text="Button 2"
    10. />

    上面的代码同样定义了一个按钮,并显示Button 2。

    SecondActivity的代码为:

    1. package com.example.activitytest
    2. import androidx.appcompat.app.AppCompatActivity
    3. import android.os.Bundle
    4. class SecondActivity : AppCompatActivity() {
    5. override fun onCreate(savedInstanceState: Bundle?) {
    6. super.onCreate(savedInstanceState)
    7. setContentView(R.layout.second_layout)
    8. }
    9. }

    同样,该Activity也由Android Studio自动注册了:

    1. "1.0" encoding="utf-8"?>
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    3. package="com.example.activitytest">
    4. <application
    5. android:allowBackup="true"
    6. android:icon="@mipmap/ic_launcher"
    7. android:label="@string/app_name"
    8. android:roundIcon="@mipmap/ic_launcher_round"
    9. android:supportsRtl="true"
    10. android:theme="@style/Theme.ActivityTest">
    11. <activity
    12. android:name=".SecondActivity"
    13. android:exported="true" />
    14. <activity
    15. android:name=".FirstActivity"
    16. android:exported="true"
    17. android:label="This is FirstActivity">
    18. <intent-filter>
    19. <action android:name="android.intent.action.MAIN" />
    20. <category android:name="android.intent.category.LAUNCHER" />
    21. intent-filter>
    22. activity>
    23. application>
    24. manifest>

    该Activity不是主Activity,因此无需额外处理,剩下的就是考虑两个Activity之间转换的问题了。

    Intent是Android程序中各个组件之间进行交互的重要方式,其不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity,启动Service,以及发送广播等场景。

    Intent大致可以分为两种:

    • 显式Intent
    • 隐式Intent

    Intent存在多个构造函数的重载,其中一个是下面的形式,该函数第一个参数Context要求提供一个启动Activity的上下文,第二个参数用于指定想要启动的目标Activity。

    Activity类中提供了startActivity方法,专门用于启动Activity,其接收一个Intent参数,将构建好的Intent传入该方法就可以启动目标Activity了。

    1. /**
    2. * Create an intent for a specific component. All other fields (action, data,
    3. * type, class) are null, though they can be modified later with explicit
    4. * calls. This provides a convenient way to create an intent that is
    5. * intended to execute a hard-coded class name, rather than relying on the
    6. * system to find an appropriate class for you; see {@link #setComponent}
    7. * for more information on the repercussions of this.
    8. *
    9. * @param packageContext A Context of the application package implementing
    10. * this class.
    11. * @param cls The component class that is to be used for the intent.
    12. *
    13. * @see #setClass
    14. * @see #setComponent
    15. * @see #Intent(String, android.net.Uri , Context, Class)
    16. */
    17. public Intent(Context packageContext, Class cls)

    这里修改FirstActivity中按钮的点击事件:

    1. button1.setOnClickListener {
    2. val intent = Intent(this, SecondActivity::class.java)
    3. startActivity(intent)
    4. }

    上面的代码首先构建了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添加内容:

    1. <activity
    2. android:name=".SecondActivity"
    3. android:exported="true" >
    4. <intent-filter>
    5. <action android:name="com.example.activityTest.ACTION_START" />
    6. <category android:name="android.intent.category.DEFAULT" />
    7. intent-filter>
    8. activity>

    在action标签中指明了当前Activity可以响应com.example.activityTest.ACTION_START,而category标签则包含了一些附加信息,更精确地指明了当前Activity能够响应的Intent中还可能带有的category。只有action和category中的内容同时匹配Intent中指定的action和category时,该Activity才能够响应该Intent。

    修改FirstActivity中按钮的点击事件,代码为:

    1. button1.setOnClickListener {
    2. val intent = Intent("com.example.activityTest.ACTION_START")
    3. startActivity(intent)
    4. }

    这里可以看到使用了Intent的另一个构造函数,直接传入了action的字符串,表示要启动能够响应com.example.activityTest.ACTION_START这个action的Activity。而之前提到要action和category同时匹配才能够响应,而这里并没有指定category,这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity方法时会自动将该category添加到Intent中。

    重新运行程序,结果是一样的。

    每个Intent中只能指定一个action,但能够指定多个category,这里修改FirstActivity中按钮的点击事件:

    1. button1.setOnClickListener {
    2. val intent = Intent("com.example.activityTest.ACTION_START")
    3. intent.addCategory("com.example.activityTest.MY_CATEGORY")
    4. startActivity(intent)
    5. }

    上面使用Intent的addCategory方法添加了一个category,这里指定了一个自定义的category。

    重新运行程序,点击按钮,程序会崩溃,错误日志为:

    1. 2022-09-12 10:27:08.792 8588-8588/com.example.activitytest E/AndroidRuntime: FATAL EXCEPTION: main
    2. Process: com.example.activitytest, PID: 8588
    3. 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中再添加一个声明:

    1. <intent-filter>
    2. <action android:name="com.example.activityTest.ACTION_START" />
    3. <category android:name="android.intent.category.DEFAULT" />
    4. <category android:name="com.example.activityTest.MY_CATEGORY" />
    5. intent-filter>

    此时重新执行代码,就正常了。

    更多隐式Intent的用法

    使用隐式Intent,不仅可以启动自己程序内的Activity,还可以启动其它程序的Activity,也就使多个应用程序之间可以进行功能共享。比如应用程序中需要展示一个网页,只需要调用系统的浏览器打开该网页即可。

    修改FirstActivity中按钮点击事件的代码:

    1. button1.setOnClickListener {
    2. val intent = Intent(Intent.ACTION_VIEW)
    3. intent.data = Uri.parse("https://www.baidu.com")
    4. startActivity(intent)
    5. }

    上面代码中首先指定了Intent的action是Intent.ACTION_VIEW,这是Android系统内置的动作,其常量值为android.intent.action.VIEW,然后通过Uri.parse方法将地址字符串解析成一个Uri对象,再调用Intent的setData方法将该Uri对象传递进去。

    运行程序,点击按钮后的结果为:

     这里再构建一个Activity,命名为ThirdActivity,同时生成布局文件,命名为third_layout,并修改布局文件内容:

    1. "1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:orientation="vertical"
    4. android:layout_width="match_parent"
    5. android:layout_height="match_parent">
    6. <Button
    7. android:id="@+id/button3"
    8. android:layout_width="match_parent"
    9. android:layout_height="wrap_content"
    10. android:text="Button 3"
    11. />
    12. LinearLayout>

    在AndroidManifest.xml中修改ThirdActivity的注册信息:

    1. <activity
    2. android:name=".ThirdActivity"
    3. android:exported="true">
    4. <intent-filter tools:ignore="AppLinkUrlError">
    5. <action android:name="android.intent.action.VIEW" />
    6. <category android:name="android.intent.category.DEFAULT" />
    7. <data android:scheme="https" />
    8. intent-filter>
    9. 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表示拨打电话:

    1. button1.setOnClickListener {
    2. val intent = Intent(Intent.ACTION_DIAL)
    3. intent.data = Uri.parse("tel:10086")
    4. startActivity(intent)
    5. }

    上面的代码指定了Intent的action为Intent.ACTION_DIAL,然后在data部分指定了协议为tel,号码为10086。运行程序,点击按钮后的结果为:

     向下一个Activity传递数据

    在启动Activity时传递数据的思路很简单,Intent中提供了一系列putExtra方法的重载,可以将数据暂存在Intent中,在启动另一个Activity后,只需要将这些数据从Intent中取出即可。

    1. button1.setOnClickListener {
    2. val data = "Hello SecondActivity"
    3. val intent = Intent(this, SecondActivity::class.java)
    4. intent.putExtra("extra_data", data)
    5. startActivity(intent)
    6. }

    比如上面的代码,使用显式Intent方式启动SecondActivity,并通过putExtra方法传递字符串,putExtra方法的第一个参数是键,用于之后从Intent中取值,第二个参数才是真正要传递的数据。

    然后修改SecondActivity的代码读取该字符串:

    1. package com.example.activitytest
    2. import android.content.Intent
    3. import androidx.appcompat.app.AppCompatActivity
    4. import android.os.Bundle
    5. import android.util.Log
    6. class SecondActivity : AppCompatActivity() {
    7. override fun onCreate(savedInstanceState: Bundle?) {
    8. super.onCreate(savedInstanceState)
    9. setContentView(R.layout.second_layout)
    10. val extraData = intent.getStringExtra("extra_data")
    11. Log.d("SecondActivity", "extra data is $extraData")
    12. }
    13. }

    上面代码中的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。

    1. /**
    2. * Modifies the standard behavior to allow results to be delivered to fragments.
    3. * This imposes a restriction that requestCode be <= 0xffff.
    4. */
    5. @Override
    6. public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
    7. int requestCode)

    该方法接收两个参数,第一个参数为Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。

    1. button1.setOnClickListener {
    2. val intent = Intent(this, SecondActivity::class.java)
    3. startActivityForResult(intent, 1)
    4. }

    这里使用了startActivityForResult方法来启动SecondActivity,请求码只要是一个唯一值即可,这里为1。SecondActivity对应的逻辑为:

    1. button2.setOnClickListener {
    2. val intent = Intent()
    3. intent.putExtra("data_reurn", "Hello FirstActiviry")
    4. setResult(RESULT_OK, intent)
    5. finish()
    6. }

    上面的代码中,构建了一个Intent用于传递数据,然后调用了setResult方法,该方法专门用于向上一个Activity返回数据。setResult方法接收两个参数,第一个参数用于向上一个Activity返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED,第二个参数则把带有数据的Intent传递回去,最后调用finish销毁当前Activity。

    而由于使用startActivityForResult方法启动SecondActivity,在SecondActivity被销毁后会回调上一个Activity的onActivityResult方法,因此要重写该方法:

    1. override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    2. super.onActivityResult(requestCode, resultCode, data)
    3. when (requestCode) {
    4. 1 -> if (resultCode == RESULT_OK) {
    5. val returnedData = data?.getStringExtra("data_return")
    6. Log.d("FirstActivity", "returned data is $returnedData")
    7. }
    8. }
    9. }

    方法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方法解决该问题。

    1. override fun onBackPressed() {
    2. val intent = Intent()
    3. intent.putExtra("data_return", "Hello FirstActivity")
    4. setResult(RESULT_OK, intent)
    5. finish()
    6. }

    可以看出,只是将代码重写了一遍而已。

  • 相关阅读:
    JS中对象数组用sort按属性排序
    【Apache Shiro】超详细笔记-1:功能、架构、集成Spring、认证
    1737C - Ela and Crickets
    [附源码]计算机毕业设计springboot医疗器械公司公告管理系统
    JAVA线程池详解
    金字塔的思维---先总后分与结论先行
    for深入学习
    Auto.js脚本开发入门
    Windows 10 布置IP安全策略
    ES6扩展运算符(...)
  • 原文地址:https://blog.csdn.net/SAKURASANN/article/details/126817528