Android应用程序开发与典型案例
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第6章 组件间通信

在第5章的学习中,主要了解了Android程序界面的开发,包括用户界面基础、用户界面控件的使用、界面布局的特点及使用方法、菜单的使用方法、界面事件的处理方法等。在此基础上,本章将对Android组件间的通信进行学习,包括Intent进行组件通信的原理、Intent启动Activity的方法、获取Activity返回值的方法、Intent过滤器的原理及其匹配机制、发送和接收广播消息的方法等。

6.1 Intent对象及其属性

Intent是一个动作的完整描述,包含了动作的产生组件、接收组件和传递的数据信息。Android则根据Intent的描述,在不同组件间传递消息,负责找到对应的组件,将Intent传递给调用的组件,组件接收到传递的消息,执行相关动作,完成组件的调用。

Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。Intent为Activity、Service和BroadcastReceiver等组件提供交互能力,还可以启动Activity和Service,在Android系统上发布广播消息。这里的广播消息是指可以接收到的特定数据或消息,也可以是手机的信号变化或电池的电量过低等信息。

因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。在SDK中给出了Intent作用的表现形式。

❑ 通过Context.startActivity() or Activity.startActivityForResult()启动一个Activity。

❑ 通过Context.startService() 启动一个服务,或者通过Context.bindService() 和后台服务交互。

❑ 通过广播方法(比如Context.sendBroadcast(),Context.sendOrderedBroadcast(), Context.sendStickyBroadcast())发给broadcast receivers。

一般情况下,Intent对某操作的抽象描述包含下面几个部分。

❑ 对执行动作的描述:操作(action)。

❑ 对这次动作相关联的数据进行描述:数据(data)。

❑ 对数据类型的描述:数据类型(type)。

❑ 对执行动作的附加信息进行描述:类别(category)。

❑ 其他一切附加信息的描述:附件信息(extras)。

❑ 对目标组件的描述:目标组件(component)。

6.1.1 Intent的action属性

action是要执行的动作,也可以是在广播Intent中已发生且正被报告的动作。action部分是一个字符串对象。它描述了Intent会触发的动作。Android系统中已经预定义了一些action常量,可以参看SDK帮助文档,下表给出了一些标准的action常量,如表6-1所示。

表6-1 SDK中定义的标准动作

:除了SDK中定义的标准动作外,可以使用自定义动作吗?

:当然,可以自定义动作。

自定义的动作在使用时,一般要加上包名作为前缀(为防止重复定义),如“com.example.project.SHOW_COLOR”,并可定义相应的Activity来处理自定义动作。

除上表介绍的action常量外,开发者也可以定义自己的action描述。一般来讲,定义自己的action字符串应该以应用程序的包名为前缀(防止重复定义)。由于action部分很大程度上决定了一个Intent的内容,特别是数据(data)和附加(extras)字段,就像一个方法名决定了参数和返回值。正是这个原因,应该尽可能明确指定动作,并紧密关联到其他Intent字段。即应该定义组件能够处理的Intent对象的整个协议,而不仅仅是单独地定义一个动作。一个Intent对象的动作通过setAction()方法设置,通过getAction()方法读取。

6.1.2 Intent的data属性

data,即执行动作要操作的数据。

data描述了Intent的动作所能操作数据的MIME类型和URL,不同的Action用不同的操作数据。例如,如果Activity字段是ACTION_EDIT,data字段将显示包含用于编辑的文档的URI;如果Activity是ACTION_CALL,data字段是一个tel://URI和将拨打的号码;如果Activity是ACTION_VIEW,data字段是一个http://URI,接收活动将被调用去下载和显示URI指向的数据。在许多情况下,数据类型能够从URI中推测出来,特别是content://URIs,它表示位于设备上的数据且被内容提供者(Content Provider)控制。但是类型也能够显示设置,setData()方法指定数据的URI,setType()指定MIME类型,setDataAndType()指定数据的URI和MIME类型。通过getData()读取URI,getType()读取类型。

