Android開發(fā)藝術(shù)探索——第二章:IPC機(jī)制(上)_第1頁
Android開發(fā)藝術(shù)探索——第二章:IPC機(jī)制(上)_第2頁
Android開發(fā)藝術(shù)探索——第二章:IPC機(jī)制(上)_第3頁
Android開發(fā)藝術(shù)探索——第二章:IPC機(jī)制(上)_第4頁
Android開發(fā)藝術(shù)探索——第二章:IPC機(jī)制(上)_第5頁
已閱讀5頁,還剩14頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、Android開發(fā)藝術(shù)探索第二章:IPC機(jī)制(上)一.Android IPC簡介IPC是Inter-Process Communication的縮寫,含義是進(jìn)程間通信或者跨進(jìn)程通信,是指兩個(gè)進(jìn)程間進(jìn)行數(shù)據(jù)交互的一個(gè)過程,說起進(jìn)程間通信,我們首先要理解什么是進(jìn)程,什么是線程,進(jìn)程和線程是截然不同的概念。按照操作系統(tǒng)中的描述,線程是CPU調(diào)度的最小單元,同時(shí)線程是一種有限的系統(tǒng)資源。而進(jìn)程一般指一個(gè)執(zhí)行單元,在PC和移動(dòng)設(shè)備上指一個(gè)程序或者一個(gè)應(yīng)用。一個(gè)進(jìn)程可以包含多個(gè)線程,因此進(jìn)程和線程是包含與被包含的關(guān)系。最簡單的情況下,一個(gè)進(jìn)程中可以只有一個(gè)線程,即主線程,在Android里面主線程也叫UI

2、線程,在UI線程里才能操作界面元素。很多時(shí)候,一個(gè)進(jìn)程中需要執(zhí)行大量耗時(shí)的任務(wù),如果這些任務(wù)放在主線程中去執(zhí)行就會(huì)造成界面無法響應(yīng),嚴(yán)重影響用戶體驗(yàn),這種情況在PC系統(tǒng)和移動(dòng)系統(tǒng)中都存在,在Android中有一個(gè)特殊的名字叫做ANR(Application Not Responding),即應(yīng)用無響應(yīng)。解決這個(gè)問題就需要用到線程,把一些耗時(shí)的任務(wù)放在線程中即可。IPC不是Andrord中所獨(dú)有的,任何一個(gè)操作系統(tǒng)都需要有相應(yīng)的IPC機(jī)制,比如Windows上可以通過剪貼板、管道和郵槽等來進(jìn)行進(jìn)程間通信,、Linux上可以通過命名管道,共享內(nèi)容、信號(hào)量等來進(jìn)行進(jìn)程間通信??梢钥吹讲煌牟僮飨到y(tǒng)系

3、統(tǒng)有著不同的進(jìn)程通信方式,對于Android來說,它是一種基于Linux內(nèi)核的操作系統(tǒng),他的進(jìn)程間通信方式并不能完全繼承自Linux,相反,它有自己的進(jìn)程間通信方式。在Android中最 有特色的進(jìn)程間通信方式就是Binder了,通過Binder可以輕松地實(shí)現(xiàn)進(jìn)程間通信。除了有特色的Binder,Android還支持Socket,通過Socket也可以實(shí)現(xiàn)任意兩個(gè)終端之間的通信,當(dāng)然同一個(gè)設(shè)備上的兩個(gè)進(jìn)程通過Socket通信自然也是可以的說到IPC的使用場景就必須提到多進(jìn)程,只有面對多進(jìn)程這種場景下,才需要考慮進(jìn)程間通信。這個(gè)是很好理解的,如果只有一個(gè)進(jìn)程在運(yùn)行,又何談多進(jìn)程呢?多進(jìn)程的情況分

4、為兩種。第一種情況是一個(gè)應(yīng)用因?yàn)槟承┰蜃陨硇枰捎枚噙M(jìn)程模式來實(shí)現(xiàn),至于原因,可能有很多,比如有些模塊由于特殊原因需要運(yùn)行在單獨(dú)的進(jìn)程中,又或者為了加大一個(gè)應(yīng)用可使用的內(nèi)存所以需要通過多進(jìn)程來獲取多份內(nèi)存空間。Android對單個(gè)應(yīng)用所 使用的最大內(nèi)存做了限制,早期的一些版本可能是16MB,不同設(shè)備有不同的大小。另一種情況是當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù),由于是兩個(gè)應(yīng)用,所以必須采用跨講程的方式來獲取所需的數(shù)據(jù),甚至我們通過系統(tǒng)提供的ContentProvider去查詢數(shù)據(jù)的時(shí)候,其實(shí)也是一種進(jìn)程間通信,只不過通信細(xì)節(jié)被系統(tǒng)內(nèi)部屏蔽了,我們無法感知而已,后續(xù)的章節(jié)我們會(huì)詳細(xì)的介紹Conten

