第5章 使用Intent和IntentFilter通信

第5章 使用Intent和IntentFilter通信

本章要点

  • 理解Intent对于Android应用的作用
  • 使用Intent启动系统组件
  • Intent的Component属性的作用
  • Intent的Action属性的作用
  • Intent的Category属性的作用
  • 为指定Action、Category的Intent配置对应的intent-filter
  • Intent的Data属性
  • Intent的Type属性
  • 为指定Data、Type的Intent配置对应的intent-filter
  • Intent的Extra属性
  • Intent的Flag属性

在前面介绍Activity时,我们已经多次使用了Intent。当一个Activity需要启动另一Activity时,程序并没有直接告诉系统要启动哪个Activity,而是通过Intent来表达自己的意图:需要启动哪个Activity。Intent的中文意思即为“意图”。

此时,可能会有读者产生疑问,假如甲Activity需要启动另一Activity,为什么不直接使用一个类似于startActivity(Class activityClass)的方法呢?这样岂不是更简单明了?实际上,这种方式虽然简洁,但却明显背离了Android的设计理念。Android使用Intent来封装程序的“调用意图”,无论是启动Activity、Service组件,还是BroadcastReceiver,Android都采用统一的Intent对象来封装这些“启动意图”,这为开发者提供了一致的编程模型。

此外,使用Intent还有一个好处:在某些情况下,应用程序可能只想启动具有某种特征的组件,而不想与某个具体组件耦合。如果直接使用startActivity(Class activityClass)这种方法来启动特定组件,势必造成一种硬编码耦合,这不利于实现更高层次的解耦。

如果读者曾有过Spring MVC、Struts2等MVC框架的编程经验,那么一定可以很好地理解Intent的设计。当MVC框架的控制器处理完用户请求后,并不会直接返回特定的视图页面,而是返回一个逻辑视图名。开发者可以在配置文件中将该逻辑视图映射到任意的物理视图资源。Android系统中的Intent设计有点类似于MVC框架中的逻辑视图设计。

总之,Intent封装了Android应用程序中启动某个组件的“意图”。不仅如此,Intent还是应用程序组件之间通信的重要媒介。正如在前面示例中所见,两个Activity可以将需要交换的数据封装成Bundle对象,然后通过Intent来携带这个Bundle对象,从而实现两个Activity之间的数据交换。

5.1 Intent对象简述

Android的应用程序包含三种重要组件:Activity、Service、BroadcastReceiver。应用程序采用一致的方式来启动这些组件——都是依靠Intent来启动的。Intent封装了程序想要启动组件的意图,并可用于与被启动的组件交换信息。

表5.1 使用Intent启动不同组件的方法

组件类型启动方法
ActivitystartActivity(Intent intent)
startActivityForResult(Intent intent, int requestCode)
ServiceComponentName startService(Intent service)
boolean bindService(Intent service, ServiceConnection conn, int flags)
BroadcastReceiversendBroadcast(Intent intent)
sendBroadcast(Intent intent, String receiverPermission)
sendOrderedBroadcast(Intent intent, String receiverPermission)
sendStickyBroadcast(Intent intent)
sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)

在上一章中,我们已经介绍了如何使用Intent来启动Activity的示例。至于如何使用Intent来启动其他两种组件,后续章节中会有相关示例,此处暂不深入介绍。

Intent对象大致包含Component、Action、Category、Data、Type、Extra和Flag这七种属性,其中Component用于明确指定需要启动的目标组件,而Extra则用于“携带”需要交换的数据。

接下来,我们将详细介绍Intent对象各属性的作用。

5.2 Intent 的属性及intent-filter 配置

Intent代表了Android应用的启动“意图”,Android 应用将会根据Intent来启动指定组件,至于到底启动哪个组件,则取决于Intent的各属性。下面将详细介绍Intent的各属性值,以及Android如何根据不同属性值来启动相应的组件。

5.2.1 Component 属性

Intent的Component属性需要接受一个ComponentName对象,ComponentName对象包含如下几个构造器:

  • ComponentName(String pkg, String cls):创建pkg所在包下的cls类所对应的组件。
  • ComponentName(Context pkg, String cls):创建pkg所对应的包下的cls类所对应的组件。
  • ComponentName(Context pkg, Class<?> cls):创建pkg所对应的包下的cls类所对应的组件。

这些构造器的本质是相同的,这说明创建一个ComponentName需要指定包名和类名,这样就可以唯一地确定一个组件类,从而应用程序即可根据给定的组件类启动特定的组件。

除此之外,Intent还包含了如下三个方法:

  • setClass(Context packageContext, Class<?> cls):设置该Intent将要启动的组件对应的类。
  • setClassName(Context packageContext, String className):设置该Intent将要启动的组件对应的类名。
  • setClassName(String packageName, String className):设置该Intent将要启动的组件对应的类名。

提示:
Android应用的Context代表了访问该应用环境信息的接口,而Android应用的包名则作为应用的唯一标识。因此,Android应用的Context对象与该应用的包名有对应关系。上面三个setClass()方法正是通过Context或String指定组件的包名和实现类。

指定Component属性的Intent已经明确了它将要启动哪个组件,因此这种Intent也被称为显式Intent;没有指定Component属性的Intent被称为隐式Intent——隐式Intent没有明确指定要启动哪个组件,应用将会根据Intent指定的规则去启动符合条件的组件,但具体是哪个组件则不确定。

提示:
例如,一个女孩子有找男朋友的意图,此时有两种方式来表达她的意图:

  1. 她已经芳心暗许,明确地告诉父母“我要找‘梁山伯’做男朋友”,这种方式被称为显式Intent。
  2. 她告诉父母,要找“高”“富”“帅”做男朋友,至于到底是谁不重要,只要符合这三个条件即可,这种方式被称为隐式Intent。