匹配一个Intent到一个能够处理data的组件,知道data的类型(它的MIME类型)和它的URI很重要。例如,一个组件能够显示图像数据就不应该被调用去播放音频文件。

6.1.3 Intent的type属性

数据类型(type),显式指定Intent的数据类型(MIME)。一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。

6.1.4 Intent的category属性

category(类别),被执行动作的附加信息。例如,LAUNCHER_CATEGORY表示Intent的接受者应该在Launcher中作为顶级应用出现;而ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一个,这些动作可以在同一块数据上执行。其他的如表6-2所示。

表6-2 SDK中定义的标准动作

通过addCategory()方法添加一个种类到Intent对象中;通过removeCategory()方法删除一个之前添加的种类;通过getCategories()方法获取Intent对象中的所有种类。

6.1.5 Intent的extras属性

extras(附加信息)是一组键值对,包含了需要传递给目标组件并有其处理的一些附加信息。

就像动作关联的特定种类的数据URIs,也关联到某些特定的附加信息。例如,一个ACTION_TIMEZONE_CHANGE intent有一个“time-zone”的附加信息,标识新的时区,ACTION_HEADSET_PLUG有一个“state”附加信息,标识头部现在是否塞满或未塞满;有一个“name”附加信息,标识头部的类型。如果你自定义了一个SHOW_COLOR动作,颜色值将可以设置在附加的键值对中。例如,如果要执行“发送电子邮件”这个动作,可以将电子邮件的标题、正文等保存在extras里,传给电子邮件发送组件。

Intent有一系列putXXX()方法用于插入各种附加数据,有一系列getXXX()方法可以取出一系列数据。

使用Extras可以为组件提供扩展信息。

6.1.6 Intent的ComponentName属性

ComponentName(组件),指定Intent的目标组件的类名称。ComponentName包含两个String成员,分别代表组件的全称类名和包名,包名必须和AndroidManifest.xml文件标记中的对应信息一致。ComponentName通过setComponent()、setClass()或setClassName()设置,通过getComponent()读取。

通常Android会根据Intent中包含的其他属性的信息(如,action、data/type、category)进行查找,最终找到一个与之匹配的目标组件。但是,如果ComponentName这个属性有指定,将直接使用指定的组件,而不再执行上述查找过程。指定了这个属性以后,Intent的其他所有属性都是可选的。

对于Intent,组件名并不是必需的。如果一个Intent对象添加了组件名,则称该Intent为“显式Intent”,这样的Intent在传递时会直接根据组件名去寻找目标组件。如果没有添加组件名,则称为“隐式Intent”,Android会根据Intent中的其他信息来确定响应该Intent的组件。

总之,action、data/type、category和extras一起使系统能够理解诸如“查看某联系人的详细信息”或“给某人打电话”之类的短语。随着应用不断地加入系统中,Android系统可以添加新的action、data/type、category来扩展功能。当然,最受益的还是应用本身,可以利用这套语言机制来处理不同的动作和数据。

6.2 系统标准ActivityAction应用

6.2.1 启动Activity

在Android系统中,应用程序一般都有多个Activity,Intent可以实现不同Activity之间的切换和数据传递。

启动Activity方式有以下两种方式。

❑ 显式启动,必须在Intent中指明启动的Activity所在的类。

❑ 隐式启动,Android系统根据Intent的动作和数据来决定启动哪一个Activity,即在隐式启动时,Intent中只包含需要执行的动作和所包含的数据,而无须指明具体启动哪一个Activity,选择权由Android系统和最终用户来决定。

1.显式启动

使用Intent显式启动Activity,首先需要创建一个Intent,指定当前的应用程序上下文及要启动的Activity,并把创建好的Intent作为参数传递给startActivity()方法。代码如代码清单6-1所示。

代码清单6-1显式启动

      Intent intent = new Intent(IntentDemo.this, Activity2.class);
      startActivity(intent);

以下将通过一个IntentDemo示例来详细讲解如何使用Intent显式启动新的Activity。

IntentDemo示例中包含IntentDemo和Activity2这两个Activity类,程序启动是默认启动IntentDemo这个Activity,启动画面如图6-1所示。

