展会信息港展会大全

Android引入了一个名为Intent的概念用来唤醒各种组件
来源:互联网   发布日期:2016-01-14 09:29:52   浏览:4419次  

导读: Android引入了一个名为Intent的概念用来唤醒各种组件。Android中的组件包括:activities(UI 组件),services(后台代码),broadcast recei...

Android引入了一个名为Intent的概念用来唤醒各种组件。Android中的组件包括:activities(UI 组件),services(后台代码),broadcast receivers(用来接收广播消息的代码)和content providers(用来抽象数据的代码)。

Android的Intent基础

尽管将intent作为唤醒其他组件机制是很好理解的,不过Android还赋予了Intent这个概念更多的含义。你可以在你的应用中通过intent唤醒其他的应用,还可以唤醒应用内部及外部的各种组件。你可以通过intent发起事件,而其它人则通过一种类似发布与订阅的方式来做出相应。你可以通过intent唤醒闹钟提醒。

注:什么是intent,简而言之,可以说intent就是一个动作,并且该动作上负载着数据。

从最简单的水平来看,intent是一个让Android去做(唤醒)什么的动作。Android唤醒某个动作取决于为该动作注册了什么内容。假设你的Activity内容如下:

public class BasicViewActivity extends Activity

{

@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.some_view);

}

}//eof-class

some_view布局应该指向res/layout下的一个合法文件。Android然后允许你通过在该应用的manifest文件中注册这个activity,这样改activity就可以被唤醒了。注册方法如下:

<activity android:name=".BasicViewActivity"

android:label="Basic View Tests">

<intent-filter>

<action android:name="com.androidbook.intent.action.ShowBasicView"/>

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

</intent-filter>

</activity>

这种注册方法不仅仅包括注册一个activity,还包括一个可以唤醒该activity的动作(action)。Activity的设计者通常会为这个action取个名字,并且将这个action作为intent filter(意图过滤器)的一部分。本章后面会介绍更多关于intent的内容。

现在你已经指定了一个activity并通过action对其进行了注册,这样就可以通过一个intent来唤醒这个BasicViewActivity了。

public static void invokeMyApplication(Activity parentActivity)

{

String actionName= "com.androidbook.intent.action.ShowBasicView";

Intent intent = new Intent(actionName);

parentActivity.startActivity(intent);

}

注:action名字的通常形式为:<你的包名>.intent.action.具体名称。

一旦BasicViewActivity被唤醒,它就可以查看唤醒它的intent了。下面是重写的可以处理唤醒BasicViewActivity的intent的代码:

public class BasicViewActivity extends Activity

{

@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.some_view);

Intent intent = this.getIntent();

if (intent == null)

{

Log.d("test tag", "This activity is invoked without an intent");

}

}

}//eof-class

Android中的Intent

你可以通过测试来用intent唤醒Android自带的一些应用。http://developer.android.com/guide/appendix/g-appintents.html页面介绍了一些可以被唤醒的Android自带应用。

注:这些名单可能根据Android发布版本不同而发生变化。

剩下的可被唤醒的应用包括:

浏览器应用,用来打开浏览器窗口。

打电话应用。

拨号键盘,用户通过其拨打号码。

地图应用,用来显式给定经纬度的地址。给定经纬度的地址。

谷歌街景应用。

Listing5-1介绍如何根据这些应用发布的intents来唤醒它们:

Listing 5–1. Exercising Android’s Prefabricated Applications

public class IntentsUtils

{

public static void invokeWebBrowser(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_VIEW);

intent.setData(Uri.parse("http://www.google.com"));

activity.startActivity(intent);

}

public static void invokeWebSearch(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);

intent.setData(Uri.parse("http://www.google.com"));

activity.startActivity(intent);

}

public static void dial(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_DIAL);

activity.startActivity(intent);

}

public static void call(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_CALL);

intent.setData(Uri.parse("tel:555–555–5555"));

activity.startActivity(intent);

}

public static void showMapAtLatLong(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_VIEW);

//geo:lat,long?z=zoomlevel&q=question-string

intent.setData(Uri.parse("geo:0,0?z=4&q=business+near+city"));