5、tProvider的底層實(shí)現(xiàn),這里就先不做介紹了,總之,不管我們處于何種原因,我們采用了多進(jìn)程的設(shè)計(jì)方法,那么應(yīng)用中就必須妥善地處理進(jìn)程間通信的各種問題了。二.Android中的多進(jìn)程模式在正式介紹進(jìn)程間通信之前,我們必須先去理解Android中的多進(jìn)程模式,通過給四大組件指定android:process屬性,我們可以輕易的開啟多進(jìn)程模式,這看起來很簡單,但是實(shí)際使用過程中卻暗藏殺機(jī),多進(jìn)程遠(yuǎn)遠(yuǎn)沒有我們想的那么簡單,有時(shí)候我們可以通過多進(jìn)程得到的好處甚至都不足以彌補(bǔ)使用多進(jìn)程所帶來的代碼層面的負(fù)面影響,下面會(huì)詳細(xì)分析這些問題1.開啟多進(jìn)程模式正常情況下,在Android中多進(jìn)程是指一個(gè)應(yīng)用中

6、存在多個(gè)進(jìn)程的情況,因此這里不討論兩個(gè)應(yīng)用之間的情況,首先在Android中使用多進(jìn)程只有一種方法,那就是給四大組件指定android:process,除此之外,沒有其他辦法,也就是說我們無法給一個(gè)線程或者實(shí)體類指定其運(yùn)行時(shí)所在的進(jìn)程,其實(shí)還有一種非常規(guī)的多進(jìn)程方法,那就是通過JNI在native層去fork一個(gè)新的進(jìn)程,但是這種方法屬于特殊情況,也不是常用的創(chuàng)建多進(jìn)程的方式,因此我們暫時(shí)不考慮這種方式,下面是一個(gè)實(shí)例,描述了;如何在Android中創(chuàng)建多進(jìn)程 <activity android:name=".MainActivity" android:configC

7、hanges="orientation|screenSize" android:launchMode="standard"> <intent-filter> <action android:name="ent.action.MAIN" /> <category android:name="ent.category.LAUNCHER" /> </intent-filter> </activity> <a

8、ctivity android:name=".SecondActivity" android:configChanges="screenLayout" android:process=":remote" /> <activity android:name=".ThirdActivity" android:configChanges="screenLayout" android:process="com.liuguilin.multiprocesssample.remote&

9、quot; />上面的示例分別為SecondActivity和ThirdActivity指定了process屬性,并且他們的屬性值不同,當(dāng)SecondActivity啟動(dòng)時(shí),系統(tǒng)會(huì)為它創(chuàng)建一個(gè)單獨(dú)的進(jìn)程,進(jìn)程名為con.liuguilin.multiprocesssample:remote,當(dāng)ThridActivity啟動(dòng)的時(shí)候,系統(tǒng)也會(huì)為他創(chuàng)建一個(gè)單獨(dú)的進(jìn)程,進(jìn)程名com.liuguilin.multiprocesssample.remote,同時(shí),入口Activity是MainActivity,沒有為他指定process屬性,那么他運(yùn)行在默認(rèn)進(jìn)程中,當(dāng)我們運(yùn)行的時(shí)候就可以看到,進(jìn)程列

10、表末尾存在三個(gè)進(jìn)程,這說明我們成功的開啟了多進(jìn)程,是不是很簡單尼?這只是一個(gè)開始,實(shí)際上多進(jìn)程是有很多問題需要處理的除了在DDMS中可以查看進(jìn)程,我們還可以通過shell命令來查看adb shell ps 或者 adb shell ps | grep com.liuguilin.multiprocesssample不知道讀者有沒有注意到,SecondActivity和ThirdActivity的進(jìn)程名分別為“:remote”和包名.remote,那么這兩種方式有區(qū)別嗎?其實(shí)是有區(qū)別的,區(qū)別在兩面,首先”:“的含義,是指在當(dāng)前的進(jìn)程名前附加上包名,這是一種簡寫的方法,對于SecondActivi

11、ty來說,他完整的進(jìn)程名為com.liuguilin.multiprocesssample:remote,而對于ThirdActivity中的申明方式,它是一種完整的命名方式,不會(huì)附加包名信息;其次,進(jìn)程以”:“開頭的屬于當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中,而進(jìn)程名不以”:“開頭的進(jìn)程屬于全局進(jìn)程,其他應(yīng)用通過ShareUID方式可以和它跑在同一進(jìn)程中。我們知道Android系統(tǒng)會(huì)為每個(gè)應(yīng)用分配一個(gè)唯一的UID,具有相同UID的應(yīng)用才能共享數(shù)據(jù)。這里要說明的是,兩個(gè)應(yīng)用通過ShareUID跑在同一個(gè)進(jìn)程由是有要求的,需要這兩個(gè)應(yīng)用有相同的ShareUID并且簽名相同才