图6-1 名为IntentDemo的Activity界面

在图6-1的界面中,单击“跳转到Activity2”按钮后,程序启动Activity2这个Activity,界面如图6-2所示。

图6-2 名为Activity2的Activity界面

为使程序达到上述效果,首先,在AndroidManifest.xml文件中注册上面两个Activity,应使用<activity>标签,嵌套在<application>标签内部。代码如代码清单6-2所示。

代码清单6-2 AndroidManifest.xml

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.IntentDemo"
        android:versionCode="1"
        android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
          <activity android:name=".IntentDemo"
              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 android:name=".Activity2"
              android:label="@string/app_name">
          </activity>
        </application>
      <uses-sdk android:minSdkVersion="3" />
      </manifest>

在Android应用程序中,用户使用的每个组件都必须在AndroidManifest.xml文件中的<application>节点内定义,所以<application>节点下共有两个<activity>节点,分别代表应用程序中所使用的两个Activity:IntentDemo和Activity2。

在IntentDemo.java文件中,包含了显示使用Intent启动Activity2的核心代码,如代码清单6-3所示。

代码清单6-3 IntentDemo.java

      Button button = (Button)findViewById(R.id.btn);
      button.setOnClickListener(new OnClickListener(){
        public void onClick(View view){
            Intent intent = new Intent(IntentDemo.this, Activity2.class);
            startActivity(intent);
        }
      });

在单击事件的处理函数中,Intent构造函数的第1个参数是应用程序上下文,应用程序上下文是IntentDemo;第2个参数是接收Intent的目标组件,使用的是显式启动方式,直接指明了需要启动的Activity。

同理,在Activity2.java文件中,包含了显示使用Intent启动IntentDemo的核心代码,如代码清单6-4所示。

代码清单6-4 Activity2.java

      Button button = (Button)findViewById(R.id.btn);
      button2.setOnClickListener(new OnClickListener(){
        public void onClick(View view){
            Intent intent = new Intent(Activity2.this, IntentDemo.class);
            startActivity(intent);
        }
      });

2.隐式启动

隐式启动Activity时,Android系统在应用程序运行时解析Intent,并根据一定的规则对Intent和Activity进行匹配,使Intent上的动作、数据与Activity完全吻合。而匹配的Activity可以是应用程序本身的,也可以是Android系统内置的,还可以是第三方应用程序提供的。因此,这种方式更强调了Android应用程序中组件的可复用性。

由此可以看出,隐式启动不需要指明需要启动哪一个Activity,而由Android系统来决定,有利于使用第三方组件。

在默认情况下,Android系统会调用内置的Web浏览器,代码如代码清单6-5所示。

代码清单6-5隐式启动默认情况调用内置Web浏览器

      Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));
      startActivity(intent);

在上述代码中,Intent的动作是Intent.ACTION_VIEW,是根据Uri的数据类型来匹配动作;数据部分的Uri是Web地址,使用Uri.parse(urlString)方法,可以简单地把一个字符串解释成Uri对象。

Intent的语法如代码清单6-6所示。

代码清单6-6 Intent语法

      Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlString));

Intent构造函数的第1个参数是Intent需要执行的动作;第2个参数是Uri,表示需要传递的数据。

Android系统支持的常见动作字符串常量表,如表6-3所示。

表6-3 Android系统支持的常见动作字符串常量表

以下将通过一个WebViewIntentDemo示例来了解如何隐式启动Activity。

如图6-3所示,当用户在文本框中输入要访问的网址后,通过单击“Go”按钮,程序根据用户输入的网址生成一个Intent,并以隐式启动的方式调用Android内置的Web浏览器,并打开指定的Web页面。本例输入的网址是google主页的主站地址,地址是:http://www.google.com。

图6-3 隐式启动Activity

其中,main.xml代码如代码清单6-7所示。

代码清单6-7 main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="horizontal"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" >
      <EditText
        android:id="@+id/url_field"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1.0"
        android:lines="1"
        android:inputType="textUri"
        android:imeOptions="actionGo" />
      <Button
        android:id="@+id/go_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/go_button" />
      </LinearLayout>