下面的示例程序示范了如何通过显式Intent(指定了Component属性)来启动另一个Activity。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bn = findViewById(R.id.bn);
        //为bn按钮绑定事件监听器
        bn.setOnClickListener(view -> {
            // 创建一个ComponentName对象
            ComponentName comp = new ComponentName(MainActivity.this, SecondActivity.class);
            Intent intent = new Intent();
            // 为Intent设置Component属性
            intent.setComponent(comp);
            startActivity(intent);
        });
    }
}

上面程序中的三行代码用于创建ComponentName对象,并将该对象设置成Intent对象的Component属性,这样应用程序即可根据该Intent的“意图”去启动指定组件。

实际上,上面三行代码完全可以简化为如下形式:

// 根据指定组件类来创建Intent
Intent intent = new Intent(MainActivity.this, SecondActivity.class);

从上面的代码可以看出,当需要为Intent设置Component属性时,实际上Intent已经提供了一个简化的构造器,这样方便程序直接指定启动其他组件。

当程序通过Intent的Component属性(明确指定了启动哪个组件)启动特定组件时,被启动组件几乎不需要使用<intent-filter>元素进行配置。

程序的SecondActivity也很简单,它的界面布局中只包含一个简单的文本框,用于显示该Activity对应的Intent的Component属性的包名、类名。该Activity的代码如下:

public class SecondActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView show = findViewById(R.id.show);
        // 获取该Activity对应的Intent的Component属性
        ComponentName comp = getIntent().getComponent();
        // 显示该ComponentName对象的包名、类名
        show.setText("组件包名为:" + comp.getPackageName() + "\n组件类名为:" + comp.getClassName());
    }
}

运行上面的程序,通过第一个Activity中的按钮进入第二个Activity中,将看到如图5.1所示的界面。

5.2.2 Action、Category 属性与intent-filter配置

Intent的Action、Category属性的值都是一个普通的字符串,其中Action代表该Intent所要完成的一个抽象“动作”,而Category则用于为Action增加额外的附加类别信息。通常Action属性会与Category属性结合使用。

Action要完成的只是

一个抽象动作,这个动作具体由哪个组件(或许是Activity,或许是BroadcastReceiver)来完成,Action这个字符串本身并不管。比如Android提供的标准Action:Intent.ACTION_VIEW,它只表示一个抽象的查看操作,但具体查看什么、启动哪个Activity来查看,Intent.ACTION_VIEW并不知道,这取决于Activity的<intent-filter>配置。只要某个Activity的<intent-filter>配置中包含了该ACTION_VIEW,该Activity就有可能被启动。

提示:
有过Struts2开发经验的读者都知道,当Struts2的Action处理用户请求结束后,Action并不会直接指定“跳转”到哪个Servlet(通常是JSP,但JSP的本质就是Servlet)。Action的处理方法只是返回一个普通字符串,然后在配置文件中配置该字符串对应到哪个Servlet。Struts2采用这种设计思路是为了把Action与呈现视图的Servlet分离开。类似地,Intent通过指定Action属性(属性值其实就是一个普通字符串),就可以把该Intent与具体的Activity分离,从而提供高层次的解耦。

下面通过一个简单的示例来示范Action属性(就是普通字符串)的作用。下面程序的第一个Activity非常简单,它只包括一个普通按钮,当用户单击该按钮时,程序会“跳转”到第二个Activity——但第一个Activity指定跳转的Intent时,并不以“硬编码”的方式指定要跳转的目标Activity,而是为Intent指定Action属性。

public class MainActivity extends Activity {
    public static final String CRAZYIT_ACTION = "org.crazyit.intent.action.CRAZYIT_ACTION";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bn = findViewById(R.id.bn);
        // 为bn按钮绑定事件监听器
        bn.setOnClickListener(view -> {
            // 创建Intent对象
            Intent intent = new Intent();
            // 为Intent设置Action属性(属性值就是一个普通字符串)
            intent.setAction(MainActivity.CRAZYIT_ACTION);
            startActivity(intent);
        });
    }
}

上面程序中的代码指定了根据Intent来启动Activity,但该Intent并未以“硬编码”的方式指定要启动哪个Activity,从上面程序中无法看出该程序将要启动哪个Activity。那么到底程序会启动哪个Activity呢?这取决于Activity配置中<intent-filter>元素的配置。

<intent-filter>元素是AndroidManifest.xml文件中<activity>元素的子元素,前面已经介绍过,<activity>元素用于为应用程序配置Activity,<activity><intent-filter>子元素则用于配置该Activity所能“响应”的Intent。

<intent-filter>元素里通常可包含如下子元素:

  • 0-N个<action>子元素。
  • 0-N个<category>子元素。
  • 0-1个<data>子元素。

提示:
<intent-filter>元素也可以是<service><receiver>两个元素的子元素,用于表明它们可以响应的Intent。

<action><category>子元素的配置非常简单,它们都可指定android:name属性,该属性的值就是一个普通字符串。

<activity>元素的<intent-filter>子元素里包含多个<action>子元素(相当于指定了多个字符串)时,就表明该Activity能响应Action属性值为其中任意一个字符串的Intent。

还是借用前面介绍的女孩子找男朋友的例子,可以把Intent中的Action、Category属性理解为她对男朋友的要求,每个Intent只能指定一个Action“要求”,但可以指定多个Category“要求”;IntentFilter(使用<intent-filter>元素配置)则用于声明该组件(比如Activity、Service、BroadcastReceiver)能满足的要求,每个组件可以声明自己满足多个Action要求、多个Category要求。只要某个组件能满足的要求大于、等于Intent所指定的要求,那么该Intent就能启动该组件。

