待學的
- 任務棧
Activity的 lauchMode
在android系統默認狀態下,當我們啟動activity的時候,系統會創建多個實例,并把它們放到一個任務棧中,當我們回退的時候,這些Activity 會一一回退。 任務棧是一個“先進后出”的站結構,當棧中內容為空的時候,系統會回收這個任務棧。 在創建 activity 的時候,有四種啟動方式,下面以一一列舉:
standard 標準的啟動模式,也系統默認啟動模式。 每次啟動都會重建一個新的實例。它的 onCreate、 onStart、onResume 都會被調用。
一個任務棧可以有多個實例,每個實例可以屬于不同的任務棧。 在這種模式下,誰啟動了這個activity,那么這個activity就運行在誰的任務棧中。
當使用 ApplicationContext 去啟動 standard 模式的activity時候,會報錯;
這是因為非 activity 類型的 context 并沒有所謂的任務棧,所以會出現問題。解決方法就是為待啟動的activity指定 FLAG_ACTIVITY_NEW_TASK 標記位,這樣啟動的時候就會新建一個任務棧,這時候,實際以 singleTask 模式啟動的。singleTop 棧頂復用模式。這種模式下,如果該 Activity 已經有實例位于棧頂, 那么該 activity 的實例不會被重新重建,同時它的 onNewIntent 方法會被回調,但是 onCreate、onStart 等不會被調用。
singleTask 棧內復用模式。這是一種單例模式,在這種模式下,只要棧存在于需要的任務棧中,多次調用都不會重新創建實例, 系統也會回調 OnNewIntent。
具體說:當一個具有 singleTask 模式的activity A請求啟動后, 系統首先會尋找是否存在 A 想要的任務棧, 如果不存在,就新建一個任務棧,然后創建 A的實例后放入棧中。 如果A所需的任務棧存在,這時要看A是否在棧中有實例存在, 如果有實例存在,系統會把A調到棧頂并調用它的 onNewIntent, 如果實例不存在,就創建A的實例,并壓入棧中。
singleTask 默認具有 clearTop 的效果, 會導致已存在的實例之上的activity全部出棧。
- singleInstance 單實例模式,這是一種加強的 singleTask 模式,它除了具有 singleTask 模式的所有特性以外, 還加強了一點,那就是具有此種模式的 activity 只能單獨地位于一個棧中。比如 Activity A是singleInstance 模式, 當A啟動以后,系統會為它創建一個新的任務棧,然后 A 獨自存在在這個新的任務棧中,由于棧內復用的特點, 后續均不會創建新的實例,除非這個獨特的任務棧被系統銷毀了。
幾個問題
問題1: 文中提到的某個 activity所需的任務棧, 什么是 activity 所需要的任務棧呢?
這要從一個參數說起: TaskAffinity 翻譯為任務相關性。這個參數標識了一個 activity 所需要的任務棧的名字,默認情況下,所有 activity 所需的任務棧的名字為應用的包名。 當然我們可以為每個 activity 單獨指定 taskaffinity 屬性,這個屬性要和包名不同, 否則相當于沒指定。
TaskAffinity 屬性主要和 singleTask 或者 allowTaskReparenting 屬性配對使用,其他情況下沒意義。
當TaskAffinity和singleTask啟動模式配對使用的時候, 他是具有該模式的 Activity 的目前任務棧的名字,待啟動的 activity 會運行在名字和 taskAffinity 名字相同的任務棧中。
問題2 什么情況下 TaskAffinity 會生效?
只有Activity設置了 lauchMode = "singleTask" 或者 allowTaskReparenting 才起作用
Activity 的 Flags
Activity 的標記位有很多,列舉一些常用的標記位。標記位有多種作用。可以設定Activity的啟動模式,也可以影響 activity 的運行狀態。
影響啟動模式的:
- FLAG_ACTIVITY_NEW_TASK 等同于xml設置 "singleTask"啟動模式
- FLAG_ACTIVITY_SINGLE_TOP 等同于 xml 中設置 "singleTop"啟動模式
影響運行狀態的:
- FLAG_ACTIVITY_CLEAR_TOP 如果啟動模式為 "singleTask",且該activity已存在,則清除棧中該activity之上的,只調用 onNewIntent() ,不重新啟動。
如果啟動模式為 “standard”,那么它連同它之上的都要出棧,系統會創建新的 activity并放入棧頂。 - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 具有這個標記的不會出現在歷史 activity 列表中,相當于在 xml 中設置屬性 android:excludeFromRecents="true"
IntentFilter 的匹配規則
啟動 Activity 有兩種方式,顯式啟動和隱式啟動。
顯式啟動要指定被啟動的組件信息,比較簡單具體不談。
隱式啟動則需要 intent 能夠匹配目標組件的 IntentFilter 中所設置的過濾信息。
IntentFilter 中的過濾信息有 action、category、data 三個類別,給了匹配過濾列表需要同時匹配列表中的 action, category, data信息。一個activity可以有多個 IntentFilter, 一個intent只要能匹配其中一組 intentFilter 就可以啟動對應的 activity。
- action 的匹配規則
action 是一個字符串,一個intent一般只攜帶一個action。如果攜帶多個action,則都要在 IntentFilter中包含才能正常啟動,但如果 Intent 中沒有指定 action 則匹配失敗。 匹配時嚴格區分大小寫。 intent.setAction("..........")
- category 的匹配規則
category 是一個字符串。 如果 intent 中攜帶 category,不管攜帶幾個,都要在 IntentFilter 中找到相匹配的才能啟動,若不攜帶, 系統會默認設置 "android.intent.category.DEFAULT"這個 category,所以在 IntentFilter 中至少要設置 <category android:name="android.intent.category.DEFAULT"/> 這一個配置項。否則啟動失敗。
- data 的匹配規則
data 的匹配規則和 action 類似, 但是 data 要復雜的多,先了解一下data 的結構 :
<data android:scheme="String"
android:host="String"
android:port="String"
android:path="String"
android:pathPattern="String"
android:pathPrefix="String"
android:mimeType="String"/>
data由兩部分組成: mimeType 和 URL.
mimeType 指媒體類型,如 text/plain,image/jpeg, audio/mpeg4-generic 和 video/* 等,可以表示 文本,圖像,音頻,視頻等不同的媒體格式。
URL 包含的數據略多, 下面是 URL 的結構:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
例子:
content://com.example.project:200/floder/subfloder/etc
http://www.baidu.com:80/search/info
每個數據的含義:
scheme: URL的模式,如 http, file, content 等,如果URL中沒有指定 scheme,那么整個 URL 的其他參數無效,也就意味著URL無效。
Host: URL的主機名,比如 www.baidu.com, 如果 host 未指定,則其他參數無效,URL也無效。
Port : 端口號, 只有指定了 scheme 和 host 時候, port 才有意義。
Path, PathPrefix, PathPattern: 這三個參數表述路徑信息,其中 path 表示完整的路徑信息;pathPattern
示例1:
<intent-filter>
<data android:mimeType=" image/* " />
......
</intent-filter>
這種規則制定了媒體類型為照片,那么intent中的mimeType為“ image/* ”才能匹配,這種情況下沒有指定URL, 但是有默認值, 默認值是 content 和 file, 也就是說,雖然沒有指定 URL,但是 intent 中的URL部分的 scheme 必須為 content 或者 file 才能匹配。
另外,想要為 Intent 指定完整的data,必須要使用 inent.setDataAndType, 不能先使用 setData 后使用 setType ,這樣會清除對方的置。
實例2:
<intent-filter>
<data android scheme="http" .... android:mimeType=" video/mpeg " />
<data android scheme="http" .... android:mimeType=" audio/mpeg " />
......
</intent-filter>
這種規則指定了兩組 data, 為了匹配實例2的規則,正確的 intent 應該如下:
intent.setDataAndType(Url.parse("http://abc"), "video/mpeg");
或者是
intent.setDataAndType(Url.parse("http://abc"), "audio/mpeg");
最后, 如果,匹配不到合適的 activity 怎么辦?只能奔潰么?
當我們隱式啟動一個 Activity 的時候,可以做一下判斷,看是否能夠匹配我們的 Intent 。方法有兩種, 都在 Context 中:
public abstract List<ResolveInfo> getPackageManager().queryIntentActivities(Intent intent,int flag)
或者
public abstract ResolveInfo getPackageManager().resolveActivity(Intent intent,int flag);
上述兩個方法,第一個參數沒什么好說的, 第二個參數,我們要使用 PackageManager.MATCH_DEFAULT_ONLY 這個標記, 這個標記的含義是僅僅匹配那些在 intent-filter 中聲明了 <category android:name="android.intent.category.DEFAULT"> 這個 Catrgory 的 activity, 是要返回不為null, 那么就有匹配到的activity, 就一定能隱式啟動成功。因為不含有 DEFAULT 這個category 的activity 是無法接受隱式intent的。
在匹配規則中, 有一類 action 和 category 比較重要,那就是:
<action android:name="anroid.intent.action.MAIN"/>
<categoty android:name="android.intent.category.LAUCHER"/>
這兩者公共來表明一個入口 activity,并且會出現在Home Launcher上。少了任何一個都不行。如果在 Intent-Filter 中還有data選項, 則不匹配限制條件,不能出現在 Home Laucher上。 如下面這種 intent-filter:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="urlscheme"
android:host="auth_activity">
</intent-filter>
</activity>
針對 service 和 BroadcastReceiver ,PacketManager 同樣提供了類似的方法去獲取成功匹配的組件信息。