activity.startActivity(intent);

}

public static void tryOneOfThese(Activity activity)

{

IntentsUtils.invokeWebBrowser(activity);

}

}

只要你创建一个简单的带有菜单的activity,你就可以通过 tryOneOfThese(Activity activity)方法来测试上述代码。创建一个简单的菜单十分简单,见Listing5-2:

Listing 5–2. A Test Harness to Create a Simple Menu

public class MainActivity extends Activity

{

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

TextView tv = new TextView(this);

tv.setText("Hello, Android. Say hello");

setContentView(tv);

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

super.onCreateOptionsMenu(menu);

int base=Menu.FIRST; // value is 1

MenuItem item1 = menu.add(base,base,base,"Test");

return true;

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

if (item.getItemId() == 1) {

IntentUtils.tryOneOfThese(this);

}

else {

return super.onOptionsItemSelected(item);

}

return true;

}

}

注:可以参考第二章来创建Android工程,编译并运行应用。你也可以通过第7章的前半部分来查看更多创建菜单的代码。或者你可以通过本章末尾关于这部分的eclipse工程代码链接下载代码。不过,当你下载完代码后,这个主要的activity可能有些不同,但是要表达的意思是相同的。在示例代码中,我们还通过XML文件来创建菜单。

Intent的组成

另一个确定的可以更好的理解intent的方法就是查看intent对象的组成结构。一个intent包括action、data(通过URI表示)、一个用来存储外部数据的键值对映射和一个显式的类名(称为组件名称component name)。只要intent中至少包含上述内容的一个,其它的内容都是可选的。

注:如果一个intent包含一个组件名称,那么该intent被称为显式intent。如果intent不包含组件名称,而是依赖于其它部分,如action、data,那么该intent被称为隐式intent。随着我们进一步深入,你将会发现这两种intent是有着细微的差别的。

Intents和数据URIs

现在我们已经看到了最简单的intent,这种intent仅需要一个action名称。Listing5-1的ACTION_DIAL就是一例。要唤醒拨号器,我们除了设置intent的aciton之外,不需要其他任何设置。

public static void dial(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_DIAL);

activity.startActivity(intent);

}

与ACTION_DIAL不同,ACTION_CALL用来通过给定的号码来进行呼叫,而给定的号码则存储在名为Data的参数中。这个参数指向一个URI,而这个URI又指向一个号码。

public static void call(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_CALL);

intent.setData(Uri.parse("tel:555–555–5555"));

activity.startActivity(intent);

}

Intent的action是一个字符串或者字符串常量,通常将java的包名作为前缀。

Intent的data部分通常并不是一个data数据,而是指向data的引用。data部分通过字符串来表示URI。Intent的URI可以包含参数,与网页URL类似。

每个被action唤醒的activity都应该指定URI的格式。本例中,“call”方法决定了需要什么样的数据URI。通过该URI,可以解析出电话号码。

注:被唤醒的activity也可以使用这个URI作为一个数据指针,从而解析出数据并加以使用。对于媒体,如音频、视频和图像来说正是如此。

通用Actions

Intent.ACTION_CALL和Intent.ACTION_DIAL会很容易误导我们,让我们认为action和其唤醒的对象之间存在着一对一的关系。为了反证这个观点,我们看一个相反的例子,如Listing5-1中的代码所示:

public static void invokeWebBrowser(Activity activity)

{

Intent intent = new Intent(Intent.ACTION_VIEW);

intent.setData(Uri.parse("http://www.google.com"));

activity.startActivity(intent);

}

请注意其action仅仅是简单的定义为ACTION_VIEW。Android如何通过这个宽泛的action来确定要唤醒那个activity呢。这种情况下,Android就不仅仅依靠这个通用的action了,还需要依赖URI的特性。Android会查看URI的scheme部分,这部分恰好是http,然后查询所有注册的activities,看谁可以理解这个scheme。这样就可以查询哪个activity可以处理View动作并被唤醒。由于这个原因,browser activity就应该注册一个View intent,并且包含http作为数据scheme。在manifest文件中声明这种intent的方法如下:

