• 05.显式意图、隐式意图


    一个应用程序不可能只有一个活动,创建活动的方法我们之前已经学会了,那么如何从一个活动跳转到其他活动呢?

    这就需要用到意图(Intent)

    Intent(意图)是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent虽然不是四大组件,但却是连接四大组件的桥梁。

    Intent一般可被用于启动活动、启动服务、以及发送广播。

    由于服务、广播等概念我们暂时还未涉及,那么我们就先学习如何使用Intent启动活动。

    Intent的用法大致可以分为两种,显式Intent隐式Intent

    1、显式Intent

    在ActivityTest项目中再创建一个活动。右击com.sdbi.activitytest包 > New > Activity > Empty Actity,会弹出一个创建活动的对话框,我们将活动命名为SecondActivity,并勾选Generate Layout File,给布局文件命名为activity_second,但不要勾选Launcher Activity选项。

    点击Finish完成创建,Android Studio会为我们自动创建SecondActivity.java和activity_second.xml两个文件。

    我们将activity_second.xml中的布局样式修改为LinearLayout,代码替换为:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按钮2" />
    </LinearLayout>

    我们还是定义了一个按钮,按钮上显示“按钮2”。然后查看SecondActivity中的代码:

    public class SecondActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second); // 重要,设置布局
        }
    }

    查看AndroidManifest.xml中是否帮我们自动完成了SecondActivity的注册。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.sdbi.activitytest">
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.ActivityTest"
            tools:targetApi="31">
            <activity
                android:name=".SecondActivity"
                android:exported="false"
                android:label="这是第二个活动" />
            <activity
                android:name=".FirstActivity"
                android:exported="true"
                android:label="这是第一个活动">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    由于SecondActivity不是主活动,因此不需要配置<intent-filter>标签里的内容,注册活动的代码也是简单了许多。

    现在第二个活动已经创建完成,剩下的问题就是如何去启动这第二个活动了,这里我们需要用到Intent(意图)

    我们先来看一下显式Intent如何使用。

    Intent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls)。这个构造函数接收两个参数:

    第一个参数Context要求提供一个启动活动的上下文(它描述的是一个应用程序环境的信息,我们可以将其理解为“场景”);

    第二个参数Class则是指定想要启动的目标活动,就是我们要启动的第二个活动。

    通过这个构造函数就可以构建出Intent的“意图”。

    怎么使用这个Intent呢?Activity类中提供了一个startActivity()方法,这个方法是专门用于启动活动的,它接收一个Intent参数,这里我们将刚刚构建好的Intent传入startActivity()方法就可以了。

    修改FirstActivity中按钮1的点击事件,代码如下所示:

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
            // Intent intent = new Intent();
            // intent.setClass(FirstActivity.this, SecondActivity.class);
            startActivity(intent);
        }
    });

    先构造一个Intent对象(两个参数的构造方法),传入FirstActivity.this作为上下文,传入SecondActivity.class作为目标活动,这样我们的“意图”就非常明显了,即在FirstActivity这个活动的基础上打开SecondActivity这个活动。然后通过startActivity()方法来执行这个Intent。

    另外,我们也可以先构造一个空的Intent对象(空参数的构造方法),然后调用它的setClass()方法,将FirstActivity.this作为上下文,SecondActivity.class作为目标活动传入,实际开发过程中,根据自己的需要选择Intent的设置方法。

    重新运行程序,在FirstActivity的界面点击一下按钮1,我们就可以启动SecondActivity这个活动了。

    如果你想要回到上一个活动怎么办呢?很简单,按下Back键就可以销毁当前活动,从而回到上一个活动了。

    2、隐式Intent

    相比于显式Intent,隐式Intent则不那么明显,它没有明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action(动作)和category(类别)等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。

    如何才能找到合适的活动呢?

    通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的action和category,打开AndroidManifest.xml,添加如下代码:

    <activity
        android:name=".SecondActivity"
        android:exported="false"
        android:label="这是第二个活动">
        <intent-filter>
            <action android:name="com.sdbi.activitytest.ACTION_START" />
            
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

    在<action>标签中我们指明了当前活动可以响应com.sdbi.activitytest.ACTION_START这个action,而<category>标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category,如果自己定义的某个Activity要通过隐式启动,必须加上android.intent.category.DEFAULT,否则不起作用。

    只有<action>和<category>中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent。

    修改FirstActivity中按钮的点击事件,代码如下所示:

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent("com.sdbi.activitytest.ACTION_START");
            startActivity(intent);
        }
    });

    可以看到,我们使用了Intent的另一个构造函数(一个字符串参数的),直接将action的字符串传了进去,表明我们想要启动能够响应com.sdbi.activitytest.ACTION_START这个action的活动。

    那前面不是说要<action>和<category>同时匹配上才能响应的吗?怎么没看到哪里有指定category呢?

    这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent

    重新运行程序,在FirstActivity的界面点击一下按钮,你同样成功启动SecondActivity了。

    不同的是,这次你是使用了隐式Intent的方式来启动的,说明我们在<activity>标签下配置的action和category的内容已经生效了!

    每个Intent中只能指定一个action,但却能指定多个category

    目前我们的Intent中只有一个默认的category,那么现在再来增加一个吧。

    修改FirstActivity中按钮的点击事件,代码如下所示:

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent("com.sdbi.activitytest.ACTION_START");
            intent.addCategory("com.sdbi.activitytest.MY_CATEGORY");
            startActivity(intent);
        }
    });

    可以调用Intent中的addCategory()方法来添加一个category,这里我们指定了一个自定义的category,值为com.sdbi.activitytest.MY_CATEGORY。

    现在重新运行程序,在FirstActivity的界面点击一下按钮,你会发现,程序崩溃了。

    我们来分析一下在LogCat中输出的错误日志,你会看到如图所示的错误信息。

    错误信息中提醒我们,没有任何一个活动可以响应我们的Intent,为什么呢?

    这是因为我们刚刚在Intent中新增了一个category,而SecondActivity的<intent-filter>标签中并没有声明可以响应这个category,所以就出现了没有任何活动可以响应该Intent的情况。

    现在我们在<intent-filter>中再添加一个category的声明,如下所示:

    <activity
        android:name=".SecondActivity"
        android:exported="false"
        android:label="这是第二个活动">
        <intent-filter>
            <action android:name="com.sdbi.activitytest.ACTION_START" />
    
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="com.sdbi.activitytest.MY_CATEGORY" />
        </intent-filter>
    </activity>

    再次重新运行程序,你就会发现一切都正常了。

    注意:之前主活动的<category android:name="android.intent.category.LAUNCHER" />的意义:

    LAUNCHER:桌面启动器,Android的桌面应用程序。如果一个App没有MAIN和LAUNCHER,则该APP不能被运行。 

    3、隐式Intent的其他用法

    上面我们掌握了通过隐式Intent来启动活动的方法,但实际上隐式Intent还有更多的内容需要我们去了解,下面我们就来展开介绍一下。

    使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。

    1.打开网页

    如果我们的应用程序里需要打开一个网页,我们没有必要自己去实现一个浏览器,而是只需要调用系统的浏览器来打开这个网页就行了。

    修改FirstActivity中按钮点击事件的代码,如下所示:

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("http://www.sdbi.edu.cn"));
            startActivity(intent);
        }
    });

    这里我们首先指定了Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW。

    然后通过静态方法Uri.parse(),将一个网址字符串(一定要写明协议名称http://)解析成一个Uri对象(通用资源标志符,Universal Resource Identifier, 简称“URI”),再调用Intent的setData()方法将这个Uri对象传递进去。

    setData()方法接收一个Uri对象,用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。

    与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指定当前活动能够响应什么类型的数据。<data>标签中主要可以配置以下内容。

    • android:scheme 用于指定数据的协议部分,如上例中的http部分。
    • android:host 用于指定数据的主机名部分,如上例中的www.sdbi.edu.cn部分。
    • android:port 用于指定数据的端口部分,一般紧随在主机名之后。
    • android:path 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
    • android:mimeType 用于指定可以处理的数据类型,允许使用通配符的方式进行指定。

    只有<data>标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。

    不过一般在<data>标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定android:scheme为http,就可以响应所有的http协议的Intent了。

    为了让你能够更加直观地理解,我们来自己建立一个活动,让它也能响应打开网页的Intent。

    右击com.sdbi.activitytest包 > New > Activity > Empty Actity,新建活动ThirdActivity,并勾选Generate Layout File,给布局文件命名为activity_third,但不要勾选Launcher Activity选项。点击Finish完成创建,然后编辑activity_third.xml布局文件,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/btn3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按钮3" />
    </LinearLayout>

    ThirdActivity代码保持不变。最后在AndroidManifest.xml中为ThirdActivity进行注册。

    <activity
        android:name=".ThirdActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
    
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
    
            <data android:scheme="http" />
        </intent-filter>
    </activity>

    我们在ThirdActivity的<activity>中修改android:exported="true",

    <intent-filter>中配置了当前活动能够响应的action是Intent.ACTION_VIEW的常量值,

    而category除了默认的android.intent.category.DEFAULT值(Java常量Intent.CATEGORY_DEFAULT),

    还必须指定android.intent.category.BROWSABLE(Java常量Intent.CATEGORY_BROWSABLE),

    指定了此category后,系统会考虑将此目标Activity列入可选列表,供用户选择以打开链接。

    另外在<data>标签中我们通过android:scheme指定了数据的协议必须是http协议,这样ThirdActivity应该就和浏览器一样,能够响应一个打开网页的Intent了。

    让我们运行一下程序试试吧,在FirstActivity的界面点击一下按钮1,结果如图所示。

         

    可以看到,系统自动弹出了一个列表,显示了目前能够响应这个Intent的所有程序。

    点击“Chrome”还会像之前一样打开浏览器,并显示主页,而如果点击了ActivityTest,则会启动ThirdActivity,如图所示。

    需要注意的是,虽然我们声明了ThirdActivity是可以响应打开网页的Intent的,但实际上这个活动并没有加载并显示网页的功能,所以在真正的项目中尽量不要去做这种有可能误导用户的行为,不然会让用户对我们的应用产生负面的印象。

    2.拨打电话

    除了http协议外,我们还可以指定很多其他协议,比如geo表示显示地理位置、tel表示拨打电话。

    下面的代码展示了如何在我们的程序中调用系统拨号界面。

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(Intent.ACTION_DIAL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }
    });

    首先指定了Intent的action是Intent.ACTION_DIAL(其常量值为"android.intent.action.DIAL"),这又是一个Android系统的内置动作。

    然后在data部分指定了协议是tel,号码是10086。

    如果要直接拨打电话,而不是跳转到系统的拨号软件界面。

    那我们应该将Intent的action设置为是Intent.ACTION_CALL(其常量值为"android.intent.action.CALL"),data部分不变。

    但是由于是直接拨打电话,所以要在清单文件AndroidManifest.xml中增加系统权限的获取,否则会报错。

    要解决这个问题,我们需要:

    ① 在清单文件AndroidManifest.xml中手动增加系统权限的获取。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.sdbi.activitytest">
    
        <uses-permission android:name="android.permission.CALL_PHONE" />
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.ActivityTest"
            tools:targetApi="31">
         ......
        </application>
    
    </manifest>

    ② 将APP的打电话权限设置为允许。

         

    3.启动摄像头

    如果我们的应用程序需要使用摄像头拍照,我们也可以通过隐式意图启动摄像头。

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            startActivity(intent);
        }
    });

    这里我们指定Intent的action是MediaStore.ACTION_IMAGE_CAPTURE,这也是一个Android系统内置的动作:启动摄像头,其常量值为"android.media.action.IMAGE_CAPTURE"

       

    启动相机功能:MediaStore.ACTION_IMAGE_CAPTURE(android.media.action.IMAGE_CAPTURE)

    启动摄像功能:MediaStore.ACTION_VIDEO_CAPTURE(android.media.action.VIDEO_CAPTURE)

    【另外】

    Android7.0之前会出现错误,是因为Android6.0(API 23)添加了运行时权限,即在运行时请求应用是否启用该权限,如果用户拒绝,则要做异常处理。我们可以通过以下两个方法解决:

    方法一:在启动摄像头前,判断是否有该权限。

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (ContextCompat.checkSelfPermission(FirstActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
                    || ContextCompat.checkSelfPermission(FirstActivity.this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivity(intent);
            }
        }
    });

    方法二:通过try…catch捕获SecurityException

    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivity(intent);
            } catch (SecurityException e) {
                e.printStackTrace();
            }
        }
    });

    我们还要在清单文件中增加摄像头权限的获取:

    <uses-permission android:name="android.permission.CAMERA" />

    关于隐式Intent的用法,我们就先介绍这么些,还有很多系统的动作我们可以调用,随着以后的使用,大家可以慢慢总结学习。

  • 相关阅读:
    控件不能与用户交互的情况
    iOS常见的延时执行有2种方式
    网络-URLConnection & URLSession
    多线程-相关定义
    单例模式
    Targeted Resumes How to Write a Targeted Resume By Alison Doyle
    Email Cover Letter Format
    什么是目标、度量、KPI、维度和细分
    在美国公司架构中,LLC、LLP 和 Corporation 的区别何在?
    (转)给明年依然年轻的我们:欲望、外界、标签、天才、时间、人生目标、现实、后悔、和经历(500强高管力荐)
  • 原文地址:https://www.cnblogs.com/lihuawei/p/16624302.html
Copyright © 2020-2023  润新知