android源碼分析工具
『壹』 Android-trace分析工具
1.TraceView
官方說明文檔:
https://developer.android.google.cn/studio/profile/cpu-profiler
android CPU profiler
CPU profiler可以實時檢查應用的CPU使用率和線程activity,並記錄函數跟蹤,以便於您可以優化和調試您的應用程序.
Flame Chart如果出現問題 顏色也會加深
2.systrace
簡介:
systrace是Android4.1版本之後推出的,對系統Performance分析的工具。systrace的功能包括跟蹤系統I/O操作,內核工作隊列,CPU負載以及Android各個子系統的運行狀況等。
主要由三部分構成:
1.內核部分
systrace採用了linux Kernel的ftrace功能,所以如果要使用systrace的話,必須開啟Kernel和ftrace相關的模塊.
2.數據採集部分
Android中定義了一個trace類,應用程序可以使用該類把統計信息輸出給trace,同時,android有一個atrace程序,它可以從ftrace中讀取統計信息然後交給數據分析工具來處理.
3.數據分析工具
Android提供一個systrace.py用來配置數據採集的方法(如採集數據的標簽,輸出文件名等)和收集ftrace統計數據並生成一個結果網頁文件供用戶查看。
簡單的說,當機器以60幀/秒顯示,用戶會感知機器流暢。如果出現顯示時丟幀的情況,就需要知道系統在做什麼?Systrace是用來收集系統和應用的數據信息和一些中間生成數據的細節,在Android4.1和4.2系統之後出現。Systrace在分析一些顯示問題上特別有用,如應用畫圖慢,顯示動作或者動畫時變形。
抓取systrace
進入本地Android/Sdk/platform-tools/systrace目錄下,執行python systrace.py view --time = 10
python腳本的option
『貳』 [Android源碼分析] - 非同步通信Handler機制
一、問題:在Android啟動後會在新進程里創建一個主線程,也叫UI線程( 非線程安全 )這個線程主要負責監聽屏幕點擊事件與界面繪制。當Application需要進行耗時操作如網路請求等,如直接在主線程進行容易發生ANR錯誤。所以會創建子線程來執行耗時任務,當子線程執行完畢需要通知UI線程並修改界面時,不可以直接在子線程修改UI,怎麼辦?
解決方法:Message Queue機制可以實現子線程與UI線程的通信。
該機制包括Handler、Message Queue、Looper。Handler可以把消息/ Runnable對象 發給Looper,由它把消息放入所屬線程的消息隊列中,然後Looper又會自動把消息隊列里的消息/Runnable對象 廣播 到所屬線程里的Handler,由Handler處理接收到的消息或Runnable對象。
1、Handler
每次創建Handler對象時,它會自動綁定到創建它的線程上。如果是主線程則默認包含一個Message Queue,否則需要自己創建一個消息隊列來存儲。
Handler是多個線程通信的信使。比如在線程A中創建AHandler,給它綁定一個ALooper,同時創建屬於A的消息隊列AMessageQueue。然後在線程B中使用AHandler發送消息給ALooper,ALooper會把消息存入到AMessageQueue,然後再把AMessageQueue廣播給A線程里的AHandler,它接收到消息會進行處理。從而實現通信。
2、Message Queue
在主線程里默認包含了一個消息隊列不需要手動創建。在子線程里,使用Looper.prepare()方法後,會先檢查子線程是否已有一個looper對象,如果有則無法創建,因為每個線程只能擁有一個消息隊列。沒有的話就為子線程創建一個消息隊列。
Handler類包含Looper指針和MessageQueue指針,而Looper里包含實際MessageQueue與當前線程指針。
下面分別就UI線程和worker線程講解handler創建過程:
首先,創建handler時,會自動檢查當前線程是否包含looper對象,如果包含,則將handler內的消息隊列指向looper內部的消息隊列,否則,拋出異常請求執行looper.prepare()方法。
- 在 UI線程 中,系統自動創建了Looper 對象,所以,直接new一個handler即可使用該機制;
- 在 worker線程 中,如果直接創建handler會拋出運行時異常-即通過查『線程-value』映射表發現當前線程無looper對象。所以需要先調用Looper.prepare()方法。在prepare方法里,利用ThreadLocal<Looper>對象為當前線程創建一個Looper(利用了一個Values類,即一個Map映射表,專為thread存儲value,此處為當前thread存儲一個looper對象)。然後繼續創建handler, 讓handler內部的消息隊列指向該looper的消息隊列(這個很重要,讓handler指向looper里的消息隊列,即二者共享同一個消息隊列,然後handler向這個消息隊列發送消息,looper從這個消息隊列獲取消息) 。然後looper循環消息隊列即可。當獲取到message消息,會找出message對象里的target,即原始發送handler,從而回調handler的handleMessage() 方法進行處理。
- handler與looper共享消息隊列 ,所以handler發送消息只要入列,looper直接取消息即可。
- 線程與looper映射表 :一個線程最多可以映射一個looper對象。通過查表可知當前線程是否包含looper,如果已經包含則不再創建新looper。
5、基於這樣的機制是怎樣實現線程隔離的,即在線程中通信呢。
核心在於 每一個線程擁有自己的handler、message queue、looper體系 。而 每個線程的Handler是公開 的。B線程可以調用A線程的handler發送消息到A的共享消息隊列去,然後A的looper會自動從共享消息隊列取出消息進行處理。反之一樣。
二、上面是基於子線程中利用主線程提供的Handler發送消息出去,然後主線程的Looper從消息隊列中獲取並處理。那麼還有另外兩種情況:
1、主線程發送消息到子線程中;
採用的方法和前面類似。要在子線程中實例化AHandler並設定處理消息的方法,同時由於子線程沒有消息隊列和Looper的輪詢,所以要加上Looper.prepare(),Looper.loop()分別創建消息隊列和開啟輪詢。然後在主線程中使用該AHandler去發送消息即可。
2、子線程A與子線程B之間的通信。
1、 Handler為什麼能夠實現不同線程的通信?核心點在哪?
不同線程之間,每個線程擁有自己的Handler、消息隊列和Looper。Handler是公共的,線程可以通過使用目標線程的Handler對象來發送消息,這個消息會自動發送到所屬線程的消息隊列中去,線程自帶的Looper對象會不斷循環從裡面取出消息並把消息發送給Handler,回調自身Handler的handlerMessage方法,從而實現了消息的線程間傳遞。
2、 Handler的核心是一種事件激活式(類似傳遞一個中斷)的還是主要是用於傳遞大量數據的?重點在Message的內容,偏向於數據傳輸還是事件傳輸。
目前的理解,它所依賴的是消息隊列,發送的自然是消息,即類似事件中斷。
0、 Android消息處理機制(Handler、Looper、MessageQueue與Message)
1、 Handler、Looper源碼閱讀
2、 Android非同步消息處理機制完全解析,帶你從源碼的角度徹底理解
謝謝!
wingjay

