Pro Android 4 第五章 理解Intent

时间:2022-09-21 21:39:21
     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. 第五章&colon;理解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开发艺术探索》读书笔记 &lpar;5&rpar; 第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. windows server 注意windows的temp目录

    windows解压缩包.安装软件时,会生成一些临时文件存放在temp目录中,windows不会自动删除这些文件. 临时文件目录可以在环境变量中查看和配置 在工作机or个人PC机中中这个目录一般不会有什 ...

  2. 英语语法最终珍藏版笔记-6&OpenCurlyDoubleQuote;情态动词+have&plus; done”的含义

    “情态动词+have+ done”的含义 1.Must have done的含义.“must have+过去分词”表示对过去的推测,意思是“一定已经,想必已经,准是已经….”,只用于肯定句中.例如: ...

  3. LRU Cache的简单c&plus;&plus;实现

    什么是 LRU LRU Cache是一个Cache的置换算法,含义是“最近最少使用”,把满足“最近最少使用”的数据从Cache中剔除出去,并且保证Cache中第一个数据是最近刚刚访问的,因为这样的数据 ...

  4. 解决Windows和Linux使用npm打包js和css文件不同的问题

    1.问题出现 最近公司上线前端H5页面,使用npm打包,特别奇怪的是每次打包发现css和js文件与Windows下打包不一致(网页使用Windows环境开发),导致前端页面功能不正常. 2.问题排查 ...

  5. nginx系列13:最少连接算法以及如何跨worker进程生效

    最少连接算法 使用最少连接算法可以使得nginx优先选择连接最少的上游服务器,需要用到upstream_least_conn模块. 如何跨worker进程生效 因为nginx是多进程结构的,默认多个w ...

  6. Building gRPC Client iOS Swift Note Taking App

    gRPC is an universal remote procedure call framework developed by Google that has been gaining inter ...

  7. redis集群服务启动

    1 启动redis服务器 redis-server.exe redis.windows.conf 需要配置config节点的bind ip 2 启动redis集群 开启redis.xx.conf 服务 ...

  8. mfc怎么显示jpg png图像

    如果是VS2005以上版本可以直接使用MFC自带的CImage类,如果不是可以用网上比较流行的CxImage,或者使用GDI+

  9. Windows环境下ELK&lpar;5&period;X&rpar;平台的搭建

    一.Windows环境下ELK平台的搭建(2.*) 1.安装配置Java环境 在Oracle官网获取最新版的Java版本,由于只是运行不是开发,所以也可以只下载JRE.官网:http://www.or ...

  10. Visual Studio调试之断点技巧篇

    原文链接地址:http://blog.csdn.net/Donjuan/article/details/4618717 函数断点 在前面的文章Visual Studio调试之避免单步跟踪调试模式里面我 ...