最全面的Android 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切換
通常一個應用程式中需要多個UI 螢幕,也就需要多個Activity 類,並且在這些 Activity 之間進行切換,這種切換就是通過 Intent 機制來實現的。
在同一個應用程式中切換 Activity時,我們通常都知道要啟動的 Activity 具體是哪一個,因此常用顯式的 Intent 來實現。下面的例子用來實現一個非常簡單的應用程式 SimpleIntentTest ,它包括兩個UI 螢幕也就是兩個 Activity——SimpleIntentTest類和 TestActivity 類, SimpleIntentTest類有一個按鈕用來啟動 TestActivity。
程式的程式碼非常簡單, 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(Implicit Intent)
如果 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);
這裡使用的建構函式的原型如下:
Intent ( String action);
這裡讀者可暫時將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 。
相關文章
- Android——RxJava2史上最全講解AndroidRxJava
- 史上最全redux,react-redux,中介軟體機制講解ReduxReact
- Android Service最全面的解析Android
- 【Android基礎】講講Android的事件分發機制Android事件
- Android 螢幕適配:最全面的解決方案Android
- 面試:講講 Android 的事件分發機制面試Android事件
- Android Intent ServiceAndroidIntent
- Android中的intentAndroidIntent
- Android事件分發機制:基礎篇:最全面、最易懂Android事件
- Android 訊息機制詳解(Android P)Android
- android事件分發機制詳解Android事件
- 圖解Android中的binder機制圖解Android
- 最全面的UML教程
- Android Classloader機制Android
- Android NestedScrolling機制Android
- 深入淺出Android事件分發機制:最全面最易懂:基礎篇(一)Android事件
- Android進階;Handler訊息機制詳解Android
- Android--Handler機制及原始碼詳解Android原始碼
- Android Handler訊息機制原始碼解讀Android原始碼
- Android 碎片(Fragment)講解AndroidFragment
- Android Intent 傳遞資料大小限制AndroidIntent
- Android IPC 機制分析Android
- Android簽名機制Android
- Android包管理機制Android
- android中反射機制Android反射
- 史上最全的Rxjava2講解(使用篇)RxJava
- 詳解 Android 中的 IPC 機制:基礎篇Android
- Android事件分發機制,你瞭解過嗎?Android事件
- Android之Handler訊息傳遞機制詳解Android
- Android頁面跳轉與返回機制詳解Android
- 每日一問:Android 訊息機制,我有必要再講一次!Android
- Android Handler機制之Message及Message回收機制Android
- Android之Intent顯示和隱式呼叫AndroidIntent
- Android架構系列-如何優美的寫IntentAndroid架構Intent
- Android studio(建立、監聽器intent選單)AndroidIntent
- Android基礎及應用 Intent的呼叫AndroidIntent
- Rxjava2最全面的解析RxJava
- 深度解讀 MongoDB 最全面的增強版本 4.4 新特性MongoDB
- Zookeeper的選舉機制和同步機制超詳細講解,面試經常問到!面試