12、可以。在這種情況下,它們可以互相訪問對方的私有數(shù)據(jù),比如data目錄、組件信息等,不管它們是否跑在同一個(gè)進(jìn)程中。當(dāng)然它們跑在同一個(gè)進(jìn)程中,那么除了能共享data目錄、組件信息,還可以共享內(nèi)存數(shù)據(jù),或者說它們看起來就像是一個(gè)應(yīng)用的兩個(gè)部分。2.多進(jìn)程模式的運(yùn)行機(jī)制如果用一句話來形容多進(jìn)程,那筆者只能這樣說:“當(dāng)應(yīng)用開啟了多進(jìn)程以后,各種奇怪的現(xiàn)象都出現(xiàn)了”,為什么這么說呢?這是有原因的。大部分人都認(rèn)為開啟多進(jìn)程是很簡單的事情,只需要給四大組件指定android:process屬性即可。比如說在實(shí)際的產(chǎn)品開發(fā)中,可能會(huì)有多進(jìn)程的需求,需要把某些組件放在單獨(dú)的進(jìn)程中去運(yùn)行,很多人都會(huì)覺得這不很簡單嗎

13、?然后迅速地給那些組件指定了android:process屬性,然后編譯運(yùn)行,發(fā)現(xiàn)“正常地運(yùn)行起來了”。這里筆者想說的是,那是真的正常地運(yùn)行起來了嗎?現(xiàn)在先不置可否,下面先給舉個(gè)例子,然后引入本節(jié)的話題。還是本章剛開始說的那個(gè)例子,其中SecondActvity通過指定android:process屬性從而使其運(yùn)行在一個(gè)獨(dú)立的進(jìn)程中,這里做了一些改動(dòng),我們新建了一個(gè)類,叫做UserManager,這個(gè)類中有一個(gè)public的靜態(tài)成員變量,如下所示。/* * Created by lgl on 16/9/24. */public class UserManager public static i

14、nt mUserId = 1;然后在MainActivity 的 onCreate中我們把這個(gè)mUserld重新賦值為2,打印出這個(gè)靜態(tài)變量的值后再啟動(dòng)SecondActivity,在SecondActivity中我們再打印一下mUserld的值。按照正常的邏輯,靜態(tài)變量是可以在所有的地方共享的,并且一處有修改處處都會(huì)同步,下面是是運(yùn)行時(shí)所打印的日志,我們看一下結(jié)果如何當(dāng)你看完這個(gè)日志,發(fā)現(xiàn)結(jié)果和我們想的完全不一致,正常情況下SecondActivily打印的mUserld的值應(yīng)該是2才對,但是從日志上看它竟然還是1,可是我們的確已經(jīng)在MainActivitv中把mUserld重新賦值為2了。

15、看到這里,大家應(yīng)該明白了這就是多進(jìn)程所帶來的問題,多進(jìn)程絕非只是僅僅指定一個(gè)android:process屬性那么簡單。上述問題出現(xiàn)的原因是SecondActivity運(yùn)行在一個(gè)單獨(dú)的進(jìn)程中,我們知道Android為每一個(gè)應(yīng)用都分配了一個(gè)獨(dú)立的虛擬機(jī),或者說為每個(gè)進(jìn)程都分配一個(gè)獨(dú)立的虛擬機(jī),不同的虛擬機(jī)在內(nèi)存上有不同的地址空間,這就導(dǎo)致在不同的虛擬機(jī)中訪問同一個(gè)類的對象會(huì)產(chǎn)生多份副本,就我們這個(gè)例子來說,在進(jìn)兩個(gè)進(jìn)程中都存在一個(gè)UserManager類,并且這兩個(gè)類是互不干擾的,在一個(gè)進(jìn)程中修改mUseld的值只會(huì)影響當(dāng)前進(jìn)程,對其他進(jìn)程不會(huì)造成任何影響,這樣我們就可以理解為什么在MainAc

16、tivitv中修改了mUserld的值,但是在 SecondActivity 中這個(gè)值卻 沒有發(fā)生改變這個(gè)現(xiàn)象。所有運(yùn)行在不同進(jìn)程中的四大組件,只要它們之間需要通過內(nèi)存來共享數(shù)據(jù),都會(huì)共享失敗這也是多進(jìn)程帶來的主要影響,正常情況下四大組件中間不可能不通過一些中間層來共享數(shù)據(jù),那么通過簡單地指定進(jìn)程名來開啟多進(jìn)程都會(huì)無法正確運(yùn)行。當(dāng)然,特殊情況下,某些組件之間不需要共享數(shù)據(jù),這個(gè)時(shí)候可以直接指定 android:process屬性來開啟多進(jìn)程,但是這種場景是不常見的,幾乎所有情況都需要共享數(shù)據(jù)。一般來說,使用多進(jìn)程會(huì)造成如下幾方面的問題:1.靜態(tài)成員和單例模式完全失效2.線程同步機(jī)制完全失效。3

17、.SharedPreferences的可靠性下降。4.Application會(huì)多次創(chuàng)建。第1個(gè)問題在上面已經(jīng)進(jìn)行了分析。第2個(gè)問題本質(zhì)上和第一個(gè)問題是類似的,既然都不是一塊內(nèi)存了,那么不管是鎖對象還是鎖全局類都無法保證線程同步,因?yàn)椴煌M(jìn)程鎖都不是同一個(gè)對象。第3個(gè)問題是因?yàn)镾haredPreferences不支持兩個(gè)進(jìn)程同時(shí)去執(zhí)行寫操作,否則會(huì)導(dǎo)致一定幾率的數(shù)據(jù)丟失,這是因?yàn)镾haredPreferences底層是通過讀/寫XML文件來實(shí)現(xiàn)的,并發(fā)寫顯然是可能出問題的,甚至并發(fā)讀/寫都有可能出問題。第4個(gè)問題也是顯而易見的,當(dāng)一個(gè)組件跑在一個(gè)新的進(jìn)程中的時(shí)候,由于系統(tǒng)要在創(chuàng)建新的進(jìn)程同時(shí)分配

