Android開發(fā)進(jìn)階<一>--Intent與IntentFilter

1.前言

在Android中有四大組件,這些組件中有三個組件與Intent相關(guān),可見Intent在Android整個生態(tài)中的地位高度。Intent是信息的載體,用它可以去請求組件做相應(yīng)的操作,但是相對于這個功能,Intent本身的結(jié)構(gòu)更值得我們?nèi)パ芯俊?/p>

讀完本篇你將會了解:

1.Intent的顯示調(diào)用與隱式調(diào)用
2.隱式調(diào)用的匹配規(guī)則(重點(diǎn))
3.隱式調(diào)用的使用場景
4.隱式調(diào)用的優(yōu)點(diǎn)

2. Intent與組件

Intent促進(jìn)了組件之間的交互,這對于開發(fā)者非常重要,而且它還能做為消息的載體,去指導(dǎo)組件做出相應(yīng)的行為,也就是說Intent可以攜帶數(shù)據(jù),傳遞給Activity/Service/BroadcastReceiver。

  • 啟動Activity。Activity可以簡單的理解為手機(jī)屏幕中的一個頁面,你可以通過將Intent傳入startActivity方法來啟動一個Activity的實(shí)例,也就是一個頁面,同時,Intent也可以攜帶數(shù)據(jù),傳遞給新的Activity。如果想要獲取新建的Activity執(zhí)行結(jié)果,可以通過onActivityResult()方法去啟動Activity。
  • 啟動Service。Service是一個不呈現(xiàn)交互畫面的后臺執(zhí)行操作組件,可以通過將Intent穿入startService()方法來啟動一個Service來啟動服務(wù)。
  • 傳遞廣播BroadCast。廣播是任何應(yīng)用都可以接收到的消息,通過將Intent傳遞給 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast()方法,可以將廣播傳遞接收方。

3.Intent類型

Intent分為兩種類型,分別為:

  1. Intent顯示調(diào)用;
  2. Intent隱式調(diào)用;

3.1 Intent顯示調(diào)用

就是我們平常最常用的調(diào)用方式,直接指定組件的信息,包括包名和類名。比較簡單,這里詳細(xì)說說Intent的隱式調(diào)用。

3.2 Intent隱式調(diào)用

Intent隱式調(diào)用不指定相應(yīng)的組件,而是通過Intent和IntentFilter來過濾信息,來達(dá)到啟動對應(yīng)組件的目的。下面用新的章節(jié)來說說隱式調(diào)用相關(guān)。

4.Intent隱式調(diào)用的作用與場景

4.1 Intent隱式調(diào)用的作用

降低耦合性:在組件化的時代,我們可以在不需要依賴的情況下,直接隱式調(diào)用組件。
更為安全:不需要指定組件的信息,只要是符合匹配規(guī)范的組件才會被調(diào)用。
更為靈活: 有著更為廣泛的應(yīng)用場景,我們可以調(diào)用許多系統(tǒng)提供的服務(wù),如撥打電話,發(fā)短信等。

4.2 Intent隱式調(diào)用的場景

相信很多人都做過撥打電話的demo,通過如下代碼就可以實(shí)現(xiàn)。

Intent intent = new Intent(Intent.ACTION_DIAL);
Uri data = Uri.parse("tel:" + "手機(jī)號碼");
intent.setData(data);
startActivity(intent);

這就是一個簡單的隱式調(diào)用,我們通過設(shè)置Intent的action和data來匹配對應(yīng)的組件。(這里有個匹配規(guī)則,后面會具體講)

當(dāng)然還有許多應(yīng)用場景:

  1. 不同應(yīng)用間組件的啟動。
  2. lib與module之間的交互。
    等等,都可以應(yīng)用到Intent隱式調(diào)用。

5.Intent隱式調(diào)用匹配規(guī)則--IntentFilter

我們都只知道隱式調(diào)用是去匹配相應(yīng)的組件從而達(dá)到啟動組件的目的,但是具體是怎么匹配的呢?我們就要詳細(xì)來了解下IntentFilter。

IntentFilter中可以設(shè)置的過濾信息有三種,為action,category,data。

我們先看看一個簡單的過濾規(guī)則

<activity android:name="SecondActivity">
    <intent-filter>
        <action android:name="android.intent.action.Second"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="image/*"/>
    </intent-filter>
</activity>

這就是一個簡單的Intent,我們可以通過如下規(guī)則來啟動這個組件。

Intent intent = new Intent();  
intent.setAction("android.intent.action.Second");  
intent.addCategory("android.intent.category.DEFAULT");  
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
startActivity(intent);   

通過這個就可以隱式啟動SecondActivity了。

這樣我們對IntentFilter有了個基本的了解,下面我們在詳細(xì)說說他的匹配規(guī)則。

5.1 action匹配規(guī)則

action是一個字符串,系統(tǒng)定義了一些action,如撥打電話等等,我們也可以自定義action。action匹配規(guī)則是一個intent filter中可以有多個action,那么只要intent中的action能和intent filter中的任何一個action相同集合匹配成功。需要注意的是,intent中沒有指定action,那么匹配失敗。除此之外,action區(qū)分大小寫。

5.2 category匹配規(guī)則

