MyException - 我的异常网
当前位置:我的异常网» Android » Pro Android 四 第五章 理解Intent

Pro Android 四 第五章 理解Intent

www.MyException.Cn  网友分享于:2013-09-10  浏览:4次
Pro Android 4 第五章 理解Intent

     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的含义:

     http://developer.android.com/reference/android/app/PendingIntent.html

     通常情况下,你可以为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的区别是什么?

文章评论

“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
Java程序员必看电影
Java程序员必看电影
每天工作4小时的程序员
每天工作4小时的程序员
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
代码女神横空出世
代码女神横空出世
那些争议最大的编程观点
那些争议最大的编程观点
漫画:程序员的工作
漫画:程序员的工作
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序员应该关注的一些事儿
程序员应该关注的一些事儿
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
为什么程序员都是夜猫子
为什么程序员都是夜猫子
 程序员的样子
程序员的样子
我的丈夫是个程序员
我的丈夫是个程序员
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
旅行,写作,编程
旅行,写作,编程
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员的鄙视链
程序员的鄙视链
我是如何打败拖延症的
我是如何打败拖延症的
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
老程序员的下场
老程序员的下场
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员都该阅读的书
程序员都该阅读的书
程序员和编码员之间的区别
程序员和编码员之间的区别
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
编程语言是女人
编程语言是女人
程序员必看的十大电影
程序员必看的十大电影
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
中美印日四国程序员比较
中美印日四国程序员比较
如何成为一名黑客
如何成为一名黑客
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
总结2014中国互联网十大段子
总结2014中国互联网十大段子
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有