18、獨(dú)立的虛擬機(jī),所以這個(gè)過程其實(shí)就是啟動(dòng)一個(gè)應(yīng)用的過程。因此,相當(dāng)于系統(tǒng)又把這個(gè)應(yīng)用重啟動(dòng)了一遍,既然重新啟動(dòng)了,那么自然會(huì)創(chuàng)建新的Application。這個(gè)問題其實(shí)可以這么理解,運(yùn)行在同一個(gè)進(jìn)程中的組件是屬于同一個(gè)虛擬機(jī)和同一個(gè)Application的,同理,運(yùn)行在不同進(jìn)程中的組件是屬于兩個(gè)不同的虛擬機(jī)和Application的。為了更加清晰地展示這一點(diǎn),下面我們來做一個(gè)測試,首先在Application 的onCreate方法中打印出當(dāng)前進(jìn)程的名字,然后連續(xù)啟動(dòng)三個(gè)同一個(gè)應(yīng)用內(nèi)但屬于不同進(jìn)程的Activity,按照期望,Application的onCreate應(yīng)該執(zhí)行三次并打印出三次進(jìn)程名

19、不同的log,代碼如下所示。/* * Created by lgl on 16/9/24. */public class MyApplication extends Application Override public void onCreate() super.onCreate(); String processName = getCurProcessName(); Log.i("TAG", "process name :" + processName); /* * 獲取當(dāng)前進(jìn)程名 * * return 進(jìn)程名 */ private String g

20、etCurProcessName() int pid = android.os.Process.myPid(); ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager .getRunningAppProcesses() if (appProcess.pid = pid) return appP

21、cessName; return "獲取失敗" 我們可以查看一下打印的log通過log可以看出,Application執(zhí)行了三次onCreate,并且每次的進(jìn)程名都不一樣,他們的進(jìn)程名和我們在清單文件中指定的process一致,這也就證實(shí)了在多進(jìn)程模式中,不同進(jìn)程的組件的確會(huì)擁有獨(dú) 虛擬機(jī)、Application以及內(nèi)存空間,這會(huì)給實(shí)際的開發(fā)帶來很多困擾,是尤其需要注意的,或者我們也可以這么理解同一個(gè)應(yīng)用間的多進(jìn)程:它就相當(dāng)于兩個(gè)不同的應(yīng)用采用sharedUID的模式,這樣能夠更加直接地理解多進(jìn)程模式的本質(zhì)。本節(jié)我們分析了多進(jìn)程所帶來的問題,但是我們不能因?yàn)槎噙M(jìn)程有許多問題

22、就不去正視他。為了解決這個(gè)問題,系統(tǒng)提供了很多跨進(jìn)程通信方法,雖然不能夠直接的共享內(nèi)存,但是通過跨進(jìn)程通信我們還是可以實(shí)現(xiàn)數(shù)據(jù)交互。實(shí)現(xiàn)跨進(jìn)程通信的力式很多,比如Intent來傳遞數(shù)據(jù),共享文件和SharedPreferences,基于Binder的Messenger和 AIDL以及Socket等,但是為了更好地理解各種IPC方式,我們需要先熟悉一些基礎(chǔ)概念,比如序列化相關(guān)的Serializable和Parcelable接口,以及Binder的概念,熟悉完這些基礎(chǔ)概念以后,再去理解各種IPC方式就比較簡單了。三.IPC基礎(chǔ)概念本節(jié)主要介紹IPC中的一些基礎(chǔ)概念,主要包含三方面內(nèi)容;Serial

23、izable接口,Parcelable接口以及Binder,只有熟悉這三方面的內(nèi)容后,我們才能更好地理解跨進(jìn)程通信的各種方式。Serializable和Parcelable接口可以完成對象的序列化過程,當(dāng)我們需要通過Intent和Binder傳輸數(shù)據(jù)時(shí)就需要使用Parcelable或者Serializable。還有的時(shí)候我們需要把對象持久化到存儲(chǔ)設(shè)備上或者通過網(wǎng)絡(luò)傳輸給其他客戶端,這個(gè)時(shí)候也需要使用Serializable來完成對象的持久化,下面先介紹如何使用Senalizabie來完成對象的序列化1.SerializableSerializable 是 Java所提供的一個(gè)序列化接口,它是一

