從源碼剖析Android中的Intent組件
我們知道,Intent主要用來(lái)激活安卓幾大組件,那么它具體是怎樣來(lái)激活的?激活時(shí)是否可以攜帶java對(duì)象?為何要將對(duì)象序列化后才能傳遞?
一、Intent官網(wǎng)解釋
Intent可以被startActivity用來(lái)加載Activity,也可以被broadcastIntent發(fā)送給指定的BroadReceiver組件,
或者被startService、bingService來(lái)與后臺(tái)service通信。
Intent最主要作用就是加載Activity,好比Activity之間的膠水。
Intent數(shù)據(jù)結(jié)構(gòu):
- action:所要執(zhí)行的動(dòng)作;(例如:ACTION_CALL創(chuàng)建打電話Activity;ACTION_BATTERY_LOW 發(fā)出廣播警告電池電量低,)
- data: 要使用的數(shù)據(jù)(Uri);
- category:關(guān)于目標(biāo)組件的信息;
- component:目標(biāo)組件的類名;
- extras :這是Bundle數(shù)據(jù)。
Intent解析:
- 顯式Intent,指定了目標(biāo)組件的類名,即component,則已知目標(biāo)組件,不需解析;
- 隱式Intent,未指定目標(biāo)組件component,或者不知道、不關(guān)心誰(shuí)來(lái)接收Intent,需要Android自己去解析找到目標(biāo)組件。
隱式Intent解析方法:
1.在AndroidManifest.xml里所有<intent-filter>及其中定義的Intent;
2.通過(guò)PackageManager(獲取當(dāng)前設(shè)備所安裝的應(yīng)用程序package)查找能處理這個(gè)Intent的component。匹配Action、type、category三個(gè)變量來(lái)尋找。
二、簡(jiǎn)單解釋:
Intent可以激活A(yù)ndorid的三大組件:Activity、Service和BroadcastReceiver。使用Intent時(shí)一般要顯式指定目標(biāo)組件,若未指定則要根據(jù)Intent附帶的action、type、category三個(gè)值來(lái)解析,查找能處理的組件。
三、問(wèn)題:Intent如何實(shí)現(xiàn)組件的切換,具體流程?
1、基本方法:(以啟動(dòng)Activity為例)
Intent i = new Intent(MainActivity.this, TargetActivity.class); startActivity(i);
2、實(shí)例化Intent:
/** * 創(chuàng)建一個(gè)Intent,直接指定Intent要激活的**組件類名**,而不用依賴**系統(tǒng)去解析**合適的類來(lái)處理intent * @param packageContext 要執(zhí)行這個(gè)intent的context對(duì)象 * @param cls intent要激活的組件類名 */ public Intent(Context packageContext, Class cls) { //創(chuàng)建一個(gè)組件并賦值給Intent的Component成員 mComponent = new ComponentName(packageContext, cls); }
3、啟動(dòng)Activity
startActivity(i) -> startActivity(Intent intent, @Nullable Bundle options)-> startActivityForResult(intent, -1, options) /** *startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) * 加載一個(gè)Activity并獲取結(jié)果 * @param intent 要啟動(dòng)的intent. * @param requestCode 如果大于0則會(huì)被返回,且只有返回值返回成功后才會(huì)顯示視圖 * @param options 其他信息. */ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { //如果沒(méi)有父Activity;Instrumentation是用來(lái)與程序指南清單AndroidManifest文件交互的。 Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options); //執(zhí)行startActivity命令 ..... } else { //如果有父Activity if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); } ..... }
4、執(zhí)行startActivity命令核心代碼:
啟動(dòng)Activity的任務(wù)交給了底層ActivityManagerNative來(lái)做。
intent.migrateExtraStreamToClipData(); //將intent里的bundle數(shù)據(jù)進(jìn)行處理以便給底層處理 intent.prepareToLeaveProcess(); //準(zhǔn)備離開(kāi)應(yīng)用程序進(jìn)程,進(jìn)入ActivityManagerService進(jìn)程(意味著bundle的數(shù)據(jù)要在進(jìn)程間傳遞) int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); //調(diào)用系統(tǒng)的activity manager服務(wù)來(lái)啟動(dòng)新的Activity??紤]如果是顯式Intent,則直接找對(duì)對(duì)應(yīng)的組件類(此處是Activity組件);如果是隱式Intent,為指定目標(biāo)組件類名,則自動(dòng)去Application->system搜索合適的組件來(lái)處理。 //todo:具體的系統(tǒng)級(jí)代碼下次進(jìn)行分析
四、核心問(wèn)題:為何Intent不能直接在組件間傳遞對(duì)象而要通過(guò)序列化機(jī)制?
根據(jù)上面代碼可以看到,Intent在啟動(dòng)其他組件時(shí),會(huì)離開(kāi)當(dāng)前應(yīng)用程序進(jìn)程,進(jìn)入ActivityManagerService進(jìn)程(intent.prepareToLeaveProcess()),這也就意味著,Intent所攜帶的數(shù)據(jù)要能夠在不同進(jìn)程間傳輸。首先我們知道,Android是基于Linux系統(tǒng),不同進(jìn)程之間的java對(duì)象是無(wú)法傳輸,所以我們此處要對(duì)對(duì)象進(jìn)行序列化,從而實(shí)現(xiàn)對(duì)象在 應(yīng)用程序進(jìn)程 和 ActivityManagerService進(jìn)程 之間傳輸。
而Parcel或者Serializable都可以將對(duì)象序列化,其中,Serializable使用方便,但性能不如Parcel容器,后者也是Android系統(tǒng)專門推出的用于進(jìn)程間通信等的接口。
附加知識(shí):
在不同進(jìn)程之間,常規(guī)數(shù)據(jù)類型可以直接傳遞,如整數(shù),以傳遞字符串為例,要從A進(jìn)程傳遞到B進(jìn)程,只需在B進(jìn)程的內(nèi)存區(qū)開(kāi)辟一樣大小的空間,然后復(fù)制過(guò)去即可。
但是,對(duì)象卻不能直接跨進(jìn)程傳遞。即使成員變量值能傳遞過(guò)去,成員方法是無(wú)法傳遞過(guò)去的,此時(shí)如果B進(jìn)程要調(diào)用成員方法則出錯(cuò)。
具體傳遞對(duì)象的方法:
1. 在進(jìn)程A中把類中的非默認(rèn)值的屬性和類的唯一標(biāo)志打成包(這就叫序列化);
2. 把這個(gè)包傳遞到進(jìn)程B;
3. 進(jìn)程B接收到包后,根據(jù)類的唯一標(biāo)志把類創(chuàng)建出來(lái)(java反射機(jī)制);
4. 然后把傳來(lái)的屬性更新到類對(duì)象中。
這樣進(jìn)程A和進(jìn)程B中就包含了兩個(gè)完全一樣的類對(duì)象。
五、Intent如何實(shí)現(xiàn)對(duì)象傳遞?
Object implements Serializable {...};bundle.putSerializable(Key, Object);
Object implements Parcelable {...} ; bundle.putParcelable(Key, Object);
Serializable接口:這是Java的序列化技術(shù),將Java對(duì)象序列化為二進(jìn)制文件。讓對(duì)象實(shí)現(xiàn)Serializable接口,使用ObjectInputStream 和 ObjectOutputStream 進(jìn)行對(duì)象讀寫。
Parcelable接口:這是Android提供的用作封裝數(shù)據(jù)的容器,封裝后的數(shù)據(jù)可以通過(guò)Intent或IPC來(lái)傳遞。只有基本類型和實(shí)現(xiàn)了Parcelable接口的類才能被放入Parcel中。
六、Serializable接口 - Java
屬于java序列化機(jī)制:只需讓java類實(shí)現(xiàn)該接口,不用實(shí)現(xiàn)任何方法,即可標(biāo)記該類可序列化。
class Person implements Serializable {...} Person per = new Person(); bundle.putSerializable("person", per); //傳遞Person對(duì)象的引用 Person mPerson = (Person)getIntent().**getSerializableExtra**("person");
注意:如果此處序列化類Person內(nèi)部包含其他類(如:PersonInfo)的引用,如:
class Person implements Serializable { PersonInf**o info; }
那么所引用的類必須也可序列化,即實(shí)現(xiàn)Serializable接口。因?yàn)镻erson對(duì)象在序列化過(guò)程中,也會(huì)對(duì)成員變量序列化。
七、Parcelable接口 - Android
此處圍繞 - Android中如何使用Parcel實(shí)現(xiàn)對(duì)象的傳遞 - 簡(jiǎn)單介紹一下原因。
首先要了解Android里面的Parcel容器。
Parcel是一個(gè)容器,用來(lái)存儲(chǔ)可通過(guò)IBindler傳送的消息(數(shù)據(jù)或?qū)ο笠茫?br />
主要用于輕量級(jí)、高性能IPC進(jìn)程間通信的消息容器。在Android里,一個(gè)“process”是一個(gè)標(biāo)準(zhǔn)Linux進(jìn)程,一般而言一個(gè)進(jìn)程無(wú)法接觸到另一個(gè)進(jìn)程的內(nèi)存區(qū)。而通過(guò)Parcel,Android系統(tǒng)會(huì)將對(duì)象分解成可序列化與反序列化,從而實(shí)現(xiàn)進(jìn)程間通信。
不過(guò),Parcel同樣可用于進(jìn)程內(nèi)通信,主要實(shí)現(xiàn)在應(yīng)用程序的不同組件之間傳遞數(shù)據(jù)。例如,我們可以使用Intent封裝Parcel對(duì)象在Activity之間傳遞。
簡(jiǎn)單來(lái)說(shuō),Parcel容器實(shí)現(xiàn)了進(jìn)程內(nèi)與進(jìn)程間通信,而且還能實(shí)現(xiàn)遠(yuǎn)程調(diào)用。
組件間傳遞對(duì)象的具體方法:
讓要傳遞的對(duì)象所屬類實(shí)現(xiàn) Parcelable 接口;
實(shí)現(xiàn) describeContents 方法;
實(shí)現(xiàn)抽象方法 writeToParcel,用于獲取對(duì)象的當(dāng)前狀態(tài)并寫入一個(gè)Parcel容器中;
給該目標(biāo)類添加一個(gè)靜態(tài)域 CREATOR ,它是一個(gè)實(shí)現(xiàn)了Parcelable.Creator接口的對(duì)象;
添加一個(gè)參數(shù)為一個(gè)Parcel對(duì)象的構(gòu)造函數(shù),CREATOR會(huì)調(diào)用這個(gè)構(gòu)造函數(shù)來(lái)重新改造我們的對(duì)象。
問(wèn)題:
為什么已經(jīng)有了Java的Serializable接口還要?jiǎng)?chuàng)建一個(gè)Parcelable接口?
性能
雖然Parcelable使用起來(lái)更復(fù)雜一點(diǎn),但是它的性能更好。
Parcelable的限制:
當(dāng)使用Parcelable來(lái)傳遞圖片Bitmap時(shí)不太理想,雖然Bitmap也實(shí)現(xiàn)了Parcelable接口。比較優(yōu)的方法是傳遞
Parcelable不能用來(lái)當(dāng)做常規(guī)的序列化存儲(chǔ),因?yàn)锳ndroid系統(tǒng)版本不同,Parcelable的具體實(shí)現(xiàn)方法也不完全一樣,可能導(dǎo)致無(wú)法讀取Parcel數(shù)據(jù)。
相關(guān)文章
Android編程實(shí)現(xiàn)兩個(gè)Activity相互切換而不使用onCreate()的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)兩個(gè)Activity相互切換而不使用onCreate()的方法,結(jié)合實(shí)例形式分析了多個(gè)Activity切換而不重新創(chuàng)建的操作技巧,需要的朋友可以參考下2017-01-01Android自定義view實(shí)現(xiàn)圓環(huán)效果實(shí)例代碼
本文通過(guò)實(shí)例代碼給大家介紹了Android自定義view實(shí)現(xiàn)圓環(huán)效果,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07Android仿人人網(wǎng)滑動(dòng)側(cè)邊欄效果
這篇文章主要為大家詳細(xì)介紹了Android仿人人網(wǎng)滑動(dòng)側(cè)邊欄效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09android項(xiàng)目從Eclipse遷移到Android studio中常見(jiàn)問(wèn)題解決方法
android項(xiàng)目從Eclipse遷移到Android studio中經(jīng)常會(huì)遇到一些問(wèn)題,本文提供了Android studio使用中常見(jiàn)問(wèn)題解決方法2018-03-03Json數(shù)據(jù)解析模擬美團(tuán)界面顯示
這篇文章主要介紹了Json數(shù)據(jù)解析模擬美團(tuán)界面顯示,涉及到j(luò)son數(shù)據(jù)解析相關(guān)知識(shí),本文寫的非常不錯(cuò),具有參考價(jià)值,特此分享供大家學(xué)習(xí)2016-01-01Android利用ViewPager實(shí)現(xiàn)滑動(dòng)廣告板實(shí)例源碼
利用ViewPager我們可以做很多事情,從最簡(jiǎn)單的導(dǎo)航,到頁(yè)面切換菜單等等。ViewPager的功能就是可以使視圖滑動(dòng),就像Lanucher左右滑動(dòng)那樣2013-06-06android 捕捉異常并上傳至服務(wù)器的簡(jiǎn)單實(shí)現(xiàn)
本篇文章主要介紹了android 捕捉異常并上傳至服務(wù)器的簡(jiǎn)單實(shí)現(xiàn),具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04