由于上面的程序指定启动Action属性为MainActivity.CRAZYIT_ACTION常量(常量值为org.crazyit.intent.action.CRAZYIT_ACTION)的Activity,这就要求被启动的Activity对应的配置元素的<intent-filter>元素里至少包括一个如下的<action>子元素:

<action android:name="org.crazyit.intent.action.CRAZYIT_ACTION" />

需要指出的是,一个Intent对象最多只能包括一个Action属性,程序可调用Intent的setAction(String str)方法来设置Action属性值;但一个Intent对象可以包括多个Category属性,程序可调用Intent的addCategory(String str)方法来为Intent添加Category属性。当程序创建Intent时,该Intent默认启动Category属性值为Intent.CATEGORY_DEFAULT常量(常量值为android.intent.category.DEFAULT)的组件。

因此,虽然上面程序中的代码并未指定目标Intent的Category属性,但该Intent已有一个值为android.intent.category.DEFAULT的Category属性值,因此被启动Activity对应的配置元素的<intent-filter>元素里至少还包括一个如下的<category>子元素:

<category android:name="android.intent.category.DEFAULT" />

下面是被启动的Activity的完整配置。

<activity android:name=".SecondActivity" android:label="第二个Activity">
    <intent-filter>
        <!-- 指定该Activity能响应Action属性值为指定字符串的Intent -->
        <action android:name="org.crazyit.intent.action.CRAZYIT_ACTION" />
        <!-- 指定该Activity能响应Action属性值为helloWorld的Intent -->
        <action android:name="helloWorld" />
        <!-- 指定该Activity能响应Category属性值为DEFAULT的Intent -->
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

上面Activity配置的代码指定该Activity能响应具有指定Action属性值、指定Category属性值的Intent。

上面配置代码中配置了一个实现类为SecondActivity的Activity,因此程序还应该提供这个Activity代码。代码如下。

public class SecondActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView show = findViewById(R.id.show);
        // 获取该Activity对应的Intent的Action属性
        String action = getIntent().getAction();
        // 显示Action属性
        show.setText("Action为:" + action);
    }
}

上面的程序代码很简单,它只是在启动时把启动该Activity的Intent的Action属性值显示在指定文本框内。

运行上面的程序,并单击程序中的“启动指定Action、默认Category对应的Activity”按钮,将看到如图5.2所示的界面。

接下来的示例程序将会示范Category属性的用法。

public class MainActivity extends Activity {
    // 定义一个Action常量
    public static final String CRAZYIT_ACTION = "org.crazyit.intent.action.CRAZYIT_ACTION";
    // 定义一个Category常量
    public static final String CRAZYIT_CATEGORY = "org.crazyit.intent.category.CRAZYIT_CATEGORY";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bn = findViewById(R.id.bn);
        bn.setOnClickListener(view -> {
            Intent intent = new Intent();
            // 设置Action属性
            intent.setAction(MainActivity.CRAZYIT_ACTION);
            // 添加Category属性
            intent.addCategory(MainActivity.CRAZYIT_CATEGORY);
            startActivity(intent);
        });
    }
}

上面程序指定了该Intent的Action属性值为org.crazyit.intent.action.CRAZYIT_ACTION字符串,并为该Intent添加了字符串为org.crazyit.intent.category.CRAZYIT_CATEGORY的Category属性。这意味着上面的程序所要启动的目标Activity里应该包含如下<action>子元素和<category>子元素:

<!-- 指定该Activity能响应action为指定字符串的Intent -->
<action android:name="org.crazyit.intent.action.CRAZYIT_ACTION" />
<!-- 指定该Activity能响应category为指定字符串的Intent -->
<category android:name="org.crazyit.intent.category.CRAZYIT_CATEGORY" />
<!-- 指定该Activity能响应category为DEFAULT的Intent -->
<category android:name="android.intent.category.DEFAULT" />

下面是程序要启动的目标Activity所对应的配置代码。

<activity android:name=".SecondActivity" android:label="第二个Activity">
    <intent-filter>
        <!-- 指定该Activity能响应action为指定字符串的Intent -->
        <action android:name="org.crazyit.intent.action.CRAZYIT_ACTION" />
        <!-- 指定该Activity能响应category为指定字符串的Intent -->
        <category android:name="org.crazyit.intent.category.CRAZYIT_CATEGORY" />
        <!-- 指定该Activity能响应category为DEFAULT的Intent -->
        <category android:name="android.intent.category.DEFAULT"

 />
    </intent-filter>
</activity>

上面配置Activity时也指定该Activity的实现类为SecondActivity,该实现类的代码如下:

public class SecondActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView show = findViewById(R.id.show);
        // 获取该Activity对应的Intent的Action属性
        String action = getIntent().getAction();
        // 显示Action属性
        show.setText("Action为:" + action);
        TextView cate = findViewById(R.id.cate);
        // 获取该Activity对应的Intent的Category属性
        Set<String> cates = getIntent().getCategories();
        // 显示Category属性
        cate.setText("Category属性为:" + cates);
    }
}

上面的程序代码也很简单,它只是在启动时把启动该Activity的Intent的Action、Category属性值分别显示在不同的文本框内。运行上面的程序,并单击程序中的“启动指定Action、指定Category对应的Activity”按钮,将看到如图5.3所示的界面。

5.2.3 指定Action、Category调用系统Activity