24、個(gè)空接口,為對象提供標(biāo)準(zhǔn)的序列化和反序列化的操作。使用Serializable來實(shí)現(xiàn)序列化相當(dāng)簡單,只需要在類的聲明中指定一個(gè)類似下面的標(biāo)識(shí)即可自動(dòng)實(shí)現(xiàn)默認(rèn)的序列化過程private static final long serialVersionUID = 8711368828010083044L;在Anarond中也提供了新的序列化方式,那就是Parcelable接口來實(shí)現(xiàn)對象的序列化,其過程要稍微復(fù)雜一點(diǎn),本章節(jié)先介紹Serializable接口,上面提到,想讓一個(gè)對象實(shí)現(xiàn)序列化,只需要這個(gè)類實(shí)現(xiàn)Serializable接口并且聲明一個(gè)serialversionUID即可,實(shí)際上,甚至這個(gè)

25、serialversionUID也是不是必須的,我們不聲明這個(gè)serialversionUID同樣也可以實(shí)現(xiàn)序列化,但是這將會(huì)對反序列化過程產(chǎn)生影響,具體什么影響,我們后面再介紹,User類就是一個(gè)實(shí)現(xiàn)了 Seralizable接口的類,它是可以被序列化和反序列化的:/* * Created by lgl on 16/9/24. */public class User implements Serializable private static final long serialVersionUID = 8711368828010083044L; public int userId; publ

26、ic String userName; public boolean isMale; .通過Serializable方式來實(shí)現(xiàn)對象的序列化,實(shí)現(xiàn)起來非常的簡單,幾乎所有工作都被系統(tǒng)自動(dòng)完成了,如何進(jìn)行對象的序列化和反序列化也非常的簡單,只需要采用ObjectOutputStream和ObjectInputStream即可輕松實(shí)現(xiàn),下面舉一個(gè)簡單的例子 /序列化 User user = new User(0, "lgl", true); try ObjectOutputStream out = new ObjectOutputStream(new FileOutputStre

27、am("cache.txt"); out.writeObject(user); out.getClass(); catch (IOException e) e.printStackTrace(); /反序列化 try ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"); User newUser = (User) in.readObject(); in.close(); catch (IOException e) e.printStackTrace()

28、; catch (ClassNotFoundException e) e.printStackTrace(); 上述代碼演示了采用Serializable方式序列化對象的典型過程,很簡單,只需要把實(shí)現(xiàn)了 Serializable接口的 User對象寫到文件中就可以快速恢復(fù)了,恢復(fù)后的對象newUser和user的內(nèi)容完全一樣,但是兩者并不是同一個(gè)對象。剛開始提到,即使不指定serialVersionUID也可以實(shí)現(xiàn)序列化,那到底要不要指定呢?如果指定的話,serialversiontID后面那一長串?dāng)?shù)字又是什么含義呢?我們要明白,系統(tǒng)既然既然提供了了serialversionUID,那么他必然

29、是有用的,這個(gè)serialversionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數(shù)據(jù)中的serialversionUID只有和當(dāng)前類的serialversionUID相同才能正常的序列化,serialversionUID的詳細(xì)工作機(jī)制是這樣的,序列化的時(shí)候會(huì)把當(dāng)前類的serialversionUID寫進(jìn)序列化的文件中(也可能是其他中介),當(dāng)反序列化的時(shí)候系統(tǒng)會(huì)去檢測文件中的serialversionUID,看它是否和當(dāng)前類的serialversionUID一致,如果一致就說明序列化的類的版本和當(dāng)前類的版本是相同的,這個(gè)時(shí)候可以成功反序列化;否則就說明當(dāng)前類和序列化的類相比發(fā)生了

30、某些變換,比如成員,類型可能發(fā)生了變化,這個(gè)時(shí)候是無法正常的反序列化的;一般來說,我們應(yīng)該手動(dòng)指定serialversionUID的值,比如1L,也可以讓Eclipse根據(jù)當(dāng)前類的結(jié)構(gòu)自動(dòng)去生成它的hash值,這樣序列化和反序列化時(shí)兩者的serialversionUID是相同的,因此可以正常進(jìn)行反序列化。如果不手動(dòng)指定serialversionUID的值,反序列化時(shí)當(dāng)前類有所改變,比如增加或者刪除了某些成員變量,那么系統(tǒng)就會(huì)重新計(jì)算當(dāng)前類的hasj值并把它賦值給serialversionUID,這個(gè)時(shí)候當(dāng)前類的serialversionUID就和序列化數(shù)據(jù)中的serialversionUID不

31、一致,于是反序列化失敗,程序就會(huì)出現(xiàn)crash,所以我們可以明顯的感覺到serialversionUID的作用,當(dāng)我們手動(dòng)指定了它以后;就可以很大程度上避免反序列化過程的失敗。比如當(dāng)版本升級后,我們可能刪除了某個(gè)變量或者新增加了一些新的成員變量,這個(gè)時(shí)候我們的反向序列化過程仍然能夠成功,程序仍然能夠最小限度的恢復(fù)數(shù)據(jù),相反,如果不指定serialversionUID的話,程序則會(huì)掛掉當(dāng)然我們還要考慮另外一種情況,如果類結(jié)構(gòu)發(fā)生了非常規(guī)性改變,比如修改了類名,修改了成員常量的類型,這個(gè)時(shí)候盡管serialversionUID驗(yàn)證通過了,但是反序列化過程還是會(huì)失敗;因?yàn)轭惤Y(jié)構(gòu)有了毀滅性的改變,根本