其中,strings.xml代码如代码清单6-8所示。

代码清单6-8 strings.xml

      <?xml version="1.0" encoding="utf-8"?>
      <resources>
      <string name="app_name"> WebViewIntentDemo </string>
      <string name="go_button">Go</string>
      </resources>

WebViewIntentDemo.java代码如代码清单6-9所示。

代码清单6-9 WebViewIntentDemo.java

      import android.app.Activity;
      import android.content.Intent;
      import android.net.Uri;
      import android.os.Bundle;
      import android.view.KeyEvent;
      import android.view.View;
      import android.view.View.OnClickListener;
      import android.view.View.OnKeyListener;
      import android.widget.Button;
      import android.widget.EditText;
      public class WebViewIntentDemo extends Activity {
      private EditText urlText;
      private Button goButton;
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Get a handle to all user interface elements
        urlText = (EditText) findViewById(R.id.url_field);
        goButton = (Button) findViewById(R.id.go_button);
        // Setup event handlers
        goButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                openBrowser();
            }
        });
        urlText.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View view, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_ENTER) {
                    openBrowser();
                    return true;
                }
                return false;
            }
        });
      }
      }

在上述代码中第26行对按钮“Go”添加监听,当触摸单击或通过手机按键单击该按钮时,触发openBrowser()方法。其中,openBrowser()方法代码如代码清单6-10所示。

代码清单6-10 openBrowser()方法

      /** Open a browser on the URL specified in the text box */
      private void openBrowser() {
      Uri uri = Uri.parse(urlText.getText().toString());
      Intent intent = new Intent(Intent.ACTION_VIEW, uri);
      startActivity(intent);
      }

6.2.2 获取Activity返回值

在6.2.1节的IntentDemo示例中,通过使用startActivity(Intent)方法启动Activity后,两个Activity之间相互独立,没有任何关联。然而,在很多情况下,后启动的Activity是为了让用户对特定信息进行选择,在关闭这个Activity后,用户的选择信息需要返回给未关闭的那个Activity。由此,按照Activity启动的先后顺序,先启动的称为父Activity,后启动的称为子Activity,如果需要将子Activity的部分信息返回给父Activity,则可以使用Sub-Activity的方式去启动子Activity。

获取子Activity的返回值,一般分以下3个步骤。

1.以Sub-Activity的方式启动子Activity

首先,调用startActivityForResult(Intent, requestCode)函数,其中,参数Intent用于决定启动哪个Activity,参数requestCode是唯一的标识子Activity的请求码。

显式启动子Activity的代码如代码清单6-11所示。

代码清单6-11显式启动子Activity

      int SUBACTIVITY1 = 1;
      Intent intent = new Intent(this, SubActivity1.class);
      startActivityForResult(intent, SUBACTIVITY1);

隐式启动子Activity的代码如代码清单6-12所示。

代码清单6-12隐式启动子Activity

      int SUBACTIVITY2 = 2;
      Uri uri = Uri.parse("content://contacts/people");
      Intent intent = new Intent(Intent.ACTION_PICK, uri);
      startActivityForResult(intent, SUBACTIVITY2);

2.设置子Activity的返回值

在子Activity调用finish()函数关闭前,调用setResult()函数将所需的数据返回给父Activity。其中,setResult()函数有两个参数:结果码和返回值。结果码表明了子Activity的返回状态,通常为Activity.RESULT_OK或Activity.RESULT_CANCELED,或自定义的结果码,结果码均为整数类型;返回值封装在Intent中,子Activity通过Intent将需要返回的数据传递给父Activity。数据主要是Uri形式,可以附加一些额外信息,这些额外信息用Extra的集合表示。

代码清单6-13所示代码说明了如何在子Activity中设置返回值。

代码清单6-13在子Activity中设置返回值

      Uri data = Uri.parse(“tel:” + tel_number);
      Intent result = new Intent(null, data);
      result.putExtra(“address”, “ “);
      setResult(RESULT_OK, result);
      finish();

3.在父Activity中获取返回值