Intent代表了启动某个程序组件的“意图”,实际上Intent对象不仅可以启动本应用内程序组件,也可以启动Android系统的其他应用的程序组件,包括系统自带的程序组件(只要权限允许)。

实际上,Android内部提供了大量标准的Action、Category常量,其中用于启动Activity的标准的Action常量及对应的字符串如表5.2所示。

表5.2 启动Activity的标准的Action常量及对应的字符串

Action 常量对应字符串简单说明
ACTION_MAINandroid.intent.action.MAIN应用程序入口
ACTION_VIEWandroid.intent.action.VIEW查看指定数据
ACTION_ATTACH_DATAandroid.intent.action.ATTACH_DATA指定某块数据将被附加到其他地方
ACTION_EDITandroid.intent.action.EDIT编辑指定数据
ACTION_PICKandroid.intent.action.PICK从列表中选择某项,并返回所选数据
ACTION_CHOOSERandroid.intent.action.CHOOSER显示一个Activity选择器
ACTION_GET_CONTENTandroid.intent.action.GET_CONTENT让用户选择数据,并返回所选数据
ACTION_DIALandroid.intent.action.DIAL显示拨号面板
ACTION_CALLandroid.intent.action.CALL直接向指定用户打电话
ACTION_SENDandroid.intent.action.SEND向其他人发送数据
ACTION_SENDTOandroid.intent.action.SENDTO向其他人发送消息
ACTION_ANSWERandroid.intent.action.ANSWER应答电话
ACTION_INSERTandroid.intent.action.INSERT插入数据
ACTION_DELETEandroid.intent.action.DELETE删除数据
ACTION_RUNandroid.intent.action.RUN运行数据
ACTION_SYNCandroid.intent.action.SYNC执行数据同步
ACTION_PICK_ACTIVITYandroid.intent.action.PICK_ACTIVITY用于选择Activity
ACTION_SEARCHandroid.intent.action.SEARCH执行搜索
ACTION_WEB_SEARCHandroid.intent.action.WEB_SEARCH执行Web搜索
ACTION_FACTORY_TESTandroid.intent.action.FACTORY_TEST工厂测试的入口点
ACTION_ANSWERandroid.intent.action.ANSWER应答电话

标准的Category常量及对应的字符串如表5.3所示。

表5.3 标准的Category常量及对应的字符串

Category 常量对应字符串简单说明
CATEGORY_DEFAULTandroid.intent.category.DEFAULT默认的Category
CATEGORY_BROWSABLEandroid.intent.category.BROWSABLE指定该Activity能被浏览器安全调用
CATEGORY_TABandroid.intent.category.TAB指定Activity作为TabActivity的Tab页
CATEGORY_LAUNCHERandroid.intent.category.LAUNCHERActivity显示顶级程序列表
CATEGORY_INFOandroid.intent.category.INFO用于提供包信息
CATEGORY_HOMEandroid.intent.category.HOME设置该Activity随系统启动而运行
CATEGORY_PREFERENCEandroid.intent.category.PREFERENCE该Activity是参数面板
CATEGORY_TESTandroid.intent.category.TEST该Activity是一个测试
CATEGORY_CAR_DOCKandroid.intent.category.CAR_DOCK指定手机被插入汽车底座(硬件)时运行该Activity
CATEGORY_DESK_DOCKandroid.intent.category.DESK_DOCK指定手机被插入桌面底座(硬件)时运行该Activity
CATEGORY_CAR_MODEandroid.intent.category.CAR_MODE设置该Activity可在车载环境下使用

表5.2、表5.3所列出的都只是部分较为常用的Action常量、Category常量。关于Intent所提供的全部Action常量、Category常量,应参考Android API文档中关于Intent的说明。

下面将以两个实例来介绍Intent系统Action、系统Category的用法。

实例:查看并获取联系人电话
这个实例将在程序中提供一个按钮,用户单击该按钮时会显示系统的联系人列表,当用户单击指定联系人之后,程序将会显示该联系人的名字、电话。

这个程序非常有用,比如我们要开发一个发送短信的程序,当用户编写短信完成之后,可能需要浏览联系人列表,并从联系人列表中选出短信接收人,那就可以用到该程序了。

该程序的界面布局代码如下。

<?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:gravity="center_horizontal"
    android:orientation="vertical">
    <!--显示联系人姓名的文本框-->
    <TextView
        android:id="@+id/show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textSize="18sp" />
    <!-- 显示联系人电话号码的文本框-->
    <TextView
        android:id="@+id/phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textSize="18sp" />
    <Button
        android:id="@+id/bn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查看联系人" />
</LinearLayout>

上面的界面布局中包含了两个文本框、一个按钮,其中按钮用于浏览系统的联系人列表并选择其中的联系人。两个文本框分别用于显示联系人的名字和电话号码。

由于该程序会用到系统的联系人应用,因此读者在运行该程序之前应该先进入Android系统自带的Contacts程序,并通过该程序添加几个联系人。

该程序的Activity代码如下。

