Android Intent機制實例詳解(Activity篇)php
Android 中提供了 Intent 機制來協助應用間的交互與通信,或者採用更準確的說法 是, Intent 不只可用於應用程序之間,也可用於應用程序內部的 Activity/Service 之間的交互。 Intent 這個英語單詞的本意是「目的、意向」等,對於較少從事 於大型平臺開發工做的程序員來講, 這可能 是一個不太容易理解的抽象概念,由於它與咱們日常使用的簡單函數/ 方法調用,或者上節中提到的經過庫調用接口的方式不太同樣。在 Intent 的使用中你看不到直接的函數調用,相對函數調用來講, Intent 是更爲抽象的概念,利用 Intent 所實現的軟件複用的粒度是Activity/Service ,比函數複用更高一些,另外耦合也更爲鬆散。html
Android 中與Intent 相關的還有 Action/Category 及 Intent Filter 等,另外還有用於廣播的 Intent ,這些元素摻雜在一塊兒,致使初學者不太容易迅速掌握 Intent 的用法。在講解這些名詞以前,咱們先來從下面的例子中 感覺一下 Intent 的一些基本用法,看看它能作些什麼,以後再來思考這種機制背後的意義。java
理解 Intent 的關鍵之一是理解清楚Intent 的兩種基本用法:一種是顯式的 Intent ,即在構造 Intent 對象時就指定接收者,這種方式與普通的函數調用相似, 只是複用的粒度有所差異;另外一種是隱式的 Intent ,即Intent 的發送者在構造 Intent 對象時,並不知道也不關心接收者是誰,這種方式與函數調用差異比較大,有利於下降發送者和接收 者之間的耦合。另外 Intent 除了發送外,還可用於廣播,這些都將在後文進行詳細講述。android
下面的一小節咱們來看看顯式 Intent 的用法。程序員
同一個應用程序中的Activity切換app
咱們在前面的章節已經討論過 Activity 的概念,一般一個應用程序中須要多個UI 屏幕,也就須要多個Activity 類,而且在這些 Activity 之間進行切換,這種切換就是經過 Intent 機制來實現的。框架
在同一個應用程序中切換 Activity時,咱們一般都知道要啓動的 Activity 具體是哪個,所以經常使用顯式的 Intent 來實現。下面的例子用來實現一個很是簡單的應用程序 SimpleIntentTest ,它包括兩個UI 屏幕也就是兩個 Activity——SimpleIntentTest類和 TestActivity 類, SimpleIntentTest類有一個按鈕用來啓動 TestActivity。ide
程序的代碼很是簡單, SimpleIntentTest類的源代碼以下:函數
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 或其餘開發環境下添加一個新的類,本書不做詳述,請參 考其餘文檔),以下所示:
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 的聲明,以下所示,注意粗體字部分:
<? 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 > |
完成這個修改後再從新運行該 程序,就一切都正常了。
從 AndroidManifest.xml修改的過程咱們能夠體會到, Intent 機制即便在 程序內部 且 顯式指定 接收者,也仍是須要在 AndroidManifest.xml 中聲明 TestActivity。這個過程並不像一個簡單的函數調用,顯式的 Intent 也一樣 通過了Android 應用程序框架所提供的支持,從知足條件的 Activity 中進行選擇,若是不在 AndroidManifest.xml中進行聲明,則 Android 應用程序框架找不到所須要的 Activity。
請讀者經過咱們的示例來逐步 理解 AndroidManifest.xml在這個過程當中所扮演的角色,這樣有利於理解 Intent的做用 ,及後面的 Intent Filter。固然,這個例子僅僅是開始,且看下文分解 。
² 不一樣應用程序之間的Activity切換
上面的例子咱們所作的是在同 一應用程序中進行 Activity 的切換,那麼在不一樣的應用程序中,是否也能這麼作呢,答案是確定的,不過對應的代碼要稍做修改。本例中咱們須要兩個應用程序,可利用上例中 的SimpleIntentTest做爲其中之一,另外還須要寫一個新的程序,來調用 SimpleIntentTest 應用程序中的 TestActivity。
咱們新建程序 CrossIntentTest(注意不是新建一個類,若是是 Eclipse 環境,選擇 File->New->Project新建工程),其中只有一個 Activity ,其源代碼與 SimpleIntentTest.java 相似 :
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 對象的過程:
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 機制僅僅提供上面的顯式 Intent 用法的話,這種相對複雜的機制彷佛意義並非很大。確 實,Intent 機制更重要的做用在於下面這種隱式的 Intent ,即 Intent 的發送者不指定接收者,極可能不知道也不關心接收者是誰,而由 Android 框架去尋找最匹配的接收者。
² 最簡單的隱式 Intent
咱們先從最簡單的例子開始。 下面的 ImplicitIntentTest 程序用來啓動 Android 自帶的打電話功能的 Dialer 程序。
ImplicitIntentTest 程序只包含一個java 源文件 ImplicitIntentTest.java,代碼以下所示:
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 :
Intent intent = new Intent(Intent. ACTION_DIAL ); startActivity(intent); |
這裏使用的構造函數的原型如 下:
有關 Action 的做用後文將有詳細說明,這裏讀者可暫時將它理解爲描 述這個 Intent 的一種方式,這種使用方式看上去比較奇怪, Intent 的發送者只是指定了 Action爲 Intent.ACTION_DIAL ,那麼怎麼找到 接收者呢?來看下面的例子。
² 增長一個接收者
事實上接收者若是但願可以接 收某些 Intent ,須要像上節例子中同樣,經過在 AndroidManifest.xml中增長Activity 的聲明,並設置對應的 Intent Filter 和 Action ,才能被 Android 的應用程序框架所匹配。爲了證實這一點,咱們修改上一 節 SimpleIntentTest 程序中的 AndroidManifest.xml 文件,將 TestActivity 的聲明部分改成:
< 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 程序)
這個截圖中的第二幅表示能夠 選擇 Dialer 或者 SimpleIntentTest 程序來完成 Intent.ACTION_DIAL ,也就是說,針對 Intent.ACTION_DIAL, Android 框架找到了兩個符合條件的 Activity,所以它將這兩個 Activity 分別列出,供用戶選擇。
回過頭來看咱們是怎麼作到這 一點的。咱們僅僅在 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 。