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_LAUNCHER  activity使用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,应该在intent  filter中注册相应的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是相同的。

     flag参数表明当存在一个pending intent时需要做些什么,是否是返回一个null,还是重写extras,等等。下面网址可以获得更多flag的含义:


     通常情况下,你可以为requestCode何flag传入0来获取默认的属性。

     资源

     下面是一些帮助你更好理解本章内容的有用的链接:

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

介绍intents相关内容,包括常用的的actions, extras等内容介绍.

http://developer.android.com/guide/appendix/g-app-intents.html: Lists

Google应用中intents集合。这里你可以看到如何唤醒Browser, Map, Dialer和 Google Street View.

http://developer.android.com/reference/android/content/IntentFilter.html:

介绍intent filters,当你需要注册filters时非常有用。

http://developer.android.com/guide/topics/intents/intents-filters.html:

Intent filters的关键规则。

http://developer.android.com/resources/samples/get.html: 
NotePad应用下载网址,你需要该应用测试一些intents.

http://developer.android.com/resources/samples/NotePad/index.html:

NotePad应用线上代码。

www.openintents.org/: 
一个尝试集合不同第三方厂商提供的intents的网站。

 www.androidbook.com/proandroid4/projects: 
本章示例工程下载网址。其zip文件名称为ProAndroid4_ch05_TestIntents.zip. 

     总结

     本章涵盖下面内容:

     一个隐式的intent就是actions、data URIs和extras传入的显式数据的集合。

     显式的intent就是直接绑定组件名称,而忽略其他所有隐式内容。

     在Android你通过itent来唤醒Activity或其他组件。

     activity等组件通过intent filter来声明其对哪些intents做出响应。

     intents和intent filters的区分规则。

     如何用intent来启动activity。

     如何启动可以返回数据的activity。

     intent category扮演的角色。

     default category的细微差别。

     什么是pending intent?如何使用?

     pending intent的不同之处?

     如何使用PICK和GET_CONTENT这两个actions?


     复习问题

     1、你如何通过一个intent来唤醒activity?

     2、什么是显式、隐式的intents?

     3、intent的组成?

     4、你如何通过intent想接收intent的组件传入数据。

     5、你能说出android应用中的主要组件吗?

     6、intent中的data部分是直接包含数据吗?

     7、intent中的action部分可以直接引用activity或其他组件吗?

     8、当指明intent中类名时,其它部分如何处理?

     9、action.MAIN有什么含义?

     10、如果你在intent filters中不指定任何action,是否意味着其可以与所有action相匹配?

     11、如果在intent filter中不指定data,那么什么样的intents可以与之匹配?

     12、在你的intent filter中加入一个default category为何很有必要?

     13、你的laucher activity需要default category吗?

     14、你如何唤醒一个可以返回给调用者数据的activity?

     15、唤醒一个activity最快的方法是什么?

     16、action_pick和action_get_content的区别是什么?