public class MainActivity extends Activity {
    private static final int PICK_CONTACT = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bn = findViewById(R.id.bn);
        // 为bn按钮绑定事件监听器
        bn.setOnClickListener(view -> {
            // 运行时获取读取联系人信息的权限
            requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0x133);
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == 0x133) {
            // 如果用户同意授权访问
            if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 创建Intent
                Intent intent = new Intent();
                // 设置Intent的Action属性
                intent.setAction(Intent.ACTION_PICK);
                // 设置Intent的Type属性
                intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
                // 启动Activity,并希望获取该Activity的结果
                startActivityForResult(intent, PICK_CONTACT);
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case PICK_CONTACT:
                if (resultCode == Activity.RESULT_OK) {
                    // 获取返回的数据
                    Uri contactData = data.getData();
                    CursorLoader cursorLoader

 = new CursorLoader(this, contactData, null, null, null, null);
                    // 查询联系人信息
                    Cursor cursor = cursorLoader.loadInBackground();
                    // 如果查询到指定的联系人
                    if (cursor != null && cursor.moveToFirst()) {
                        String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
                        // 获取联系人的名字
                        String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
                        String phoneNumber = "此联系人暂未输入电话号码";
                        // 根据联系人查询该联系人的详细信息
                        Cursor phones = getContentResolver().query(
                                ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
                                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null, null);
                        if (phones != null && phones.moveToFirst()) {
                            // 取出电话号码
                            phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                        }
                        // 关闭游标
                        if (phones != null) phones.close();
                        TextView show = findViewById(R.id.show);
                        // 显示联系人的名称
                        show.setText(name);
                        TextView phone = findViewById(R.id.phone);
                        // 显示联系人的电话号码
                        phone.setText(phoneNumber);
                        // 关闭游标
                        if (cursor != null) cursor.close();
                    }
                }
                break;
        }
    }
}

提示:
该实例使用了后面章节中关于ContentProvider的知识,如果读者一时看不懂这个程序,可以参考后面章节的讲解进行理解。

由于上面程序需要查看系统联系人信息,因此要获取系统对应的权限。首先需要在AndroidManifest.xml文件中增加如下代码来获取权限。

<!--请求获取读取联系人的权限-->
<uses-permission android:name="android.permission.READ_CONTACTS" />

最新的Android要求在获取系统资源时不能仅靠在AndroidManifest.xml文件中申请权限,而是在App运行时动态请求获取权限。因此,上面按钮的单击事件处理代码中的权限代码只是在运行时动态获取权限。

当用户对应用授权完成后,系统会自动激发Activity的onRequestPermissionsResult方法,因此上面Activity重写了该方法来处理用户的授权结果:只有当用户授权本Activity访问Contacts(通讯录)时,本Activity才会继续定义actionIntent.ACTION_PICK、指定type的Intent,并通过该Intent启动Activity,如上面Activity中的代码所示。

运行上面的程序,单击程序界面中的“查看联系人”按钮,该应用将会向用户请求授权。如果用户同意访问联系人信息,程序将会显示如图5.4所示的界面。

在图5.4所示的联系人列表中单击某个联系人,系统将会自动返回上一个Activity,程序会在上一个Activity中显示所选联系人的名字和电话号码,如图5.5所示。

上面的Intent对象除设置了Action属性之外,还设置了Type属性,关于Intent的Type属性的作用,5.2.4节将会进行更详细的介绍。

实例:返回系统Home桌面
本实例将会提供一个按钮,当用户单击该按钮时,系统将会返回Home桌面,就像单击模拟器右边的按钮一样。这也需要通过Intent来实现,程序为Intent设置合适的Action、Category属性,并根据该Intent来启动Activity即可返回Home桌面。该实例程序如下。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bn = findViewById(R.id.bn);
        bn.setOnClickListener(view -> {
            // 创建Intent对象
            Intent intent = new Intent();
            // 为Intent设置Action、Category属性
            intent.setAction(Intent.ACTION_MAIN);
            intent.addCategory(Intent.CATEGORY_HOME);
            startActivity(intent);
        });
    }
}

上面程序中的代码设置了Intent的Action属性值为"android.intent.action.MAIN"字符串、Category属性值为"android.intent.category.HOME"字符串,满足该Intent的Activity其实就是Android系统的Home桌面。因此,运行上面的程序时单击“返回桌面”按钮,即可返回Home桌面。

5.2.4 Data、Type属性与intent-filter配置

Data属性通常用于向Action属性提供操作的数据。Data属性接受一个Uri对象,该Uri对象通常通过如下形式的字符串来表示:

content://com.android.contacts/contacts/1
tel:123

Uri字符串通常满足如下格式:

scheme://host:port/path

例如上面给出的content://com.android.contacts/contacts/1,其中content是scheme部分,com.android.contacts是host部分,port部分被省略了,/contacts/1是path部分。

Type属性用于指定该Data属性所指定Uri对应的MIME类型,这种MIME类型可以是任何自定义的MIME类型,只要符合abc/xyz格式的字符串即可。

Data属性与Type属性的关系比较微妙,这两个属性会相互覆盖,例如:

  • 如果为Intent先设置Data属性,后设置Type属性,那么Type属性将会覆盖Data属性。
  • 如果为Intent先设置Type属性,后设置Data属性,那么Data属性将会覆盖Type属性。
  • 如果希望Intent既有Data属性,也有Type属性,则应该调用Intent的setDataAndType方法。

下面的示例演示了Intent的Data属性与Type属性互相覆盖的情形,该示例的界面布局文件很简单,只是定义了三个按钮,并为三个按钮绑定了事件处理函数。

下面是该示例的Activity代码。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void overrideType(View source) {
        Intent intent = new Intent();
        // 先为Intent设置Type属性
        intent.setType("abc/xyz");
        // 再为Intent设置Data属性,覆盖Type属性
        intent.setData(Uri.parse("lee://www.fkjava.org:888/test"));
        Toast.makeText(this, intent.toString(), Toast.LENGTH_LONG).show();
    }

    public void overrideData(View source) {
        Intent intent = new Intent();
        // 先为Intent设置Data属性
        intent.setData(Uri.parse("lee://www.fkjava.org:888/mypath"));
        // 再为Intent设置Type属性,覆盖Data属性
        intent.setType("abc/xyz");
        Toast.makeText(this, intent.toString(), Toast.LENGTH_LONG).show();
    }

    public void dataAndType(View source) {
        Intent intent = new Intent();
        // 同时设置Intent的Data、Type属性
        intent.setDataAndType(Uri.parse("lee://www.fkjava.org:888/mypath"), "abc/xyz");
        Toast.makeText(this, intent.toString(), Toast.LENGTH_LONG).show();
    }
}