当子Activity关闭时,启动它的父Activity的onActivityResult()函数将被调用;如果需要在父Activity中处理子Activity的返回值,则覆盖此函数即可。此函数的语法如代码清单6-14所示。

代码清单6-14 onActivityResult()语法

      public void onActivityResult(int requestCode, int resultCode, Intent data);

在上述代码中,第1个参数requestCode,用来表示是哪一个子Activity的返回值;第2个参数resultCode用于表示子Activity的返回状态;第3个参数data是子Activity的返回数据,返回数据类型是Intent。返回数据的用途不同,Uri数据的协议也就不同。也可以使用Extra方法返回一些原始类型的数据。

代码清单6-15所示代码说明如何在父Activity中处理子Activity的返回值。

代码清单6-15在父Activity中处理子Activity的返回值

      private static final int SUBACTIVITY1 = 1;
      private static final int SUBACTIVITY2 = 2;
      @Override
      public void onActivityResult(int requestCode, int resultCode, Intent data){
        Super.onActivityResult(requestCode, resultCode, data);
        switch(requestCode){
            case SUBACTIVITY1:
                if (resultCode == Activity.RESULT_OK){
                    Uri uriData = data.getData();
                }else if (resultCode == Activity.RESULT_CANCEL){
                }
            break;
            case SUBACTIVITY2:
                if (resultCode == Activity.RESULT_OK){
                    Uri uriData = data.getData();
                }
            break;
            }
        }

在上述代码中,第1行代码和第2行代码是两个子Activity的请求码;第7行代码对请求码进行匹配;第9行和第11行代码对结果码进行判断,如果返回的结果码是Activity.RESULT_OK,则在代码的第10行使用getData()函数获取Intent中的Uri数据;反之,若返回的结果码是Activity.RESULT_CANCELED,则不进行任何操作。

ActivityCommunication示例说明了如何以Sub-Activity方式启动子Activity,以及使用Intent进行组件间的通信。

在如图6-4所示的主界面中,当用户单击“启动Activity1”和“启动Activity2”按钮时,程序将分别启动两个不同的子Activity:子SubActivity1和SubActivity2,它们的界面分别如图6-5所示。

图6-4 ActivityCommunication主界面

图6-5 SubActivity1和SubActivity2界面

SubActivity1提供了一个输入框,以及“接受”和“撤销”两个按钮,如果在输入框中输入信息后单击“接受”按钮,程序会把输入框中的信息传递给父Activity,并在父Activity界面上显示;如果用户单击“撤销”按钮,则程序不会向父Activity传递任何信息。

SubActivity2主要为了说明如何在父Activity中处理多个子Activity,因此仅提供了用于关闭SubActivity2的“关闭”按钮。

下面介绍ActivityCommunication的核心代码。

首先是ActivityCommunication.java文件,如代码清单6-16所示。

代码清单6-16 ActivityCommunication.java

      public class ActivityCommunication extends Activity {
        private static final int SUBACTIVITY1 = 1;
        private static final int SUBACTIVITY2 = 2;
        TextView textView;
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.main);
          textView = (TextView)findViewById(R.id.textShow);
          final Button btn1 = (Button)findViewById(R.id.btn1);
          final Button btn2 = (Button)findViewById(R.id.btn2);
          btn1.setOnClickListener(new OnClickListener(){
              public void onClick(View view){
              Intent intent = new Intent(ActivityCommunication.this, SubActivity1.class);
              startActivityForResult(intent, SUBACTIVITY1);
              }
          });
            btn2.setOnClickListener(new OnClickListener(){
              public void onClick(View view){
              Intent intent = new Intent(ActivityCommunication.this, SubActivity2.class);
              startActivityForResult(intent, SUBACTIVITY2);
              }
          });
        }
        @Override
          protected void onActivityResult(int requestCode, int resultCode, Intent data) {
              super.onActivityResult(requestCode, resultCode, data);
              switch(requestCode){
                  case SUBACTIVITY1:
                    if (resultCode == RESULT_OK){
                          Uri uriData = data.getData();
                          textView.setText(uriData.toString());
                  }
                  break;
                  case SUBACTIVITY2:
                  break;
            }
        }
      }