category是一個字符串,系統(tǒng)定義了一些category,我們也可以自定義category,category的匹配規(guī)則和action不同,intent中如果出現(xiàn)了category,不管有幾個,都必須是intent filter中已經(jīng)定義的category,也就是說intent可以沒有category,一旦有,每個都必須和intent filter中定義的任意一個category相同。那為什么我們沒在intent filter中加android.intent.category.DEFAULT這個category會報錯呢?原因是系統(tǒng)在調(diào)用startActivity或者startActivityForResult的時候會默認(rèn)在intent中加上android.intent.category.DEFAULT這個category。

5.3 data匹配規(guī)則

data的匹配規(guī)則和action類似,如果intent filter中定義了data,那么intent中也必須要定義可匹配的data,否則匹配失敗。
首先來了解一下data的結(jié)構(gòu),data由兩部分組成,mimeType和URI。

5.3.1 mimetype

mimeType指媒體類型,比如image/jpeg、text/plain、video/*等,可以表示圖片、文本、視頻等不同的媒體格式。

5.3.2 URI

URI包括scheme、host、port 和path四個部分,host和port合起來也成authority(host:port)部分

<scheme>://<host>:<port>/<path>

1.Scheme: URI 的模式,比如 http,file,content,如果URI沒有指定Scheme,那么整個URI的其他參數(shù)無效,也就意味著URI無效;
2.Host:URI的主機(jī)名,比如www.baidu.com,如果host未指定,那么整個URI的其他參數(shù)無效,也就意味著URI無效;
3.Port:URI的端口號,比如80,僅當(dāng)URI中制定了scheme和host參數(shù)的時候port參數(shù)才有意義。
4.Path:路徑。

例如:

content://192.168.10.1:8080/fold/etc

在這個URI中,scheme是content,host是192.168.10.1,port是8080,path是folder/etc。我們平時使用的網(wǎng)絡(luò)url就是這種格式。

在URI中,每個組成部分都是可選的,但是有線性的依賴關(guān)系

當(dāng)進(jìn)行URI匹配時候,并不是比較全部,而是局部對比,以下是URI匹配規(guī)則。

1.如果一個URI僅聲明了scheme部分,那么所有擁有與其相同的scheme的URI都會通過匹配,其他部分不做匹配

2.如果一個URI聲明了scheme部分和authority部分,那么擁有與其相同scheme和authority的URI才能匹配成功,path部分不做匹配

3.如果一個URI所有的部分都聲明了,那么只有所有部分都相同的URI才能匹配成功

5.3.3 data過濾規(guī)則

了解了data的結(jié)構(gòu),接下來開始介紹data的過濾規(guī)則,下面分情況說明

  • 情況一:data規(guī)則不完整。如下所示
<intent-filter>
      <data android:mimeType="image/*"/>
</intent-filter>

這種規(guī)則指定了媒體類型為所有類型的圖片,那么intent中的mimeType屬性必須為“image/*”才能匹配成功,這種情況雖然沒有指定URI,但intent中的URL部分的schema默認(rèn)值為content和file。也就是說雖然沒有指定URI,但是intent中的URI部分的schema必須為content或者file才能匹配成功,這點(diǎn)尤其需要注意。

  • 情況二:定義了多組data規(guī)則,并且每個data都定義了完整屬性,既有URI又有mimeType。如下所示
<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
</intent-filter>

為了匹配這種intent filter,我們也需要在intent中完整定義其中一組data規(guī)則才能匹配成功。另外,如果要為intent指定完整的data,必須調(diào)用setDataAndType方法,不能先調(diào)用setData再調(diào)用setType,因?yàn)檫@兩個方法彼此會清除對方的值。
最后,但我們通過隱式方式啟動一個Activity時,可以做一下判斷,看是否有Activity能夠匹配我們的intent,以免不必要的奔潰。判斷方法有兩種:一是采用PackageManager的resolveActivity方法,二是采用Intent的resolveActivity方法,如果他們找不到匹配的Activity就會返回null,根據(jù)其返回值我們就規(guī)避上述問題了。
首先我們來看看Intent的resolveActivity方法源碼

public ComponentName resolveActivity(PackageManager pm) {
        if (mComponent != null) {
            return mComponent;
        }

        ResolveInfo info = pm.resolveActivity(
            this, PackageManager.MATCH_DEFAULT_ONLY);
        if (info != null) {
            return new ComponentName(
                    info.activityInfo.applicationInfo.packageName,
                    info.activityInfo.name);
        }

        return null;
    }

從中我們可以看出其內(nèi)部也是直接調(diào)用了PackageManager的resolveActivity方法來判斷匹配是否成功。
我們再來看看PackageManager的resolveActivity方法

public abstract ResolveInfo resolveActivity(Intent intent, int flags);

第一個參數(shù)很好理解,就是我們要解析的intent,第二個參數(shù)源碼中使用的是MATCH_DEFAULT_ONLY這個標(biāo)記位,這個標(biāo)記位的含義是僅僅匹配那些在intent filter中聲明了<category android:name="android.intent.category.DEFAULT"/>這個category的Activity。使用它的意義在于只要此方法不返回null,那么startActivity一定可以成功。最后是示例:

Intent intent = new Intent();
intent.setAction("com.dev.test");
intent.setType("image/*");
ComponentName componentName = intent.resolveActivity(getPackageManager());
if (componentName == null) {
    //如果為null,表示匹配失敗
    Toast.makeText(MainActivity.this, "匹配失敗", Toast.LENGTH_SHORT).show();
} else {
    //如果不為null,啟動Activity
    startActivity(intent);
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容