购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.2 Android组件间的通信

在实际的App软件中,几乎每个应用都涉及页面跳转的操作,诸如通讯录的联系人列表→联系人详细信息等。这些跳转都是借助于Android的Intent实现的。Intent是一种消息传递机制,用于Android的核心组件(Activity,BroadcastReceiver,Service)通信和数据交换。它不仅可以在同一个应用程序内部的不同组件之间进行通信,也可以在不同应用程序的组件之间传递信息。因此,由于Intent的存在,使得Android系统中相互独立的组件不再是一个个独立的孤岛,而成为可以互相通信的集合。

3.2.1 Intent对象

Intent,中文意思是“意图,意向”,可以理解为,应用程序要启动另一个组件就需要用到Intent。Intent负责对应用中一次操作的动作、涉及的数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。由于Intent可以方便地启动Activity和Service组件,也可以在Android系统上发布广播消息,从而将系统中互相独立的组件连接成可以互相通信的整体。本章的案例都是针对两个Activity进行通信的。根据Activity的启动方式,Intent支持显式启动和隐式启动。

1.显式启动

形式如下:

Intent intent=new Intent(MainActivity.this,SubActivity.class); //定义一个Intent

startActivity(intent); //启动Activity

以上示例代码的作用是从MainActivity这个Activity类跳转到SubActivity中。Intent构造函数中的两个参数分别代表当前组件和目标组件,然后通过startXXX()方法启动该Intent对象,即可实现两个组件之间的跳转。由于在参数中明确指定了组件,所以这种启动方式称作显式启动。这种启动方式代码简单,易于理解。

2.隐式启动

形式如下:

Intent intent=new Intent(Intent.ACTION_VIEW,Uri.parse("content://com.android.contacts/contacts"));

startActivity(intent);

以上示例是使用了Intent的另一个构造函数,它没有明确指定需要启动的组件,而是由Android系统决定。至于Android系统应该如何选择组件作为要启动的对象呢?在程序运行时解析Intent,根据一定的规则对Intent和组件进行匹配,匹配的组件可以是程序本身的组件,也可以是Android系统内置的组件,甚至是第三方应用程序中的组件。上述示例代码的功能是打开了系统中的通讯录界面,其中Uri代表统一资源标识符,是Uniform Resource Identifier的简称。

这种启动方式虽然稍微有些复杂,但是它的好处是不必与某个具体的组件耦合,降低了Android系统中组件之间的耦合度,有利于组件分离,并允许替换应用程序中的元素,强调了Android组件的可复用性。当隐式启动和显式启动同时存在时,隐式启动会被忽略。

下面详细介绍Intent的匹配机制。

3.2.2 Intent过滤器

当通过显式启动方式来进行组件启动时,系统会根据指定的参数,直接启动目标组件。当隐式启动时,Android系统则需要通过某种匹配机制来寻找目标组件。这种匹配机制就是依赖于Android系统中的Intent过滤器(Intent Filters)来实现的。如图3-9所示。

图3-9 Intent隐式启动

Intent过滤器是根据Intent中的动作(action)、类别(category)和数据(data)内容,对目标组件进行匹配和筛选的机制。当Intent匹配到一个过滤器上,系统就会启动对应的组件并传递相应的Intent对象;如果匹配出多个过滤器,系统会弹出对话框,由用户进行选择。

通过上述的描述说明,Intent过滤器需要依附于Android的组件上,其格式内容如下(Android Manifest文件):

<activity android:name="ShareActivity">

<!--This activity handles "SEND" actions with text data-->

<intent-filter>

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

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

<data android:mimeType="text/plain"/>

</intent-filter>

<!--This activity also handles "SEND" and "SEND_MULTIPLE" with media data-->

<intent-filter>

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

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

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

<data android:mimeType="application/vnd.google.panorama360+jpg"/>

<data android:mimeType="image/*"/>

<data android:mimeType="video/*"/>

</intent-filter>

</activity>

上述代码定义了两个Intent过滤器。Intent过滤器由<intent-filter> 进行定义,嵌入到对应的组件中(例如本例中的activity)。在每个<intent-filter> 中,可以定义以下3种标签。

1.<action>

通过android:name属性指定组件能响应的动作,用字符串表示。表3-2列举了几种常用的动作,随后的示例我们便是以ACTION_VIEW为例的。

表3-2 常见Action常量及说明

【说明】 以上常量对应的值,通常是android.intent.action.XXX 的形式。例如,示例中的android.intent.action.SEND,其实就是ACTION_SEND。