<activity......>

<intent-filter>

<action android:name="android.intent.action.VIEW" />

<data android:scheme="http"/>

<data android:scheme="https"/>

</intent-filter>

</activity>

你可以通过http://developer.android.com/guide/topics/manifest/data-element.html 来学习更多的关于数据节点的属性。

Intent 过滤器节点中数据xml子节点的子元素或特性包括下面内容:

host

mimeType

path

pathPattern

pathPrefix

port

scheme

mimeType将是你经常用到的属性。例如下面的例子表明展示notes列表的activity的intent filter的mimeType是一个包含多个notes的目录。

<intent-filter>

<action android:name="android.intent.action.VIEW" />

<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />

</intent-filter>

该intent filter可以读作“唤醒该activity来浏览notes集合”。

另一方法,如果只展示一个单独note,其intent filter使用的MIME类型为单条目类型:

<intent-filter>

<action android:name="android.intent.action.VIEW" />

<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />

</intent-filter>

该intent filter可以读作“唤醒该activity来浏览单个note”。

使用Extra信息

Intent除了action和数据等主要属性外,还有一个名为extras的属性。Extra可以为接收该intent的组件提供更多的信息。Extra数据时以键值对的形式存在:键名通常以包名开始,而值可以是基本数据类型或其他任意实现了android.os.Parcelable的对象。这个extra信息在Android中被称为android.os.Bundle。

Intent类提供了两种方法来访问extra Bundle:

// 从intent获取bundle

Bundle extraBundle = intent.getExtras();

// 将一个bundle放进intent中

Bundle anotherBundle = new Bundle();

// 为bundle填充键值对

。。。

// 将bundle设置仅intent中

intent.putExtras(anotherBundle);

getExtras()很简单明了:就是返回intent所持有的bundle。putExtras()首先检查当前intent是否已持有bundle,如果有,则将新的bundle中的键值对转移到原有的bundle中,如果没有,则先创建一个bundle,然后将新的bundle中的键值对复制到新创建的bundle中。

注:putExtra方法是复制原有bundle内容,而非引用。这样,如果你稍后修改传入的bundle也不会修改已经存入intnet的bundle。

你可以用很多方法来存储基本数据类型到bundle中。下面是一些将简单数据类型存入bundle中的方法:

putExtra(String name, boolean value);

putExtra(String name, int value);

putExtra(String name, double value);

putExtra(String name, String value);

还有一些不是太简单的数据类型的例子:

//simple array support

putExtra(String name, int[] values);

putExtra(String name, float[] values);

//Serializable objects

putExtra(String name, Serializable value);

//Parcelable support

putExtra(String name, Parcelable value);

//Add another bundle at a given key

//Bundles in bundles

putExtra(String name, Bundle value);

//Add bundles from another intent

//copy of bundles

putExtra(String name, Intent anotherIntent);

//Explicit Array List support

putIntegerArrayListExtra(String name, ArrayList arrayList);

putParcelableArrayListExtra(String name, ArrayList arrayList);

putStringArrayListExtra(String name, ArrayList arrayList);

在接收侧,会有相应的获取数据的方法。这些方法将键的名字作为参数。在下面网址可以查询更多相关内容:

http://developer.android.com/reference/android/content/Intent.html#EXTRA_ALARM_COUNT.

下面我们看一下该网址列举的在发送email时涉及到的两个例子:

EXTRA_EMAIL:你可以通过该键值获取emal地址集合。该键值为android.intent.extra.EMAIL。它应该指向一个包含email文本地址的数组。

EXTRA_SUBJECT:你可以通过该键值获取邮件的主题。该键值为android.intent.extra.SUBJECT.它指向一个包含主题的字符串。

使用组件直接唤醒Activity

你已经看到一些用intent唤醒activity的方法了。你见到了用一个显式的action唤醒activity,也见到了用一个通用的action借助data URI的帮助来唤醒activity。Android还提供了一种更为直接的方法来唤醒activity:你可以直接通过组件名称来实现,而组件名就是对象的包名和类名的一种抽象。下面是Intent类指定组件名的方法:

