Android开发基础——Activity和Intent

使用Intent在Activity之间穿梭

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

使用显式Intent

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

修改second_layout的代码为:

<?xml version="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/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 2"
        />

</LinearLayout>

上面的代码同样定义了一个按钮,并显示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自动注册了:

<?xml version="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,并修改布局文件内容:

<?xml version="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()
    }

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

猜你喜欢

转载自blog.csdn.net/SAKURASANN/article/details/126817528
今日推荐