32、無法從老版本的數(shù)據(jù)中還原出一個(gè)新的類結(jié)構(gòu)的對象。根據(jù)上面的分析,我們可以知道,給serialversionUID指定為1L或者采用Eclipse根據(jù)當(dāng)前類結(jié)構(gòu)去生成的hash值,這兩者并沒有本質(zhì)區(qū)別,效果完全一樣。以下兩點(diǎn)需要特別提一下,首先靜態(tài)成員變量屬于類不屬于對象,所以不會(huì)參與序列化過程,其次用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過程。另外,系統(tǒng)的默認(rèn)序列化過程也是可以改變的,通過實(shí)現(xiàn)如下 writeObject和readObject兩個(gè)方法即可重寫系統(tǒng)默認(rèn)的序列化和反序列化過程,具體怎么去重寫這兩個(gè)方法就是很簡單的事了,這里就不再介紹了,畢竟這不是本章的重點(diǎn),而且大部分情

33、況下我們不需要重寫這兩個(gè)方法2.Parcelable上一節(jié)我們介紹了通過Serializable方式來實(shí)現(xiàn)序列化的方法,我們本節(jié)接著介紹另外一種序列化的方法Parcelable,Parcelable也是一個(gè)借口,一個(gè)類的對象就可以實(shí)現(xiàn)序列化并可以通過Intent和Binder傳遞,下面的實(shí)例就是一個(gè)典型的用法/* * Created by lgl on 16/9/24. */public class User implements Parcelable public int userId; public String userName; public boolean isMale; publi

34、c Book book; public User(int userId, String userName, boolean isMale) this.userId = userId; this.userName = userName; this.isMale = isMale; Override public int describeContents() return 0; Override public void writeToParcel(Parcel dest, int flags) dest.writeInt(userId); dest.writeString(userName); d

35、est.writeByte(byte) (isMale ? 1 : 0); public static final Creator<User> CREATOR = new Creator<User>() Override public User createFromParcel(Parcel in) return new User(in); Override public User newArray(int size) return new Usersize; ; protected User(Parcel in) userId = in.readInt(); user

36、Name = in.readString(); isMale = in.readByte() != 0; 我里先說一下Paicel, Paicel內(nèi)部包裝了可序列化的數(shù)據(jù),可以在Binder中自由傳輸,從上述代碼中可以看出,在序列化過程中需要實(shí)現(xiàn)的功能有序列化、反序列化和內(nèi)容描述,序列化功能由writeToParcel方法來完成,最終是通過Parcel中的一系列write方法來完成的,反序列化功能由CREATOR來完成,其內(nèi)部標(biāo)明了如何創(chuàng)建序列化對象和數(shù)組,并通過Parcel的一系列read方法來完成反序列化過程;內(nèi)容描述功能由describeContents方法來完成,幾乎在所有情況下這個(gè)方

37、法都應(yīng)該返回0,僅當(dāng)當(dāng)前對象中存在文件描述符時(shí),此方法返回1。需要注意的是,在User(Parcel in)方法中,由于book是另一個(gè)可序列化對象,所以它的反序列化過程需要傳遞當(dāng)前線程的上下文類加載器,否則會(huì)報(bào)無法找到類的錯(cuò)誤系統(tǒng)已經(jīng)為我們提供了很多實(shí)現(xiàn)了Parcelable接口的類,他們都是可以直接序列化的,比如Intent,Bundle等,問同時(shí)List和Map也可以序列化,前提是它們里面的每個(gè)元素都是可以序列化。既然 Parcelatle 和Serializable都能實(shí)現(xiàn)序列化并且都可用于Inient間的數(shù)據(jù)傳遞,那么二者該如何選取呢? Serializable是Java中的序列化接

38、口,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量I/O操作。而Parcelable是Andrord中的序列化方式,因此更適合用在Android平臺(tái)上,它的缺點(diǎn)就是使用起來稍微麻煩點(diǎn),但是它的效率很高,這是Android推薦的序列化方式,因此我們要首選Parcelatle,Parcelable主要用在內(nèi)存序列化上,通過Parcelable將對象序列化到存儲(chǔ)設(shè)備中或者將對象序列化后通過網(wǎng)絡(luò)傳輸也都是可以的,但是這個(gè)過程會(huì)稍顯復(fù)雜,因此在這兩種情況下建議大家使用Serializabie.以上就是Parcelable和Serializable的區(qū)別。3.BinderBinder是一個(gè)很深入

39、的話題,筆者也看過一些別人寫的Binder相關(guān)的文章,發(fā)現(xiàn)很少有人能把它介紹清楚,不是深入代碼細(xì)節(jié)不能自拔,就是長篇大論不知所云,看完后都是暈暈的感覺。所以,本節(jié)筆者不打算深入探討B(tài)inder的底層細(xì)節(jié),因?yàn)锽inder太復(fù)雜了。本節(jié)的側(cè)重點(diǎn)是介紹Binder的使用以及上層原理,為接下來的幾節(jié)內(nèi)容做鋪墊.直觀來說,Binder是Android中的一個(gè)類,它實(shí)現(xiàn)了IBinder接口。從IPC角度來說,Binder是Android中的一種跨進(jìn)程通信方式,Binder還可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動(dòng)是/dev/binder,該通信方式在Linux中沒有;從Android Framewor