2.<data>

通过一个或多个属性来指定响应的scheme,host,port,path和MIME type等值。接受的是一个Uri对象,形式如下:

content://com.android.contacts/contacts/1

其中,content是scheme部分,com.android.contacts是host部分,/contacts/1是path部分。例子中主要定义了MIME type属性,用于声明该组件所能匹配的Intent的Type属性。

3.<category>

通过android:name属性指定组件能响应的服务方式,用字符串表示。每个过滤器可以定义多个<category> 标签。为了能够响应隐式启动,必须定义一个CATEGORY_DEFAULT。因为startActivity()和 startActivityForResult()方法只对具有该标签的过滤器进行解析,否则隐式启动将永远无法启动对应的组件。表3-3列举了几种常用的动作,随后的示例我们便是以CATEGORY_DEFAULT为例的。

表3-3 常见Category常量及说明

【说明】 以上常量对应的值,通常是android.intent.category.XXX 的形式。例如,示例中的android.intent.category.DEFAULT,其实就是CATEGORY_DEFAULT。

【例 3-2】 在项目 ActivityIntentDemo 中的主界面里,有两个按钮,单击后,分别进入SubActivity1 和SubActivity2 中,效果如图3-10 所示。

图3-10 运行效果图

实现步骤如下:

(1)新建项目ActivityIntentDemo,并在该项目下增加两个Activity类。操作界面如图3-11所示,同时也会自动生成相关的布局文件。

图3-11 项目中增加Activity子类

因此,该项目中存在如图3-12所示的目录结构。其中,MainActivity管理activity_main.xml布局文件;SubActivity1管理activity_sub1.xml布局文件;SubActivity2管理activity_sub2.xml布局文件。

图3-12 项目结构

(2)activity_main布局文件:增加两个按钮,并为其设置id值分别为button1和button2。布局文件中增加按钮的代码如下:

<Button

android:layout_width="wrap_content"

android:layout_depth="wrap_content"

android:text="显式启动"

android:id="@+id/button1"

android:layout_alignParentTop="true"

android:layout_alignParentStart="true" />

<Button

android:layout_width="wrap_content"

android:layout_depth="wrap_content"

android:text="隐式启动"

android:id="@+id/button2"

android:layout_below="@+id/button1"

android:layout_alignParentStart="true"

android:layout_marginTop="27dp" />

(3)Activity类:在对应的MainActivity类中完成以下代码:

public class MainActivity extends Activity {

private Button button1,button2;

@Override

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//映射activity_main.xml中的id=button1按钮

button1=(Button)findViewById(R.id.button1);

//映射activity_main.xml中的id=button2按钮

button2=(Button)findViewById(R.id.button2);

//为两个按钮增加单击事件监听

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent1=new Intent(MainActivity.this,SubActivity1.class);

startActivity(intent1);

}

});

button2.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent2=new Intent();

//设置Action响应方式

intent2.setAction(Intent.ACTION_VIEW);

//Data内容:scheme://host形式

intent2.setData(Uri.parse("intentdemo://cn.edu.neusoft"));

startActivity(intent2);

}

});

}

}

上述代码主要是将布局文件中的两个按钮通过 findViewById方法进行获取,然后对其增加单击事件监听,从而保证单击按钮后实现各自的Intent对象调用。显然,id为button1的按钮启动了SubActivity1界面。而button2的启动对象,需要通过Manifest文件中的过滤器进行匹配。

(4)增加过滤器:在Android Manifest文件中,对新建的两个Activity类代码进行调整。其中,SubActivity2类的过滤器定义如下:

<activity android:name=".SubActivity1" >

</activity>

<activity android:name=".SubActivity2" >

<intent-filter>

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

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

<data android:scheme="intentdemo" android:host="cn.edu.neusoft"></data>

</intent-filter>

</activity>

在<intent-filter>标签中,通过action、category、data三个标签对SubActivity2类进行过滤器定义:android:name="android.intent.action.VIEW"表示将通过ACTION_VIEW的方式进行动作响应,即通过Uri的内容进行解析;android:name="android.intent.category.DEFAULT"表示隐式启动可以找到该组件;<data android:scheme="intentdemo" android:host="cn.edu.neusoft">则是Uri中的内容,根据scheme://host/path的结构进行分解,则为intentdemo://cn.edu.neusoft。

(5)调整两个布局文件的内容:第一个子页面的布局文件(activity_sub1.xml)中增加TextView和Button的代码,内容如下:

<TextView

android:layout_width="wrap_content"

android:layout_depth="wrap_content"

android:text="第一个子页面"

android:id="@+id/textView"

android:layout_alignParentTop="true"

android:layout_alignParentStart="true" />

<Button

android:layout_width="wrap_content"

android:layout_depth="wrap_content"

android:text="返回"

android:id="@+id/button"

android:layout_below="@+id/textView"

android:layout_alignParentStart="true" />

第二个子页面(activity_sub2.xml)中,增加TextView的内容即可:

<TextView

android:layout_width="wrap_content"

android:layout_depth="wrap_content"

android:text="第二个子页面"

android:id="@+id/textView2"

android:layout_alignParentTop="true"

android:layout_alignParentStart="true" />

(6)运行项目,查看结果:以上代码完成后,启动模拟器运行,可以实现图3-10的跳转效果。

3.2.3 Intent传递数据

Intent在实现两个组件之间跳转的同时,经常也需要传递数据,也就是在Intent对象上附加Bundle对象的数据。在MainActivity中执行如下代码:

Bundle bundle=new Bundle();

bundle.putString("data","test");

Intent intent=new Intent(MainActivity.this,SubActivity.class);

intent.putExtras(bundle);

startActivity(intent);

在SubActivity中,获取Intent中的数据“data”,代码如下:

Bundle bundle=this.getIntent().getExtras();

String result=bundle.getString("data");

以上代码就实现了Activity之间的数据传递。

3.2.4 获取Activity返回值

在上述的代码上,通过startActivity方法启动其他界面以后,两个Activity之间便失去了联系。但是,在一些情况下,启动的Activity(父Activity)希望能够获得被启动Activity(子Activity)的返回结果。具体的实施步骤如下:

① 父Activity通过startActivityForResult方法启动Intent对象;

② 子Activity通过setResult方法设置返回结果;

③ 父Activity通过onActivityResult方法获取子Activity返回的结果,并进行处理。

【例3-3】 修改例3-2:显示启动第一个子页面后,在第一个子页面中随机生成一个随机数,单击“返回”按钮后,将该随机数显示在第一个页面中。运行效果如图3-13 所示。

图3-13 运行效果图

实现步骤如下:

(1)修改启动方法(startActivity→startActivityForResult),将MainActivity类中的显式启动按钮监听事件修改:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent1=new Intent(MainActivity.this,SubActivity1.class);

//此处的第二个参数用在(3)步中的requestCode参数上

startActivityForResult(intent1,1);

}

});

(2)通过 setResult方法设置返回结果:在第一个界面对应的Activity类中增加返回按钮的事件监听:

Button btn=(Button)findViewById(R.id.button);

btn.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

//1.生成随机数

Random rand=new Random();

int r=rand.nextInt(100);

//2.准备Bundle对象传递Intent数据

Bundle bundle=new Bundle();

bundle.putInt("random",r);

Intent intent3=new Intent();

intent3.putExtras(bundle);

//3.设置返回结果intent3,并且其中包含传递的参数

setResult(RESULT_OK,intent3);

finish();//关闭当前页面

}

});

上述代码准备了一个0~99的随机数,然后将其装入Bundle对象中,在返回跳转时将该Bundle对象传递到父Activity中。setResult方法将返回到父Activity中,并传递resultCode值和Intent对象(其中封装了返回结果Bundle对象)。

(3)父Activity通过onActivityResult方法获取子Activity返回的结果,并进行处理。在MainActivity类中重载onActivityResult方法,根据返回的requestCode和resultCode确定Intent对象data的内容来自于何处。此处的requestCode值和startActivityForResult方法的requestCode参数一致;resultCode值和setResult方法中的resultCode参数内容一致;data中则可以获取对应的内容。代码如下:

@Override

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

if(requestCode==1)

{

if(resultCode==RESULT_OK)

{

Bundle random=data.getExtras();

Toast.makeText(MainActivity.this,random.getInt("random")+"",Toast.LENGTH_LONG).show();

}

}

}

上述代码中,使用Toast显示随机数。

(4)运行项目。

【案例延伸】 尝试在第二个页面中增加返回按钮,并准备内容(诸如开发人员的名字),在父Activity 中显示。 x1qnrP0CQZG2LYPtGO6rj49KkwFEPwtjVbBlSk9GW/ccVTAi2uqAnxS5vnakhNdk

点击中间区域
呼出菜单
上一章
目录
下一章
×