setComponent(ComponentName name);

setClassName(String packageName, String classNameInThatPackage);

setClassName(Context context, String classNameInThatContext);

setClass(Context context, Class classObjectInThatContext);

而最终这些方法都是setComponent(ComponentName name) 方法的简写。

组件名称包含包名和类名。例如,下面就是一个唤醒模拟器自带的cotacts activity的方法:

Intent intent = new Intent();

intent.setComponent(new ComponentName(

"com.android.contacts"

,"com.android.contacts.DialContactsEntryActivity");

startActivity(intent);

请注意包名和类名是全称,且在传入intent之前首先用于构建ComponentName。

你也可以直接使用类名,而无需用组件名来唤醒activity。再次看下面的activity:

public class BasicViewActivity extends Activity

{

@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.some_view);

}

}//eof-class

你可以通过下面方法来唤醒此activity:

Intent directIntent = new Intent(activity, BasicViewActivity.class);

activity.start(directIntent);

不管你用什么方法来唤醒activity,你都有在AndroidManifest.xml文件中注册这个activity:

<activity android:name=".BasicViewActivity"

android:label="Test Activity">

注:如果通过类名来唤醒activity,则不需要任何intent filter。正如前面所说,这种intent称为显式intent。由于这种显式的intent已经指明了要唤醒的组件的全称,则不需要其他额外的部分。

理解intent category

你可以将activities划分为多个类别,这样你可以根据类别名来搜索它们。例如,启动阶段,Andorid会寻找category为CATEGORY_LAUNCHER的activity,然后将该activity的名字及图表放在主界面上用于启动。

还有一个例子:android会搜索一个category为CATEGORY_HOME的activity,在开始阶段作为主屏幕显示。类似的,如果activity的类别名为CATEGORY_GADGET,则其会作嵌入到其它activity中进行复用。

以CATEGORY_LAUNCHER为例,类别名的格式如下:

android.intent.category.LAUNCHER

你需要知道这些具体的字符串,因为category将作为intent filter的一部分写入AndroidManifest.xml文件中。下面是一个例子:

<activity android:name=".HelloWorldActivity"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

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

</intent-filter>

</activity>

注:activity可能有一些特定的属性来进行限制或者使用,例如你是否想将activity嵌入到父activity中。这种activity属性也是通过category来进行设定的。

下面我们看一下android预定义的category,以及如何使用它们:

Table 5–1.Activity Categories 及其描述

category名称 描述

CATEGORY_DEFAULT 如果activity想被一个隐式的intent唤醒,那么可以声明为CATEGORY_DEFAULT。如果不定义该属性,那么activity需要显式的intent进行唤醒。因此,你可以看到那些被通用的action或其他action唤醒的activity使用缺省的category说明

CATEGORY_BROWSABLE 用来想浏览器声明当其被唤醒时不会影响到浏览器的安全需求

CATEGORY_TAB 该activity被嵌在一个父tabbed activity中

CATEGORY_ALTERNATIVE activity使用CATEGORY_ALTERNATIVE用来浏览特定类型的数据。当你查阅文档是,这些部分通常作为一个可选菜单。例如打印界面相对于其他界面可以称之为alternative

CATEGORY_SELECTED_ALTERNATIVE activity使用CATEGORY_ALTERNATIVE用来浏览特定类型的数据。这与列出一系列的文本文件或html文件编辑器很类似。

CATEGORY_LAUNCHERactivity使用CATEGORY_LAUNCHER属性可以使其在launcher(桌面)中显示

CATEGORY_HOME activity使用CATEGORY_HOME后可以作为主屏幕。特别的,应该只有一个activity具有该属性,如果有多个,系统会让你做出选择。

CATEGORY_PREFERENCE activity使用CATEGORY_PREFERENCE属性表明其为preference activity。这样该activity将作为preference screen的一部分进行显示

CATEGORY_GADGET activity使用CATEGORY_GADGET就可以嵌入到父activity中

CATEGORY_TEST表明这是一个测试activity

CATEGORY_EMBED 该属性已被CATEGORY_GADGET取代,保留只为后向兼容

