本文旨在將Framework的框架描繪出來,主要是記錄我一段時間關於android framework的學習,希望拋磚引玉,對於讀者有一定的幫助。本人才疏學淺,若各位覺得裡面有寫得好的地方,都是借鑑前輩的;若有寫得不好的地方,都是本人的問題,望各位批評指正。
前言
寫在前面:
1、有沒有必要學習linux核心?
我認為是很有必要的。學習linux核心有助於我們加深對一些概念的理解,比如“程式”、“執行緒”。推薦入門的教程:中國大學MOOC李治軍老師的作業系統課程(我覺得我完全是因為要想推薦這個課程才寫的前言)。
2、有沒有必要自己編譯android原始碼?
非必須,可以直接用android studio檢視sdk原始碼,除非要除錯一些功能。但是要親自操練起來才能更熟悉。
android framework與我們的開發息息相關,本文將從開機,即framework的初始化講起,然後再涉及android執行過程中的幾個使用場景。比如使用者啟動app(點選桌面圖示後發生了什麼),使用者使用app(一次觸控,Android到底幹了啥)。其中會涉及主執行緒、anr、handler、binder、zygote、app的入口是什麼、自定義view為什麼要三步走等一些我們經常接觸的概念,並一一解答。涉及原始碼為api 27。
一、初始化篇
當按開機鍵的時候,裝置首先執行BootLoader,BootLoader負責把Linux核心從載入到記憶體,並執行核心的初始化,最後核心將讀取init.rc檔案,並啟動該檔案中定義的各種服務程式。Android framework對於核心而言就是一個Linux程式而已,而該程式就在init.rc檔案中被定義。Android framework的初始化過程由此開始。
首先被建立的是zygote程式,這是系統中執行的第一個Dalvik虛擬機器程式,顧名思義,後面所有Dalvik虛擬機器程式都是通過它“孵化”而來(學過生物的我們都知道,人體所有的細胞都是由受精卵分裂而來,所以本人覺得這個名稱取得非常準確巧妙)。
zygote孵化出的第一個 Dalvik1 程式叫做 SystemServer,是Framework相當重要的程式。 SystemServer 僅僅是該程式的別名,而該程式具休對應的程式依然是 app_process, 因為 SystemServer 是從 app_process中孵化出來的。Ams、Wms、Pms等等都在此程式中建立,可以說SystemServer管理Framework所有的活動。
注1:Andoird 4.4引入ART
SystemServer 中建立了一個 Socket2 客戶端,並有AmS負責管理該客戶端,之後所有的 Dalvik 程式都將通過該 Socket 客戶端間接被啟動。當要啟動新的 APK 程式時 ,AmS 中會通過該 Socket 客戶端向 zygote 程式的 Socket服務端傳送一個啟動命令,然後zygote會孵化出新的程式。
注2:此處涉及Android程式中通訊的一種方法Socket,學過計算機網路的讀者應該對此有一定的概念。以後還會提及pipe、binder兩種程式通訊方法,無論如何,它們最終的目的都是為了讓開發者跨程式呼叫時都像是在進行本地呼叫。至於它們的優缺點以及實現方式,讀者可以自行探究。
1、zygote的啟動
前面我們提到核心初始化時,會啟動在init.rc檔案中配置的程式,zygote相關的配置資訊如下:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
簡單說明一下這個語句的意思,核心會執行/system/bin/app_process3目錄下的程式,啟動一個叫zygote的服務。其中引數--start-system-server, 僅在指定 -- zygote 引數時才有效,意思是告知Zygotelnit啟動完畢後孵化出第一個程式SystemServer。由此可知,zygote啟動後做的事情之一就是啟動SystemServer。
注3:Android支援64位以後,會根據不同的ABI分別執行/system/bin/app_process32和/system/bin/app_process64目錄。
當 zygote 服務從 app_process 開始啟動後,會啟動一個 Dalvik 虛擬機器,而虛擬機器執行的第一個 Java類就是 ZygoteInit.java。(app程式fork自zygote程式,所以ZygoteInit.main同樣也是app的入口,只不過會根據程式的不同執行不同的邏輯。這就是有時候我們程式錯誤日誌的呼叫棧裡面可以看到"…ZygoteInit.main……"的原因。)ZygoteInit會做另外兩件事:一是前面提到的,啟動一個Socket服務埠,該Socket埠用於接收啟動新程式的命令;二是預載入的Framework大部分類及資源供後續app使用。zygote fork app程式時,並不需要複製這一部分,而是使用共享記憶體的方式。
總結: zygote的程式啟動後主要做了三件事:分別是啟動一個Socket服務,用於接收啟動新程式的命令、預載入的Framework大部分類及資源以及啟動SystemServer。
2、SystemServer的啟動
SystemServer是在zygote程式中最終呼叫到Zygote.forkSystemServer方法啟動的。啟動後會做一些初始的配置,比如關閉Socket服務端(非zygote程式不需要),配置SystemServer執行環境。然後呼叫SystemServer.main。
SystemServer啟動後,主要做兩件事:一是通過SystemServerManager啟動各種服務執行緒,比如AMS、WMS、PMS等等,並將其註冊到ServiceManager(AMS、WMS與app的執行息息相關,其具體內容後面再展開);二是啟動HomeActivity,也就是啟動launcher,launcher與普通app的啟動大同小異,後面再詳細介紹。
3、ServiceManager的啟動
此處的ServiceManager不是java世界的,而是native世界的。它也是通過init.rc配置啟動的,其功能相當於service4的DNS伺服器。SystemServer啟動的各個服務都會註冊於其中,我們在使用binder進行跨程式呼叫時,首先回去查詢ServiceManager獲取到對應service的binder引用,然後再進行後續操作。這個過程與我們通過域名查詢dns伺服器獲得ip最後訪問網站類似。
注意:我們檢視Framework程式碼時候會發現SystemServiceRegistry類,這個類和系統服務的註冊沒有半毛錢關係,它只不過是將查詢各種service的工具類快取起來。
注4:這裡不是指Android的四大元件之一的Service。
二、執行時篇
我們在使用android裝置時,就是Framework的執行時。下面我會從兩個關鍵場景說起:第一個場景,點選桌面圖示,這個場景會涉及到android的訊息機制、app的啟動、activity的建立、window的建立和view的繪製。第二個場景,我們在滑動螢幕或者點選按鈕等等,這個場景會涉及到Framework怎麼獲得硬體的事件以及app的事件分發機制。
1、點選桌面圖示後發生了什麼
Activity的啟動
我們都知道桌面程式也就是launcher和普通的app基本沒什麼差別,我們點選桌面圖示其實呼叫了Activity的startActivity方法。Activity是Context的子類5,所以本來應該是呼叫了Context的startActivity方法,不過Activity過載了該方法,和Context區別是少了是否有Intent.FLAG_ACTIVITY_NEW_TASK的判斷。這也是為什麼我們在非Activity的Context(比如Service)啟動時要加Intent.FLAG_ACTIVITY_NEW_TASK。不論是Activity還是Context,最終都會呼叫到Instrumentation的execStartActivity方法,然後通過Binder跨程式呼叫Ams的startActivity方法。
注5:更準確的說Activity是ContextWrapper的子類,而ContextWrapper不過是個代理類。實際上Activity具體的操作都是由其成員變數mBase完成,而mBase是一個ContextImpl類(繼承Context)。所以如果Context是一個Interface的話,那麼這就是一個標準的靜態代理模式。
Ams會呼叫到startActivityAsUser方法,然後呼叫ActivityStarter的startActivityMayWait方法。
ActivityStarter.java
final int startActivityMayWait(...){
...
//根據intent通過PMS查詢activity相關資訊
//如何沒有在AndroidManifest.xml註冊過就無法找到activity
ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
...
//見下方
int res = startActivityLocked(...);
...
}
int startActivityLocked(...){
...
//見下方
mLastStartActivityResult = startActivity(...);
...
}
private int startActivity(...){
...
//ActivityRecord是Activity在Ams的記錄,與我們app的activity是一一對應的,
//它有一個成員變數appToken是一個binder類,後面app的activity就是通過這個
//類與Ams的activity通訊的
ActivityRecord r = new ActivityRecord(...);
...
//呼叫startActivity的另一個過載,見下方
return startActivity(...);
}
private int startActivity(...){
...
//見下方
result = startActivityUnchecked(...);
...
}
private int startActivityUnchecked(...){
//初始化一些引數,比如mLaunchSingleTop(launchMode是否為singletop),調整mLaunchFlags等
setInitialState(...);
//進一步調整mLaunchFlags,比如原activity為singleinstance或者要啟動的activity為
//singleinstance或singletask時,確保mLaunchFlags擁有 FLAG_ACTIVITY_NEW_TASK屬性
computeLaunchingTaskFlags();
...
//查詢是否有已啟動的activity
ActivityRecord reusedActivity = getReusableIntentActivity();
if (reusedActivity != null) {
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| mLaunchSingleInstance || mLaunchSingleTask) {
//清理task使得目標activity在頂部,這裡我們就可以明白
//FLAG_ACTIVITY_CLEAR_TOP或者singletask的原理。
...
if (top != null) {
...
//回撥onNewIntent
deliverNewIntent(top);
}
...
}
}
...
//調整好stack以及task
mTargetStack.startActivityLocked(...);
...
//見下方
mSupervisor.resumeFocusedStackTopActivityLocked(...);
}
複製程式碼
至此我們先總結一下,Ams根據intent通過PMS查詢activity相關資訊,這解釋了為什麼沒有在AndroidManifest.xml註冊就無法被啟動。然後根據activity的launchMode、taskAffinity以及intent的launchFlags為activity找到合適的stack和task。stack、task以及ActivityRecord的關係如下圖。Ams通過ActivityRecord儲存activity的各種狀態資訊,以及通過其成員變數appToken(binder類)來和app的activity通訊。
我們接著講ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,該方法會接著呼叫ActivityStack的resumeTopActivityUncheckedLocked方法,接著呼叫resumeTopActivityInnerLocked方法,然後再返回到ActivityStackSupervisor的startSpecificActivityLocked方法。
ActivityStackSupervisor.java
void startSpecificActivityLocked(...) {
if (app != null && app.thread != null) {
...
//如果該activity對應的app已啟動,則直接啟動activity
//具體見後面
realStartActivityLocked(...);
...
}
//通過Ams啟動程式,具體見下方
mService.startProcessLocked(...);
}
final boolean realStartActivityLocked(...){
//這裡的app.thread是一個binder類,用於與app的ActivityThread通訊
//通過binder跨程式呼叫ActivityThread的scheduleLaunchActivity方法。
app.thread.scheduleLaunchActivity();
}
複製程式碼
這裡我們先接著講通過Ams啟動程式,Ams呼叫startProcessLocked後會緊接著呼叫另一個startProcessLocked過載
ActivityManagerService.java
final ProcessRecord startProcessLocked(...){
...
if (app != null && app.pid > 0) {
//如果app已啟動且滿足一些條件則直接返回
}
...
//見下方
startProcessLocked(...);
...
}
private final void startProcessLocked(...){
...
//entryPoint將作為引數通過socket傳遞,後面成為程式java程式碼執行的入口
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
...
//見下方
startResult = Process.start(entryPoint,...);
...
}
複製程式碼
Process的start方法會緊接著呼叫ZygoteProcess的start方法,然後呼叫startViaZygote方法。
ZygoteProcess.java
private Process.ProcessStartResult startViaZygote(...){
...
//openZygoteSocketIfNeeded方法作用是和zygote程式建立socket連線
//之前我們提到zygote程式會扮演socket服務端的角色接受命令然後fork出進出
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
...
}
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(...){
//傳送fork的命令以及上面提到entryPoint等其他引數
...
}
複製程式碼
我們回到zygote程式,在zygote程式啟動時,我們是呼叫到ZygoteInit的main方法進行初始化,其中會開啟ZygoteServer的runSelectLoop執行緒一直迴圈接收命令。而其中的主要方法時ZygoteConnection的processOneCommand方法。
ZygoteConnection.java
Runnable processOneCommand(...){
//讀取命令和引數
...
//fork程式
pid = Zygote.forkAndSpecialize(...);
...
//對linux下c程式設計有一定了解的朋友會知道,fork後子程式的pid為0
if (pid == 0) {
...
//處理子程式,見下方
return handleChildProc(parsedArgs, descriptors, childPipeFd);
}
}
private Runnable handleChildProc(...){
...
return ZygoteInit.zygoteInit(...);
}
ZygoteInit.java
public static final Runnable zygoteInit(...){
...
return RuntimeInit.applicationInit(...);
}
RuntimeInit.java
protected static Runnable applicationInit(...) {
//args.startClass就是我們之前提到的entryPoint,也就是"android.app.ActivityThread"
//由此可知app第一個被呼叫的方法是ActivityThread的main方法,這就是應用程式的入口。
return findStaticMain(args.startClass, args.startArgs, classLoader);
}
複製程式碼
我們終於回到了自己的程式,也很明確ActivityThread的main方法(提到main方法,總是有一種無以言表的親切感)就是應用程式的入口。接著繼續探索。
ActivityThread.java
public static void main(String[] args) {
//建立looper,looper其實很好理解,就是一直在迴圈,一直在取指執行。
//(我們的計算機的原理不也是一直取指執行嗎)
Looper.prepareMainLooper();
//建立ActivityThread,一開始我們看到這個名字會以為它是一個Thread的類
//事實上它也完全可以代表app的主執行緒,因為它擁sMainLooper,
//擁有sMainThreadHandler,它會和Ams以及其他系統服務打交道
//而我個人的理解,activity即活動,thread即線,它就是一條線串起了所有app的活動。
ActivityThread thread = new ActivityThread();
//建立與Ams的Binder通道,見下方
thread.attach(false);
//建立handler,handler其實就是一個工具,讓我們往MessageQueue放訊息,移除訊息以及處理訊息
//looper才是主角,looper會從MessageQueue一直取訊息,然後執行
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
}
private void attach(boolean system) {
...
//mgr是一個binder類用於與Ams通訊
mgr.attachApplication(mAppThread);
...
}
複製程式碼
看到這我們已經很清楚什麼是主執行緒了,程式最初執行的那個執行緒就是主執行緒。而後面我們會發現一個“恐怖”的事實,我們的app會一直執行在sMainLooper之中(也就是主執行緒之中,當然排除我們建立的其他執行緒),包括啟動activity,傳送觸控訊息、按鍵訊息,我們都會通過sMainThreadHandler交由sMainLooper處理(我們開發時通過handler進行同步也是這個原理)。既然清楚了主執行緒的概念,那麼anr的原理也就很好理解了,sMainLooper在處理這個訊息的時候如果超過5s(activity)或者20s(service)就會anr,這種確保使用者體驗的一個做法。
接下來我們回到Ams,Ams會緊接著呼叫attachApplicationLocked方法。
ActivityManagerService.java
private final boolean attachApplicationLocked(...){
...
//通過binder IPC通知ActivityThread建立Application
thread.bindApplication(...);
...
mStackSupervisor.attachApplicationLocked(app);
...
}
ActivityStackSupervisor.java
boolean attachApplicationLocked(...) throws RemoteException {
...
//看我們發現了什麼,似曾相識。沒錯就是上面我們留下來的問題。
//道理很簡單,我們在啟動一個activity的時候發現程式未啟動,
//當我們啟動程式後當然得重新啟動activity
realStartActivityLocked(...);
...
}
final boolean realStartActivityLocked(...){
...
//這裡的thread是一個binder類,和ActivityThread是對應的
app.thread.scheduleLaunchActivity(...);
...
}
複製程式碼
重新回到app程式,先看建立Application的流程
ActivityThread$ApplicationThread.java
public final void bindApplication(...){
//通過handler呼叫到handleBindApplication方法,接著呼叫到performLaunchActivity方法
sendMessage(H.BIND_APPLICATION, data);
}
ActivityThread.java
private void handleBindApplication(AppBindData data) {
...
//建立Instrumentation
mInstrumentation = new Instrumentation();
...
//建立Application,回撥attachBaseContext方法
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
//回撥onCreate方法
mInstrumentation.callApplicationOnCreate(app);
...
}
複製程式碼
接著看建立Activity的流程
ActivityThread$ApplicationThread.java
public final void scheduleLaunchActivity(...){
//通過handler呼叫到handleLaunchActivity方法,接著呼叫到performLaunchActivity方法
sendMessage(H.LAUNCH_ACTIVITY, r);
}
ActivityThread.java
private Activity performLaunchActivity(...){
//真正的Context類,也就是我們上面提到的mBase
ContextImpl appContext = createBaseContextForActivity(r);
...
//利用發射建立activity
activity = mInstrumentation.newActivity(...);
...
//將appContext賦給mBase,並且回撥attachBaseContext(context);
//getInstrumentation()之前提到過呼叫Instrumentation的execStartActivity方法
//r.token為binder類與Ams的ActivityRecord對應,我的另一篇文章(見注6)提到它有重要作用
activity.attach(appContext, this, getInstrumentation(),r.token,...,app,...,window,...);
...
//回撥onCreate
mInstrumentation.callActivityOnCreate(...);
}
複製程式碼
注6:文章:一個極簡的RePlugin
至此startActivity的框架已描述完畢。
View的繪製
這一部分並不是要講自定義view,而是將視窗的建立(包括新增與繪製)。 從WmS的角度來觀察,一個視窗並不是一個Window類,而是一個View類。當WmS收到使用者的訊息後,需要把訊息派發到視窗,View類本身並不能直接接收WmS傳遞過來的訊息,真正接收使用者訊息的必須是IWindow類(binder類),而實現IWindow類的是ViewRoot.W類,每一個W內部都包含了一個View變數。這兩句話引用自《Android核心剖析》,從後面講解可知,Window類更多是視窗的抽象,而其中的view才是視窗的內容。
Framework中定義了三種視窗,分別為應用視窗、子視窗和系統視窗。其中應用視窗對應一個Activity,接下來就是講解應用視窗(下面簡稱為視窗)的建立。既然視窗對應一個Activity,那麼視窗就是在startActivity的過程中建立的。上面提到Activity的建立會回撥onCreate的,而我們在開發的時候會在其中呼叫setContentView方法。而setContentView會呼叫Window類的setContentView方法,如果你去檢視Activity的attach方法時,會發現Window類實際上是一個PhoneWindow類。
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
...
installDecor();
...
//載入app自定義的佈局,由下可知我們的佈局至少包裹著兩個view,
//先是由mContentParent,然後再由mDecor包裹
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
private void installDecor() {
...
//建立DecorView(繼承自FrameLayout),Decor顧名思義,視窗的所有內容都在這個view中展示,
//這個View是Activity所有View的root。
//這也是我們檢視Activity view hierarchy,最外層都是FrameLayout的原因
mDecor = generateDecor(-1);
...
//根據不同的style配置inflate不同的xml佈局,
//這些xml有個共同特點:都有一個id為ID_ANDROID_CONTEN的view
//所以可以通ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
//為contentParent賦值
mContentParent = generateLayout(mDecor);
...
}
複製程式碼
這一步只是將View建立出來,接下來還會涉及到兩步:1、將視窗新增到Wms,Wms也會和Ams一樣將視窗以一定形式管理起來。2、將View要展示的內容轉成資料儲存於螢幕緩衝區記憶體,交由系統繪製出來。
在startActivity的過程中,建立Activity後會接著呼叫handleResumeActivity。
ActivityThread.java
private void handleLaunchActivity(...){
...
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
//見下方
handleResumeActivity(...);
...
}
}
final void handleResumeActivity(...){
...
//回撥onResume
r = performResumeActivity(token, clearHide, reason);
...
//將decor設為不可見
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
...
//呼叫Activity的makeVisible
r.activity.makeVisible();
...
}
Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
//最後會呼叫到WindowManagerGlobal的addView
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
WindowManagerGlobal.java
public void addView(...) {
...
//建立ViewRootImpl(View root的抽象)
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//將view(這裡其實是mDecor)、root(View的抽象)以及layoutParam快取起來
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
//見下方
root.setView(view, wparams, panelParentView);
...
}
ViewRootImpl.java
public void setView(...) {
...
//下面小節接著講
requestLayout();
...
//ipc到Wms,Session類為服務端
//mInputChannel是一個InputChannel類,是管道在java層的實現,後面講到Android事件的時候會細說
res = mWindowSession.addToDisplay(...,mInputChannel);
...
}
Session.java
@Override
public int addToDisplay(...) {
//見下方
return mService.addWindow(...);
}
WindowManagerService.java
public int addWindow(...) {
//建立WindowState
//其中session是Session的Binder服務端
//client是IWindow的Binder客戶端
final WindowState win = new WindowState(..., session, client,...);
...
//會呼叫到Session的windowAddedLocked方法,見下方
win.attach();
//將win快取起來
mWindowMap.put(client.asBinder(), win);
...
if (win.canReceiveKeys()) {
//如果該視窗是可互動視窗(比如Toast為不可互動視窗),則更新聚焦視窗
focusChanged = updateFocusedWindowLocked(...);
...
}
}
Session.java
void windowAddedLocked(String packageName) {
...
if (mSurfaceSession == null) {
//建立SurfaceSession,該類是直接和Surfaceflinger互動的類,
//用於向SurfaceFlinger中新增、刪除、變換視窗。由千每個應用程式僅對應一個Session物件,
//因此,mSurfaceSession實際上只會被建立一次,
//即應用程式中的第一個視窗建立時會構造一個SurfaceSession物件,
//同一個應用程式中後續的視窗新增不會再構造 SurfaceSession 物件.
mSurfaceSession = new SurfaceSession();
...
//儲存Session
mService.mSessions.add(this);
...
}
...
}
複製程式碼
到這裡可以看到Wms將視窗的資訊儲存下來,也就是管理起來,是事件分發的基礎。
回到上面的requestLayout方法,requestLayout呼叫了scheduleTraversals方法,該方法發起一個View樹遍歷的訊息,該訊息是非同步處理的,對應的處理函式為performTraversals方法。
ViewRootImpl.java
private void performTraversals() {
final View host = mView;
if (mFirst) {
...
//如果是視窗第一次顯示,為mAttachInfo初始化,並賦給mView,
//呼叫ViewGroup的dispatch方法,將mAttachInfo遞迴地傳遞給子View
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
...
//如果視窗尺寸發生了改變,則呼叫 host.measure()重新計算視窗中檢視的大小
//Android的View和ViewGroup是很經典的組合模式
//measure過程會遍歷整個View tree,會呼叫每個View的measure以及回撥onMeasure
//layout和draw的過程也是類似
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//根據以七步驟的執行結果,判斷是否需要進行重新佈局。比如當任意檢視的大小發生變化時,
//它會影響其他檢視的佈局
performLayout(lp, mWidth, mHeight);
...
//判斷沒有取消繪製,並且不是 newSurface, 則開始繪製
//newSurface變拉的含義是指,ViewRoot中建立了Surface物件,但是該物件還沒有被WmS分配真正的視訊記憶體,
//ViewRoot中是呼叫sWindowSession.relayoutWindow()為該Surface物件中分配真正的視訊記憶體,
//在一般情況下,此處的newSurface都是false。
performDraw();
...
}
複製程式碼
第一次由於視窗設定不可見,所以前面的程式碼可以看到,在Activity的makeVisible方法會呼叫mDecor.setVisibility(View.VISIBLE),經過一系列呼叫會再次呼叫到ViewRootImpl的performTraversals方法,然後呼叫performDraw方法。
在講繪製之前,首先我們要清楚幾個概念。
- Surface:
Surface是原始影象緩衝區(raw buffer)的一個控制程式碼。也就是說Surface對應一段記憶體,這段記憶體的資料就是要繪製的內容,Surface 類本質上就是表示一個平面- Canvas:
我們知道繪製不同圖案顯然是一種橾作,而不是一段資料。Android用Canvas類來表示這些操作,也就是說Canvas就是繪製的功能類。看Canvas的描述:A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap)——Canvas的功能就是響應draw的呼叫,並將其寫入bitmap,而這個bitmap用於儲存所有畫素,你就會更清楚Canvas的概念。
ViewRootImpl.java
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
//之前向Wms申請的
Surface surface = mSurface;
...
if (!drawSoftware(...)) {
return;
}
...
}
private boolean drawSoftware(...) {
...
//獲取canvas並鎖定
canvas = mSurface.lockCanvas(dirty);
...
//遍歷子類的draw,很顯然這一過程就是將所有子類的內容轉成畫素寫入Canvas的bitmap中
mView.draw(canvas);
...
//將儲存所有view內容的Canvas提交給surface,交由系統底層繪製。
surface.unlockCanvasAndPost(canvas);
...
}
複製程式碼
至此view繪製的框架已描述完畢。
2、一次觸控,Android到底幹了啥
這個標題來自《一次觸控,Android到底幹了啥》,所以下面很多內容會引用自這篇文章。
一次觸控意味著什麼?我們在使用Android裝置的過程中,點選、長按、滑動(TouchEvent)以及按實體按鍵(KeyEvent)都可以成為“一次觸控”。因此,一次觸控可以代表所有我們使用過程中的操作。
我們的觸控當然是從硬體開始,硬體會將事件傳遞到核心,核心傳遞到Framework。前面提到SystemServer啟動時會啟動各種服務:
SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
...
startOtherServices();
...
}
private void startOtherServices() {
...
//建立InputManagerService
inputManager = new InputManagerService(context);
...
//建立WindowManagerService,且持有InputManagerService
wm = WindowManagerService.main(..., inputManager,...);
...
//將InputMonitor設為回撥,且啟動InputManagerService
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
InputManagerService.java
public InputManagerService(Context context) {
...
//初始化native物件(mPtr是long,儲存native物件的指標,這是jni常用的保持native物件的方法)
//InputManagerService對應native的InputManager.cpp
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
...
}
public void start() {
...
//用於啟動下面提到的兩個執行緒
nativeStart(mPtr);
...
}
InputManager.cpp
InputManager::InputManager(...) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
/**
由上面的程式碼,我們可以看到InputManager初始化時建立了InputDispatcher、InputReader
以及InputReaderThread、InputDispatcherThread。InputReader用來讀取輸入資訊(也就是各種事件),
InputDispatcher用於分發事件。而兩個執行緒很顯然就是來執行這兩個功能。
*/
複製程式碼
InputReader執行緒會持續呼叫輸入裝置的驅動,讀取所有使用者輸入的訊息該執行緒和InputDispatcher執行緒都在系統程式(system_process)空間中執行。InputDispatcher執行緒從自己的訊息佇列中取出原始訊息,取出的訊息有可能經過兩種方式進行派發。第一種是經過管道(Pipe)直接派發到客戶視窗中,另一種則是先派發到WmS中,由WmS經過一定的處理,如果WmS沒有處理該訊息,則再派發到客戶視窗中,否則 ,不派發到客戶視窗(引用自《Android核心剖析》)。如圖(圖片同樣來自《Android核心剖析》):
如果是按鍵訊息,InputDispatcher會先回撥InputManager中定義的回撥函式,這既會回撥InputMonitor中的回撥函式,這又會呼叫WmS中定義的相關函式。對於系統按鍵訊息,比如"Home"鍵、電話按鍵等,WmS內部會按照預設的方式處理,如果事件被消耗,InputDispatcher則不會繼續把這些按鍵訊息傳遞給客戶視窗對於觸控式螢幕訊息,InputDispatcher則直接傳遞給客戶視窗。
InputManagerService.java
//按鍵事件的回撥,前面我們提到回撥物件是InputMonitor
// Native callback.
private long interceptKeyBeforeDispatching(InputWindowHandle focus,
KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
InputMonitor.java
@Override
public long interceptKeyBeforeDispatching(
InputWindowHandle focus, KeyEvent event, int policyFlags) {
WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
//回撥Wms,mPolicy在SystemServer初始化時建立,為PhoneWindowManager類,可以看到其中對各種按鍵的處理
return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
}
複製程式碼
當InputDispatcher直接傳遞給視窗時,通過findTouchedWindowAtLocked方法找到當前獲得焦點的視窗,然後通過Pipe(管道)進行ipc。也就是說在native層也會維護一組視窗相關的資訊。我們回到Wms新增視窗的過程:
WindowManagerService.java
public int addWindow(...) {
//建立WindowState,構造方法中會建立InputWindowHandle
final WindowState win = new WindowState(...);
...
//建立管道通訊,其中outInputChannel是從app程式傳遞過來的
win.openInputChannel(outInputChannel是從app程式);
...
//更新視窗焦點改變的資訊到native層,同理當視窗切換或者銷燬時也會更新
mInputMonitor.updateInputWindowsLw(false /*force*/);
...
}
WindowState.java
void openInputChannel(InputChannel outInputChannel) {
...
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
/*Channel[0]儲存在server端*/
mInputWindowHandle.inputChannel = inputChannels[0];
...
/* Channel[1]返回給app的ViewRootImpl端*/
mClientChannel.transferTo(outInputChannel);
...
/*註冊到InputManagerService native層*/
mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
複製程式碼
可以看到,在Wms中維護一組WindowState,用於視窗的建立、銷燬切換,而在InputManagerService則維護一組InputWindowHandle,用於事件的分發。
我們回到ViewRootImpl中。在新增視窗時,我們呼叫了setView方法。
ViewRootImpl.java
public void setView(...) {
...
//InputChannel類,是管道在java層的實現
mInputChannel = new InputChannel();
...
//檢視IWindowSession.aidl會發現這裡的mInputChannel標註著out,
//也就是在另一個程式的改變都會同步到本程式
res = mWindowSession.addToDisplay(...,mInputChannel);
...
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
//建立管道的回撥,見下方
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
}
}
ViewRootImpl$WindowInputEventReceiver.java
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
//見下方
super(inputChannel, looper);
}
InputEventReceiver.java
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...
//建立管道的回撥,native層收到事件就會回撥給InputEventReceiver
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
...
}
複製程式碼
當InputManagerService的InputDispatcher通過管道將訊息傳遞給app程式,app程式的管道會回撥InputEventReceiver(也就是WindowInputEventReceiver),進行事件分發。後面的程式碼可以說是責任鏈模式的標準答案,非常精彩讀者可以自行學習。
總結
Android Framework可以說是一個龐大的工程,如果我們在一開始的過程終究陷入細節,就無法走通一條路。君子善假於物也,藉助前輩的研究學習成果,我們可以先學習整體的框架,有必要時再各個擊破。非常感謝各個前輩,下面的參考文獻可能有所遺漏,在此致歉!希望本篇文章對於讀者有所幫助。
參考文獻:
- 柯元旦的《Android核心剖析》
- gityuan Android系統開篇
- 任韜 一次觸控,Android到底幹了啥
- 遊騎兵810 View繪製流程及原始碼解析(一)——performTraversals()原始碼分析