Activity的生命週期和啓動模式——IntentFilter的匹配規則

我們知道,啓動Activity分爲兩種,顯示調用和隱式調用。二者的區別這裏就不多說了,顯示調用需要明確地指定被啓動對象的組件信息,包括包名和類名,而隱式啓動則不需要明確指定組件信息。原則上一個Intent不應該既是顯示調用又是隱式調用,如果二者共存的話以顯示調用爲主。顯示調用很簡單,這裏主要介紹下隱式調用。隱式調用需要Intent能夠匹配目標組件的IntentFilter中所設置的過濾信息,如果不匹配將無法啓動目標Activity。IntentFilter中的過濾信息有action、category、data,下面式一個過濾規則的實例。

        <activity android:name=".ThirdActivity"
            android:taskAffinity="chapter1.chen.task1"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="chapter1.chen.chapter1.c"/>
                <action android:name="chapter1.chen.chapter1.d"/>
                <category android:name="chapter1.chen.chapter1.c"/>
                <category android:name="chapter1.chen.chapter1.d"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="text/plain"/>
            </intent-filter>
        </activity>

爲了匹配過濾列表,需要同時匹配過濾列表中的action、category、data信息,否則匹配失敗。一個過濾表中的action、category、data可以有多個,同一類別的信息公告約束當前類別的匹配過程。只有一個Intent同時匹配action類別、category類別、data類別纔算完全匹配,只有完全匹配才能成功啓動目標Activity。另外一點,一個Activity中可以有多個intent-filter,一個intent只要能匹配任何一組intent-filter即可成功啓動對應的Activity,如下所示。

        <activity android:name=".Test1Activity" >
            <intent-filter>
                <action android:name="android.intent.action.SEND"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="text/plain"/>
            </intent-filter>

            <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>

下面詳細分析各種屬性的匹配規則。

1.action的匹配規則
action是一個字符串,系統定義了一些ACTION,同時我們也可以在應用中定義自己的action。action的匹配規則是Intent中的action必須能夠和過濾規則中的action匹配,這裏說的匹配是值action的字符串值完全一樣。一個過濾規則中可以有多個action,那麼只要Intent中的action能夠和過濾規則中的任何一個action相同即可匹配成功。針對上面的過濾規則,只要我們的Intent中的Action值爲chapter1.chen.chapter1.c或者chapter1.chen.chapter1.d都能夠成功匹配。需要注意的是,Intent中如果沒有指定action,那麼匹配失敗。總結一下,action的匹配要求Intent中的action存在且必須和過濾規則中的其中一個action相同,這裏需要注意它和category匹配規則的不同。另外,action區分大小寫,大小寫不同字符串相同的action會匹配失敗。

2.category的匹配規則
category是一個字符串,系統預定義了一些category,同時我們也可以在應用中定義自己的category。category的匹配規則和action不同,它要求Intent中如果含有category,那麼所有的category都必須和過濾規則中的一個category相同。換句話說,Intent中如果出現了category,不管有幾個category,對於每個category來說,它必須是過濾規則中已經定義了的 category。當然Intent中可以沒有category,如果沒有category的話,按照上面的描述,這個Intent仍然可以匹配成功。這裏需要注意下它和action匹配過程的不同,action要求Intent中必須有一個action且必須能夠和過濾規則中的某個action相同,而category要求Intent可以沒有category,但是如果你一旦有了category,不管幾個,每個都要能夠和過濾規則中的任何一個category相同。爲了匹配前面的過濾規則中的category,我們可以寫出下面的Intent,intent.addCategory("chapter1.chen.chapter1.c")或者intent.addCategory("chapter1.chen.chapter1.d")亦或者不設置category。爲什麼不設置category也可以匹配呢?原因是系統在調用startActivity或者startActivityForResult的時候會默認爲Intent加上"android.intent.category.DEFAULT“這個category,所以這個category就可以匹配前面過濾規則中的第三個category。同時爲了我們的activity能夠接收隱式調用,就必須在intent-filter中指定"android.intent.category.DEFAULT"這個category,原因剛纔已經說明了。

3.data的匹配規則
data的匹配規則和action類似,如果過濾規則中定義了data,那麼intent中必須要定義可匹配的data。在介紹data的匹配規則之前,我們需要先了解下data的結構,因爲data稍微有些複雜。

data由兩部分組成,mimeType和URI。mimeType指媒體類型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示圖片、文本、視頻等不同的媒體格式,而URI中包含的數據就比較多了,下面是URI的結構:

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

這裏再結合幾個實際的例子就比較好理解了,如下所示.
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

看了上面的兩個示例應該就瞬間明白了,沒錯,就是這麼簡單.不過下面還是要介紹一下每個數據的含義.
scheme:URI的模式,比如http,file,content等,如果URI中沒有指定scheme,那麼整個URI的其他參數是無效的.這也意味着URI是無效的.

host:URI的主機名,比如www.baidu.com,如果host未指定,那麼整個URI中的其他參數無效,這也意味着URI是無效的.
port:URI中的端口號,比如80,僅當URI中指定了scheme和host參數的時候,port參數纔是有意義的.