你可以在下面网址阅读更多关于category的介绍:

http://developer.android.com/android/reference/android/content/Intent.html#CATEGORY_ALTERNATIVE.

当你唤醒一个activity时,你可以通过设置category来确定要唤醒什么类型的activity。或者你可以搜索到满足特定category的activity。下面是一个获取与CATEGORY_LAUNCHER相匹配的activity的方法:

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);

mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

PackageManager pm = getPackageManager();

List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);

PackageManager是一个可以让你获取到与特定category匹配的activity而又不用将其唤醒的关键类。你可以遍历返回的activities,当遇到合适的activity可以再将其唤醒。下面是一个如何遍历activities,以及如何唤醒其中相匹配的一个activity的例子,我们用了一个随机的名字来进行测试:

for(ResolveInfo ri: list)

{

//ri.activityInfo.

Log.d("test",ri.toString());

String packagename = ri.activityInfo.packageName;

String classname = ri.activityInfo.name;

Log.d("test", packagename + ":" + classname);

if (classname.equals("com.ai.androidbook.resources.TestActivity"))

{

Intent ni = new Intent();

ni.setClassName(packagename,classname);

activity.startActivity(ni);

}

}

你也可以仅仅依靠category来唤醒一个activity,如CATEGORY_LAUNCHER.

public static void invokeAMainApp(Activity activity)

{

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);

mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

activity.startActivity(mainIntent);

}

会有不止一个activity与之匹配,那么android会选择哪一个呢?为了解决这个问题,Android弹出一个相匹配的activity列表(complete action using)对话框,这样你就可以选择其中一个运行。

下面是另一个去往home主页的例子:

//Go to home screen

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);

mainIntent.addCategory(Intent.CATEGORY_HOME);

startActivity(mainIntent);

如果你不喜欢android默认的主页,那么你可以自己写一个,并与category HOME进行标记。这样,再使用前面的代码,就会弹出选项,让你选择主页。这是因为现在已经注册有不止一个主页了。

//Replace the home screen with yours

<intent-filter>

<action android:value="android.intent.action.MAIN" />

<category android:value="android.intent.category.HOME"/>

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

</intent-filter>

Intent唤醒组件的规则

到目前为止,我们已经讨论了intent的许多方面。简单总结一下就是:actions、data URIs、extra data和category。有了这些,android通过intent filter,依据多种策略来匹配最合适的activity。

最顶层是与intent相关联的组件名。如果设置了这个,那么intent就是显式intent。对于的intent,只有组件名是重要的,其他的所有属性都可以忽略。如果一个intent没有指明组件名,那么该intent被称为隐式intent。而隐式intent的处理规则则有很多。intent。对于显式的intent,只有组件名是重要的,其他的所有属性都可以忽略。如果一个intent没有指明组件名,那么该intent被称为隐式intent。而隐式intent的处理规则则有很多。

最基本的规则就是传入的intent的action、category和data属性必须与intent filter中声明的属性相匹配。Intent filter与intent不同,可以声明多个actions、categories和data属性。这表明,同一个intent filter可以匹配多个intent,也就是说一个activity可以对多个intent做出反应。不过,“匹配”这个概念在action、category和data中并不相同。下面我们看一下对于不同的属性匹配规则有何不同。

Action

如果一个intent设定了action属性,那么intent的filter中必须有相同的action或者不包含任何action。所以,对于不定义任何action的intent filter可以与任何action相匹配。

如果一个intent filter定义了多个actions,那么至少需要含有一个action与传入intent的action相匹配。

Data

如果intent filter中没有定义data属性,那么它将不会与任何定义了data属性的intent相匹配。这说明该intent filter只寻找没有定义data属性的intent。

Filter中缺少data属性和缺少action属性是截然相反的。如果没有action属性,那么所有的action都会匹配。如果没有data属性,那么即使data只有1个bit,也不会匹配。

Data类型

为了匹配data的类型,传入的intent的data的类型必须与intent filter定义的数据类型相同。Intent中的data类型必须在intent filter中列出。

