Android Intent学习

 

Android中提供了Intent机制来协助应用间的交互与通讯,或者采用更准确的说法是,Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。Intent这个英语单词的本意是“目的、意向”等,对于较少从事于大型平台开发工作的程序员来说,这可能是一个不太容易理解的抽象概念,因为它与我们平常使用的简单函数/方法调用,或者上节中提到的通过库调用接口的方式不太一样。在Intent的使用中你看不到直接的函数调用,相对函数调用来说,Intent是更为抽象的概念,利用Intent所实现的软件复用的粒度是Activity/Service,比函数复用更高一些,另外耦合也更为松散。

Android中与Intent相关的还有Action/Category及Intent Filter等,另外还有用于广播的Intent,这些元素掺杂在一起,导致初学者不太容易迅速掌握Intent的用法。在讲解这些名词之前,我们先来从下面的例子中感受一下Intent的一些基本用法,看看它能做些什么,之后再来思考这种机制背后的意义。

理解Intent的关键之一是理解清楚Intent的两种基本用法:一种是显式的Intent,即在构造Intent对象时就指定接收者,这种方式与普通的函数调用类似,只是复用的粒度有所差别;另一种是隐式的Intent,即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,这种方式与函数调用差别比较大,有利于降低发送者和接收者之间的耦合。另外Intent除了发送外,还可用于广播,这些都将在后文进行详细讲述。

下面的一小节我们来看看显式Intent的用法。

显式的Intent(Explicit Intent)
  • 同一个应用程序中的Activity切换

我们在前面的章节已经讨论过Activity的概念,通常一个应用程序中需要多个UI屏幕,也就需要多个Activity类,并且在这些Activity之间进行切换,这种切换就是通过Intent机制来实现的。

在同一个应用程序中切换Activity时,我们通常都知道要启动的Activity具体是哪一个,因此常用显式的Intent来实现。下面的例子用来实现一个非常简单的应用程序SimpleIntentTest,它包括两个UI屏幕也就是两个Activity——SimpleIntentTest类和TestActivity类,SimpleIntentTest类有一个按钮用来启动TestActivity。

 

程序的代码非常简单,SimpleIntentTest类的源代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package  com.tope.samples.intent.simple;
import  android.app.Activity;
import  android.content.Intent;
import  android.os.Bundle;
import  android.view.View;
import  android.widget.Button;
 
public  class  SimpleIntentTest extends  Activity implements  View.OnClickListener{
     /** Called when the activity is first created. */
     @Override
     public  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.main);
         Button startBtn = (Button)findViewById(R.id.start_activity);
         startBtn.setOnClickListener( this );
     }
 
     public  void  onClick(View v) {
         switch  (v.getId()) {
         case  R.id.start_activity:
             Intent intent = new  Intent( this , TestActivity. class );
             startActivity(intent);
             break ;
         default :
             break ;
         }
     }  
}

上面的代码中,主要是为“Start activity”按钮添加了OnClickListener,使得按钮被点击时执行onClick()方法,onClick()方法中则利用了Intent机制,来启动TestActivity,关键的代码是下面这两行:

Intent intent = new Intent(this, TestActivity.class);

startActivity(intent);

这里定义Intent对象时所用到的是Intent的构造函数之一:

Intent(Context packageContext, Class<?> cls)

两个参数分别指定Context和Class,由于将Class设置为TestActivity.class,这样便显式的指定了TestActivity类作为该Intent的接收者,通过后面的startActivity()方法便可启动TestActivity。

TestActivity的代码更为简单(定义TestActivity类需要新建TestActivity.java文件,如果你是一个初学者,你需要学会如何在Eclipse或其他开发环境下添加一个新的类,本书不作详述,请参考其他文档),如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
package  com.tope.samples.intent.simple;
import  android.app.Activity;
import  android.os.Bundle;
 
public  class  TestActivity extends  Activity {
     /** Called when the activity is first created. */
     @Override
     public  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.test_activity);
     }
}

可见TestActivity仅仅是调用setContentView来显示test_activity.xml中的内容而已。对于test_activity.xml及本例中所用到其他xml文件这里不作多余说明,读者练习时可自行参考本书所附光盘中的源代码。

如果我们仅仅是做上面的一些工作,还不能达到利用SimpleIntentTest启动TestActivity的目的。事实上,这样做会出现下面的Exception,导致程序退出。以下是利用logcat工具记录的log信息(省略了后半部分):

I/ActivityManager(  569): Displayed activity com.tope.samples/.SimpleIntentTest: 3018 ms

I/ActivityManager(  569): Starting activity: Intent { comp={com.tope.samples/com.tope.samples.TestActivity} }

