Android點將臺:外交官[-Intent-]
至美不過回首,往事重重,顧來時,荊棘漫漫,血浸途中。 今銅衣鐵靴,再行來路,任荊棘漫漫,唯落綠葉殘枝。 ----張風捷特烈 複製程式碼
零、前言
在此之前希望你已經閱讀了: Android點將臺:顏值擔當[-Activity-]
1.本文的知識點
隱式呼叫
和
顯示呼叫
2).物件的序列化與反序列化:
Parcelable(簡)
和
Serializable
3). Bundle類
的及其在intent的資料傳遞
4). Android原始碼
中 intent-filter
的解析流程
2.Intent總覽

類名:Intent父類:Object 實現的介面:[Parcelable, Cloneable] 包名:android.content'依賴類個數:52 內部類/介面個數:3 原始碼行數:10086原始碼行數(除註釋):3407 屬性個數:24方法個數:164 複製程式碼
一、Intent類簡單認識
Intent
上面可見Intent挺普通的,就是比較大,看起來10086行,感覺挺大的
除註釋和空行,裸碼3407,註釋比率之高,家庭背景繼承 Object
,介面平平,可謂白手起家
Activity
,傳送
BroadcastReceiver
,開啟
Service
元件之間通過Intent互相聯絡,並且傳遞資料,可謂名副其實的"外交官"
1.Intent建構函式

原始碼上來看一共有8個建構函式,上面兩個是空參和隱藏的,不用管
左邊兩個通過拷貝來生成Intent物件,兩參的拷貝是似有的
右邊兩個通過 設定匹配資訊
方法來生成Intent物件(隱式)
下面兩個加入了 ComponentName
來生成Intent物件 (顯式)
2.Intent中的常用成員變數

component(元件):目的元件(應用包名+元件全類名) action(動作):意圖的行為action category(類別):行為action的類別 data(資料):表示與動作要操縱的資料 type(資料型別):對於data範例的描寫 extras(擴充套件資訊):擴充套件資訊 Flags(標誌位):期望這個意圖的執行模式 複製程式碼
二、Intent的隱式使用
即不指定元件名,通過 action,category,data,type
等資訊開啟元件
系統中內建了很多應用,我們可以通過這些資訊來匹配開啟需要的應用
1.僅匹配Action
1.1:新建一個Activity: ActivityJustAction
intent-filter
自定義action:
www.toly1994.com.ActivityJustAction
這個名字隨便起,只要使用時對應就行了(一般是唯一的),當然也可以不唯一
沒有category會崩掉,這裡給個預設的category,也就是action的類別
class ActivityJustAction : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(LinearLayout(this)) title = "ActivityJustAction" } } ---->[AndroidManifest.xml配置]------------ <activity android:name=".activity.ActivityJustAction"> <intent-filter> <action android:name="www.toly1994.com.ActivityJustAction"></action> <category android:name="android.intent.category.DEFAULT"></category> </intent-filter> </activity> 複製程式碼
1.2:intent開啟指定Action:
就像一個人在喊,我要找 旺財
,然後 旺財
就來了

---->[IntentActivity]-------------- id_btn_just_action.setOnClickListener { v -> val intent = Intent("www.toly1994.com.ActivityJustAction") startActivity(intent) } 複製程式碼
1.3:兩個都叫 旺財
怎麼辦?
新建一個ActivityJustAction2,intent-filter設定的一樣
既然兩個都叫 旺財
,就把兩個都帶來,讓你選一個唄(你應該經常遇到)