出入的数据类型由两种方式决定。第一种:如果data URI是一个content或者file URI,那么content provider或者Android本身将会识别出其类型。第二种:检查intent中显式定义的数据类型。对于第二章,传入的intent不应该设置data URI,因为当intent的setType方法调用时会自动处理data URI。

Android也允许其MIME类型的子类型用星号(*)来代替所有的子类型。

另外,data类型时大小写敏感的。

Data Scheme

为了匹配data scheme,传入的intente的data scheme必须与intent filter中的scheme属性相匹配。也就是说传入的data scheme必须存在于intent filter中。

传入的data scheme是intent的data URI的第一部分。Intent中没有设置scheme的方法。其仅能从传入的data URI中(如http://www.somesite.com/somepath. )解析出来。

如果传入的intent的data URI是content:或者file:,那么就认为其与intent filter相匹配,而不必考虑scheme、domain和path。根据Android SDK,这样的原因是所有的组件都被设计为知道如何从content或者file URLs中读取数据。换句话说,所有的组件都被设计为支持这两种类型的URLs。

Scheme也是大小写敏感的。

如果intent filter中没有指明authority属性,那么传入的任何URI的authority(域名)都与之匹配。如果在filter中指定了authority,例如www.somesite.com,那么其中一个scheme和一个authoriy必须与传入的intent中的data URI相匹配。

例如,我们在intent filter中指定authority是www.somesite.com,scheme是https。那么intent中的http://www.somesite.com将不会与之匹配。因为http并没有被filter指定为支持的scheme。

Authority也是大小写敏感的。

Data Path

如果intent filter中没有定义data path,表示与任意的传入的intent的data path相匹配。如果filter中指定了一个data path,例如somepath,那么一个scheme,一个authority和一个path就应该与传入的intent的data URI相匹配。

换而言之,scheme、authority和path一起来确定某个传入的intent是否合法,例如http://www.somesite.com/somepath.所以,scheme、authority和path并不是独立工作,而是一起工作。

Path也是大小写敏感。

Intent Category

所有传入的intent的category必须在intent filter中列举出来。在filter中可以包含多个categories。如果filter中没有设置category,那么也只能匹配没有设置category的intent。

不过,这里有一个忠告。Android这样处理传入到startActivity中的隐式intent:默认intent至少包含一个category,也就是android.intent.category.DEFAULT。startActivity()中的代码会寻找filter中包含DEFAULT Category的activities。所以,任何想要被隐式intent唤醒的activity都必须在intent filter中设定DEFAULT Category。

即使一个activity的intent filter中不包含default category,如果你知道其显式的组件名称,你也可以向laucher那样启动该activity。如果你不考虑default category而显式的搜索与intent匹配的activity,你也可以用那种方法启动这些activities。

这样,DEFAULT category是根据startActivity()的实现的产物,而不是filter中固有的行为。

还有一个利好的消息:如果activity仅仅想被laucher唤醒,那么default category并不是必须的。所以这些activities仅仅设置了MAIN和LAUCHER category。不过这些activities中也可以设置DEFAULT category。

以ACTION_PICK为例

到现在我们已经介绍了一些用来唤醒activity而不需要返回数据的intents或者actions。下面我们再介绍一种稍微复杂点的能够在唤醒activity后返回数据的action。ACTION_PICK就是其中一个。

ACTION_PICK的目的是唤醒一个activity,该activity列出一系列条目,然后运行你选择其中的某个条目。一旦用户选中了某个条目,则该activity应该返回选中条目的URI给调用者。这样就可以复用UI的功能来进行选择了。

你应该通过MIME类型指明要选择的条目集合,该MIME类型指向Android的content cursor。其URI的MIME类型应该类似于下面的形式:

vnd.android.cursor.dir/vnd.google.note

Activity负责从基于URI的content provider中提取数据。这也是为什么需要尽可能的把数据封装到content provider中的原因。

对于返回数据的action,我们不能使用startActivity(),因为startActivity()并不返回数据。startActivity()不返回数据的原因是该方法通过一个独立的线程来启动activity,而将主线程继续用来处理事务。换句话说,startActivity()是一个异步的方法,且没有回调函数,这样就无法得知被唤醒的activity的状态。如果你想要返回数据,那么可以使用startActivity()的一个变形startActivityForResult(),该方法带有一个回调函数。

让我们看一下startActivityForResult()的定义:

public void startActivityForResult(Intent intent, int requestCode)

这个方法启动一个activity,并且你想从该activity中获取返回数据。如果这个activity退出后,原来的activity中的onActivityResult()方法将被调用,并且返回之前传入的requestCode。该方法定义如下:

protected void onActivityResult(int requestCode, int resultCode, Intent data)

其中requestCode就是你传入startActivityForResult()的参数。而resultCode可以是RESULT_OK, RESULT_CANCELLED或者用户定义的数字。用户自定义数字应该从RESULT_FIRST_USER开始。Intent参数包含activity返回的其他数据。在ACTION_PICK例子中,该intent返回指向某个条目的data URI。

Listing5-3是一个返回结果的activity的例子。

注:Listing5-3中的例子认为你已经安装了android sdk包中的NotePad应用。我们后面会给出链接来告诉你如何下载该应用。

Listing 5–3. Returning Data After Invoking an Action

public class SomeActivity extends Activity

{

.....

.....

public static void invokePick(Activity activity)

{

Intent pickIntent = new Intent(Intent.ACTION_PICK);

int requestCode = 1;

pickIntent.setData(Uri.parse(

"content://com.google.provider.NotePad/notes"));

activity.startActivityForResult(pickIntent, requestCode);

}

protected void onActivityResult(int requestCode

,int resultCode

,Intent outputIntent)

{

//This is to inform the parent class (Activity)

//that the called activity has finished and the baseclass

//can do the necessary clean up

super.onActivityResult(requestCode, resultCode, outputIntent);

parseResult(this, requestCode, resultCode, outputIntent);

}

public static void parseResult(Activity activity

, int requestCode

, int resultCode

, Intent outputIntent)

{

if (requestCode != 1)

{

Log.d("Test", "Some one else called this. not us");

return;

}

if (resultCode != Activity.RESULT_OK)

{

Log.d(Test, "Result code is not ok:" + resultCode);

return;

}

Log.d("Test", "Result code is ok:" + resultCode);

Uri selectedUri = outputIntent.getData();

Log.d("Test", "The output uri:" + selectedUri.toString());

//Proceed to display the note

outputIntent.setAction(Intent.ACTION_VIEW);

startActivity(outputIntent);

}

RESULT_OK, RESULT_CANCELED,和 RESULT_FIRST_USER常量都在Activity类中定义。其对应的数值为:

RESULT_OK = -1;

RESULT_CANCELED = 0;

RESULT_FIRST_USER = 1;

为了保证PICK操作能够成功,实现者必须显式的指明PICK需要什么。我们看一下在google的NotePad例子中是如何实现的。当列表中的条目被选中时,会检查传入的用来唤醒activity的intent的action是否是ACTION_PICK.如果是,则选中项目的URI将被放入一个新的intent中并通过setResult()方法传回。

@Override

protected void onListItemClick(ListView l, View v, int position, long id) {

Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);

String action = getIntent().getAction();

if (Intent.ACTION_PICK.equals(action) ||

Intent.ACTION_GET_CONTENT.equals(action))

{

// The caller is waiting for us to return a note selected by

// the user. They have clicked on one, so return it now.

setResult(RESULT_OK, new Intent().setData(uri));

} else {

// Launch activity to view/edit the currently selected item

startActivity(new Intent(Intent.ACTION_EDIT, uri));

}

}

以GET_CONTENT Action为例

ACTION_GET_CONTENT与ACTION_PICK类似。在ACTION_PICK的例子中,你指定一个URI,使其指向多个条目的集合,例如多个notes的集合。你期待着选取一个条目然后将其返回给调用者。而在ACTION_GET_CONTENT例子中,你告诉Android你想要一个特定MIME类型的条目。Android会寻找那些能够创建该MIME类型条目的activities或者已经存在该MIME类型的条目可以从中选择的activities。

使用ACTION_GET_CONTENT,你可以通过下面的代码从notes集合中选取一个NotePad应用所支持的note:

public static void invokeGetContent(Activity activity)

{

Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);

int requestCode = 2;

pickIntent.setType("vnd.android.cursor.item/vnd.google.note");

activity.startActivityForResult(pickIntent, requestCode);

}

