第 21 章 隐式 intent
请参考教材,全面理解和完成本章节内容... ...复制工程ch20,将工程目录改名为ch21。
同显式Intent一样,在Android系统中,使用隐式intent可以启动其他应用,比如微信、QQ、浏览器等,但隐式Intent的启动方式比较含蓄,它并不明确指出启动哪一个Activity,仅隐式Intent中指定要完成任务的信息,然后交由系统去分析这个隐式Intent,并帮我们找出能匹配任务的Activity去启动。
比如,你的应用程序中需要打开一个网页,这时你没有必要自己去实现一个浏览器,只需要调用系统的浏览器来打开网页就行了。使用隐式Intent完成这个任务的代码如下所示:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
在”陋习手记”应用中,将使用隐式intent实现从联系人列表中选取有此Crime的「嫌疑人」,然后发送「陋习报告」给他。实际使用时,用户从设备上安装的联系人应用中选取联系人,然后再从系统提供的多种“信息发送应用“中,选取一个应用将报告发送出去。
图21-1 打开联系人和消息发送应用
从开发者角度来看,使用隐式intent利用其他应用完成任务,远比自己编写代码从头实现要容易得多。从用户角度来看,他们也乐意在当前应用中协同使用自己熟悉或喜爱的应用。
创建使用隐式intent前,需先在「陋习手记」应用中完成以下准备工作:
· 在CrimeFragment的布局上添加Choose Suspect和Send Crime Report按钮;
· 在Crime类中添加保存「嫌疑人」姓名的mSuspect变量;
· 使用格式化的字符串资源创建陋习报告模板。
21.1 添加按钮组件
首先,我们需要在CrimeFragment布局中添加两个投诉用按钮。添加按钮前,先来添加显示在按钮上的字符串资源,如代码清单21-1所示。
代码清单21-1 为按钮添加字符串(strings.xml)
<string name="take">取证!</string> <string name="crime_suspect_text">选择嫌疑人</string> <string name="crime_report_text">发送陋习报告</string> </resources>
然后,分别在layout和layout-land目的fragment_crime.xml布局文件中,参照图21-2,添加两个按钮组件。注意,在布局定义示意图中,为集中注意力在新增组件的内容定义上,我们隐藏了第一个线形布局及其全部子元素。
图21-2 添加嫌疑人选取和陋习报告发送按钮
在水平模式布局中,需要将新增按钮作为子元素添加到一个新的水平线性布局中,并放在包含日期按钮和“Solved?”单选框的线性布局下方。
如图21-3所示,在比较小的设备屏幕上,新添加的按钮无法完整的显示。为解决这个问题,需要将整个布局定义放在一个ScrollView中。
图21-3 水平模式下,新增按钮没有完全显示
图21-4展示了更新后的布局定义。现在,因为ScrollView是整个布局的根元素,记得不要忘了将命名空间定义从原来的根元素移至ScrollView根元素。
图21-4 添加嫌疑人选取和陋习报告发送按钮
现在,可以预览和直接运行「陋习手记」应用,确认新增加的按钮是否显示正常。
21.2 添加嫌疑人信息至模型层
接下来,返回到Crime.java中,新增JSON常量以及存储「嫌疑人」姓名的mSuspect成员变量各一个。然后修改JSON方法,将嫌疑人信息序列化为JSON格式(以及从JSON格式还原嫌疑人信息)并添加相应的存取方法,如代码清单21-2所示。
代码清单21-2 添加嫌疑人成员变量(Crime.java)
21.3 使用格式化字符串
最后一项准备任务是创建可填充的「陋习报告」模板。应用运行前,我们无法获知具体陋习细节。因此,必须使用带有占位符(可在应用运行时替换)的格式化字符串。下面是将要使用的格式化字符串:
<string name="crime_report">%1$s! The crime was discovered on %2$s. %3$s, and %4$s
%1$s、%2$s等特殊字符串即为占位符,它们接受字符串参数。在代码中,我们将调用getString(...)方法,并传入格式化字符串资源ID以及四个其他字符串参数(与要替换的占位符顺序一致),如下所示:
String report = getString(R.string.crime_report, mCrime.getTitle(), dateString, solvedString, suspect);
首先,在strings.xml中,添加代码清单21-3所示的字符串资源。(注:此时字符串资源不能用中文,否则点击「发送陋习报告」按钮是出现异常。)
代码清单21-3 添加字符串资源(strings.xml)
然后,在CrimeFragment.java中,添加getCrimeReport()方法创建四段字符串信息,并返回拼接完整的陋习报告文本信息,如代码清单21-4所示。
代码清单21-4 新增getCrimeReport()方法(CrimeFragment.java)
至此,准备工作全部完成了,接下来学习如何使用隐式intent。
21.4 使用隐式 intent
不论使用隐式还是显示的 Intent
,都需要向操作系统描述需要处理的任务。使用显式intent,我们需明确地告诉操作系统要启动的activity类名。下面是之前创建过的显式intent:
Intent i = new Intent(getActivity(), CrimeCameraActivity.class);startActivity(i);
而使用隐式 intent,只需向操作系统描述清楚我们的工作意图。操作系统会去启动那些对外宣称能够胜任工作任务的activity。如果操作系统找到多个符合的activity,用户将会看到一个可选应用列表,然后就看用户如何选择了。
21.4.1 隐式intent的组成
一个隐式的intent由多个部分组成,我们使用它们来定义工作任务,下面是两种不同隐式intent定义:
// 发送信息Implicit intent
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_TEXT, someData);
startActivity(i);
// 访问网页Implicit intent
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
各主要的部分的含义如下:。
- 要执行的操作:通常以
Intent
类中的常量来表示,如访问某个网页,须使用Intent.ACTION_VIEW
;要发送邮件,可使用Intent.ACTION_SEND
。 - 要访问数据的位置:可能是设备以外的资源,如某个网页的URL(网址),也可能是指向某个文件的URI(Uniform Resource Identifier, 简称"URI")。
- 操作涉及数据的类型:指的是MIME形式的数据类型,如text/plain 、text/html、audio/mpeg3等。如果一个intent包含某类数据的位置,那么通常可以从中推测出数据的类型。
注:MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。
是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。如声音、视频和图像等
注:URI,类似一个资源的地址
Web上可用的每种资源, 如HTML文档、图像、视频片段、程序等 都由一个通用资源标识符(Uniform Resource Identifier, 简称"URI")进行定位。如https://www.baidu.com/img/bdlogo.png代表下面的图片:
4. 可选类别。
如果操作用于描述具体要做什么,那么类别通常用来描述我们是何时、何地或者说如何使用某个activity的。Android的android.intent.category.LAUNCHER
类别表明,activity应该显示在顶级应用启动器中。而android.intent.category.INFO
类别表明,虽然activity向用户显示了包信息,但它不应该显示在启动器中。
一个用来查看某个网址的简单隐式intent会包括一个Intent.ACTION_VIEW
操作,以及某个具体URL网址的uri
数据。
基于以上信息,操作系统将启动一个适用的APP及其对应activity(如微信里的发送信息的Activity)。如果有多个适用的APP可选,用户可自行如何选择。如图21-4-1
图21-4-1 多个适用应用可选,用户可自行选择
通过对配置文件中的intent过滤器设置,一个activity会对外宣称自己是一个适合处理ACTION_VIEW的activity。例如,如果你要开发一款浏览器应用,为响应ACTION_VIEW操作,你需要在activity声明中包含以下intent过滤器:
<activityandroid:name=".BrowserActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><data android:scheme="http" android:host="www.bignerdranch.com" /></intent-filter></activity>
DEFAULT
类别必须明确地在intent过滤器中进行设置。intent过滤器中的action
元素告诉操作系统,activity能够处理指定的任务. DEFAULT
类别告诉操作系统,activity愿意处理某项任务。DEFAULT
类别实际隐含添加到了几乎每一个隐式intent中。(唯一的例外是LAUNCHER
类别。)
如同显式intent,隐式intent也可以包含extra信息。不过,操作系统在寻找适用的activity时,它不会使用任何附加在隐式intent上的extra。
注意,隐式intent的操作和数据部分也可以与显式intent联合起来使用。这相当于要求特定activity去处理特定任务。
21.4.2 发送陋习报告
在「陋习手记」应用中,通过创建一个发送陋习报告的隐式intent,我们来看看隐式intent是如何工作的。陋习报告是由字符串组成的文本信息,我们的任务是发送一段文字信息,因此,隐式intent的操作是ACTION_SEND
。它不指向任何数据,也不包含任何类别,但会指定数据类型为text/plain
。
text/html & text/plain的区别?
需要了解的概念
Content-Type:用于定义用户的浏览器或相关设备如何显示将要加载的数据,或者如何处理将要加载的数据
MIME:MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
text/html的意思是将文件的content-type设置为text/html的形式,浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理。
text/plain的意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理。
在CrimeFragment.onCreateView(...)
方法中,首先以资源ID引用「发送陋习报告」按钮并为其设置一个监听器。然后在监听器接口实现中,创建一个隐式intent并传入startActivity(Intent)
方法,如代码清单21-5所示。
代码清单21-5 发送陋习报告(CrimeFragment.java)
以上代码使用了一个接受字符串参数的Intent
构造方法,我们传入的是一个定义操作的常量。取决于要创建的隐式intent类别,也可以使用一些其他形式的构造方法。其他全部intent构造方法及其使用说明,可以查阅Intent
参考文档。因为没有接受数据类型的构造方法可用,所以必须专门设置它。
报告文本以及报告主题字符串是作为extra附加到intent上的。注意,这些extra信息使用了Intent
类中定义的常量。因此,任何响应该intent的activity都知道这些常量,自然也知道如何使用它们关联的值。
运行「陋习手记」APP并点击「发送陋习报告」按钮。因为刚创建的intent会匹配设备上的许多activity,我们可能看到一个候选activity列表,类似图21-5所示。
图21-5 愿意发送陋习报告的全部activity
从列表中作出选择后,可以看到,陋习报告信息都加载到了所选应用中,接下来,只需填入地址,点击发送即可。
然而,有时我们可能看不到候选activity列表。出现这种情况通常有两个原因,要么是针对某个隐式intent设置了默认响应应用,要么是设备上只有唯一一个activity可以响应隐式intent。
通常,对于某项操作,最好使用用户的默认应用。不过,在「陋习手记」中,针对ACTION_SEND
操作,应该总是将选择权交给用户。要知道,也许今天用户想低调处理问题,只采取邮件的形式发送陋习报告,而明天很可能就改变主意了,他或她更希望通过微信公开抨击那些公共场所的陋习。
使用隐式intent启动activity时,也可以创建一个每次都显示的activity的选择器。和以前一样创建一个隐式intent后,调用以下Intent
方法并传入创建的隐式intent以及用作选择器标题的字符串:
public static Intent createChooser(Intent target, String title)
然后,将createChooser(...)
方法返回的intent传入startActivity(...)
方法。
在CrimeFragment.java中,创建一个选择器显示响应隐式intent的全部activity,如代码清单21-6所示。
代码清单21-6 使用选择器(CrimeFragment.java)
运行CriminalIntent应用并点击Send Crime Report按钮。可以看到,只要有多个activity可以处理隐式intent,我们都会得到一个候选activity列表,如图21-6所示。
图21-6 通过选择器选择应用发送文本信息
21.4.3 获取联系人信息
现在,创建另一个隐式intent,实现让用户从联系人应用里选择嫌疑人。新建的隐式intent将由操作以及数据获取位置组成。操作为Intent.ACTION_PICK
。联系人数据获取位置为ContactsContract.Contacts.CONTENT_URI
。简言之,我们是请求Android协助从联系人数据库里获取某个具体联系人。
因为要获取启动activity的返回结果,所以我们调用startActivityForResult(...)
方法并传入intent和请求码。在CrimeFragment.java中,新增请求码常量和按钮成员变量,如代码清单21-7所示。
代码清单21-7 添加suspect按钮成员变量(CrimeFragment.java)
在onCreateView(...)
方法的末尾,引用新增按钮并为其设置监听器。在监听器接口实现中,创建一个隐式intent并传入startActivityForResult(...)
方法。最后,如果Crime
有与之关联的联系人,那么就将其姓名显示在按钮上,如代码清单21-8所示。
代码清单21-8 发送隐式intent(CrimeFragment.java)
运行CriminalIntent应用并点击Choose Suspect按钮。我们会看到一个类似图21-7所示的联系人列表。
图21-7 包含嫌疑人的联系人列表
注意,如果设备上安装了其他联系人应用,那么应用界面可能会有所不同。这里,我们又一次受益于隐式intent。可以看到,从当前应用中调用联系人应用时,我们无需知道应用的名字。因此,用户可以安装任何喜爱的联系人应用,操作系统会负责找到并启动它。
1. 从联系人列表中获取联系人数据
现在,我们需要从联系人应用中获取返回结果。很多应用都在共享联系人信息,因此,Android提供了一个深度定制的API用于处理联系人信息,这主要是通过ContentProvider
类来实现的。该类的实例封装了联系人数据库并提供给其他应用使用。我们可以通过一个ContentResolver
访问ContentProvider
。
前面,activity是以需返回结果的方式启动的,所以我们会调用onActivityResult(...)
方法来接收一个intent。该intent包括了数据URI。该数据URI是一个指向用户所选联系人的定位符。
在CrimeFragment.java中,将以下代码添加到onActivityResult(...)
实现方法中,如代码清单21-9所示。
代码清单21-9 获取联系人姓名(CrimeFragment.java)
代码清单21-9创建了一条查询语句,请求返回全部联系人的显示名字。然后在联系人清单里找到有此陋习的人。单击某个联系人,他的名字作为嫌疑人,显示在「选择嫌疑人」按钮上,如图21-7-1所示:
图21-7-1嫌疑人名字,显示在「选择嫌疑人」按钮上
因为已经知道此陋习的相关记录:陋习标题、发生时间、是否解决和嫌疑人。在发送消息时自动完善我们之前定的的字符串模板。如下所示:
<string name="crime_report">%1$s! The crime was discovered on %2$s. %3$s, and %4$s
String report = getString(R.string.crime_report, mCrime.getTitle(), dateString, solvedString, suspect);
如果你选择发送消息,我们的APP将利用陋习记录和字符串模板,填充一条完整的字符串.此字符串,是我们即将发送的陋习报告内容,如图21-7-2所示:
图21-7-2 利用陋习记录和字符串模板生成陋习报告
(联系人数据库本身是一个比较复杂的主题,这里不会展开讨论。如果需要了解更多信息,可以阅读Contacts Provider API指南:http://developer.android.com/guide/topics/providers/contactsprovider.html)