D/AndroidRuntime(  932): Shutting down VM

W/dalvikvm(  932): threadid=3: thread exiting with uncaught exception (group=0x4000fe70)

E/AndroidRuntime(  932): Uncaught handler: thread main exiting due to uncaught exception

E/AndroidRuntime(  932): android.content.ActivityNotFoundException: Unable to find explicit activity class {com.tope.samples/com.tope.samples.TestActivity}; have you declared this activity in your AndroidManifest.xml?

E/AndroidRuntime(  932):        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1480)

E/AndroidRuntime(  932):        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1454)

E/AndroidRuntime(  932):        at android.app.Activity.startActivityForResult(Activity.java:2656)

E/AndroidRuntime(  932):        at android.app.Activity.startActivity(Activity.java:2700)

E/AndroidRuntime(  932):        at com.tope.samples.SimpleIntentTest.onClick(SimpleIntentTest.java:24)

从这些log中我们可以看到点击按钮后startActivity的调用过程,主要的原因是:“android.content.ActivityNotFoundException: Unable to find explicit activity class {com.tope.samples/com.tope.samples.TestActivity}; have you declared this activity in your AndroidManifest.xml?”

从这些log我们可以看到原因是找不到TestActivity这个Activity,并且log中还给出了提示:你是否在AndroidManifest.xml中声明了这个Activity?解决问题的方法也就是按照提示在AndroidManifest.xml中增加TestActivity的声明,如下所示,注意粗体字部分:

 

完成这个修改后再重新运行该程序,就一切都正常了。

从AndroidManifest.xml修改的过程我们可以体会到,Intent机制即使在程序内部且显式指定接收者,也还是需要在AndroidManifest.xml中声明TestActivity。这个过程并不像一个简单的函数调用,显式的Intent也同样经过了Android应用程序框架所提供的支持,从满足条件的Activity中进行选择,如果不在AndroidManifest.xml中进行声明,则Android应用程序框架找不到所需要的Activity。

请读者通过我们的示例来逐步理解AndroidManifest.xml在这个过程中所扮演的角色,这样有利于理解Intent的作用,及后面的Intent Filter。当然,这个例子仅仅是开始,且看下文分解。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<? xml  version = "1.0"  encoding = "utf-8" ?>
< manifest  xmlns:android = "http://schemas.android.com/apk/res/android"
     package = "com.tope.samples"
     android:versionCode = "1"
     android:versionName = "1.0" >
     < application  android:icon = "@drawable/icon"  android:label = "@string/app_name" >
         < activity  android:name = ".SimpleIntentTest"
             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 = ".TestActivity" />
     </ application >
     < uses-sdk  android:minSdkVersion = "3"  />
</ manifest >
  • 不同应用程序之间的Activity切换

上面的例子我们所做的是在同一应用程序中进行Activity的切换,那么在不同的应用程序中,是否也能这么做呢,答案是肯定的,不过对应的代码要稍作修改。本例中我们需要两个应用程序,可利用上例中的SimpleIntentTest作为其中之一,另外还需要写一个新的程序,来调用SimpleIntentTest应用程序中的TestActivity。

我们新建程序CrossIntentTest(注意不是新建一个类,如果是Eclipse环境,选择File->New->Project新建工程),其中只有一个Activity,其源代码与SimpleIntentTest.java类似:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package  com.tope.samples.intent.cross;
import  android.app.Activity;
import  android.content.Intent;
import  android.os.Bundle;
import  android.view.View;
import  android.widget.Button;
 
public  class  CrossIntentTest extends  Activity
implements  View.OnClickListener{
/** Called when the activity is first created. */
@Override
     public  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.main);
         Button startBtn = (Button)findViewById(R.id.start_activity);
         startBtn.setOnClickListener( this );
     }
 
     public  void  onClick(View v) {
         switch  (v.getId()) {
         case  R.id.start_activity:
             Intent intent = new  Intent();
             intent.setClassName( "com.tope.samples.intent.simple" , "com.tope.samples.intent.simple.TestActivity" );
             startActivity(intent);
             break ;
         default :
         break ;
         }
    
}

注意比较它与SimpleIntentTest的不同之处主要在于初始化Intent对象的过程:

?
1
2
3
Intent intent = new  Intent();
intent.setClassName( "com.tope.samples.intent.simple" "com.tope.samples.intent.simple.TestActivity" );
startActivity(intent);