请注意如何设置一个单条目的MIME类型。与ACTION_PICK不同,其输入的是一个data URI:

public static void invokePick(Activity activity)

{

Intent pickIntent = new Intent(Intent.ACTION_PICK);

int requestCode = 1;

pickIntent.setData(Uri.parse(

"content://com.google.provider.NotePad/notes"));

activity.startActivityForResult(pickIntent, requestCode);

}

对于负责响应ACTION_GET_CONTENT的activity,应该在intentfilter中注册相应的MIME类型。下面是sdk中NotePad应用如何实现这一点的方法:

<activity android:name="NotesList" android:label="@string/title_notes_list">

......

<intent-filter>

<action android:name="android.intent.action.GET_CONTENT" />

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

<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />

</intent-filter>

......

</activity>

至于如何处理onActivityResult,与ACTION_PICK是完全相同的。如果有多个activities都可以返回相同的MIME类型,android会弹出选择菜单供你选择。

Pending Intents介绍

Android有一个intent的变种称为Pending intent。该intent运行android的组件在某个位置将intent存储起来比便在将来使用,这样该组件可以被再次唤醒。例如,在闹钟管理器中,你想要在闹钟关闭后再开启一个服务。Android先创建一个pending intent将对应的普通intent包装起来,然后存储在某个地方,这样即使调用的进程被杀死后,这个intent也可以被传递给目标。在pending intent创建时,android会存储足够的原始进程的信息,这样在分配或唤醒时,可以查看其安全证书。