40、k角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager,等等)和相應(yīng)Managerservice的橋梁:從Android應(yīng)用層來說,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介,當(dāng)bindService的時(shí)候,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象,通過這個(gè)Binder對象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)。Android開發(fā)中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及進(jìn)程

41、間通信,所以較為簡單,無法觸及Binder的核心,而Messenger的的底層其實(shí)是AIDL,所以這里選擇用AIDL來分析Binder的工作機(jī)制。為了分析Binder的工作機(jī)制,我們需要新建一個(gè)AIDL示例,SDK會(huì)自動(dòng)為我們生產(chǎn)AIDL所對應(yīng)的Binder類,然后我們就可以分析Binder的工作過程。還是采用本章開始時(shí)用的例子,新建Java包AIDL,然后新建三個(gè)文件Book.java、Book.aidl和IBookManageraidl,代碼如下所示。Book.java/* * Created by lgl on 16/9/24. */public class Book implement

42、s Parcelable public int bookId; public String bookName; public Book(int bookId, String bookName) this.bookId = bookId; this.bookName = bookName; protected Book(Parcel in) bookId = in.readInt(); bookName = in.readString(); Override public void writeToParcel(Parcel dest, int flags) dest.writeInt(kId);

43、 dest.writeString(bookName); Override public int describeContents() return 0; public static final Creator<Book> CREATOR = new Creator<Book>() Override public Book createFromParcel(Parcel in) return new Book(in); Override public Book newArray(int size) return new Booksize; ;Book.aidl/ Boo

44、k.aidl.aidlpackage com.liuguilin.multiprocesssample;/ Declare any non-default types here with import statementsparcelable Book;IBookManager.aidl/ IBookManager.aidlpackage com.liuguilin.multiprocesssample;/ Declare any non-default types here with import statementsinterface IBookManager List<Book&g

45、t;getBookList(); void addBook(in Book book);上面的三個(gè)文件,Book.java是一個(gè)表示圖書信息的類,他實(shí)現(xiàn)了Parcelable接口,而Book.aidl是Book類在AIDL中的聲明,IBookManager是我們定義的一個(gè)接口,里面有兩個(gè)方法,getBookList和addBook,其中g(shù)etBookList是從服務(wù)器中獲取圖書列表,而addBook用于往圖書列表中添加一本書,當(dāng)然這兩個(gè)方法主要是演示用的,并不需要有實(shí)際的意義,我們可以看到,盡管Book類和IBookManager位于相同的包中,但是在為IBookManager仍然要導(dǎo)入Boo

46、k類,這就是AIDL的特殊之處,在根目錄下的aidl包中有一個(gè)IBookManager類,這就是我們要找的類,我們通過這個(gè)生成Binder來分析Binder的工作原理相關(guān)代碼就不貼出來了,比較多上述代碼是系統(tǒng)生成的,為了方便查看,作者稍微的修改了一下格式,在gen目錄下,可以看到根據(jù)IBookManager.aidl系統(tǒng)為我們生成了這個(gè)java類,他繼承了IIntenrface接口,同時(shí)他自己也還是一個(gè)接口,所有可以在Binder中傳輸?shù)慕涌诙夹枰^承IIntenrface接口,這個(gè)類剛開始看起來比較混亂,但是實(shí)際上還是很清晰的,通過它,我們可以清楚地了解到Binder的工作機(jī)制。這個(gè)類的結(jié)構(gòu)

47、其實(shí)很簡單,首先,它聲明了兩個(gè)方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中所聲明的方法,同時(shí)它還聲明了兩個(gè)整型的id分別用于標(biāo)識(shí)這兩個(gè)方法,這兩個(gè)id用于標(biāo)識(shí)在transact過程中客戶端所請求的到底是哪個(gè)方法。接著,它聲明了一個(gè)內(nèi)部類Stub,這個(gè)Stub就是一個(gè) Binder類,當(dāng)客戶端和服務(wù)端都位于同一個(gè)進(jìn)程時(shí),方法調(diào)用不會(huì)走跨進(jìn)程的transact過程,而當(dāng)兩者位于不同進(jìn)程時(shí),方法調(diào)用需要走transact過程,這個(gè)邏輯由Stub的內(nèi)部代理類Proxy來完成。這么來看,IBookManager這個(gè)接口的確很簡單,但是我們也應(yīng)該認(rèn)識(shí)到,

48、這個(gè)接口的核心實(shí)現(xiàn)就是它的內(nèi)部類Stub和Stub的內(nèi)部代理類Proxy,下面詳細(xì)介紹針對這兩個(gè)類的每個(gè)方法的含義。DESCRIPTORBinder的唯一標(biāo)識(shí),一般用當(dāng)前Binder的類名表示,比如本例子中的“com.liuguil.multiProcess.IBookManager”asInterface(android.os.IBinder obj)用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象,這種轉(zhuǎn)換過程是區(qū)分進(jìn)程的,如果客戶端和服務(wù)端位于同一進(jìn)程,那么此方法返回的就是服務(wù)端的Stub對象本身,否則返回的是系統(tǒng)封裝后的SxyasBinder此方法用