在上述代码中,第2行和第3行分别定义了两个子Activity的请求码,而在第16行和第23行以Sub-Activity的方式分别启动两个子Activity。

第29行代码是子Activity关闭后的返回值处理函数,其中requestCode是子Activity返回的请求码,应与第2行和第3行定义的两个请求码相匹配;resultCode是结果码,在第32行代码对结果码进行判断,如果等于RESULT_OK,则在第35行代码获取子Activity的返回值中的数据,其中,data是返回值,子Activity需要返回的数据就保存在data中。

SubActivity1.java的核心代码如代码清单6-17所示。

代码清单6-17 SubActivity1.java

      public class SubActivity1 extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.subactivity1);
            final EditText editText = (EditText)findViewById(R.id.edit);
            Button btnOK = (Button)findViewById(R.id.btn_ok);
            Button btnCancel = (Button)findViewById(R.id.btn_cancel);
            btnOK.setOnClickListener(new OnClickListener(){
                public void onClick(View view){
                    String uriString = EditText.getText().toString();
                    Uri data = Uri.parse(uriString);
                    Intent result = new Intent(null, data);
                    setResult(RESULT_OK, result);
                    finish();
                }
            });
            btnCancel.setOnClickListener(new OnClickListener(){
                public void onClick(View view){
                    setResult(RESULT_CANCELED, null);
                    finish();
                }
              });
        }
      }

上述SubActivity1.java的核心代码中,第13行代码将EditText控件的内容作为数据保存在Uri中;第14行代码中使用这个Uri构造Intent;第15行代码中,将Intent作为返回值,RESUIT_OK作为结果码,通过调用setResult()函数,将返回值和结果码传递给父Activity;第16行代码调用finish()函数关闭当前的子Activity。

SubActivity2.java的核心代码如代码清单6-18所示。

代码清单6-18 SubActivity2.java

      public class SubActivity2 extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.subactivity2);
            Button btnReturn = (Button)findViewById(R.id.btn_return);
            btnReturn.setOnClickListener(new OnClickListener(){
                public void onClick(View view){
                    setResult(RESULT_CANCELED, null);
                    finish();
                }
            });
        }
      }

上述SubActivity2.java的核心代码中,第10行的setResult()函数仅设置了结果码,第2个参数为null,表示数据需要传递给父Activity。

6.3 Intent过滤器

Intent过滤器是一种根据Intent中的动作(Action)、类别(Categorie)和数据(Data)等内容,对适合接收该Intent的组件进行匹配和筛选的机制。它可以匹配数据类型、路径和协议,还可以用来确定多个匹配项顺序的优先级(Priority)。

应用程序的Activity组件、Service组件和BroadcastReceiver都可以注册Intent过滤器,这些组件在特定的数据格式上就可以产生相应的动作。

6.3.1 注册Intent过滤器

注册Intent过滤器的方法如下:

在AndroidManifest.xml文件的各个组件的节点下定义<intent-filter>节点,然后在<intent-filter>节点中声明该组件所支持的动作、执行的环境和数据格式等信息。

<intent-filter>节点支持<action>标签、<category>标签和<data>标签,其中:<action>标签定义Intent过滤器的“类别”;<category>标签定义Intent过滤器的“动作”;<data>标签定义Intent过滤器的“数据”。

<intent-filter>节点支持的标签和属性如表6-4所示。

表6-4 <intent-filter>节点支持的标签和属性

<category>标签用来指定Intent过滤器的服务方式,每个Intent过滤器可以定义多个<category>标签,程序开发人员可使用自定义的类别,或使用Android系统提供的类别。其中,Android系统提供的类别如表6-5所示。

表6-5 Android系统提供的类别

AndroidManifest.xml文件中的每个组件的<intent-filter>都被解析成一个Intent过滤器对象。当应用程序安装到Android系统时,所有的组件和Intent过滤器都会注册到Android系统中。这样,Android系统便知道了如何将任意一个Intent请求通过Intent过滤器映射到相应的组件上。