我们看一下如何创建pending intent:

Intent regularIntent;

PendingIntent pi = PendingIntent.getActivity(context, 0, regularIntent,...);

注:PendingIntent.getActivity()方法中的第二个参数叫做requestCode,本例中我们设置为0.如果两个pending intent所包装的intent一样,这个参数就可以用来区分这两个pending intent。这方面内容我们会在第20章具体讨论pending intent和闹钟时具体介绍。

对于 PendingIntent.getActivity()的名字有很多奇怪之处。这里的activity到底扮演什么角色?为什么我们在创建pending intent的时候不使用create这个词,而是使用get?

为了理解第一点,我们需要在进一步了解一下普通的intent的用途。一个普通的intent可以用来唤醒一个activity,service或broadcast receiver。(后面你会学到service和broadcast receiver)用intent来唤醒不同种类的组件本质是不同的,为了实现这个目的,android context(activity的父类)提供三种不同的方法:

startActivty(intent)

startService(intent)

sendBroadcast(intent)

由于有这几个变形,那么如果我们要想存储一个intent以后使用,android如何知道这个intent是用来唤醒activity、service还是broadcast receiver呢?这也就是为什么我们要在创建pending intent之前先指定其用途,这样就解释了下面三个方法为何如此命名:

PendingIntent.getActivity(context, 0, intent, ...)

PendingIntent.getService(context, 0, intent, ...)

PendingIntent.getBroadcast(context, 0, intent, ...)

现在我们解释一下为什么用“get”。Android存储intent并进行复用。如果你用同一个intent请求pending intent两次,你也只能得到一个pending intent。

这样,你看到PendingIntent.getActivity()的全部定义后就会稍微清晰一些了。

PendingIntent.getActivity(Context context, //originating context

int requestCode, //1,2, 3, etc

Intent intent, //original intent

int flags ) //flags

如果你的目的是获取一个不同的pending intent复本,你就要提供一个不同的requestCode。我们在第20章介绍alarm时会更详细的介绍这方面内容。如果两个intents中,处理extra bundle部分,其它都相同,那么会认为是同一个intent。如果你必须要对这样的两个其它部分一样的intents进行区分,那么就提供不同的requestCode。这样,创建的pending intent就会不同,及时其底层intent是相同的。

赞助本站

人工智能实验室

相关热词: android开发 教程

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港