上面的三个事件监听方法分别为Intent设置了Data、Type属性,第一个事件监听方法先设置了Type属性,再设置了Data属性,这将导致Data属性覆盖Type属性,单击按钮激发该事件监听方法,将可以看到如图5.6所示的Toast输出。

从图5.6可以看出,此时的Intent只有Data属性,Type属性被覆盖了。

第二个事件监听方法先设置了Data属性,再设置了Type属性,这将导致Type属性覆盖Data属性,单击按钮激发该事件监听方法,将可以看到如图5.7所示的输出。

第三个事件监听方法同时设置了Data、Type属性,这样该Intent中才会同时具有Data、Type属性。

在AndroidManifest.xml文件中为组件声明Data、Type属性都通过<data>元素,<data>元素的格式如下:

<data android:mimeType=""
      android:scheme=""
      android:host=""
      android:port=""
      android:path=""
      android:pathPrefix=""
      android:pathPattern="" />

上面的<data>元素支持如下属性:

  • mimeType:用于声明该组件所能匹配的Intent的Type属性。
  • scheme:用于声明该组件所能匹配的Intent的Data属性的scheme部分。
  • host:用于声明该组件所能匹配的Intent的Data属性的host部分。
  • port:用于声明该组件所能匹配的Intent的Data属性的port部分。
  • path:用于声明该组件所能匹配的Intent的Data属性的path部分。
  • pathPrefix:用于声明该组件所能匹配的Intent的Data属性的path前缀。
  • pathPattern:用于声明该组件所能匹配的Intent的Data属性的path字符串模板。

Intent的Type属性用于指定该Intent的要求,对应组件中<intent-filter>元素的<data>子元素的mimeType属性必须与此相同,才能启动该组件。

Intent的Data属性略有差异,程序员为Intent指定Data属性时,Data属性的Uri对象实际上可分为scheme、host、port和path部分,此时并不要求被启动组件

<intent-filter><data>子元素的android:schemeandroid:hostandroid:portandroid:path完全满足。

Data属性的“匹配”过程有些差别,它会先检查<intent-filter><data>子元素,然后:

  • 如果目标组件的<data>子元素只指定了android:scheme属性,那么只要Intent的Data属性的scheme部分与android:scheme属性值相同,即可启动该组件。
  • 如果目标组件的<data>子元素只指定了android:schemeandroid:host属性,那么只要Intent的Data属性的scheme、host部分与android:schemeandroid:host属性值相同,即可启动该组件。
  • 如果目标组件的<data>子元素指定了android:schemeandroid:hostandroid:port属性,那么只要Intent的Data属性的scheme、host、port部分与android:schemeandroid:hostandroid:port属性值相同,即可启动该组件。

提示:
如果<data>子元素只有android:port属性,没有指定android:host属性,那么android:port属性将不会起作用。

  • 如果目标组件的<data>子元素只指定了android:schemeandroid:hostandroid:path属性,那么只要Intent的Data属性的scheme、host、path部分与android:schemeandroid:hostandroid:path属性值相同,即可启动该组件。

如果<data>子元素只有android:path属性,没有指定android:host属性,那么android:path属性将不会起作用。

  • 如果目标组件的<data>子元素指定了android:schemeandroid:hostandroid:portandroid:path属性,那么就要求Intent的Data属性的scheme、host、port、path部分依次与android:schemeandroid:hostandroid:portandroid:path属性值相同,才可启动该组件。

下面的示例测试了Intent的Data属性与<data>元素配置的关系,该示例依次配置了如下5个Activity。

<activity android:icon="@drawable/ic_scheme" android:name=".SchemeActivity" android:label="指定scheme的Activity">
    <intent-filter>
        <action android:name="xx" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- 只要Intent的Data属性的scheme是lee,即可启动该Activity -->
        <data android:scheme="lee" />
    </intent-filter>
</activity>

<activity android:icon="@drawable/ic_host" android:name=".SchemeHostPortActivity" android:label="指定scheme、host、port的Activity">
    <intent-filter>
        <action android:name="xx" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- 只要Intent的Data属性的scheme是lee,且host是www.fkjava.org,port是8888,即可启动该Activity -->
        <data android:scheme="lee" android:host="www.fkjava.org" android:port="8888" />
    </intent-filter>
</activity>

<activity android:icon="@drawable/ic_sp" android:name=".SchemeHostPathActivity" android:label="指定scheme、host、path的Activity">
    <intent-filter>
        <action android:name="xx" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- 只要Intent的Data属性的scheme是lee,且host是www.fkjava.org,path是/mypath,即可启动该Activity -->
        <data android:scheme="lee" android:host="www.fkjava.org" android:path="/mypath" />
    </intent-filter>
</activity>

<activity android:icon="@drawable/ic_path" android:name=".SchemeHostPortPathActivity" android:label="指定scheme、host、port、path的Activity">
    <intent-filter>
        <action android:name="xx" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- 需要Intent的Data属性的scheme是lee,且host是www.fkjava.org,port是8888,path是/mypath,才可启动该Activity -->
        <data android:scheme="lee" android:host="www.fkjava.org" android:port="8888" android:path="/mypath" />
    </intent-filter>