Pro Android 4 第五章 理解Intent的更多相关文章

  1. Android开发艺术探索第五章——理解RemoteViews

    Android开发艺术探索第五章--理解RemoteViews 这门课的重心在于RemoteViews,RemoteViews可以理解为一种远程的View,其实他和远程的Service是一样的,Rem ...

  2. Pro Android 4 第六章 构建用户界面以及使用控件(一)

         目前为止,我们已经介绍了android的基础内容,但是还没开始接触用户界面(UI).本章我们将开始探讨用户界面和控件.我们先讨论一下android中UI设计的一般原理,然后我们在介绍一下an ...

  3. Android笔记(五)利用Intent启动活动

    Intent是意图的意思,分为显式 Intent 和隐式 Intent. 以下我们试图在FirstActivity中通过点击button来启动SecondActivity 1.显式Intent 在应用 ...

  4. 第五章:理解RemoteViews

    RemoteView应该是一种远程View,表示的是一个View结构,他可以在其它进程中显示. 在android中使用场景有两种:通知栏和桌面小部件 5.1 RemoteView的应用 5.1.1 R ...

  5. 《Linux命令行与shell脚本编程大全》 第五章理解shell

    5.1 1. cat /etc/passwd 可以查看每个用户自己的默认的shell程序. 2.默认的交互shell会在用户登录某个虚拟控制台终端时启动. 不过还有另外一个默认的shell是/bin/ ...

  6. 简单的学习心得:网易云课堂Android开发第五章SharedPreferences与文件管理

    一.SharedPreferences (1)SharedPreferences能够用来保存一些属于基本数据类型的数据. (2)保存数据,删除数据都是由SharedPreferences的内部接口Ed ...

  7. Android开发艺术探索》读书笔记 (5) 第5章 理解RemoteViews

    第5章 理解RemoteViews 5.1 RemoteViews的应用 (1)RemoteViews表示的是一个view结构,它可以在其他进程中显示.由于它在其他进程中显示,为了能够更新它的界面,R ...

  8. Android群英传笔记——第五章:Android Scroll分析

    Android群英传笔记--第五章:Android Scroll分析 滑动事件算是Android比较常用的效果了,而且滑动事件他本身也是有许多的知识点,今天,我们就一起来耍耍Scroll吧 一.滑动效 ...

  9. 【转】Pro Android学习笔记(十二):了解Intent(下)

    解析Intent,寻找匹配Activity 如果给出component名字(包名.类名)是explicit intent,否则是implicit intent.对于explicit intent,关键 ...

随机推荐

  1. Ruby学习: 类的定义和实例变量

    ruby是完全面向对象的,所有的数据都是对象,没有独立在类外的方法,所有的方法都在类中定义的. 一.类的定义语法 类的定义以 class 关键字开头,后面跟类名,以 end标识符结尾. 类中的方法以 ...

  2. PHP中magic_quotes_gpc和 magic_quotes_runtime区别及其反斜线转义问题

    php中关于反斜线转义: php中数据的魔法引用函数 magic_quotes_gpc  或 magic_quotes_runtime      设置为on时,当数据遇到 单引号' 和 双引号&quo ...

  3. Asteroids(最小点覆盖)

    Asteroids Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 18183   Accepted: 9905 Descri ...

  4. 全局获取Context的技巧

    全局获取Context的技巧 在android开发中,非常多地方都要用到Context上下文这个类对象,比方:弹出 Toast 的时候须要.启动活动的时候须要.发送广播的时候须要.操作数据库的时候须要 ...

  5. 【JavaScript】双引号问题

    拼装字符串是遇到双引号冲突问题. 最后用"代替了平时的转码手段.

  6. 计算阶乘并显示_winform (20以后的阶乘溢出)

    编写一个窗体应用程序,计算n的阶乘,显示其结果,同时,将结果显示在一个标签中. 新建窗体应用程序(如下),新建控件label1,label2,label3,textBOX1,button1,butto ...

  7. 百度SiteApp构建网站APP

    现在很多个人网站和企业网站都是传统的Web方式,有没有想过个人/企业网站也能做成APP应用对外宣传呢?专门找人去开发Android和IOS上的APP又太贵,为了赶上移动互联网时髦,我以个人网站试做了一 ...

  8. Android访问网络(可以正常使用)

    以下是MainActiviy.java,有必要的注释,里面用到了handler,以及线程,workThread如何更新mainThread才能够更新的内容. package com.wyl.httpt ...

  9. 函数嵌套 lisp表达式求值

    问题 D: lisp表达式求值 时间限制: 1 Sec  内存限制: 128 MB提交: 105  解决: 43[提交][状态][讨论版] 题目描述 lisp是一种非常古老的计算机语言,是由约翰·麦卡 ...

  10. Python 数据处理扩展包: numpy 和 pandas 模块介绍

    一.numpy模块 NumPy(Numeric Python)模块是Python的一种开源的数值计算扩展.这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list str ...