49、于返回當(dāng)前Binder對象onTransact這個(gè)方法運(yùn)行在服務(wù)端的Binder線程池中,當(dāng)客戶端發(fā)起跨進(jìn)程請求時(shí),遠(yuǎn)程請求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理。該方法的原型為public Boolean onTransact (int code,android.os.Pareel data,android.os.Pareel reply,int fiags),服務(wù)端通過code可以確定客戶端所請求的目標(biāo)方法是什么,接著從data中取出目標(biāo)方法所需要的參數(shù)(如果目標(biāo)方法中有參數(shù)的話),然后執(zhí)行目標(biāo)方法,當(dāng)目標(biāo)方法執(zhí)行完畢后,就向reply中寫入返回值(如果目標(biāo)方法有返回值的話),onTrans

50、act方法的執(zhí)行過程就是這樣的。需要注意的是,如果此方法返回false,那么客戶端的請求會(huì)失敗,因此我們可以利用這個(gè)特性來做權(quán)限驗(yàn)證,畢竟我們也不希望隨便一個(gè)進(jìn)程都能遠(yuǎn)程調(diào)用我們的服務(wù)。Proxy#getBookList這個(gè)方法運(yùn)行在客戶端,當(dāng)客戶端遠(yuǎn)程調(diào)用此方法時(shí),它的內(nèi)部實(shí)現(xiàn)是這樣的:首先創(chuàng)建該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List然后把該方法的參數(shù)信息寫入_data中(如果有參數(shù)的話):接著調(diào)用transact方法來發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請求,同時(shí)當(dāng)前線程掛起;然后服務(wù)端的onTransact方法會(huì)被調(diào)用。直到RPC過程返

51、回后,當(dāng)前線程繼續(xù)執(zhí)行,并從_reply中取出RPC過程的返回結(jié)果;最后返回_reply中的數(shù)據(jù)。Proxy#addBook這個(gè)方法路行在客戶端,它的執(zhí)行過程和getBookList是一樣的,addBook沒有返回值,所以它不需要從.reply中取出返回值。通過上面分析,讀者應(yīng)該已經(jīng)理解到Binder工作機(jī)制,但是有兩點(diǎn)還是需要額外說明一下,首先,當(dāng)客戶端發(fā)起遠(yuǎn)程請求時(shí),由于當(dāng)前線程會(huì)被掛起直至服務(wù)器進(jìn)程返回?cái)?shù)據(jù),所以如果一個(gè)遠(yuǎn)程方法是很耗時(shí)的,那么不能再UI線程中發(fā)起此遠(yuǎn)程請求,其次,由于服務(wù)器的Binder方法運(yùn)行在Binder的線程池中,所以Binder方法不管是否耗時(shí)都應(yīng)該給出一個(gè)Bi

52、nder的工作機(jī)制圖從上述分析過程來看,我們完全可以不提供AIDL文件即可實(shí)現(xiàn)Binder,之所以提供AIDL文件,是為了方便系統(tǒng)為我們生成代碼。系統(tǒng)根據(jù)AIDL文件生成Java文件的格式是固定的,我們可以拋開AIDL文件直接寫一個(gè)Binder出來,接下來我們就介紹如何手動(dòng)寫-個(gè)Binder。還是上面的例子,但是這次我們不提供AIDL文件。參考上面系統(tǒng)自動(dòng)生成的IBookManager.java這個(gè)類的代碼,可以發(fā)現(xiàn)這個(gè)類是相當(dāng)有規(guī)律的,根據(jù)它的特點(diǎn),我們完全可以自己寫一個(gè)和它一模一樣的類出來,然后這個(gè)不借助AIDL文件的Binder就完成了。但是我們發(fā)現(xiàn)系統(tǒng)生成的類看起來結(jié)構(gòu)不清晰,我們想試

53、著對它進(jìn)行結(jié)構(gòu)上的調(diào)整,可以發(fā)現(xiàn)這個(gè)類主要由兩部分組成,首先它本身是一個(gè)Binder的接口(繼承了IInterface),其次它的內(nèi)部有個(gè)Stub類,這個(gè)類就是個(gè)Binder,還記得我們怎么寫一個(gè)Binder的服務(wù)端嗎? private final IBookManager.Stub mBinder = new IBookManager.Stub() Override public List<com.liuguilin.multiprocesssample.Book> getBookList() throws RemoteException return mBookList; Override public void addBook(com.liuguilin.multiprocesssample.Book book) throws RemoteException synchronized (mBookList) if(!mBookList.contains(book) mBookList.add(book); ;首先我們會(huì)實(shí)現(xiàn)一個(gè)創(chuàng)建了Stud對象并在內(nèi)部實(shí)現(xiàn)了IBookManager的接口方法,并且在Service的onBind中返回這個(gè)Stud對象,因此,從這一點(diǎn)來看,我們完全可以把Stud類提取出來直接作為一個(gè)獨(dú)立的Bi

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論