</activity>

<activity android:icon="@drawable/ic_type" android:name=".SchemeHostPortPathTypeActivity" android:label="指定scheme、host、port、path、type的Activity">
    <intent-filter>
        <action android:name="xx" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- 需要Intent的Data属性的scheme是lee,且host是www.fkjava.org,port是8888,path是/mypath,type是abc/xyz,才可启动该Activity -->
        <data android:scheme="lee" android:host="www.fkjava.org" android:port="8888" android:path="/mypath" android:mimeType="abc/xyz" />
    </intent-filter>
</activity>

在上面的配置文件中配置了5个Activity,这5个Activity的实现类都非常简单,它们都仅在界面上显示一个TextView,并不显示其他内容。关于这5个Activity的<data>子元素配置说明如下:

  • 第1个Activity:只要Intent的Data属性的scheme是lee,即可启动该Activity。
  • 第2个Activity:只要Intent的Data属性的scheme是lee,且host是www.fkjava.org,port是888,即可启动该Activity。
  • 第3个Activity:只要Intent的Data属性的scheme是lee,且host是www.fkjava.org,path是/mypath,即可启动该Activity。
  • 第4个Activity:需要Intent的Data属性的scheme是lee,且host是www.fkjava.org,port是888,path是/mypath,才可启动该Activity。
  • 第5个Activity:需要Intent的Data属性的scheme是lee,且host是www.fkjava.org,port是8888,path是/mypath,type是abc/xyz,才可启动该Activity。

上面配置Activity的<intent-filter>元素时,<action>子元素的name属性是随意指定的,这也是必需的。如果希望<data>子元素能正常起作用,至少要配置一个<action>子元素,但该子元素的android:name属性值可以是任意的字符串。

下面是第1个启动Activity的方法。

public void scheme(View source) {
    Intent intent = new Intent();
    // 只设置Intent的Data属性
    intent.setData(Uri.parse("lee://www.crazyit.org:1234/test"));
    startActivity(intent);
}

上面Intent的Data属性,只有scheme为lee,也就是只有第1个Activity符合条件,因此通过该方法启动Activity时,将可以看到启动如图5.8所示的Activity。

下面是第2个启动Activity的方法。

public void schemeHostPort(View source) {
    Intent intent = new Intent();
    // 只设置Intent的Data属性
    intent.setData(Uri.parse("lee://www.fkjava.org:8888/test"));
    startActivity(intent);
}

上面Intent的Data属性,scheme为lee,因此第1个Activity符合条件;且该Intent的Data属性的host为www.fkjava.org,port为888,因此第2个Activity也符合条件。通过该方法启动Activity时,将可以看到启动如图5.9所示的选择Activity界面。

下面是第3个启动Activity的方法。

public void schemeHostPath(View source) {
    Intent intent = new Intent();
    // 只设置Intent的Data属性
    intent.setData(Uri.parse("lee://www.fkjava.org:1234/mypath"));
    startActivity(intent);
}

上面Intent的Data属性,scheme为lee,因此第1个Activity符合条件;且该Intent的Data属性的host为www.fkjava.org,path为/mypath,因此第3个Activity也符合条件。通过该方法启动Activity时,将可以看到启动如图5.10所示的选择Activity界面。

下面是第4个启动Activity的方法。

public void schemeHostPortPath(View source) {
    Intent intent = new Intent();
    // 只设置Intent的Data属性
    intent.setData(Uri.parse("lee://www.fkjava.org:8888/mypath"));
    startActivity(intent);
}

上面Intent的Data属性,scheme为lee,因此第1个Activity符合条件;且该Intent的Data属性的host为www.fkjava.org,port为888,因此第2个Activity符合条件;该Intent的Data属性的host为www.fkjava.org,path为/mypath,因此第3个Activity符合条件;该Intent的Data属性的host为www.fkjava.org,port为888,path为/mypath,因此第

4个Activity也符合条件。通过该方法启动Activity时,将可以看到启动如图5.11所示的选择Activity界面。

下面是第5个启动Activity的方法。

public void schemeHostPortPathType(View source) {
    Intent intent = new Intent();
    // 同时设置Intent的Data、Type属性
    intent.setDataAndType(Uri.parse("lee://www.fkjava.org:8888/mypath"), "abc/xyz");
    startActivity(intent);
}

上面的Intent不仅指定了Data属性,也指定了Type属性,此时符合条件的只有第5个Activity。通过该方法启动Activity时,将可以看到启动如图5.12所示的Activity。

实例:使用Action、Data属性启动系统Activity
一旦为Intent同时指定了Action、Data属性,Android就可根据指定的数据类型来启动特定的应用程序,并对指定数据执行相应的操作。

下面是几个常见的Action属性、Data属性的组合:

  • ACTION_VIEW content://com.android.contacts/contacts/1:显示标识为1的联系人的信息。
  • ACTION_EDIT content://com.android.contacts/contacts/1:编辑标识为1的联系人的信息。
  • ACTION_DIAL content://com.android.contacts/contacts/1:显示向标识为1的联系人拨号的界面。
  • ACTION_VIEW tel:123:显示向指定号码123拨号的界面。
  • ACTION_DIAL tel:123:显示向指定号码123拨号的界面。
  • ACTION_VIEW content://contacts/people/:显示所有联系人列表的信息,通过这种组合可以非常方便地查看系统联系人。