6.3.2 Intent解析

Intent到Intent过滤器的映射过程称为“Intent解析”。Intent解析可以在所有组件中找到一个可与请求的Intent达成最佳匹配的Intent过滤器。

Intent解析的匹配规则。

❑ Android系统把所有应用程序包中的Intent过滤器集合在一起,形成一个完整的Intent过滤器列表。

❑ 在Intent与Intent过滤器进行匹配时,Android系统会将列表中所有Intent过滤器的“动作”和“类别”与Intent进行匹配,任何不匹配的Intent过滤器都将被过滤掉。没有指定“动作”的Intent过滤器可以匹配任何的Intent,但没有指定“类别”的Intent过滤器只能匹配没有“类别”的Intent。

❑ 把Intent数据Uri的每个子部与Intent过滤器的<data>标签中的属性进行匹配,如果<data>标签指定了协议、主机名、路径名或MIME类型,那么这些属性都要与Intent的Uri数据部分进行匹配,任何不匹配的Intent过滤器均被过滤掉。

❑ 如果Intent过滤器的匹配结果多于一个,则可以根据在<intent-filter>标签中定义的优先级标签来对Intent过滤器进行排序,优先级最高的Intent过滤器将被选择。

在此以6.2.1 节中隐式启动Activity的例子WebViewIntentDemo为基础,在AndroidManifest.xml文件中注册Intent过滤器,以及设置<intent-filter>节点属性来捕获指定的Intent。

在AndroidManifest.xml中添加如代码清单6-19所示。

代码清单6-19 AndroidManifest.xml中添加代码

      <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <data android:schema="http" />
      </intent-filter>

利用 <intent-filter> 可以把应用程序的操作注册到系统中,当用户调用Intent时,可以根据输入的ACTION和Uri参数来找到这个应用程序。例如,在上述代码中,以http协议为例,打开Google Map可以用URI: geo:38.899533,-77.036476。

此外,“协议”在Android里都可以随便定义。例如,写一个打开文件的关联Intent,如file:///sdcard/abc.txt。也可以用type进行关联,其代码如代码清单6-20所示。

代码清单6-20用type进行关联

      <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <type android:value="test.item"/>
      </intent-filter>

然后,通过代码清单6-21所示代码就能定位到应用。

代码清单6-21用type定位到应用代码

      Intent it = new Intent(Intent.ACTION_VIEW);
      it.setType("test.item”);
      startActivity(it)

6.4 广播消息

应用程序和Android系统都可以使用Intent发送广播消息。其中,广播消息的内容可以是与应用程序密切相关的数据信息,也可以是Android的系统信息,例如,网络连接变化、电池电量变化、接收短信和系统设置变化等。如果应用程序注册了BroadcastReceiver,则可以接收到指定的广播消息。

下面将介绍广播信息的使用方法。

首先,创建一个Intent。调用sendBroadcast()函数,就可把Intent携带的消息广播出去,如果要在Intent传递额外数据,可以用Intent的putExtra()方法。

注意:

在构造Intent时必须用全局唯一的字符串标识其要执行的动作,通常使用应用程序包的名称。

利用Intent发送广播消息,并添加了额外的数据,然后调用sendBroadcast()发送广播消息的代码如代码清单6-11所示。

代码清单6-22利用Intent发送广播消息

      String UNIQUE_STRING = "com.example.BroadcastReceiverDemo";
      Intent intent = new Intent(UNIQUE_STRING);
      intent.putExtra("key1", "value1");
      intent.putExtra("key2", "value2");
      sendBroadcast(intent);

广播消息发送后,利用BroadcastReceiver监听广播消息。具体方法如下:在AndroidManifest.xml文件或在代码中注册一个BroadcastReceiver,并在其中使用Intent过滤器指定要处理的广播消息。在BroadcastReceiver接收到与之匹配的广播消息后,onReceive()方法会被调用;onReceive()方法必须要在5秒内执行完毕,否则Android系统会认为该组件失去响应,并提示用户强行关闭该组件。

创建BroadcastReceiver需继承BroadcastReceiver类,并重载onReceive()方法。代码如代码清单6-23所示。

代码清单6-23利用BroadcastReceiver监听广播消息

      public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //TODO: React to the Intent received.
        }
      }