class ActivityJustAction2 : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(LinearLayout(this)) title = "ActivityJustAction2" } } <activity android:name=".activity.ActivityJustAction2"> <intent-filter> <action android:name="www.toly1994.com.ActivityJustAction2"></action> <category android:name="android.intent.category.DEFAULT"></category> </intent-filter> </activity> 複製程式碼
2.匹配 action+category
名字加類別,一個 intent-filter
可以加多個類別,就像一件事物可以劃分在多個領域
如人、程式設計師、中國公民可以指同一人,新增category之後,相當於你喊了句:
我要找一個叫旺財的程式設計師
,這樣就能更精確匹配,縮小撞名的可能,方便管理
---->[AndroidManifest.xml配置]------------ <activity android:name=".activity.ActivityJustAction"> <intent-filter> <action android:name="www.toly1994.com.ActivityJustAction"></action> <category android:name="android.intent.category.DEFAULT"></category> <category android:name="www.toly1994.com.people"></category> <category android:name="www.toly1994.com.coder"></category> </intent-filter> </activity> <activity android:name=".activity.ActivityJustAction2"> <intent-filter> <action android:name="www.toly1994.com.ActivityJustAction"></action> <category android:name="android.intent.category.DEFAULT"></category> <category android:name="www.toly1994.com.dog"></category> <category android:name="www.toly1994.com.erha"></category> </intent-filter> </activity> ---->[IntentActivity]-------------- id_btn_just_action.setOnClickListener { v -> val intent = Intent("www.toly1994.com.ActivityJustAction") //intent.addCategory("www.toly1994.com.coder")//開1 //intent.addCategory("www.toly1994.com.people")//開1 //intent.addCategory("www.toly1994.com.dog")//開2 intent.addCategory("www.toly1994.com.erha")//開2 startActivity(intent) } 複製程式碼
3.行為+新增資源定位識別符號: action + data
說起Uri(Uniform Resource Identifier),統一資源識別符號
形式為: <scheme>://<authority><path>?<query>
3.1:開啟網頁

id_btn_open_web.setOnClickListener { v -> val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse("https://juejin.im/user/5b42c0656fb9a04fe727eb37") startActivity(intent) } 複製程式碼
3.2:開啟簡訊

/** * 傳送簡訊 * @param number 號碼 * @param body 內容 */ private fun sendMsg(number: String, body: String) { val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:$number")) intent.putExtra("sms_body", body) startActivity(intent) } 複製程式碼
4.Intent開啟相簿Activity( action+type
)
根據action打一個應用,附加 MIME型別

/** * 開啟相簿 */ private fun openGallery() { val intent = Intent(Intent.ACTION_PICK) intent.type = "image/*"; startActivity(intent) } 複製程式碼
看一下相簿的原始碼是如何配置

5.Intent開啟檔案 action+type+data

5.1:適配
Android API 24 及以上對file的Uri做了限制,需要適配一下
/** * 作者:張風捷特烈<br/> * 時間:2018/10/30 0030:18:38<br/> * 郵箱:[email protected]<br/> * 說明:適配類 */ public class Compat { public static void fileUri(Context context, Intent intent, File file, String type) { //判斷是否是AndroidN以及更高的版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", file); intent.setDataAndType(contentUri, type); } else { intent.setDataAndType(Uri.fromFile(file), type); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } } } ---->[AndroidManifest.xml配置provider]------ <!--android:authorities="本應用包名.fileProvider"--> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.toly1994.tolyservice.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ---->[xml/file_paths.xml]----------- <?xml version="1.0" encoding="utf-8"?> <paths> <!--Android/data/本應用包名/--> <external-path path="Android/data/com.toly1994.tolyservice/" name="files_root" /> <external-path path="." name="external_storage_root" /> </paths> 複製程式碼
5.2:使用
需要加檔案讀許可權 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
id_btn_music.setOnClickListener { v ->//音訊 val intent = Intent(Intent.ACTION_VIEW) val file = File("/sdcard/toly/勇氣-梁靜茹-1772728608-1.mp3") Compat.fileUri(this, intent, file, "audio/mp3") startActivity(intent) } id_btn_video.setOnClickListener { v ->//視訊 val intent = Intent(Intent.ACTION_VIEW) val file = File("/sdcard/toly/cy3d.mp4") Compat.fileUri(this, intent, file, "video/mp4") startActivity(intent) } id_btn_txt.setOnClickListener { v ->//文字 val intent = Intent(Intent.ACTION_VIEW) val file = File("/sdcard/toly/應龍.txt") Compat.fileUri(this, intent, file, "text/*") startActivity(intent) } id_btn_pic.setOnClickListener { v ->//圖片 val intent = Intent(Intent.ACTION_VIEW) val file = File("/sdcard/toly/touch.jpg.png") Compat.fileUri(this, intent, file, "image/*") startActivity(intent) } 複製程式碼
相簿原始碼中對於開啟一張圖片的配置:
隱式的intent抓住 action、category、data、type
四個要點就行了

三、Intent顯式呼叫
即已經明確需要開啟的元件
1.開啟元件 本元件上下文+目標元件位元組碼
這個是我們最常用的,開啟 Activity,Service,BroadcastReceiver
private fun openComponent() { val intent = Intent(this, MainActivity::class.java) startActivity(intent) } 複製程式碼

2.ComponentName的簡介
一直用Intent開啟Activity,貌似沒有分析過,現在進原始碼裡看看吧
---->[Intent#Intent(Context, Class<?>)]------- public Intent(Context packageContext, Class<?> cls) { mComponent = new ComponentName(packageContext, cls); } 可見該方法核心是ComponentName,顧名思義"元件名稱" 原始碼首行註釋說:特定應用程式元件的識別符號 ---->[ComponentName#ComponentName(Context, lang.Class<?>)]-------- public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) { mPackage = pkg.getPackageName(); mClass = cls.getName(); } ------------------------------------------------------------------- ComponentName是一個比較簡單的類,核心是兩個成員變數mPackage和mClass 這個兩參構造中,mPackage是傳入的context的包名,mClass是目標元件的類名 複製程式碼

看一下兩個String的ComponentName構造,更能表達出它們的作用
也能夠實現開啟元件的功能,所以知道專案的包名,和元件的全類名,就能開啟元件
val intent = Intent() val compName = ComponentName( "com.toly1994.tolyservice",//專案的包名 "com.toly1994.tolyservice.activity.MainActivity")//要開啟的元件全類名 intent.component = compName startActivity(intent) 複製程式碼
3.開啟微信: 元件包名+目標元件全類名+flag
private fun openComponent() { val intent = Intent() intent.flags=Intent.FLAG_ACTIVITY_NEW_TASK val compName = ComponentName( "com.tencent.mm",//本元件的包名 "com.tencent.mm.ui.LauncherUI")//要開啟的元件全類名 intent.component = compName startActivity(intent) } 複製程式碼

4.拷貝構造原始碼
---->[Intent拷貝構造]--------- public Intent(Intent o) { this(o, COPY_MODE_ALL); } //|--使用兩參的[COPY_MODE_ALL]模式 ---->[Intent兩參拷貝]--------- private Intent(Intent o, @CopyMode int copyMode) { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; this.mPackage = o.mPackage; this.mComponent = o.mComponent; if (o.mCategories != null) { this.mCategories = new ArraySet<>(o.mCategories); } //|--至此把category,action,data, type,component,package 的欄位拷貝了 //|--COPY_MODE_ALL顧名思義,把所有的內容都拷貝 if (copyMode != COPY_MODE_FILTER) { this.mFlags = o.mFlags; this.mContentUserHint = o.mContentUserHint; this.mLaunchToken = o.mLaunchToken; if (o.mSourceBounds != null) { this.mSourceBounds = new Rect(o.mSourceBounds); } if (o.mSelector != null) { this.mSelector = new Intent(o.mSelector); } if (copyMode != COPY_MODE_HISTORY) { if (o.mExtras != null) { this.mExtras = new Bundle(o.mExtras); } if (o.mClipData != null) { this.mClipData = new ClipData(o.mClipData); } } else { if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) { this.mExtras = Bundle.STRIPPED; } // Also set "stripped" clip data when we ever log mClipData in the (broadcast) // history. } } } ---->[還有個clone方法]------------------ @Override public Object clone() { return new Intent(this); } |----根據呼叫的intent物件,直接返回了一個新的例項,本質上合拷貝構造並無區別 複製程式碼
四、系列化與反序列化
序列化有什麼用?
1.永久的儲存物件資料(儲存在檔案當中,或者是磁碟中),需要時反序列化生成物件 2.將物件資料轉換成位元組流進行網路傳輸 3.使用Intent時傳遞序列化物件 複製程式碼
1.物件的序列化 Serializable

//類的可序列化,只要實現Serializable即可,非常簡單 class Person(var name: String?, var age: Int) : Serializable { override fun toString(): String { return "Person{" + "name='" + name + '\''.toString() + ", age=" + age + '}'.toString() } } 複製程式碼
2.1: Serializable
序列化儲存到磁碟

val toly = Person("toly", 24) val file = File(cacheDir, "toly.obj") val oos = ObjectOutputStream(FileOutputStream(file)) oos.writeObject(toly) oos.close() 複製程式碼
2.2:反序列化從磁碟例項化物件

val ois = ObjectInputStream(FileInputStream(file)) val toly = ois.readObject() as Person ois.close() 複製程式碼
2.3:限制欄位的序列化方式
當某些欄位不需要序列化時,可使用 @Transient(kotlin)
或 transient(Java)關鍵字
比如我不想讓name欄位序列化。(因為欄位越多,消耗的資源越多)

class Person(@Transient var name: String?, var age: Int) : Serializable { override fun toString(): String { return "Person{" + "name='" + name + '\''.toString() + ", age=" + age + '}'.toString() } } 複製程式碼
2.4:關於 serialVersionUID

看一下Android原始碼,實現Serializable的類都有一個`serialVersionUID`的常量 Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。 在進行反序列化時,JVM會把傳來的位元組流和當前類中的serialVersionUID進行對比, 是一致的則進行反序列化,否則拋序列化版本不一致的異常(InvalidCastException) 複製程式碼
3. Parcelable
實現物件的序列化(Java版)
當一個實現Parcelable介面時必須實現 describeContents和writeToParcel
方法
感覺怪麻煩的,還好AndroidStudio有快捷生成方式

/** * 作者:張風捷特烈<br/> * 時間:2019/1/21/021:22:30<br/> * 郵箱:[email protected]<br/> * 說明:Parcelable序列化 */ public class Book implements Parcelable { private String name; private int price; public Book(String name, int price) { this.name = name; this.price = price; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeInt(this.price); } protected Book(Parcel in) { this.name = in.readString(); this.price = in.readInt(); } public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() { @Override public Book createFromParcel(Parcel source) { return new Book(source); } @Override public Book[] newArray(int size) { return new Book[size]; } }; @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", price=" + price + '}'; } } 複製程式碼
3. Parcelable
與 Serializable
的比較
Parcelable所屬包android.os Serializable所屬包java.io |---所屬包說明了Parcelable只能在Android中使用 P以Ibinder作為資訊載體的,在記憶體上的開銷比較小,P在效能方面要強於S S在序列化操作的時候會產生大量的臨時變數,(反射機制)從而導致GC的頻繁呼叫 |---Parcelable的效能要強於Serializable 在讀寫資料的時候,Parcelable是在記憶體中直接進行讀寫 而Serializable是通過使用IO流的形式將資料讀寫入在硬碟上 Parcelable無法將資料進行持久化(磁碟儲存),Serializable可以 (在不同的Android版本當中,Parcelable可能會不) 複製程式碼
五、Intent的資料傳遞
Intent 除來一大堆對屬性的set之外,還有一大堆的putExtra來盛放資料
Intent不僅傳遞"命令"還能攜帶資料傳達,put資料的方法躲到令人髮指
可以說應有盡有,有put,當然對應有get,下面僅列舉出put資料的方法

1.常見資料型別的傳輸
由於常見型別很多,這裡選三個代表,其他的用法類似,怎麼放怎麼取

---->[FromActivity點選時]-------- val intent = Intent(this, ToActivity::class.java) //String型別資料 intent.putExtra("stringData", "張風捷特烈") //int型別資料 intent.putExtra("intData", 100) //容器型別資料 val arr = arrayListOf(1, 2, 3, 4, 5) intent.putExtra("arrData", arr) startActivity(intent) ---->[ToActivity#onCreate]-------- var result = "" val stringData = intent.getStringExtra("stringData") val intData = intent.getIntExtra("intData", 10) val arrData = intent.getIntegerArrayListExtra("arrData") result+=intData.toString()+"\n" if (stringData != null) { result+=stringData+"\n" } if (arrData != null) { result+=arrData.toString()+"\n" } id_tv_result.append(result) 複製程式碼
2.Intent傳遞Bundle物件
簡單來看就是鍵值對,並沒有什麼非常神奇的。也有一堆的put,get
其中最重要的是有put序列化物件( Parcelable/Serializable
)的方法
A mapping from String keys to various {@link Parcelable} values. 字串型的鍵到不同值得對映(link 到 Parcelable) 複製程式碼

---->[FromActivity點選時]-------- val intent = Intent(this, ToActivity::class.java) val bundle = Bundle() //存放Serializable序列化物件 val toly = Person("toly", 24) bundle.putSerializable("person", toly) //存放Parcelable序列化物件 val book = Book("《幻將錄》", 10000) bundle.putParcelable("book", book) intent.putExtra("bean", bundle) startActivity(intent) ---->[ToActivity#onCreate]-------- val bundle = intent.getBundleExtra("bean") if (bundle != null) { val personBean = bundle.get("person") as Person val bookBean = bundle.get("book") as Book } 複製程式碼

六:Android原始碼 intent-filter
的解析流程
1.解析流程

PackageManagerService在啟動後會掃描系統和第三方的app資訊, 在scanPackageLI方法中例項化PackageParser物件pp,使用pp對包進行解析 PackageParser的parseBaseApk在呼叫之後解析AndroidManifest.xml,返回一個Package物件 將手機中所有的app的AndroidManifest.xml解析完畢,構建出一個手機中所有app的資訊樹 從這顆棵樹上 複製程式碼
---->[PackageParser#parseMonolithicPackage]------------ @Deprecated public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { //略... final AssetManager assets = new AssetManager(); try { final Package pkg = parseBaseApk(apkFile, assets, flags); pkg.codePath = apkFile.getAbsolutePath(); return pkg; } finally { IoUtils.closeQuietly(assets); } } private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; ---->[PackageParser#parseBaseApk 3參]------------ private Package parseBaseApk(File apkFile, AssetManager assets, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); //略... Resources res = null; XmlResourceParser parser = null;//構建Xml的解析器 try { res = new Resources(assets, mMetrics, null); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);//開啟`AndroidManifest.xml`檔案 final String[] outError = new String[1]; final Package pkg = parseBaseApk(res, parser, flags, outError); if (pkg == null) { throw new PackageParserException(mParseError, apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]); } pkg.volumeUuid = volumeUuid; pkg.applicationInfo.volumeUuid = volumeUuid; pkg.baseCodePath = apkPath; pkg.mSignatures = null; return pkg; //略... } ---->[PackageParser#parseBaseApk 4參]------------ |--------核心的解析xml邏輯全在這個方法裡,非常長,---------- ----------這裡從application的解析開始看------------------ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { //略... String tagName = parser.getName(); if (tagName.equals("application")) {//下面開始解析application //略... //這裡呼叫了parseBaseApplication方法,activity的解析就在其中 if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) { return null; } ---->[PackageParser#parseBaseApplication]------------ private boolean parseBaseApplication(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError) //略... while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if (tagName.equals("activity")) {//這裡開始解析activity Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false, owner.baseHardwareAccelerated); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.activities.add(a); } else if (tagName.equals("receiver")) {//這裡開始解析receiver Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.receivers.add(a); } else if (tagName.equals("service")) {//這裡開始解析service Service s = parseService(owner, res, parser, attrs, flags, outError); if (s == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.services.add(s); } else if (tagName.equals("provider")) {//這裡開始解析provider Provider p = parseProvider(owner, res, parser, attrs, flags, outError); if (p == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.providers.add(p); //略...還有很多解析的東西 return true; } ---->[PackageParser#parseActivity]------------ private Activity parseActivity(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError, boolean receiver, boolean hardwareAccelerated) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(attrs, R.styleable.AndroidManifestActivity); //略... //下面開始解析:intent-filter if (parser.getName().equals("intent-filter")) { //建立ActivityIntentInfo ActivityIntentInfo intent = new ActivityIntentInfo(a); //呼叫parseIntent方法 if (!parseIntent(res, parser, attrs, true, true, intent, outError)) { return null; } if (intent.countActions() == 0) { } else { a.intents.add(intent); //略... return a; } ---->[PackageParser#parseIntent]------------ private boolean parseIntent(Resources res, XmlPullParser parser, AttributeSet attrs, boolean allowGlobs, boolean allowAutoVerify, IntentInfo outInfo, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestIntentFilter); //略... int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String nodeName = parser.getName(); if (nodeName.equals("action")) {//解析action String value = attrs.getAttributeValue( ANDROID_RESOURCES, "name"); if (value == null || value == "") { outError[0] = "No value supplied for <android:name>"; return false; } XmlUtils.skipCurrentTag(parser); outInfo.addAction(value); } else if (nodeName.equals("category")) {//解析category String value = attrs.getAttributeValue( ANDROID_RESOURCES, "name"); if (value == null || value == "") { outError[0] = "No value supplied for <android:name>"; return false; } XmlUtils.skipCurrentTag(parser); outInfo.addCategory(value); } else if (nodeName.equals("data")) {//解析data sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestData); String str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_mimeType, 0); if (str != null) { try { outInfo.addDataType(str); } catch (IntentFilter.MalformedMimeTypeException e) { outError[0] = e.toString(); sa.recycle(); return false; } } return true; } 複製程式碼
2. startActivity(intent)
做了什麼?
startActivity一連串的呼叫之後,最終核心是下面的方法
前一篇已經涉及過Instrumentation類,它真可謂Activity的忠實僕人

------>[Activity#startActivityForResult]---------------------- public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { options = transferSpringboardActivityOptions(options); Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); //略... } } ------>[Instrumentation#execStartActivity]---------------------- public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null; } break; } } } } try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; } ------>[ActivityManagerNative#getDefault]---------------------- static public IActivityManager getDefault() { return gDefault.get(); } ------>[ActivityManagerNative#Singleton]---------------------- private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); if (false) { Log.v("ActivityManager", "default service binder = " + b); } IActivityManager am = asInterface(b);//IActivityManager的建立 if (false) { Log.v("ActivityManager", "default service = " + am); } return am; } }; ------>[ActivityManagerNative#asInterface]---------------------- |--------這裡可以看出get的IActivityManager物件是一個ActivityManagerProxy物件 static public IActivityManager asInterface(IBinder obj) { if (obj == null) { return null; } IActivityManager in = (IActivityManager)obj.queryLocalInterface(descriptor); if (in != null) { return in; } return new ActivityManagerProxy(obj); } >現在焦點在ActivityManagerProxy的身上 複製程式碼
3.ActivityManagerProxy是何許人也?
先看IActivityManager,他是一個介面定義了很多關於Activity管理的方法
ActivityManagerProxy作為它的實現類,當然也就實現了這些方法


---->[ActivityStackSupervisor#startActivity]-------- @Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, ---->[ActivityStackSupervisor#startActivityAsUser]-------- @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) { enforceNotIsolatedCaller("startActivity"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, options, false, userId, null, null); ---->[ActivityStackSupervisor#resolveActivity]-------- ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags, ProfilerInfo profilerInfo, int userId) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { ResolveInfo rInfo = //這裡通過AppGlobals獲取了getPackageManager,也就是包管理器 AppGlobals.getPackageManager().resolveIntent( intent, resolvedType, PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS, userId); aInfo = rInfo != null ? rInfo.activityInfo : null; ---->[ActivityStackSupervisor#resolveActivity]-------- public static IPackageManager getPackageManager() { //通過ActivityThread獲取PackageManager return ActivityThread.getPackageManager(); } ---->[ActivityThread#getPackageManager]-------- public static IPackageManager getPackageManager() { if (sPackageManager != null) { //Slog.v("PackageManager", "returning cur default = " + sPackageManager); return sPackageManager; } //通過ServiceManager獲取包管理器的IBinder IBinder b = ServiceManager.getService("package"); //Slog.v("PackageManager", "default service binder = " + b); //生成的IPackageManager物件 sPackageManager = IPackageManager.Stub.asInterface(b); //Slog.v("PackageManager", "default service = " + sPackageManager); return sPackageManager; } //接下來的焦點集中到了PackageManager和IPackageManager身上 複製程式碼
IPackageManager.aidl
的描述中有這個方法

PackageManagerService作為IPackageManager.Stub的實現類
肯定也實現了queryIntentActivities方法,就是他檢視intent是否匹配
其中aidl的相關知識,會寫一篇進行詳述

---->[PackageManagerService#queryIntentActivities]------------ @Override public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) { //略... synchronized (mPackages) {//有包名 final String pkgName = intent.getPackage(); if (pkgName == null) { //略... //ActivityIntentResolver#queryIntent進行查詢 List<ResolveInfo> result = mActivities.queryIntent( intent, resolvedType, flags, userId); //略... } return result; } final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { return filterIfNotPrimaryUser( mActivities.queryIntentForPackage( intent, resolvedType, flags, pkg.activities, userId), userId); } return new ArrayList<ResolveInfo>(); } } ---->[PackageManagerService$ActivityIntentResolver#queryIntent]------------ final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, boolean defaultOnly, int userId) { if (!sUserManager.exists(userId)) return null; mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; //這裡呼叫了父類的queryIntent方法 return super.queryIntent(intent, resolvedType, defaultOnly, userId); } ---->[IntentResolver#queryIntent]------------ public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,int userId) { String scheme = intent.getScheme(); ArrayList<R> finalList = new ArrayList<R>(); final boolean debug = localLOGV || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); if (debug) Slog.v( TAG, "Resolving type=" + resolvedType + " scheme=" + scheme + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent); F[] firstTypeCut = null; F[] secondTypeCut = null; F[] thirdTypeCut = null; F[] schemeCut = null; // If the intent includes a MIME type, then we want to collect all of // the filters that match that MIME type. if (resolvedType != null) { int slashpos = resolvedType.indexOf('/'); if (slashpos > 0) { final String baseType = resolvedType.substring(0, slashpos); if (!baseType.equals("*")) { if (resolvedType.length() != slashpos+2 || resolvedType.charAt(slashpos+1) != '*') { // Not a wild card, so we can just look for all filters that // completely match or wildcards whose base type matches. firstTypeCut = mTypeToFilter.get(resolvedType); if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTyp secondTypeCut = mWildTypeToFilter.get(baseType); if (debug) Slog.v(TAG, "Second type cut: " + Arrays.toString(secondTypeCut)); } else { // We can match anything with our base type. firstTypeCut = mBaseTypeToFilter.get(baseType); if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTyp secondTypeCut = mWildTypeToFilter.get(baseType); if (debug) Slog.v(TAG, "Second type cut: " + Arrays.toString(secondTypeCut)); } // Any */* types always apply, but we only need to do this // if the intent type was not already */*. thirdTypeCut = mWildTypeToFilter.get("*"); if (debug) Slog.v(TAG, "Third type cut: " + Arrays.toString(thirdTypeCut } else if (intent.getAction() != null) { // The intent specified any type ({@literal *}/*).This // can be a whole heck of a lot of things, so as a first // cut let's use the action instead. firstTypeCut = mTypedActionToFilter.get(intent.getAction()); if (debug) Slog.v(TAG, "Typed Action list: " + Arrays.toString(firstType } } } // If the intent includes a data URI, then we want to collect all of // the filters that match its scheme (we will further refine matches // on the authority and path by directly matching each resulting filter). if (scheme != null) { schemeCut = mSchemeToFilter.get(scheme); if (debug) Slog.v(TAG, "Scheme list: " + Arrays.toString(schemeCut)); } // If the intent does not specify any data -- either a MIME type or // a URI -- then we will only be looking for matches against empty // data. if (resolvedType == null && scheme == null && intent.getAction() != null) { firstTypeCut = mActionToFilter.get(intent.getAction()); if (debug) Slog.v(TAG, "Action list: " + Arrays.toString(firstTypeCut)); } FastImmutableArraySet<String> categories = getFastIntentCategories(intent); if (firstTypeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, firstTypeCut, finalList, userId); } if (secondTypeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, secondTypeCut, finalList, userId); } if (thirdTypeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, thirdTypeCut, finalList, userId); } if (schemeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme, schemeCut, finalList, userId); } sortResults(finalList); if (debug) { Slog.v(TAG, "Final result list:"); for (int i=0; i<finalList.size(); i++) { Slog.v(TAG, "" + finalList.get(i)); } } return finalList; } 複製程式碼
後記:捷文規範
1.本文成長記錄及勘誤表
專案原始碼 | 日期 | 備註 |
---|---|---|
V0.1--無 | 2018-1-22 | Android點將臺:外交官[-Intent-] |
2.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的CSDN | 個人網站 |
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援