『叄』 Android RecyclerView的布局管理器 GridLayoutManager源碼分析<三>
Android RecyclerView繪制頁面的源碼分析<一>
Android RecyclerView布局管理器LinearLayoutManager源碼分析<二>
以上兩篇講述了RecycerView LinearLayoutManager 頁面繪制以及子條目的布局,LinearLayoutManager 是一個線性的管理器即是控制垂直以及水平展示 一個條目表示一行,然而顯示生活中有很多需求是一行展示多個子條目這個時候就用到了 GridLayoutManager 表格布局管理器 來實現這種需求
GridLayoutManager :繼承於LinearLayoutManager的網格狀布局管理器 默認一行展示一個
GridLayoutManager網格布局管理器 實現一行展示多個條目,本章解釋了GridLayoutManager的簡單源碼實現表格布局的大概調用,下一章展示復雜的表格布局即展示不顧地條目數的表格布局
『肆』 Android 分析OOM工具介紹
如圖1所示, 步驟
** 1, 2, 3** 為打開Android Monitor並切換標簽到monitor的過程
4, 5, 6 對應的圖標和文字含義分別是
MAT 工具識別,並解析hprof文件,
有兩種方式可以獲得hprof文件
MAT並不能直接打開這兩個hprof, 必須通過hprof-conv來轉換一次
如圖3所示,選中(過濾出MainActivity), 然後通過Objects可以看出它有8個實例
接著選中 com.example.wowo.MainActivity 然後右鍵選擇
Merge shortest paths to GC Roots -> exclude week references
因為弱引用是會被回收的,所以排除掉更加容易發現OOM.
什麼是OOM out-of-memory?
Android下的APP運行在VM中(Dalvik or ART), 一個APP需要的內存是有限,這個值在不同的平台, 不同的手機上是不同的,當APP需要的內存超過了內存上限,就會引起OOM.
下面給出一個最基本的Android APP顯示HelloWorld的例子.
這時如果不停的旋轉屏幕, 這時通過觀察Android Monitor里的Free和allocated的memory會發現 allocated 的memory會不斷增加,而Free的memory會不斷減小
這時通過圖1中步聚5 mp java heap, 然後filter到MainActivity, 會發現MainActivity有多個實例
接著再通過MAT來分析, 圖4所示
發現有很多FinalizerReference, 應該是與GC有關,由於旋轉屏幕會導致MainActivity銷毀並重新創建實例,而JVM在創建MainActivity實例時還有free的memory, 所以並沒有觸發GC,即原來的MainActivity的實例並沒有被回收,所以多次旋轉後,在free memory還有的情況下就會保存多個MainActivity的實例造成內存泄露的假象。當free memory 不夠時,這時會觸發GC, 會回收之前銷毀的MainActivity的實例。
所以在查看OOM問題時,當allocated內存不斷增大時,應該人為先觸發GC(點擊圖1的4)。
如果allocated的內存沒有下降,說明當前並沒有可回收的實例占據內存了。
而在該例中,如果點擊了initiate GC後,allocated的內存立即減少了。
Android Monitor看到MainActivity也就只有一個實例了。
『伍』 如何對Android的本地代碼進行profiling
現在用Android native code寫程序庫的人越來越多。對於那些需要寫的庫實時性要求特別強的應用,通過profiling來進行優化是一個非常有用的特性,因為它能幫你理解程序編譯後的本質,比如多少instruction,哪些method調用多少次,多長時間,等等。
Android開發環境提供了Traceview這樣一個工具,可以點到這個鏈接裡面去看官方對他的介紹。總的來說,就是它提供給程序開發者目標程序的執行日誌,以此幫助你調試程序和優化性能。有兩種方法能夠聲稱traceview所需的log,一種是利用DDMS的profiling特性,通過控制什麼時候開始和結束logging來獲得log。這個方法在你沒有程序源代碼的時候有用,因為只需要Run程序就能獲得log信息,但是沒有精準的起始中止控制。另一種是通過將Android自帶的Debug類加到code中,然後調用裡面的method來開始和中止trace信息的紀錄。這個方法能讓開發者非常精準地控制什麼時候開始紀錄,什麼時候結束紀錄,因為開始和技術都是在code中執行的。
對於Java程序來說,官方網頁介紹了一個標准流程,就是在程序中引入Debug類,然後在你想要開始紀錄profiling信息的時候調用startMethodTracing(),然後在准備結束的時候調用stopMethodTracing()方法。紀錄的log文件默認放在sdcard中。
然而對於本地native代碼,該方法就無效了。原因是這個方式只能trace你的java層的方法和其對Android API的調用,卻無法trace Android API背後的那些方法,也無法trace你自己寫的native code。如果你希望trace這些更底層的代碼,就需要用Debug類提供的Debug.startNativeTracing()和 Debug.stopNativeTracing()。而且,這個配對只能工作的虛擬機emulator中,因為只有trace qemu emulator,才能去trace每一個進程的每一條cpu指令,甚至包括內核的代碼,我們也才能獲得更多的信息比如context switch,cache misses。
下面就來看看,利用該方法對來profile native code是怎樣一個流程:
1. 新建一個Android Virtual Device,給一個名字,比如Profile。可以在AVD manager中創建。
2. 在命令行中通過命令」emulator -avd -trace
」 來運行該AVD。比如emulator -avd Profile -trace myTrace。
3. 將startNativeTracing()和stopNativeTracing()添加到你想profile的代碼中。
4. 在Eclipse中build代碼,確保沒有錯誤。安裝到正在運行的AVD中。
5. 去AVD中運行代碼,確保你希望trace的代碼段正常運行了。如果你觀察運行AVD的那個terminal的窗口,應該會有比如「–start tracing–」 和 「–stop tracing–」這樣的消息出現,這就說明代碼正常運行了。
6. App運行完畢後,退出emulator。
7. 去你的用戶目錄找trace文件。這個目錄是存儲你AVD settings的目錄,默認一般都在/Home/User/.android/avd/下。這個User是你自己的用戶名,如果你是用的Mac或者Linux,這個路徑也就是~/.android/avd。
8. 找到和你AVD名字對應的文件夾,裡面有另一個子文件夾,命名就是你的trace名字,比如這里就是myTrace。裡面的文件包括:
qtrace.bb
qtrace.exc
qtrace.insn
qtrace.method
qtrace.pid
qtrace.static
9.你需要用tracedmmp這個工具來將這些文件轉化為符合Traceview格式的文件。問題在於,坑爹的Android SDK/NDK環境不原生提供這個工具。所以……..請看下一步
10. 好吧,這個工具來自於Android的源代碼環境。那我們需要做的,就是下載整個Android源代碼,編譯。這個過程通常會持續……一個小時以上。請參考官方手冊來進行編譯。如果你使用的是Linux,恭喜你,什麼別的資料都不用找,就一步一步按照手冊來就行了。如果你是Mac用戶……哥們,還是按照官方手冊來吧,但過程就聽天由命了。
11. Okay,假設到這里,你已經完成了整個Android源代碼的編譯。接下來,在源代碼的根目錄下運行「source build/envsetup.sh」,然後將根目錄下的/out/host/xxxx/bin加到PARH路徑中。xxx表示你的編譯平台。這下你就可以run tracedmmp了。開一個終端,執行「tracedmmp ~/.android/avd/Profile/myTrace/」,tracedmmp去分析剛才那些五花八門的二進制文件,挖掘裡面的symbolic信息,然後將其和trace數據對應。等一小會,就可以得到instruction的信息,並會生成一個更詳細的包括所有profiling信息的html文檔,這個文檔和Traceview兼容的,可以直接打開,也可以用Traceview工具分析。
這樣,整個profiling過程就結束了。
需要注意一點的是,這個方法和method tracing比有一個局限,就是因為工作在真實設備上,所以emulator不能模擬所有的真實設備效果,比如memory contention和bus contention,同時也無法模擬真實的cache效果,因為emulator中的cache設計是大大簡化了的。
『陸』 android framework層用什麼工具開發代碼
framework的開發比應用層就要煩的多啦。做應用在eclipse中就足夠了,用android系統中的控制項等工具,或者是自己寫個類來實現特定的功能。而framework層的開發,需要往源碼中添加代碼、xml、圖片、id等等數據,這個id可是費了我好大的勁才搞定的。在項目開始的一個半月里,我探索、嘗試了很多,現在把我的經驗分享出來。網上關於framework層的開發信息很少,多是靠自己。
最有效的方式就是分析android的源碼,看google是怎樣實現一個類的,以及類的層次。我現在看的主要是widget和app中的代碼,其他的還沒涉及。像View,ViewGroup,Activity,ActivityThread都是非常重要的類,也是代碼量很大的類,我只是大概地過了下,還沒有仔細分析過。
我花大力氣的地方是資源文件夾下values中幾個文件的作用。
『柒』 誰有android4.4 Email源碼分析文檔
Google提供的Android包含了原始Android的目標機代碼,主機編譯工具、模擬環境,下載的代碼包經過解壓後(這里是Android2.2的源碼包),源代碼的第一層目錄結構如下:
|-- Makefile
|-- bionic (bionic C庫)
|-- bootable (啟動引導相關代碼)
|-- build (存放系統編譯規則及generic等基礎開發包配置)
|-- cts (Android兼容性測試套件標准)
|-- dalvik (dalvik JAVA虛擬機)
|-- development (應用程序開發相關)
|-- external (android使用的一些開源的模組)
|-- frameworks (核心框架——java及C++語言)
|-- hardware (主要保護硬解適配層HAL代碼)
|-- libcore
|-- ndk
|-- device
|-- out (編譯完成後的代碼輸出與此目錄)
|-- packages (應用程序包)
|-- prebuilt (x86和arm架構下預編譯的一些資源)
|-- sdk (sdk及模擬器)
|-- system (文件系統庫、應用及組件——C語言)
`-- vendor (廠商定製代碼)
bionic 目錄
|-- libc (C庫)
| |-- arch-arm (ARM架構,包含系統調用匯編實現)
| |-- arch-x86 (x86架構,包含系統調用匯編實現)
| |-- bionic (由C實現的功能,架構無關)
| |-- docs (文檔)
| |-- include (頭文件)
| |-- inet
| |-- kernel (Linux內核中的一些頭文件)
| |-- netbsd (?netbsd系統相關,具體作用不明)
| |-- private (?一些私有的頭文件)
| |-- stdio (stdio實現)
| |-- stdlib (stdlib實現)
| |-- string (string函數實現)
| |-- tools (幾個工具)
| |-- tzcode (時區相關代碼)
| |-- unistd (unistd實現)
| `-- zoneinfo (時區信息)
|-- libdl (libdl實現,dl是動態鏈接,提供訪問動態鏈接庫的功能)
|-- libm (libm數學庫的實現,)
| |-- alpha (apaha架構)
| |-- amd64 (amd64架構)
| |-- arm (arm架構)
| |-- bsdsrc (?bsd的源碼)
| |-- i386 (i386架構)
| |-- i387 (i387架構?)
| |-- ia64 (ia64架構)
| |-- include (頭文件)
| |-- man (數學函數,後綴名為.3,一些為freeBSD的庫文件)
| |-- powerpc (powerpc架構)
| |-- sparc64 (sparc64架構)
| `-- src (源代碼)
|-- libstdc++ (libstdc++ C++實現庫)
| |-- include (頭文件)
| `-- src (源碼)
|-- libthread_db (多線程程序的調試器庫)
| `-- include (頭文件)
`-- linker (動態鏈接器)
`-- arch (支持arm和x86兩種架構)
bootable 目錄
|-- bootloader (適合各種bootloader的通用代碼)
| `-- legacy (估計不能直接使用,可以參考)
| |-- arch_armv6 (V6架構,幾個簡單的匯編文件)
| |-- arch_msm7k (高通7k處理器架構的幾個基本驅動)
| |-- include (通用頭文件和高通7k架構頭文件)
| |-- libboot (啟動庫,都寫得很簡單)
| |-- libc (一些常用的c函數)
| |-- nandwrite (nandwirte函數實現)
| `-- usbloader (usbloader實現)
|-- diskinstaller (android鏡像打包器,x86可生產iso)
`-- recovery (系統恢復相關)
|-- edify (升級腳本使用的edify腳本語言)
|-- etc (init.rc恢復腳本)
|-- minui (一個簡單的UI)
|-- minzip (一個簡單的壓縮工具)
|-- mttils (mtd工具)
|-- res (資源)
| `-- images (一些圖片)
|-- tools (工具)
| `-- ota (OTA Over The Air Updates升級工具)
`-- updater (升級器)
build目錄
|-- core (核心編譯規則)
|-- history (歷史記錄)
|-- libs
| `-- host (主機端庫,有android 「cp」功能替換)
|-- target (目標機編譯對象)
| |-- board (開發平台)
| | |-- emulator (模擬器)
| | |-- generic (通用)
| | |-- idea6410 (自己添加的)
| | `-- sim (最簡單)
| `-- proct (開發平台對應的編譯規則)
| `-- security (密鑰相關)
`-- tools (編譯中主機使用的工具及腳本)
|-- acp (Android "acp" Command)
|-- apicheck (api檢查工具)
|-- applypatch (補丁工具)
|-- apriori (預鏈接工具)
|-- atree (tree工具)
|-- bin2asm (bin轉換為asm工具)
|-- check_prereq (檢查編譯時間戳工具)
|-- dexpreopt (模擬器相關工具,具體功能不明)
|-- droiddoc (?作用不明,java語言,網上有人說和JDK5有關)
|-- fs_config (This program takes a list of files and directories)
|-- fs_get_stats (獲取文件系統狀態)
|-- iself (判斷是否ELF格式)
|-- isprelinked (判斷是否prelinked)
|-- kcm (按鍵相關)
|-- lsd (List symbol dependencies)
|-- releasetools (生成鏡像的工具及腳本)
|-- rgb2565 (rgb轉換為565)
|-- signapk (apk簽名工具)
|-- soslim (strip工具)
`-- zipalign (zip archive alignment tool)
『捌』 Android源碼解析RPC系列(一)---Binder原理
看了幾天的Binder,決定有必要寫一篇博客,記錄一下學習成果,Binder是Android中比較綜合的一塊知識了,目前的理解只限於JAVA層。首先Binder是幹嘛用的?不用說,跨進程通信全靠它,操作系統的不同進程之間,數據不共享,對於每個進程來說,它都天真地以為自己獨享了整個系統,完全不知道其他進程的存在,進程之間需要通信需要某種系統機制才能完成,在Android整個系統架構中,採用了大量的C/S架構的思想,所以Binder的作用就顯得非常重要了,但是這種機制為什麼是Binder呢?在Linux中的RPC方式有管道,消息隊列,共享內存等,消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開辟的緩存區中,然後再從內核緩存區拷貝到接收方緩存區,這樣就有兩次拷貝過程。共享內存不需要拷貝,但控制復雜,難以使用。Binder是個折中的方案,只需要拷貝一次就行了。其次Binder的安全性比較好,好在哪裡,在下還不是很清楚,基於安全性和傳輸的效率考慮,選擇了Binder。Binder的英文意思是粘結劑,Binder對象是一個可以跨進程引用的對象,它的實體位於一個進程中,這個進程一般是Server端,該對象提供了一套方法用以實現對服務的請求,而它的引用卻遍布於系統的各個進程(Client端)之中,這樣Client通過Binder的引用訪問Server,所以說,Binder就像膠水一樣,把系統各個進程粘結在一起了,廢話確實有點多。
為了從而保障了系統的安全和穩定,整個系統被劃分成內核空間和用戶空間
內核空間:獨立於普通的應用程序,可以訪問受保護的內存空間,有訪問底層硬體設備的所有許可權。
用戶空間:相對與內核空間,上層運用程序所運行的空間就是用戶空間,用戶空間訪問內核空間的唯一方式就是系統調用。一個4G的虛擬地址空間,其中3G是用戶空間,剩餘的1G是內核空間。如果一個用戶空間想與另外一個用戶空間進行通信,就需要內核模塊支持,這個運行在內核空間的,負責各個用戶進程通過Binder通信的內核模塊叫做Binder驅動,雖然叫做Binder驅動,但是和硬體並沒有什麼關系,只是實現方式和設備驅動程序是一樣的,提供了一些標准文件操作。
在寫AIDL的時候,一般情況下,我們有兩個進程,一個作為Server端提供某種服務,然後另外一個進程作為Client端,連接Server端之後,就 可以使用Server裡面定義的服務。這種思想是一種典型的C/S的思想。值得注意的是Android系統中的Binder自身也是C/S的架構,也有Server端與Client端。一個大的C/S架構中,也有一個小的C/S架構。
先籠統的說一下,在整個Binder框架中,由系列組件組成,分別是Client、Server、ServiceManager和Binder驅動程序,其中Client、Server和ServiceManager運行在用戶空間,Binder驅動程序運行內核空間。運行在用戶空間中的Client、Server和ServiceManager,是在三個不同進程中的,Server進程中中定義了服務提供給Client進程使用,並且Server中有一個Binder實體,但是Server中定義的服務並不能直接被Client使用,它需要向ServiceManager注冊,然後Client要用服務的時候,直接向ServiceManager要,ServiceManager返回一個Binder的替身(引用)給Client,這樣Client就可以調用Server中的服務了。
場景 :進程A要調用進程B裡面的一個draw方法處理圖片。
分析 :在這種場景下,進程A作為Client端,進程B做為Server端,但是A/B不在同一個進程中,怎麼來調用B進程的draw方法呢,首先進程B作為Server端創建了Binder實體,為其取一個字元形式,可讀易記的名字,並將這個Binder連同名字以數據包的形式通過Binder驅動發送給ServiceManager,也就是向ServiceManager注冊的過程,告訴ServiceManager,我是進程B,擁有圖像處理的功能,ServiceManager從數據包中取出名字和引用以一個注冊表的形式保留了Server進程的注冊信息。為什麼是以數據包的形式呢,因為這是兩個進程,直接傳遞對象是不行滴,只能是一些描述信息。現在Client端進程A聯系ServiceManager,說現在我需要進程B中圖像處理的功能,ServiceManager從注冊表中查到了這個Binder實體,但是呢,它並不是直接把這個Binder實體直接給Client,而是給了一個Binder實體的代理,或者說是引用,Client通過Binder的引用訪問Server。分析到現在,有個關鍵的問題需要說一下,ServiceManager是一個進程,Server是另一個進程,Server向ServiceManager注冊Binder必然會涉及進程間通信。當前實現的是進程間通信卻又要用到進程間通信,這就好象蛋可以孵出雞前提卻是要找只雞來孵蛋,確實是這樣的,ServiceManager中預先有了一個自己的Binder對象(實體),就是那隻雞,然後Server有個Binder對象的引用,就是那個蛋,Server需要通過這個Binder的引用來實現Binder的注冊。雞就一隻,蛋有很多,ServiceManager進程的Binder對象(實體)僅有一個,其他進程所擁有的全部都是它的代理。同樣一個Server端Binder實體也應該只有一個,對應所有Client端全部都是它的代理。
我們再次理解一下Binder是什麼?在Binder通信模型的四個角色裡面;他們的代表都是「Binder」,一個Binder對象就代表了所有,包括了Server,Client,ServiceManager,這樣,對於Binder通信的使用者而言,不用關心實現的細節。對Server來說,Binder指的是Binder實體,或者說是本地對象,對於Client來說,Binder指的是Binder代理對象,也就是Binder的引用。對於Binder驅動而言,在Binder對象進行跨進程傳遞的時候,Binder驅動會自動完成這兩種類型的轉換。
簡單的總結一下,通過上面一大段的分析,一個Server在使用的時候需要經歷三個階段
1、定義一個AIDL文件
Game.aidl
GameManager .aidl
2、定義遠端服務Service
在遠程服務中的onBind方法,實現AIDL介面的具體方法,並且返回Binder對象
3、本地創建連接對象
以上就是一個遠端服務的一般套路,如果是在兩個進程中,就可以進程通信了,現在我們分析一下,這個通信的流程。重點是GameManager這個編譯生成的類。
從類的關系來看,首先介面GameManager 繼承 IInterface ,IInterface是一個介面,在GameManager內部有一個內部類Stub,Stub繼承了Binder,(Binder實現了IBinder),並且實現了GameManager介面,在Stub中還有一個內部類Proxy,Proxy也實現了GameManager介面,一個整體的結構是這樣的
現在的問題是,Stub是什麼?Proxy又是什麼?在上面說了在Binder通信模型的四個角色裡面;他們的代表都是「Binder」,一個Binder對象就代表了所有,包括了Server,Clinet,ServiceManager,為了兩個進程的通信,系統給予的內核支持是Binder,在抽象一點的說,Binder是系統開辟的一塊內存空間,兩個進程往這塊空間裡面讀寫數據就行了,Stub從Binder中讀數據,Proxy向Binder中寫數據,達到進程間通信的目的。首先我們分析Stub。
Stub 類繼承了Binder ,說明了Stub有了跨進程傳輸的能力,實現了GameManager介面,說明它有了根據游戲ID查詢一個游戲的能力。我們在bind一個Service之後,在onServiceConnecttion的回調裡面,就是通過asInterface方法拿到一個遠程的service的。
asInterface調用queryLocalInterface。
mDescriptor,mOwner其實是Binder的成員變數,Stub繼承了Binder,在構造函數的時候,對著兩個變數賦的值。
如果客戶端和服務端是在一個進程中,那麼其實queryLocalInterface獲取的就是Stub對象,如果不在一個進程queryLocalInterface查詢的對象肯定為null,因為不同進程有不同虛擬機,肯定查不到mOwner對象的,所以這時候其實是返回的Proxy對象了。拿到Stub對象後,通常在onServiceConnected中,就把這個對象轉換成我們多定義AIDL介面。
比如我們這里會轉換成GameManager,有了GameManager對象,就可以調用後querryGameById方法了。如果是一個進程,那直接調用的是自己的querryGameById方法,如果不是一個進程,那調用了就是代理的querryGameById方法了。
看到其中關鍵的一行是
mRemote就是一個IBinder對象,相對於Stub,Proxy 是組合關系(HAS-A),內部有一個IBinder對象mRemote,Stub是繼承關系(IS-A),直接實現了IBinder介面。
transact是個native方法,最終還會回掉JAVA層的onTransact方法。
onTransact根據調用號(每個AIDL函數都有一個編號,在跨進程的時候,不會傳遞函數,而是傳遞編號指明調用哪個函數)調用相關函數;在這個例子裡面,調用了Binder本地對象的querryGameById方法;這個方法將結果返回給驅動,驅動喚醒掛起的Client進程裡面的線程並將結果返回。於是一次跨進程調用就完成了。
***Please accept mybest wishes for your happiness and success ! ***