BroadcastReceiver的应用程序不需要一直运行,当Android系统接收到与之匹配的广播消息时,会自动启动此BroadcastReceiver。基于以上特征,BroadcastReceiver适合做一些资源管理的工作。

如图6-6所示,BroadcastReceiverDemo示例说明了如何在应用程序中注册BroadcastReceiver,并接收指定类型的广播消息。

图6-6 BroadcastReceiverDemo示例

如图6-6所示,在单击“发送广播消息”按钮后,EditText控件中内容将以广播消息的形式发送出去,示例内部的BroadcastReceiver将接收这个广播消息,并显示在用户界面的下方。

BroadcastReceiverDemo.java文件中包含发送广播消息的代码,其关键代码如代码清单6-24所示。

代码清单6-24 BroadcastReceiverDemo.java

      button.setOnClickListener(new OnClickListener(){
        public void onClick(View view){
            Intent intent = new Intent("com.example.BroadcastReceiverDemo");
            intent.putExtra("message", entryText.getText().toString());
            sendBroadcast(intent);
        }
      });

在上述代码中,第3行代码创建Intent,将com.example.BroadcastReceiverDem作为识别广播消息的字符串标识;第4行代码添加了额外信息;第5行代码调用sendBroadcast()函数发送广播消息。

为了能够使应用程序中的BroadcastReceiver接收指定的广播消息,首先要在AndroidManifest.xml文件中添加Intent过滤器,声明BroadcastReceiver可以接收的广播消息。其中,AndroidManifest.xml文件的完整代码如代码清单6-25所示。

代码清单6-25 AndroidManifest.xml

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.BroadcastReceiverDemo"
        android:versionCode="1"
        android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".BroadcastReceiverDemo"
                    android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <receiver android:name=".MyBroadcastReceiver">
                  <intent-filter>
                    <action android:name="com.example.BroadcastReceiverDemo" />
                  </intent-filter>
          </receiver>
        </application>
        <uses-sdk android:minSdkVersion="3" />
      </manifest>

在上述代码中,第14行代码中创建了一个<receiver>节点;在第15行中声明了Intent过滤器的动作为“com.example.BroadcastReceiverDemo”,这与BroadcastReceiverDemo.java文件中Intent的动作相一致,表明这个BroadcastReceiver可以接收动作为“com.example. BroadcastReceiverDemo”的广播消息。

MyBroadcastReceiver.java文件创建了一个自定义的BroadcastReceiver,其核心代码如代码清单6-26所示。

代码清单6-26 MyBroadcastReceiver.java

      public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String msg = intent.getStringExtra("message");
            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
        }
      }

在上述代码中,第1行代码首先继承了BroadcastReceiver类;第3行代码重载了onReceive()函数,当接收到AndroidManifest.xml文件定义的广播消息后,程序将自动调用onReceive()函数。

6.5 本章小结

本章主要对Android组件间的通信进行学习,包括Intent进行组件通信的原理、Intent启动Activity的方法、获取Activity返回值的方法、Intent过滤器的原理及其匹配机制、发送和接收广播消息的方法。

关键知识点测评

1.以下有关Intent的说法,不正确的一个是( )。

A.Intent不可以用于应用程序之间交互

B.Intent可以通过Context.startActivity()启动一个Activity

C.Intent可以启动Activity和Service,在Android系统上发布广播消息

D.Intent描述了动作的产生组件、接收组件和传递的数据信息

2.以下有关Intent的叙述,正确的一个是( )。

A.Intent显式启动时,构造函数的第2个参数是应用程序上下文

B.隐式启动Activity时,匹配的Activity不可以是第三方应用程序提供的

C.Android系统可以在Intent中指明启动的Activity所在的类,也可以根据Intent的动作和数据来决定启动哪一个Activity

D.只有Activity组件可以注册Intent过滤器