本实例程序示范通过同时为Intent指定Action、Data属性来启动特定程序并操作相应的数据。下面程序的界面很简单,只包含3个按钮,其中一个按钮用于浏览指定网页;一个按钮用于编辑指定联系人信息;另一个按钮用于呼叫指定号码。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bn = findViewById(R.id.bn);
        // 为bn按钮添加一个监听器
        bn.setOnClickListener(view -> {
            // 创建Intent
            Intent intent = new Intent();
            // 为Intent设置Action属性
            intent.setAction(Intent.ACTION_VIEW);
            // 设置Data属性
            intent.setData(Uri.parse("http://www.crazyit.org"));
            startActivity(intent);
        });

        Button edit = findViewById(R.id.edit);
        // 为edit按钮添加一个监听器
        edit.setOnClickListener(view -> {
            // 创建Intent
            Intent intent = new Intent();
            // 为Intent设置Action属性(动作为:编辑)
            intent.setAction(Intent.ACTION_EDIT);
            // 设置Data属性
            intent.setData(Uri.parse("content://com.android.contacts/contacts/1"));
            startActivity(intent);
        });

        Button call = findViewById(R.id.call);
        // 为call按钮添加一个监听器
        call.setOnClickListener(view -> {
            // 创建Intent
            Intent intent = new Intent();
            // 为Intent设置Action属性(动作为:拨号)
            intent.setAction(Intent.ACTION_DIAL);
            // 设置Data属性
            intent.setData(Uri.parse("tel:13800138000"));
            startActivity(intent);
        });
    }
}

运行上面的程序,单击第一个按钮,该按钮被单击时启动Intent(Action=Intent.ACTION_VIEW, Data=http://www.crazyit.org)对应的Activity,将看到打开www.crazyit.org的界面。

单击第二个按钮,该按钮被单击时启动Intent(Action=Intent.ACTION_EDIT, Data=content://com.android.contacts/contacts/1)对应的Activity,将看到编辑标识为1的联系人界面。

单击第三个按钮,该按钮被单击时启动Intent(Action=Intent.ACTION_DIAL, Data=tel:13800138000)对应的Activity,将看到程序向13800138000拨号的界面。

5.2.5 Extra属性

Intent的Extra属性通常用于在多个Action之间进行数据交换,Intent的Extra属性值应该是一个Bundle对象,Bundle对象就像一个Map对象,它可以存入多个key-value对,这样就可以通过Intent在不同Activity之间进行数据交换了。关于Extra属性的用法前面已有示例,此处不再赘述。

5.2.6 Flag属性

Intent的Flag属性用于为该Intent添加一些额外的控制标志,Intent可调用addFlags()方法来添加控制标志。

前面介绍启用ActionBar的程序图标返回主Activity时已经用到了Flag属性。比如前面介绍的Intent.FLAG_ACTIVITY_CLEAR_TOP标志可用于清除当前Activity栈中的Activity。

除此之外,Intent还包含了如下常用的Flag标志:

  • FLAG_ACTIVITY_BROUGHT_TO_FRONT:如果通过该Flag启动的Activity已经存在,下次再次启动时,将只是把该Activity带到前台。例如,现在Activity栈中有Activity A,此时以该标志启动Activity B(即Activity B是以FLAG_ACTIVITY_BROUGHT_TO_FRONT标志启动的),然后在Activity B中启动Activity C、D,如果此时在Activity D中再启动Activity B,将直接把Activity栈中的Activity B带到前台。此时Activity栈中情形是Activity A、C、D、B。
  • FLAG_ACTIVITY_CLEAR_TOP:该Flag相当于加载模式中的singleTask,通过这种Flag启动的Activity将会把要启动的Activity之上的Activity全部弹出Activity栈。例如,Activity栈中包含A、B、C、D四个Activity,如果采用该Flag从Activity D跳转到Activity B,那么此时Activity栈中只包含A、B两个Activity。
  • FLAG_ACTIVITY_NEW_TASK:默认的启动标志,该标志控制重新创建一个新的Activity。
  • FLAG_ACTIVITY_NO_ANIMATION:该标志控制启动Activity时不使用过渡动画。
  • FLAG_ACTIVITY_NO_HISTORY:该标志控制被启动的Activity将不会保留在Activity栈中。例如,Activity栈中原来有A、B、C三个Activity,此时在Activity C中以该Flag启动Activity D,Activity D再启动Activity E,此时Activity栈中只有A、B、C、E四个Activity,Activity D不会保留在Activity栈中。
  • FLAG_ACTIVITY_REORDER_TO_FRONT:该Flag控制如果当前已有Activity,则直接将该Activity带到前台。例如,现在Activity栈中有A、B、C、D四个Activity,如果使用FLAG_ACTIVITY_REORDER_TO_FRONT标志来启动Activity B,那么启动后的Activity栈中情形为A、C、D、B。
  • FLAG_ACTIVITY_SINGLE_TOP:该Flag相当于加载模式中的singleTop模式。例如,原来Activity栈中有A、B、C、D四个Activity,在Activity D中再次启动Activity D,Activity栈中依然还是A、B、C、D四个Activity。

Android为Intent提供了大量的Flag,每个Flag都有其特定的功能,具体请参考关于Intent的API文档。

5.3 本章小结

本章主要介绍了Android系统中Intent的功能和用法。当Android应用需要启动某个组件时,总需要借助于Intent来实现。不管是启动Activity,还是启动Service、BroadcastReceiver组件,Android系统都是由Intent来实现的。简单地说,Android使用Intent封装了应用程序的“启动意图”,但这种“意图”并未直接与任何程序组件耦合,通过这种方式即可很好地提高系统的可扩展性和可维护性。学习本章需要重点掌握Intent的Component、Action、Category、Data、Type各属性的功能与用法,并掌握如何在AndroidManifest.xml文件中配置<intent-filter>元素。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值