path,pathPrefix,pathPattern:這三個參數表述路徑信息,其中path表示完整的路徑信息;pathPattern也表示完整的路徑信息,但是它裏面可以包含通配符"*","*"表示0個或多個任意字符,需要注意的是,由於正則表達式的規範,如果想表達真是的字符串,那麼"*"要寫成"\\*","\"要寫成"\\\\";pathPrefix表示路徑的前綴信息.
介紹完data的數據格式後,我們要說一下data的匹配規則了.前面說到,data的匹配規則和action類似,它也要求Intent中必須包含有data數據,並且data數據能夠完全匹配過濾規則中的某一個data.這裏的完全匹配是指過濾規則中出現的data部分也出現再了Intent中的data中.下面分情況說明.

(1)如下過濾規則:

<intent-filter>
		<data android:mimeType="image/*" />
		...
</intent-filter>

這種規則指定了媒體類型未所有類型的圖片,那麼mimeType屬性必須未"image/*"才能匹配,這種情況下雖然過濾規則沒有指定URI,但是卻有默認值,URI的默認值未content和file,也就是說雖然沒有指定URI,但是Intent中的URI部分scheme必須爲content或者file才能匹配,這點是需要注意的.爲了匹配(1)中規則,我們可以寫出如下示例:

intent.setDataAndType(Uri.parse("file://abc"),"image/png");

另外,如果要爲Intent指定完整的data,必須要調用setDataAndType方法,不能先調用setData再調用setType,因爲這兩個方法彼此會清楚對方的值,這個看源碼就哼容易理解,比如setData:

    public Intent setData(Uri data) {
        mData = data;
        mType = null;
        return this;
    }

可以發現,setData會把mimeType置爲null,同理setType也會把URI置爲null.

(2)如下過濾規則

            <intent-filter>
                ...
                <data android:mimeType="audio/mpeg" android:scheme="http" .../>
                <data android:mimeType="video/mpeg" android:scheme="http" .../>
            </intent-filter>

這中規則指定了兩組data規則,且每個data都指定了完整的屬性值,既有URI又有mimeType.爲了匹配(2)中規則,我們可以寫出如下示例:
        intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg");

或者
        intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg");

通過上面的兩個示例,應該已經明白 了data的匹配規則,關於data還有一個特殊情況需要說明下,這也是它和action不同的地方,如下兩種特殊的寫法,他們的作用是一樣的.
            <intent-filter>
                <data android:scheme="file" android:host="www.baidu.com"/>
                ...
            </intent-filter>
            
            <intent-filter>
                <data android:scheme="file"/>
                <data android:host="www.baidu.com"/>
                ...
            </intent-filter>

到這裏我們已經把IntentFilter的過濾規則都講解了一遍,記得本節前面給出的一個intent-filter的示例嗎?現在我們給出完全匹配它的intent:
        Intent intent = new Intent();
        intent.setAction("chapter1.chen.chapter1.c");
        intent.addCategory("chapter1.chen.chapter1.c");
        intent.setDataAndType(Uri.parse("file://abc"), "text/plain");

還記得schema是有默認值的嗎?如果把上面的intent.setDataAndType(Uri.parse("file://abc"), "text.plain")這句改成intent.setDataAndType(Uri.parse("http://abc"), "text.plain"),打開Activity就會報錯,提示無法找到Activity,如下圖所示.另外一點,Intent-filter的匹配規則對於Service和BroadcastReceiver也是同樣的道理,不過系統對於Service的建議是儘量使用顯示調用方式來啓動服務.

最後,當我們通過隱式方式啓動一個Activity的時候,可以做一下判斷,看是否有Activity能夠匹配我們的隱式Intent.如果不做判斷就有可能出現上述的錯誤.判斷方法有兩種:採用PackageManager的resolveActivity方法,或者Intent的resolveActivity方法,如果他們找不到匹配的Activity就會返回null,我們通過判斷返回值就可以規避上述錯誤了.另外PackageManager還提供了queryIntentActivities方法,這個方法和resolveActivity方法不同的是:它不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息.我們看一下queryIntentActivities和resolveActivity的方法原型:
public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags);

public abstract List<ResolveInfo> queryIntentActivities(Intent intent,
            @ResolveInfoFlags int flags);

上述兩個方法的第一個參數比較好理解,第二個參數需要注意,我們要使用MATCH_DEFAULT_ONLY這個標記,這個標記位的哈尼是緊緊匹配那些再intent-filter中生命了<category android:name="android.intent.category.DEFAULT"/>這個category的Activity.使用這個標記位的意義在於,只要上述兩個方法不會返回null,那麼startActivity一定可以成功.如果不用這個標記位,就可以把intent-filter中category不含DEFAULT的那些Activity給匹配出來,從而導致startActivity可能失敗.因爲不含有DEFAULT這個category的Activity無法接手隱式Intent的.再Action和category中,有一類action和category比較重要,他們是:
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
這二中共同作用是用來標明這是一個入口Activity並且會出現再系統的應用列表中,少了任何一個都沒有實際意義,也無法出現再系統的應用列表中,也就是二者缺一不可.另外,針對Service和BroadcastReceiver,PackageManager同樣提供了類似的方法獲取成功匹配的組件信息.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章