这里采用了Intent最简单的不带参数的构造函数,然后通过setClassName()函数来指定要启动哪个包中的哪个Activity,而不是像上例中的通过Intent(Context packageContext, Class<?> cls)这个构造函数来初始化Intent对象,这是因为,要启动的TestActivity与CrossIntentTest不在同一个包中,要指定Class参数比较麻烦,所以通常启动不同程序的Activity时便采用上面的setClassName()的方式。除此之外,你也可以利用Android提供的类似的setComponent()方法,具体使用方法请参考Android SDK的文档。

另外我们还需要修改SimpleIntentTest程序中的AndroidManifest.xml文件,为TestActivity的声明添加Intent Filter,即将原来的

<activity android:name=".TestActivity"/>

修改为:

<activity android:name=".TestActivity">

    <intent-filter>

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

    </intent-filter>

</activity>

对于不同应用之间的Activity的切换,这里需要在Intent Filter中设置至少一个Action,否则其他的应用将没有权限调用这个Activity。这里我们开始接触Intent Filter和Action这些概念了,读者应该可以感觉到,设置Intent Filter和Action主要的目的,是为了让其他需要调用这个Activity的程序能够顺利的调用它。除了Action之外,Intent Filter还可以设置Category、Data等,用来更加精确的匹配Intent与Activity,这在后文将有详细介绍。

程序运行的截图与上例类似,这里就不再重复了。

隐式Intent(Implicit Intent)

如果Intent机制仅仅提供上面的显式Intent用法的话,这种相对复杂的机制似乎意义并不是很大。确实,Intent机制更重要的作用在于下面这种隐式的Intent,即Intent的发送者不指定接收者,很可能不知道也不关心接收者是谁,而由Android框架去寻找最匹配的接收者。

  1. 最简单的隐式Intent

我们先从最简单的例子开始。下面的ImplicitIntentTest程序用来启动Android自带的打电话功能的Dialer程序。

ImplicitIntentTest程序只包含一个java源文件ImplicitIntentTest.java,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package  com.tope.samples.intent.implicit;
 
import  android.app.Activity;
import  android.content.Intent;
import  android.os.Bundle;
import  android.view.View;
import  android.widget.Button;
 
public  class  ImplicitIntentTest extends  Activity
implements  View.OnClickListener{
/** Called when the activity is first created. */
     @Override
     public  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.main);
         Button startBtn = (Button)findViewById(R.id.dial);
         startBtn.setOnClickListener( this );
     }
 
     public  void  onClick(View v) {
         switch  (v.getId()) {
         case  R.id.dial:
             Intent intent = new  Intent(Intent.ACTION_DIAL);
             startActivity(intent);
             break ;
         default :
             break ;
         }
     }  
}

该程序在Intent的使用上,与上节中的使用方式有很大的不同,即根本不指定接收者,初始化Intent对象时,只是传入参数,设定Action为Intent.ACTION_DIAL:

?
1
2
Intent intent = new  Intent(Intent.ACTION_DIAL);
startActivity(intent);

这里使用的构造函数的原型如下:

Intent(String action);

有关Action的作用后文将有详细说明,这里读者可暂时将它理解为描述这个Intent的一种方式,这种使用方式看上去比较奇怪,Intent的发送者只是指定了Action为Intent.ACTION_DIAL,那么怎么找到接收者呢?来看下面的例子。

  • 增加一个接收者

事实上接收者如果希望能够接收某些Intent,需要像上节例子中一样,通过在AndroidManifest.xml中增加Activity的声明,并设置对应的Intent Filter和Action,才能被Android的应用程序框架所匹配。为了证明这一点,我们修改上一节SimpleIntentTest程序中的AndroidManifest.xml文件,将TestActivity的声明部分改为:

?
1
2
3
4
5
6
7
< activity  android:name = ".TestActivity" >
     < intent-filter >
         < action  android:name = "android.intent.action.DEFAULT"  />
         < action  android:name = "android.intent.action.DIAL"  />
         < category  android:name = "android.intent.category.DEFAULT"  />
     </ intent-filter >
</ activity >

修改完之后注意要重新安装SimpleIntentTest程序的apk包,然后再尝试运行ImplicitIntentTest程序(不是SimpleIntentTest程序)。

回过头来看我们是怎么做到这一点的。我们仅仅在SimpleIntentTest程序的AndroidManifest.xml文件中增加了下面的两行:

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

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

这两行修改了原来的Intent Filter,这样这个Activity才能够接收到我们发送的Intent。我们通过这个改动及其作用,可以进一步理解隐式Intent, Intent Filter及Action, Category等概念——Intent发送者设定Action来说明将要进行的动作,而Intent的接收者在AndroidManifest.xml文件中通过设定Intent Filter来声明自己能接收哪些Intent。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值