Android 4.4(KitKat)表格管理子系统 - 骨架
原文地址:http://blog.csdn.net/jinzhuojun/article/details/37737439
窗體管理系統(tǒng)是Android中的主要子系統(tǒng)之中的一個(gè)。它涉及到App中組件的管理,系統(tǒng)和應(yīng)用窗體的管理和繪制等工作。因?yàn)槠渖婕澳K眾多,且與用戶體驗(yàn)密切相關(guān)。所以它也是Android其中最為復(fù)雜的子系統(tǒng)之中的一個(gè)。一個(gè)App從啟動(dòng)到主窗體顯示出來(lái),須要App。ActivityManagerService(AMS)。WindowManagerService(WMS),SurfaceFlinger(SF)等幾個(gè)模塊相互合作。App負(fù)責(zé)業(yè)務(wù)邏輯,繪制自己的視圖;AMS管理組件、進(jìn)程信息和Activity的堆棧及狀態(tài)等等;WMS管理Activity相應(yīng)的窗體及子窗體,還有系統(tǒng)窗體等;SF用于管理圖形緩沖區(qū),將App繪制的東西合成渲染在屏幕上。以下分幾個(gè)部分進(jìn)行分析。
?
窗體管理系統(tǒng)的主要框架及各模塊之間的通訊接口大體例如以下:
基于Binder的本地過(guò)程調(diào)用(LPC)讓Android的模塊間耦合度更低。結(jié)構(gòu)更加清晰。每一個(gè)模塊各司其職。并向其他模塊提供接口。
進(jìn)程和uid這些Linux中的機(jī)制對(duì)這些模塊提供了天然的保護(hù),使得系統(tǒng)更加魯棒。模塊之間常常使用C/S的結(jié)構(gòu),而Service本身也可能是使用其他Service的Client。舉例來(lái)說(shuō),假設(shè)Service的實(shí)現(xiàn)叫XXXManagerService。那一般它對(duì)Client提供接口IXXXManager,然后Client要用Service的時(shí)候便會(huì)申請(qǐng)一個(gè)叫BpXXXManager的代理對(duì)象。它是遠(yuǎn)端BnXXXManager本地對(duì)象在Client端的代理。代理對(duì)象BpXXManager實(shí)現(xiàn)了IXXXManager的全部接口,僅僅只是里面的函數(shù)都是殼子,僅僅負(fù)責(zé)參數(shù)的準(zhǔn)備,然后就調(diào)用遠(yuǎn)端對(duì)象去運(yùn)行。
遠(yuǎn)端的的BnXXXManager對(duì)象及其繼承類是真正做事的,BnXXXManager繼承自IXXXManager.Stub抽象類,實(shí)現(xiàn)了IXXXManager接口。
Stub就如其名字一樣,是BnXXXManager的繼承類在BnXXXManager中的“鉤子”。通過(guò)調(diào)用這些接口便能夠調(diào)用到遠(yuǎn)端的Service功能了。概念上類似遠(yuǎn)程gdb調(diào)試。host機(jī)上的gdb和guest上的gdbserver相連以后,在host上敲命令會(huì)讓gdbserver去運(yùn)行。但感覺(jué)就像是在host本地運(yùn)行一樣。這兒的gdbserver就提供了類似于Stub的功能。
?
這樣的遠(yuǎn)程調(diào)用模型的建立過(guò)程通常是分層次的。比方WindowManagerGlobal會(huì)與WMS進(jìn)行連接,ViewRootImpl會(huì)與WMS中的Session進(jìn)行連接。高層先與高層通信,同一時(shí)候幫助建立低層間的通信。然后低層與低層直接通信。
打個(gè)比方。張三是A部門的員工。他想要和B部門合作搞一個(gè)活動(dòng),他一般不會(huì)直接沖過(guò)去B部門挨個(gè)問(wèn)的。所以他先和自己的主管李四說(shuō)。我要和B部門合作,于是李四找到B部門的主管王五,說(shuō)你出個(gè)人吧。于是王五和趙六說(shuō),你負(fù)責(zé)這事兒吧。并告訴了A部門主管李四。李四再告訴下屬?gòu)埲?#xff0c;趙六是B部門接口人,你以后直接和他聯(lián)系吧。
于是張三和趙六以后就直接聯(lián)系了。假設(shè)合作中有須要超越自己權(quán)限的操作。他們?cè)傧蚋髯缘闹鞴苌暾?qǐng)。比方App與WMS的連接,首先會(huì)建立一個(gè)Session到WMS,之后就會(huì)通過(guò)IWindowSession接口與WMS中的Session直接通信。
還有比如WMS和SF先創(chuàng)建SurfaceSession,當(dāng)中會(huì)創(chuàng)建SurfaceComposerClient,訪問(wèn)SurfaceComposerClient時(shí)會(huì)在SF中創(chuàng)建Client與之相應(yīng),這個(gè)Client實(shí)現(xiàn)了ISurfaceComposerClient接口,之后SurfaceComposerClient會(huì)通過(guò)該接口與SF中的Client直接通信。
?
看代碼過(guò)程中,各個(gè)對(duì)象間的數(shù)量及相應(yīng)關(guān)系常常讓人混淆,以下列舉了在普通情況下各對(duì)象之間的實(shí)體關(guān)系圖。當(dāng)中標(biāo)色的是相應(yīng)子系統(tǒng)中比較基礎(chǔ)核心的類。
要注意的幾點(diǎn):1. App中能夠沒(méi)有Activity。也能夠沒(méi)有PhoneWindow和DecorView,比方一個(gè)顯示浮動(dòng)窗體的Service。
2. Task中的Activity能夠來(lái)自不同進(jìn)程,比方App執(zhí)行過(guò)程中打開(kāi)相機(jī)App拍照。
3. WindowState代表WMS中的一個(gè)窗體。這和App端的Window類是不一樣的,雖然非常多時(shí)候一個(gè)Window類(即PhoneWindow)有一個(gè)相應(yīng)的WindowState,但那不是絕對(duì)的。一個(gè)Activity在WMS中有相應(yīng)的AppWindowToken,一個(gè)AppWindowToken又能夠包括多個(gè)WindowState。由于除了主窗體外,還可能有子窗體和啟動(dòng)窗體。此外對(duì)于系統(tǒng)窗體,WindowState還可能不正確應(yīng)AppWindowToken。4.這里的Application指的是App端的一個(gè)進(jìn)程,它不同于AndroidManifest.xml中的<application>標(biāo)簽。后者是配置文件里對(duì)組件的管理者,它和進(jìn)程之間沒(méi)有本質(zhì)關(guān)系,通過(guò)android:process標(biāo)簽?zāi)軌蜃屚粋€(gè)<application>下的組件跑在多個(gè)進(jìn)程,也能夠讓多個(gè)<application>中的組件跑在同一個(gè)進(jìn)程。所以假設(shè)是<application>定義的Application的話和ProcessRecord就是m:n的關(guān)系了。下面談到Application都是指一個(gè)App的進(jìn)程。
首先分析下App端的結(jié)構(gòu)。
移動(dòng)平臺(tái)一般顯示區(qū)域有限,要完畢一個(gè)工作往往不是一屏內(nèi)容中能搞定的。所以Android中有了Activity的概念,讓用戶能夠把相關(guān)的子內(nèi)容放到單獨(dú)的Activity中,然后通過(guò)Intent在Activity間跳轉(zhuǎn)。類似于瀏覽網(wǎng)頁(yè)。點(diǎn)擊鏈接跳轉(zhuǎn)到還有一個(gè)網(wǎng)頁(yè)。
這些同一交互過(guò)程中的一系列Activity成為一個(gè)Task。這些Activity執(zhí)行在主線程ActivityThread中。Activity要展現(xiàn)出來(lái)的主視圖是DecorView。它是一棵視圖樹(shù)。
ViewRootImpl負(fù)責(zé)管理這個(gè)視圖樹(shù)和與WMS交互。與WMS交互通過(guò)WindowManagerImpl和WindowManagerGlobal。DecorView被包括在系統(tǒng)的通用窗體抽象類Window其中。視圖相應(yīng)的圖形緩沖區(qū)由Surface管理。其中涉及到的基本的類包括以下幾個(gè):
Activity:描寫(xiě)敘述一個(gè)Activity,它是與用戶交互的基本單元。
ActivityThread:每個(gè)App進(jìn)程有一個(gè)主線程,它由ActivityThread描寫(xiě)敘述。它負(fù)責(zé)這個(gè)App進(jìn)程中各個(gè)Activity的調(diào)度和運(yùn)行,以及響應(yīng)AMS的操作請(qǐng)求等。
ApplicationThread:AMS和Activity通過(guò)它進(jìn)行通信。對(duì)于AMS而言,ApplicationThread代表了App的主線程。簡(jiǎn)而言之,它是AMS與ActivityThread進(jìn)行交互的接口。
注意ActivityThread和ApplicationThread之間的關(guān)系并不像Activity與Application。后者的關(guān)系是Application中包括了多個(gè)Activity。而前者ActivityThread和ApplicationThread是同一個(gè)東西的兩種"View",ApplicationThread是在AMS眼中的ActivityThread。
ViewRootImpl:主要責(zé)任包含創(chuàng)建Surface,和WMS的交互和App端的UI布局和渲染。同一時(shí)候負(fù)責(zé)把一些事件發(fā)往Activity以便Activity能夠截獲事件。
每個(gè)加入到WMS中的窗體相應(yīng)一個(gè)ViewRootImpl,通過(guò)WindowManagerGlobal向WMS加入窗體時(shí)創(chuàng)建。大多數(shù)情況下,它管理Activity頂層視圖DecorView。總得來(lái)說(shuō),它相當(dāng)于MVC模型中的Controller。
ViewRootImpl::W:用于向WMS提供接口,讓WMS控制App端的窗體。它可看作是個(gè)代理,非常多時(shí)候會(huì)調(diào)用ViewRootImpl中的功能。這樣的內(nèi)嵌類的使用方法非常多,特別是這樣的提供接口的代理類,如PhoneWindow::DecorView等。
Instrumentation:官方提供的Hook。主要用于測(cè)試。假設(shè)僅僅關(guān)注窗體管理流程的話能夠先無(wú)視。
WindowManagerImpl:Activity中與窗體管理系統(tǒng)通信的代理類,實(shí)現(xiàn)類是WindowManagerGlobal。WindowManagerGlobal是App中全局的窗體管理模塊,因此是個(gè)Singleton。
當(dāng)中管理著該App中的ViewRootImpl,DecorView等結(jié)構(gòu),以有兩個(gè)Activity的App為例:
Window:每一個(gè)App盡管都能夠做得各不同樣。可是作為有大量用戶交互的系統(tǒng),窗體之間必需要有統(tǒng)一的交互模式,這樣才干減小用戶的學(xué)習(xí)成本。
這些共性比方title, action bar的顯示和通用按鍵的處理等等。Window類就抽象了這些共性。另外。它定義了一組Callback,Activity通過(guò)實(shí)現(xiàn)這些Callback被調(diào)用來(lái)處理事件。注意要和在WMS中的窗體區(qū)分開(kāi)來(lái),WMS中的窗體更像是App端的View。
PhoneWindow:PhoneWindow是Window類的唯一實(shí)現(xiàn),至少眼下是。這種設(shè)計(jì)下假設(shè)要加其他平臺(tái)的Window類型更加方便。
每一個(gè)Activity會(huì)有一個(gè)PhoneWindow,在attach到ActivityThread時(shí)創(chuàng)建,保存在mWindow成員中。
Context:執(zhí)行上下文。Activity和Service本質(zhì)上都是一個(gè)Context。Context包括了它們作為執(zhí)行實(shí)體的共性,如啟動(dòng)Activity,綁定Service,處理Broadcast和Receiver等等。注意Application也會(huì)有Context。Activity的Context是相應(yīng)Activity的。Activity被殺掉(比方轉(zhuǎn)屏后)后就變了。
所以要注意假設(shè)有生命周期非常長(zhǎng)的對(duì)象有對(duì)Activity的Context的引用的話。轉(zhuǎn)屏、返回這樣的會(huì)引起Activity銷毀的操作都會(huì)引起內(nèi)存泄露。而Application的Context生命周期是和App進(jìn)程一致的。關(guān)于Context的類結(jié)構(gòu)圖有以下的形式。Context是抽象類,定義了接口。ContextImpl是Context的實(shí)現(xiàn)類,包括了實(shí)現(xiàn)。而ContextWrapper是Context的包裝類。它把請(qǐng)求delegate給當(dāng)中的ContextImpl類去完畢。ContextThemeWrapper是ContextWrapper的裝飾類,它在ContextWrapper的基礎(chǔ)上提供了自己定義的主題。這結(jié)構(gòu)初看有點(diǎn)亂,但結(jié)合以下的Decorator模式就一目了然了。
Surface:這是在App端管理圖形緩沖區(qū)的類,當(dāng)中最重要的是圖形緩沖區(qū)隊(duì)列。經(jīng)由WMS從SF中得到IGraphicBufferProducer接口對(duì)象BufferQueue后。Surface便能夠從該隊(duì)列中queue和dequeue圖形緩沖區(qū)。SurfaceControl在WMS中封裝了Surface以及與SF的交互。Canvas和Surface從字面意思上非常像,但前者事實(shí)上更確切地說(shuō)不是“畫(huà)布”,而是“畫(huà)家”。
Surface中的圖形緩沖區(qū)才是App的畫(huà)布。
?
上面這些基本類之間的主要關(guān)系例如以下:
當(dāng)中比較重要的三個(gè)類的PhoneWindow,DecorView和ViewRootImpl。PhoneWindow和ViewRootImpl都包括了mDecor成員,它類型為DecorView。描寫(xiě)敘述了Activity的根視圖。它也是ViewRootimpl和PhoneWindow間的樞紐。類似的。PhoneWindow父類中的Callback是PhoneWindow與Activity的樞紐,而ViewRootImpl::W是ViewRootImpl和WMS間的樞紐。DecorView依次繼承自FrameLayout,ViewGroup和View,因此它本質(zhì)上是一個(gè)View。僅僅是它是Activity最根部的View,它以下可能有非常多Subview。
DecorView描寫(xiě)敘述App窗體視圖,而它的更新是由ViewRootImpl來(lái)控制的。
粗糙點(diǎn)說(shuō)的話,假設(shè)用MVC模型來(lái)說(shuō)的話,前者是View,后者是Controller。
ViewRootImpl中的內(nèi)嵌類W就是給WMS通信的接口。
W的聲明中有兩個(gè)成員變量:mViewAncestor和mWindowSession。它一頭連著App端的ViewRootImpl,一頭連著WMS中的Session。且實(shí)現(xiàn)了IWindow的接口。
意味著它是App和WMS的橋梁。是WMS用來(lái)回調(diào)App端,讓ViewRootImpl做事用的。舉例來(lái)說(shuō)。dispatchAppVisibility()的流程就是經(jīng)過(guò)它來(lái)完畢的:WMS ->ViewRootImpl::W->ViewRootHandler->handleAppVisibility()->scheduleTraversals()。
?
Activity創(chuàng)建完后須要attach到主線程上。在attach()過(guò)程中會(huì)創(chuàng)建Window(實(shí)際是PhoneWindow),然后把PhoneWindow中的mCallback設(shè)為Activity。在PhoneWindow中兩個(gè)關(guān)鍵內(nèi)嵌類Callback和DecorView,Callback連接了Activity。DecorView連接了ViewRootImpl。這樣,當(dāng)ViewRootImpl有事件傳來(lái)時(shí),便能夠沿著ViewRootImpl->DecorView->Window.Callback->Activity這條路來(lái)通知Activity。如按鍵事件就是通過(guò)這條路來(lái)傳達(dá)的。
?
Android里還能夠找到非常多這樣的使用內(nèi)嵌類來(lái)實(shí)現(xiàn)遠(yuǎn)端代理的樣例,這樣的設(shè)計(jì)使得系統(tǒng)滿足最小隔離原則,Client端該用到哪些接口就暴露哪些接口。注意這樣的類的函數(shù)都是跑在Binder線程中的,所以當(dāng)中不能調(diào)用非線程安全的函數(shù),也不能直接操作UI控件,所以一般都是往主線程消息隊(duì)列里丟一個(gè)消息讓其異步運(yùn)行。
?
接下來(lái),看下一個(gè)Activity的啟動(dòng)過(guò)程。以Launcher中啟動(dòng)一個(gè)App為例。比方在Launcher中我們點(diǎn)了一個(gè)圖標(biāo)啟動(dòng)一個(gè)App的Activity。Launcher里會(huì)運(yùn)行:
Intent intent = new Intent("xxx");?
startActivity(intent);
接下來(lái)的大體流程例如以下:
圖比較大,抓大放小,看清里面的主要脈絡(luò)就可以。App與AMS交互流程主要分下面幾步:
1. 原App通知AMS要起一個(gè)新Activity。
2.?AMS創(chuàng)建對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu),然后通知WMS創(chuàng)建對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu),再通知原Activity暫停。
3. 原Activity暫停后通知AMS。
4.?AMS創(chuàng)建新App進(jìn)程,新App創(chuàng)建好后attach到AMS。
AMS再通知新App創(chuàng)建Activity等對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)。
?
流程上我們能夠總結(jié)出模塊間的異步工作模式:當(dāng)一個(gè)模塊要求還有一個(gè)模塊做特定任務(wù)時(shí)。通常是先調(diào)用目標(biāo)模塊的scheduleXXX(),這時(shí)目標(biāo)模塊的Binder線程僅僅是向主線程發(fā)起一個(gè)異步請(qǐng)求。然后對(duì)方主線程在消息隊(duì)列中被喚醒處理,運(yùn)行處理函數(shù)handleXXX()。另外我們也注意到非常多函數(shù)都是帶有Locked后綴。這說(shuō)明出來(lái)混。一定要申明自己是不是線程安全的。
?
為了使系統(tǒng)中的策略更加靈活,easy替換,系統(tǒng)使用了一些設(shè)計(jì)模式將之從其他邏輯中解耦。
如IPolicy是一個(gè)工廠類接口。Policy為它的詳細(xì)實(shí)現(xiàn)類。
它負(fù)責(zé)創(chuàng)建一系列策略相關(guān)的對(duì)象,如makeNewWindow()創(chuàng)建PhoneWindow等。同一時(shí)候PolicyManager還使用了Strategy模式將Policy包裝起來(lái),這為策略的替換提供了便利,也使執(zhí)行時(shí)更換策略成為可能。
盡管眼下為止貌似僅僅有一種針對(duì)“Phone”的策略,所以還沒(méi)看到這樣設(shè)計(jì)的優(yōu)點(diǎn)。可是,一方面,或許將來(lái)在多合一的移動(dòng)設(shè)備上,筆記本,平板什么的能夠切換,那么Policy自然也須要?jiǎng)討B(tài)切換。
Android里還有非常多把這樣的Policy單獨(dú)拎出來(lái)的樣例,如WindowManagerPolicy類。還有一方面,Android作為一個(gè)框架,須要讓各個(gè)廠商把Android用到自己的平臺(tái)上更加easy適配。將來(lái)假設(shè)作為眼鏡,車載。智能家電等等嵌入式設(shè)備的統(tǒng)一平臺(tái)。假設(shè)將Policy與其他模塊緊耦合,那這些個(gè)平臺(tái)上的代碼就會(huì)差異越來(lái)越大。越來(lái)越難維護(hù)。
?
以下以類圖的方式詳細(xì)看下各模塊之間的通信關(guān)系:
圖中App和AMS的交互通過(guò)Binder。使用了代理模式。從App調(diào)用AMS是通過(guò)ActivityManagerProxy代理對(duì)象,它是本地對(duì)象ActivityManagerNative在App端的代理。實(shí)現(xiàn)了IActivityManager接口。提供了startActivity()這種AMS服務(wù)函數(shù)。而ActivityManagerNative的實(shí)現(xiàn)事實(shí)上就是AMS本身。
而從AMS調(diào)用App端用的是ApplicationThreadProxy代理對(duì)象。它實(shí)現(xiàn)了IApplicationThread接口,其相應(yīng)的實(shí)現(xiàn)是ApplicationThreadNative本地對(duì)象。存在于App端,ApplicationThread是事實(shí)上現(xiàn)類。AMS能夠通過(guò)它來(lái)向App發(fā)出如scheduleXXX這些個(gè)異步消息。
?
Activity在AMS中的相應(yīng)物是ActivityRecord,在WMS中相應(yīng)物為AppWindowToken。ActivityRecord::Token能夠看作ActivityRecord的一個(gè)遠(yuǎn)端句柄。在WMS和App中分別存于AppWindowToken和ActivityClientRecord之中。ActivityThread中的mActivities存放了遠(yuǎn)程ActivityRecord::Token到本地ActivityClientRecord的映射,因?yàn)檫@個(gè)Token是全局唯一的。所以還能夠用來(lái)作為HashMap的Key。ActivityRecord::Token實(shí)現(xiàn)了IApplicationToken。
當(dāng)WMS要通知AMS窗體變化時(shí),就是用的這個(gè)接口。
?
新啟動(dòng)的Activity的創(chuàng)建初始化主要是在handleLaunchActivity()中完畢的。handleLaunchActivity()的作用是載入指定的Activity并執(zhí)行。這當(dāng)中在App端主要是創(chuàng)建ActivityThread,Activity,PhoneWindow。DecorView等對(duì)象,并調(diào)用Activity生命周期中的onCreate()。onResume()函數(shù)執(zhí)行用戶邏輯。正常點(diǎn)的App里onCreate里會(huì)調(diào)用setContentView設(shè)置主視圖。
setContentView()里主要是調(diào)用了installDecor()。當(dāng)中會(huì)設(shè)置窗體的通用元素。如title, action bar之類,還會(huì)把xml文件inflate成布局對(duì)象。
能夠看到這時(shí)創(chuàng)建了DecorView。這便是Activity的主窗體的頂層視圖。DecorView創(chuàng)建好后。handleResumeActivity()中會(huì)將它加入到WMS中去。當(dāng)App向WMS加入窗體時(shí)。會(huì)調(diào)用WindowManagerImpl的addView()。注意WindowManagerImpl中的addView()函數(shù)和ViewGroup里的addView()函數(shù)全然不一樣,后者是將View加入到本地的View hierarchy中去,和WMS沒(méi)有關(guān)系。addView()的流程例如以下:
首先通過(guò)WindowManagerImpl的addView()會(huì)創(chuàng)建相應(yīng)的ViewRootImpl,然后ViewRootImpl申請(qǐng)WMS得到Session(假設(shè)是App第一個(gè)Activity會(huì)新建),其接口為IWindowSession,然后ViewRootImpl通過(guò)addToDisplay()把自己的ViewRootImpl::W(實(shí)現(xiàn)了IWindow接口)注冊(cè)給WMS。這樣,兩方都有了對(duì)方的接口。WMS中的Session注冊(cè)到WindowManagerGlobal的成員WindowSession中,ViewRootImpl:W注冊(cè)到WindowState中的成員mClient中。前者是為了App改變View結(jié)構(gòu)時(shí)請(qǐng)求WMS為其更新布局。后者代表了App端的一個(gè)加入到WMS中的View,每個(gè)像這樣通過(guò)WindowManager接口中addView()加入的窗體都有一個(gè)相應(yīng)的ViewRootImpl,也有一個(gè)相應(yīng)的ViewRootImpl::W。
它能夠理解為是ViewRootImpl中暴露給WMS的接口,這樣WMS能夠通過(guò)這個(gè)接口和App端通信。Session建立好后,接下來(lái)就是通過(guò)ViewRootImpl的setView將ViewRootImpl中的W注冊(cè)到WMS中,WMS會(huì)創(chuàng)建相應(yīng)數(shù)據(jù)結(jié)構(gòu),并將其插入內(nèi)部維護(hù)的窗體堆棧,還會(huì)與SF建立Session以備將來(lái)為之創(chuàng)建Surface。addView()運(yùn)行完后這個(gè)Activity的主視圖就正式對(duì)WMS可見(jiàn)了。總結(jié)來(lái)說(shuō)。addView()的工作主要包含創(chuàng)建ViewRootImpl,和遠(yuǎn)程WMS建立Session,并將當(dāng)前視圖注冊(cè)到WMS這幾步。
能夠看到App端通過(guò)WindowManagerGlobal調(diào)用addView(),調(diào)用鏈到WMS就變成addWindow(),概念發(fā)生了改變,這也印證上面提到的App端和WMS端的Window概念不一樣的說(shuō)法。
?
從以上Activity啟動(dòng)的整個(gè)流程能夠看到,窗體的加入和管理須要AMS和WMS兩個(gè)Service的配合。
以下看看AMS與WMS的主要作用和結(jié)構(gòu)。
?
AMS(ActivityManagerService)
Activity的管理者。
事實(shí)上除了Activity,AMS也管Service等組件信息。另外AMS還管理Process信息。以下是AMS幾個(gè)重要數(shù)據(jù)結(jié)構(gòu):
ActivityRecord:描寫(xiě)敘述單個(gè)Activity。Activity堆棧中的基本單元。
ActivityRecord::Token:相應(yīng)ActivityRecord的IBinder對(duì)象,能夠看作遠(yuǎn)程對(duì)象的本地句柄。可用于LPC,又可用來(lái)作映射中的unique ID。常常是兩用的。
ProcessRecord:描寫(xiě)敘述一個(gè)App進(jìn)程。包括了該進(jìn)程中的Activity和Service列表。
TaskRecord:TaskRecord中的mActivities是ActivityRecord的列表,它們是依照歷史順序排序的。
ActivityStack:Activity堆棧,當(dāng)中的ActivityRecord是通過(guò)TaskRecord這一層間接地被管理著。
ActivityStackSupervisor:ActivityStackSupervisor是ActivityStack的總管。
4.4中默認(rèn)引入了兩個(gè)ActivityStack。一個(gè)叫Home stack。放Launcher和systemui,id為0;還有一個(gè)是Applicationstack,放App的Activity,id可能是隨意值。定義例如以下:
137 /** The stack containing the launcher app*/ 138 private ActivityStack mHomeStack;145 /** All the non-launcher stacks */ 146 private ArrayList<ActivityStack> mStacks = new ArrayList<ActivityStack>();?系統(tǒng)中的Activity堆棧信息能夠通過(guò)dumpsys activity命令查看:
$ adb shell am stackboxes Box id=3 weight=0.0vertical=false bounds=[0,0][1280,736] Stack=Stack id=3 bounds=[0,0][1280,736]taskId=6:com.example.android.apis/com.example.android.apis.ApiDemostaskId=7:com.android.camera/com.android.camera.CameraBox id=0 weight=0.0vertical=false bounds=[0,0][1280,736] Stack=Stack id=0 bounds=[0,0][1280,736]taskId=3:com.android.launcher/com.android.launcher2.Launcher從dump信息能夠看出。這大致是一個(gè)層級(jí)結(jié)構(gòu)。從上到下依次是Stack->Task->Activity的結(jié)構(gòu)。Stack放在ActivityStackSupervisor中的mStacks。它是一個(gè)列表,元素類型為ActivityStack。因此,基本元素ActivityRecord是以層級(jí)的結(jié)構(gòu)被AMS管理起來(lái)的:
為什么引入了Task的概念呢?首先看下Task的官方定義“A task (from the activity that started it to the next task activity)defines an atomic group of activities that the user can move to.”。Task是為了完畢一個(gè)功能的一系列相關(guān)的有序Activity集合。能夠理解為用戶與App之間對(duì)于特定功能的一次會(huì)話。一個(gè)Task中的Activity能夠來(lái)自不同的App。比方在郵件App中須要看圖片附件,然后會(huì)開(kāi)imageview的Activity來(lái)顯示它。依據(jù)不用的業(yè)務(wù)邏輯。我們會(huì)在啟動(dòng)Activity的Intent中設(shè)不同的FLAG(FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_MULTIPLE_TASK等),這些FLAG的處理中會(huì)對(duì)Task的處理,進(jìn)而對(duì)Activity的調(diào)度產(chǎn)生影響。詳細(xì)的FLAG可參見(jiàn)/frameworks/base/core/java/android/content/Intent.java。關(guān)于Task的官方介紹http://developer.android.com/guide/components/tasks-and-back-stack.html。
?
總得來(lái)說(shuō)。上面這些個(gè)數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系例如以下
WMS(WindowManagerService)
窗體的管理者。
與AMS不同,一些高層的App中的概念,如進(jìn)程等,WMS是不care的。
由于WMS僅僅對(duì)窗體進(jìn)行管理。哪個(gè)進(jìn)程的它不關(guān)心。像Activity這些概念在WMS仍然有,由于Activity對(duì)窗體的管理會(huì)產(chǎn)生影響。
WMS主要責(zé)任是維護(hù)窗體堆棧。計(jì)算每一個(gè)窗體的layer信息交給SF,替App申請(qǐng)和調(diào)整畫(huà)圖Surface,當(dāng)窗體顯示狀態(tài)變化了還要通知其他模塊,另外還要處理系統(tǒng)輸入。所以說(shuō),WMS可能是與其他模塊交互最多的模塊之中的一個(gè)了。它與AMS,App。SF及Input等模塊都交集。說(shuō)到窗體管理,首先看一下Android中有哪些窗體。Android中大體有下面幾種窗體類型:1.應(yīng)用窗體,一般來(lái)說(shuō)就是Activity的主窗體。但也有些情況App沒(méi)有Activity,直接把自己定義的View加入到WMS中。比方浮動(dòng)窗體。2.子窗體,須要有一個(gè)父窗體,如Context Menu,Option Menu,Popup Window和Dialog等。
3.系統(tǒng)窗體。如狀態(tài)欄,鎖屏窗體,輸入法窗體,壁紙窗體和Toast之流。由系統(tǒng)創(chuàng)建的,不依賴于父窗體。
?
WMS中涉及到的主要數(shù)據(jù)結(jié)構(gòu)有這么幾個(gè):
WindowState:WMS中最主要的元素,描寫(xiě)敘述WMS中的一個(gè)窗體。它既能夠是由App加入過(guò)來(lái)的View,也能夠是系統(tǒng)創(chuàng)建的系統(tǒng)窗體。
mAttrs為WindowManager.LayoutParams類型,描寫(xiě)敘述布局參數(shù)。mClient為IWindow類型,也就是App端的ViewRootImpl::W。
為了查找方便,WMS中的mWindowMap保存了IWindow到WindowState的映射,mTokenMap保存了IApplicationToken到WindowToken的映射。
Session:向App提供IWindowSession接口讓其能夠和WMS通信。每個(gè)App在WMS有一個(gè)Session對(duì)象,App就是通過(guò)這個(gè)Session來(lái)向WMS發(fā)出窗體管理申請(qǐng)的。
命令dumpsys window sessions能夠查看系統(tǒng)中的Session:
WINDOW MANAGERSESSIONS (dumpsys window sessions)Session Session{b32d7d68 1404:u0a10008}:mNumWindow=1 mClientDead=falsemSurfaceSession=android.view.SurfaceSession@b31adc20Session Session{b32dd278 1326:u0a10007}:mNumWindow=5 mClientDead=falsemSurfaceSession=android.view.SurfaceSession@b327b348Session Session{b3290f68 1275:1000}:mNumWindow=1 mClientDead=falsemSurfaceSession=android.view.SurfaceSession@b30a3890SurfaceSession:WMS和SF之間的會(huì)話。
每一個(gè)App會(huì)在WMS中有一個(gè)相應(yīng)的SurfaceSession,也會(huì)有一個(gè)相應(yīng)的SurfaceComposerClient。
用于向SF申請(qǐng)和設(shè)置圖形緩沖區(qū)等。
WindowToken:?描寫(xiě)敘述WM中一組相關(guān)的窗體。這些Window相應(yīng)的WindowState放在其成員變量windows里。
其主要繼承類AppWindowToken,它是針對(duì)App的WindowToken結(jié)構(gòu)。WindowState中的mAppToken指向所屬的AppWindowToken,假設(shè)是系統(tǒng)窗體,mAppToken為空,mToken指向WindowToken對(duì)象。
命令dumpsys window tokens用于查看WindowToken和AppWindowToken信息:
WINDOW MANAGERTOKENS (dumpsys window tokens)All tokens:WindowToken{b32921e8 null}:windows=[Window{b333cd40 u0 SearchPanel},Window{b328d920 u0 Keyguard}, Window{b32961d8 u0 NavigationBar},Window{b32c6aa0 u0 StatusBar}, Window{b3292288 u0 KeyguardScrim}]windowType=-1 hidden=false hasVisible=trueWindowToken{b3260980android.os.Binder@b3269d60}:windows=[Window{b325c180 u0com.android.systemui.ImageWallpaper}]windowType=2013 hidden=falsehasVisible=trueAppWindowToken{b322a358 token=Token{b3287ea0ActivityRecord{b3287c28 u0 com.android.launcher/com.android.launcher2.Launchert1}}}:windows=[Window{b328b0c0 u0com.android.launcher/com.android.launcher2.Launcher}]windowType=2 hidden=false hasVisible=trueapp=trueallAppWindows=[Window{b328b0c0 u0com.android.launcher/com.android.launcher2.Launcher}]groupId=1 appFullscreen=truerequestedOrientation=-1hiddenRequested=false clientHidden=falsewillBeHidden=false reportedDrawn=true reportedVisible=truenumInterestingWindows=1 numDrawnWindows=1inPendingTransaction=false allDrawn=true (animator=true)startingData=null removed=falsefirstWindowDrawn=trueWindowToken{b32b81c0android.os.Binder@b3228950}:windows=[]windowType=2011 hidden=falsehasVisible=falseWallpaper tokens:Wallpaper #0 WindowToken{b3260980android.os.Binder@b3269d60}:windows=[Window{b325c180 u0com.android.systemui.ImageWallpaper}]windowType=2013 hidden=falsehasVisible=trueAppWindowToken:每一個(gè)App的Activity相應(yīng)一個(gè)AppWindowToken。當(dāng)中的appToken為IApplicationToken類型,連接著相應(yīng)的AMS中的ActivityRecord::Token對(duì)象,有了它就能夠順著AppWindowToken找到AMS中相應(yīng)的ActivityRecord。當(dāng)中allAppWindows是一個(gè)無(wú)序的列表。包括該Activity中全部的窗體。用dumpsys window display能夠查看z-ordered的AppWindowToken列表:
Application tokens in Z order:App #4 AppWindowToken{b31c2128token=Token{b3235c98 ActivityRecord{b324c8a0 u0com.example.android.apis/.view.PopupMenu1 t9}}}:windows=[Window{b32a5eb8 u0com.example.android.apis/com.example.android.apis.view.PopupMenu1}]windowType=2 hidden=false hasVisible=trueapp=trueallAppWindows=[Window{b32a5eb8 u0com.example.android.apis/com.example.android.apis.view.PopupMenu1},Window{b32eb6f0 u0 PopupWindow:b2ff5368}]groupId=9 appFullscreen=truerequestedOrientation=-1hiddenRequested=false clientHidden=falsewillBeHidden=false reportedDrawn=true reportedVisible=truenumInterestingWindows=2 numDrawnWindows=2inPendingTransaction=false allDrawn=true (animator=true)startingData=null removed=falsefirstWindowDrawn=trueApp #3 AppWindowToken{b3429e18token=Token{b31c5e58 ActivityRecord{b31e8ff0 u0com.example.android.apis/.ApiDemos t9}}}:windows=[Window{b32a39f8 u0com.example.android.apis/com.example.android.apis.ApiDemos}]windowType=2 hidden=true hasVisible=trueapp=trueallAppWindows=[Window{b32a39f8 u0com.example.android.apis/com.example.android.apis.ApiDemos}]groupId=9 appFullscreen=truerequestedOrientation=-1hiddenRequested=true clientHidden=truewillBeHidden=false reportedDrawn=false reportedVisible=falsenumInterestingWindows=1 numDrawnWindows=1inPendingTransaction=false allDrawn=true (animator=true)startingData=null removed=falsefirstWindowDrawn=trueApp #2 AppWindowToken{b32ccde0token=Token{b333d128 ActivityRecord{b32dcf10 u0com.example.android.apis/.ApiDemos t9}}}:windows=[Window{b320fd28 u0com.example.android.apis/com.example.android.apis.ApiDemos}]windowType=2 hidden=true hasVisible=trueapp=trueallAppWindows=[Window{b320fd28 u0com.example.android.apis/com.example.android.apis.ApiDemos}]groupId=9 appFullscreen=truerequestedOrientation=-1hiddenRequested=true clientHidden=truewillBeHidden=false reportedDrawn=false reportedVisible=falsenumInterestingWindows=1 numDrawnWindows=1inPendingTransaction=false allDrawn=true (animator=true)startingData=null removed=falsefirstWindowDrawn=trueApp #1 AppWindowToken{b321ff58token=Token{b321d860 ActivityRecord{b321d990 u0com.android.launcher/com.android.launcher2.Launcher t1}}}:windows=[Window{b3268018 u0com.android.launcher/com.android.launcher2.Launcher}]windowType=2 hidden=true hasVisible=trueapp=trueallAppWindows=[Window{b3268018 u0com.android.launcher/com.android.launcher2.Launcher}]groupId=1 appFullscreen=truerequestedOrientation=-1hiddenRequested=true clientHidden=truewillBeHidden=false reportedDrawn=false reportedVisible=falsenumInterestingWindows=1 numDrawnWindows=1inPendingTransaction=false allDrawn=true (animator=true)startingData=null removed=falsefirstWindowDrawn=true注意AppWindowToken是相應(yīng)Activity的,WindowState是相應(yīng)窗體的。所以AppWindowToken和WindowState是1:n的關(guān)系。舉例來(lái)說(shuō)。上面第一項(xiàng)AppWindowToken。它包括了PopupWindow子窗體,所以有相應(yīng)兩個(gè)WindowState。一般地說(shuō)。一個(gè)Activity可能包括多個(gè)窗體。如啟動(dòng)窗體,PopupWindow等,這些窗體在WMS就會(huì)組織在一個(gè)AppWindowToken中。AppWindowToken及WindowState間的從屬結(jié)構(gòu)及WindowState間的父子結(jié)構(gòu)能夠通過(guò)下面成員表示。
Task:上面提到AppWindowToken保存了屬于它的WindowState的有序列表,而它本身也作為一個(gè)列表被管理在TaskStack中的mTasks成員中,而且是按歷史順序存放的,最老的Task在最底下。
結(jié)合前面的AppWindowToken和WindowState之間的關(guān)系,能夠了解到它們是以這樣一個(gè)層級(jí)的關(guān)系組織起來(lái)的:
這個(gè)結(jié)構(gòu)是不是非常眼熟。AMS里也有類似的結(jié)構(gòu)。WMS里的TaskStack,相應(yīng)前面AMS中的ActivityStack,這兩者及其子結(jié)構(gòu)會(huì)保持同步。從中我們能夠發(fā)現(xiàn)。WMS和AMS中的數(shù)據(jù)結(jié)構(gòu)是有相應(yīng)關(guān)系的。如AMS中的TaskRecord和WMS中的Task,AMS中的ActivityRecord和WMS中的AppWindowToken。另外WMS中TaskStack的mTasks須要和AMS中ActivityStack的mTaskHistory順序保持一致。
DisplayContent:表示一個(gè)顯示設(shè)備上的內(nèi)容,這個(gè)顯示設(shè)備能夠是外接顯示屏,也能夠是虛擬顯示屏。當(dāng)中mWindows是一個(gè)WindowState的有序(Z-ordered,底部最先)列表。
mStackBoxes包括了若干個(gè)StackBox,當(dāng)中一個(gè)為HomeStack。還有一個(gè)是App的StackBox。全部的StackBox被組織成二叉樹(shù),StackBox是當(dāng)中的節(jié)點(diǎn)。當(dāng)中有三個(gè)重要成員變量,mFirst和mSecond指向左和右子結(jié)點(diǎn)(也是StackBox),StackBox的成員mStack才是我們真正關(guān)心的東西-TaskStack。能夠看到,為了要把TaskStack存成樹(shù)的結(jié)構(gòu)。須要一個(gè)容器,這個(gè)容器就是StackBox。DisplayContent,StackBox和TaskStack的關(guān)系例如以下:
StackBox信息能夠用am stack boxes或dumpsys window displays命令查看:
$ adb shell am stackboxes WARNING: linker:libdvm.so has text relocations. This is wasting memory and is a security risk.Please fix. Box id=2 weight=0.0vertical=false bounds=[0,33][800,1216] Stack=Stack id=2 bounds=[0,33][800,1216]taskId=3:com.android.contacts/com.android.contacts.activities.PeopleActivityBox id=0 weight=0.0vertical=false bounds=[0,33][800,1216] Stack=Stack id=0 bounds=[0,33][800,1216]taskId=1:com.android.launcher/com.android.launcher2.LaunchermStackId=2{taskId=3appTokens=[AppWindowToken{b3332498 token=Token{b33006c0 ActivityRecord{b32ecbb0u0 com.android.contacts/.activities.PeopleActivity t3}}}]}上面是正常情況下的,用am stack create命令能夠創(chuàng)建分屏窗體(詳見(jiàn)http://androidinternalsblog.blogspot.com/2014/03/split-screens-in-android-exist.html),如:
$ adb shell am stackcreate 7 3 0 0.5 createStack returnednew stackId=4然后再查看stack信息就變成了這樣:
$ adb shell am stackboxes Box id=3 weight=0.5vertical=false bounds=[0,0][1280,736] First child=Box id=4 weight=0.0 vertical=falsebounds=[0,0][640,736]Stack=Stack id=4 bounds=[0,0][640,736]taskId=7:com.android.camera/com.android.camera.Camera Second child=Box id=5 weight=0.0 vertical=falsebounds=[640,0][1280,736]Stack=Stack id=3 bounds=[640,0][1280,736]taskId=6:com.example.android.apis/com.example.android.apis.ApiDemosBox id=0 weight=0.0vertical=false bounds=[0,0][1280,736] Stack=Stack id=0 bounds=[0,0][1280,736]taskId=3:com.android.launcher/com.android.launcher2.Launcher可見(jiàn),在分屏情況下。這個(gè)結(jié)構(gòu)以二叉樹(shù)的形式分裂,形成這種結(jié)構(gòu):
除了TaskStack。DisplayContent中的成員mTaskHistory也包括了一個(gè)有序的Task列表。
結(jié)合上面幾個(gè)概念,能夠得到以下的關(guān)系:
邏輯上,一個(gè)Task(Task)能夠包括多個(gè)Activity(相應(yīng)AppWindowToken),每一個(gè)Activity能夠包括多個(gè)窗體(相應(yīng)WindowState),而每一個(gè)窗體都是可能被放在隨意一個(gè)顯示屏上的(想象一個(gè)Windows操作系統(tǒng)中外接顯示器的情況)。因此就有了上面這個(gè)結(jié)構(gòu)。從這個(gè)結(jié)構(gòu)能夠看出。WMS的主要任務(wù)之中的一個(gè)就是維護(hù)各窗體的Z-order信息。Z軸可看作是屏幕法向量方向上的坐標(biāo)軸,值越大的層意味著離用戶越近,會(huì)把值小的窗體給蓋住。一方面。WMS須要知道各窗體的遮擋關(guān)系來(lái)做layout和分配釋放Surface,還有一方面,這個(gè)Z-order信息會(huì)轉(zhuǎn)化為窗體相應(yīng)Surface的layer屬性輸出到SF,指導(dǎo)SF的渲染。
那么,這個(gè)列表是怎么管理的呢?我們知道,每一個(gè)窗體在WMS都有相應(yīng)的WindowState,因此。本質(zhì)上我們須要維護(hù)一個(gè)Z-order排序的WindowState列表。首先,TaskStack中包括了歷史序的Task。每一個(gè)Task又包括了Z-ordered的AppWindowToken,AppWindowToken的成員windows又包括了一個(gè)Z-ordered的WindowState列表。前面提到過(guò),一個(gè)AppWindowToken相應(yīng)AMS中的一個(gè)ActivityRecord,因此這個(gè)列表包括了這個(gè)Activity中的全部窗體,子窗體,開(kāi)始窗體等。還有一方面。DisplayContent中也有一個(gè)成員mWindows,指向一個(gè)Z-ordered的WindowState列表(列隊(duì)越前面的在越底部),它描寫(xiě)敘述的是單個(gè)顯示屏上的窗體集合。在加入窗體時(shí)會(huì)通過(guò)addAppWindowToListLocked()函數(shù)往這個(gè)窗體堆棧插入元素。
因?yàn)椴迦脒^(guò)程要考慮子窗體,開(kāi)始窗體等的偏移量,往DisplayContent的mWindows插入元素時(shí)需考慮WindowToken中的windows列表。
得到DisplayContent中的mWindows列表后,之后會(huì)調(diào)用assignLayersLocked()來(lái)依據(jù)這個(gè)Z-order列表信息得到每一個(gè)窗體的layer值。WindowState的列表能夠用dumpsys window windows命令查看:
WINDOW MANAGERWINDOWS (dumpsys window windows)Window #7 Window{b32b2110 u0 SearchPanel}:mDisplayId=0 mSession=Session{b32369b81326:u0a10007} mClient=android.os.BinderProxy@b3222788mOwnerUid=10007 mShowToOwnerOnly=falsepackage=com.android.systemui appop=NONEmAttrs=WM.LayoutParams{(0,0)(fillxfill)gr=#800053 sim=#31 ty=2024 fl=#1820100 fmt=-3 wanim=0x10301f5}Requested w=800 h=1216 mLayoutSeq=37mHasSurface=falsemShownFrame=[0.0,0.0][0.0,0.0] isReadyForDisplay()=falseWindowStateAnimator{b32f06d8 SearchPanel}:mShownAlpha=0.0 mAlpha=1.0 mLastAlpha=0.0 ...Window #2 Window{b3282e18 u0com.example.android.apis/com.example.android.apis.ApiDemos}:mDisplayId=0 mSession=Session{b32cfba03137:u0a10045} mClient=android.os.BinderProxy@b31faaf8mOwnerUid=10045 mShowToOwnerOnly=truepackage=com.example.android.apis appop=NONEmAttrs=WM.LayoutParams{(0,0)(fillxfill)sim=#110 ty=1 fl=#1810100 pfl=0x8 wanim=0x10302f5}Requested w=800 h=1216 mLayoutSeq=82mHasSurface=truemShownFrame=[0.0,0.0][800.0,1216.0] isReadyForDisplay()=trueWindowStateAnimator{b32df438com.example.android.apis/com.example.android.apis.ApiDemos}:Surface: shown=true layer=21010 alpha=1.0rect=(0.0,0.0) 800.0 x 1216.0 ...總結(jié)一下,WMS服務(wù)端的類結(jié)構(gòu)圖:
設(shè)計(jì)中比較靈活的一個(gè)地方是當(dāng)中的WindowManagerPolicy這個(gè)類。它採(cǎi)用了Strategy模式。將策略相關(guān)的部分抽象出來(lái),用PhoneWindowManager實(shí)現(xiàn)。它也是前面提到的Policy工廠模式的一部分。
盡管如今PhoneWindowManager是唯一的繼承類。這樣的結(jié)構(gòu)看似可有可無(wú),但假設(shè)以后廠商要加其他的策略,或更改已有策略,這樣的設(shè)計(jì)就行提供良好的靈活性。
?
以上就是AMS和WMS的大體框架。粗糙地說(shuō),AMS管理Activity,Service和Process等信息。WMS管理應(yīng)用和系統(tǒng)窗體。這兩者既有聯(lián)系又有非常大不同。Activity一般有窗體。Service能夠有也能夠沒(méi)有窗體,而窗體不一定非要相應(yīng)Activity或者Service。僅僅是非常多時(shí)候一個(gè)Activity中就一個(gè)頂層視圖。相應(yīng)WMS一個(gè)窗體,所以會(huì)給人一一相應(yīng)的錯(cuò)覺(jué),但這兩者事實(shí)上沒(méi)有直接關(guān)系。這就造就了AMS,WMS兩大模塊,盡管當(dāng)中數(shù)據(jù)結(jié)構(gòu)有非常多是保持同步的,可是設(shè)計(jì)上讓它們分開(kāi),各司其職。異步工作。
從窗體管理的流程來(lái)說(shuō),App負(fù)責(zé)視圖樹(shù)的管理和業(yè)務(wù)邏輯。AMS管理和調(diào)度全部App中的組件,通知WMS組件的狀態(tài)信息。
WMS幫App到SF申請(qǐng)和調(diào)整Surface,同一時(shí)候計(jì)算維護(hù)窗體的布局,z-order等信息,另外當(dāng)顯示狀態(tài)發(fā)生變化時(shí)。WMS還要通知App作出調(diào)整。SF從WMS拿到各窗體相應(yīng)Surface的屬性和layer信息,同一時(shí)候從App拿到渲染好的圖形緩沖區(qū)。進(jìn)行進(jìn)一步的合并渲染。放入framebuffer,最后用戶就能在屏幕上看到App的窗體了。
版權(quán)聲明:本文博主原創(chuàng)文章。博客,未經(jīng)同意不得轉(zhuǎn)載。
轉(zhuǎn)載于:https://www.cnblogs.com/bhlsheji/p/4838225.html
總結(jié)
以上是生活随笔為你收集整理的Android 4.4(KitKat)表格管理子系统 - 骨架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Maximal Square 我们都在寻
- 下一篇: Swift - 使用set,get确保索