當前位置:首頁 » 安卓系統 » android文章

android文章

發布時間: 2022-10-29 17:40:27

❶ Android 之 Project Butter 詳細介紹

UI 優化系列專題,來聊一聊 Android 渲染相關知識,主要涉及 UI 渲染背景知識 如何優化 UI 渲染 兩部分內容。

《 View 繪制流程之 setContentView() 到底做了什麼? 》
《 View 繪制流程之 DecorView 添加至窗口的過程 》
《 深入 Activity 三部曲(3)View 繪制流程 》
《 Android 之 LayoutInflater 全面解析 》
《 關於渲染,你需要了解什麼? 》
《 Android 之 Choreographer 詳細分析 》

《 Android 之如何優化 UI 渲染(上) 》
《 Android 之如何優化 UI 渲染(下) 》

現在我們已經很少能夠聽到關於 Android UI 卡頓的話題了,這得益於 Google 長期以來對 Android 渲染性能的重視,基本每次 Google I/O 都會花很多篇幅講這一塊。隨著時間的推移,Android 系統一直在不斷進化、壯大,並且日趨完善。

其中,Google 在 2012 年的 I/O 大會上宣布了 Project Butter 黃油計劃,那個曾經嚴重影響 Android 口碑的 UI 流程性問題,首先在這得到有效的控制,並且在 Android 4.1 中正式開啟了這個機制。

Project Butter 對 Android Display 系統進行了重構,引入了三個核心元素,即 VSYNC Triple Buffer Choreographer

其中 VSYNC 是理解 Project Butter 的核心。接下來,我們就圍繞 VSYNC 開始介紹 Project Butter 對 Android Display 系統做了哪些優化。

VSYNC 最初是由 GPU 廠商開發的一種,用於防止屏幕撕裂的技術方案,全稱 Vertical Synchronization,該方案很早就已經被廣泛應用於 PC 上。我們可以把它理解為一種時鍾中斷。

VSYNC 是一種圖形技術,它可以同步 GPU 的 幀速率 和顯示器的 刷新頻率 ,所以在理解 VSYNC 產生的原因及其作用之前,我們有必要先來了解下這兩個概念。

表示屏幕在一秒內刷新畫面的次數, 刷新頻率取決於硬體的固定參數,單位 Hz(赫茲)。例如常見的 60 Hz、144 Hz,即每秒鍾刷新 60 次或 144 次。

逐行掃描

顯示器並不是一次性將畫面顯示到屏幕上,而是從左到右邊,從上到下逐行掃描顯示,不過這一過程快到人眼無法察覺到變化。以 60 Hz 刷新率的屏幕為例,即 1000 / 60 ≈ 16ms。

表示 GPU 在一秒內繪制操作的幀數,單位 fps。例如在電影界採用 24 幀的速度足夠使畫面運行的非常流暢。而 Android 系統則採用更加流程的 60 fps,即每秒鍾繪制 60 幀畫面。更多內容參考《 Why 60 fps 》。

現在,刷新頻率和幀率需要一起合作,才能使圖形內容呈現在屏幕上,GPU 會獲取圖形數據進行繪制, 然後硬體負責把圖像內容呈現到屏幕上,這一過程在應用程序的生命周期內一遍又一遍的發生。

如上圖,CPU / GPU 生成圖像的 Buffer 數據,屏幕從 Buffer 中讀取數據刷新後顯示。理想情況下幀率和刷新頻率保持一致,即每繪制完成一幀,顯示器顯示一幀。不幸的是,刷新頻率和幀率並不總是能夠保持相對同步,如果幀速率實際比刷新率快,例如幀速率是 120 fps,顯示器的刷新頻率為 60 Hz。此時將會發生一些視覺上的問題。

當 GPU 利用一塊內存區域寫入一幀數據時,從頂部開始新一幀覆蓋前一幀,並立刻輸出一行內容。當屏幕刷新時,此時它並不知道圖像緩沖區的狀態,因此從緩沖區抓取的幀並不是完整的一幀畫面(繪制和屏幕讀取使用同一個緩沖區)。此時屏幕顯示的圖像會出現上半部分和下半部分明顯偏差的現象,這種情況被稱之為 「tearing」(屏幕撕裂)。

那如何防止 「tearing」 現象的發生呢?由於圖像繪制和讀取使用的是同一個緩沖區,所以屏幕刷新時可能讀取到的是不完整的一幀畫面。解決方案是採用 Double Buffer。

Double Buffer(雙緩沖)背後的思想是讓繪制和顯示器擁有各自的圖像緩沖區。GPU 始終將完成的一幀圖像數據寫入到 Back Buffer ,而顯示器使用 Frame Buffer ,當屏幕刷新時,Frame Buffer 並不會發生變化,Back Buffer 根據屏幕的刷新將圖形數據 到 Frame Buffer,這便是 VSYNC 的用武之地。

在 Android 4.1 之前,Android 便使用的雙緩沖機制。怎麼理解呢?一般來說,在同一個 View Hierarchy 內的不同 View 共用一個 Window,也就是共用同一個 Surface。

每個 Surface 都會有一個 BufferQueue 緩存隊列,但是這個隊列會由 SurfaceFlinger 管理,通過匿名共享內存機制與 App 應用層交互。

整個流程如下:

但是 UI 繪制任務可能會因為 CPU 在忙別的事情,導致沒來得及處理。所以 從 Android 4.1 開始, VSYNC 則更進一步,現在 VSYNC 脈沖信號開始用於下一幀的所有處理

Project Butter 首先對 Android Display 系統的 SurfaceFlinger 進行了改造,目標是提供 VSYNC 中斷。每收到 VSYNC 中斷後,CPU 會立即准備 Buffer 數據,由於大部分顯示設備刷新頻率都是 60 Hz(一秒刷新 60 次),也就是說一幀數據的准備工作都要在 16ms 內完成。

這樣應用總是在 VSYNC 邊界上開始繪制,而 SurfaceFlinger 總是在 VSYNC 邊界上進行合成。這樣可以消除卡頓,並提升圖形的視覺表現。

如果理解了雙緩沖機制的原理,那就非常容易理解什麼是三緩沖區了。如果只有兩個 Graphic Buffer 緩沖區 A 和 B,如果 CPU / GPU 繪制過程較長,超過一個 VSYNC 信號周期。

由上圖可知:

為什麼 CPU 不能在第二個 16ms 處理繪制工作呢?原因是只有兩個 Buffer,緩沖區 B 中的數據還沒有準備完成,所以只能繼續展示 A 緩沖區的內容,這樣緩沖區 A 和 B 都分別被顯示設備和 GPU 佔用,CPU 則無法准備下一幀的數據。如果再提供一個緩沖區,CPU、GPU 和顯示設備都能使用各自的緩沖區工作,互不影響。

簡單來說,三重緩沖機制就是在雙緩沖機制基礎上增加了一個 Graphic Buffer 緩沖區,這樣可以最大限度的利用空閑時間,帶來的壞處是多使用的一個 Graphic Buffer 所佔用的內存。

由上圖可知:

Choreographer 也是 Project Butter 計劃新增的機制,用於配合系統的 VSYNC 中斷信號。它本質是一個 java 類,如果直譯的話為舞蹈指導,這是一個極富詩意的表達,看到這個詞不得不贊嘆設計者除了 Coding 之外的廣泛視野。舞蹈是有節奏的,節奏使舞蹈的每個動作更加協調和連貫;視圖刷新也是如此。

Choreographer 可以接收系統的 VSYNC 信號,統一管理應用的輸入、動畫和繪制等任務的執行時機。Android 的 UI 繪制任務將在它的統一指揮下,井然有序的完成。業界一般通過它來監控應用的幀率。

Choreographer 的構造方法:

優先順序的高低和處理順序有關。當收到 VSYNC 信號時,Choreographer 將首先處理 INPUT 類型的回調,然後 ANIMATION 類型,最後才是 TRAVERSAL 類型。

另外,Android 在 4.1 還對 Handler 機制進行了略微改造,使之支持 Asynchronous Message(非同步消息) 和 Synchronization Barrier(同步屏障)。一般情況下同步消息和非同步消息的處理方式並沒有什麼區別,只有在設置了 同步屏障 時才會出現差異。 同步屏障為 Handler 消息機制增加了一種簡單的優先順序關系,非同步消息的優先順序要高於同步消息 。簡單點說,設置了同步屏障之後,Handler 只會處理非同步消息。

以 View 的繪制流程為例:

scheleTraversals 首先禁止了後續消息的處理能力,一旦設置了消息隊列的 postSyncBarrier,所有非 Asynchronous 的消息將被停止派發。

UI 繪制任務設置了 CALLBACK 類型為 TRAVERSAL 類型的任務,即 mTraversalRunnable:

Choreographer 的 postCallback 方法將會申請一次 VSYNC 中斷信號,通過 DisplayEventReceiver 的 scheleVsync 方法。當 VSYNC 信號到達時,便會回調 Choreographer 的 doFrame 方法,內部會觸發已經添加的回調任務:

此時 UI 繪制任務 doTraversal 方法被回調,即在 Android 4.1 之後, UI 繪制任務被放置到了 VSYNC 中斷處理中了。Choreographer 確實做到了統一協調管理 UI 的繪制工作。有關 Choreographer 更詳細的分析,可以參考《 Android 之 Choreographer 詳細分析 》。

在從根本解決 Android UI 不流暢的問題上,Project Butter 黃油計劃率先邁出了最重要一步,Android 的渲染性能也確實有了很大改善。

不過優化是無止境的,Google 在後續版本中又引入了一些比較大的改變,例如 Android 5.0 的 RenderThread,Android 將所有的繪制任務都放到了該線程,這樣即便主線程有耗時的操作也可以保證動畫流暢性。

關於 UI 渲染所涉及的內容非常多,而且 Android 渲染框架演進的非常快,文章最後也會附上一些(1)android文章擴展閱讀,便於更好的學習理解。

文中如有不妥或有更好的分析結果,歡迎您的留言或指正。文章如果對你有幫助,請留個贊吧。

其他系列專題

❷ Android invalidate/postInvalidate/requestLayout-徹底釐清

系列文章:
Android Activity創建到View的顯示過程
Android Activity 與View 的互動思考
Android invalidate/postInvalidate/requestLayout-徹底釐清
Android 容易遺漏的刷新小細節

前幾篇分析了Measure、Layout、Draw 過程,這三個過程在第一次展示View的時候都會調用。那之後更改了View的屬性呢?比如更改顏色、更換文字內容、更換圖片等,還會走這三個過程嗎?循著這個思路,來分析Invalidate/RequestLayout流程。
通過本篇文章,你將了解到:

MyView 默認展示一塊紅色的矩形區域,暴露給外界的方法:setColor
用以改變繪制的顏色。顏色改變後,需要重新執行onDraw(xx)才能看到改變後的效果,通過invalidate()方法觸發onDraw(xx)調用。
接下來看看invalidate()方法是怎麼觸發onDraw(xx)方法執行的。

invalidate顧名思義:使某個東西無效。在這里表示使當前繪制內容無效,需要重新繪制。當然,一般來說常常簡單稱作:刷新。
invalidate()是View.java 里的方法。

從上可知,當前要刷新的View確定了刷新區域後即調用了父布局的invalidateChild(xx)方法。該方法為ViewGroup里的final方法。

由上可知,在該方法里區分了硬體加速繪制與軟體繪制,分別來看看兩者區別:

硬體加速繪制分支
如果該Window支持硬體加速,則走下邊流程:

onDescendantInvalidated 方法的目的是不斷向上尋找其父布局,並將父布局PFLAG_DRAWING_CACHE_VALID 標記清空,也就是繪制緩存清空。
而我們知道,根View的mParent指向ViewRootImpl對象,因此來看看它裡面的onDescendantInvalidated()方法:

做個小結:

用圖表示硬體加速繪制的invaldiate流程:

軟體繪制分支
如果該Window不支持硬體加速,那麼走軟體繪制分支:
parent.invalidateChildInParent(location, dirty) 返回mParent,只要mParent不為空那麼一直調用invalidateChildInParent(xx),實際上這也是遍歷ViewTree過程,來看看關鍵invalidateChildInParent(xx):

與硬體加速繪制一致,最終調用ViewRootImpl invalidateChildInParent(xx),來看看實現:

做個小結:

用圖表示軟體繪制invalidate流程:

上述分析了硬體加速繪制與軟體繪制時invalidate的不同,它們的最終目的都是為了重走Draw過程。重走Draw過程通過調用scheleTraversals() 觸發的,來看看是如何觸發的。

想了解更多硬體加速繪制請移步:
Android 自定義View之Draw過程(中)

觸發Draw過程
scheleTraversals 詳細分析在這篇文章:
Android Activity創建到View的顯示過程

三大流程真正開啟在ViewRootImpl->performTraversals(),在該方法里根據一定的條件執行了Measure(測量)、Layout(擺放)、Draw(繪制)。
本次著重分析如何觸發Draw過程。

可以看出,invalidate 最終觸發了Draw過程。

可以看出,啟用硬體加速繪制可以避免不必要的繪制。
關於硬體加速繪制與軟體繪制詳細區別,請移步系列文章:
Android 自定義View之Draw過程(上)

最後,用圖表示invalidate流程:

顧名思義,重新請求布局。
來看看View.requestLayout()方法:

可以看出,這個遞歸調用和invalidate一樣的套路,向上尋找其父布局,一直到ViewRootImpl為止,給每個布局設置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED標記。
查看ViewRootImpl requestLayout()

很明顯,requestLayout目的很單純:

和invalidate一樣的配方,當刷新信號來到之時,調用doTraversal()->performTraversals(),而在performTraversals()里真正執行三大流程。

由此可見:

之前設置的PFLAG_FORCE_LAYOUT標記有啥用呢?
回憶一下measure 過程:

PFLAG_FORCE_LAYOUT 標記打上之後,會觸發onMeasure()測量自身及其子布局。

試想一下,假設View的尺寸改變了,變大了,那麼調用了requestLayout後因為走了Measure、Layout 過程,測量、擺放倒是重新設置了,但是不調用Draw出不來效果啊。實際上,View layout時候已經考慮到了。
在View.layout(xx)->setFrame(xx)里

也就是說:

關於measure、layout 過程更深入的分析,請移步:

用圖表示requestLayout過程:

結合requestLayout和invalidate與View三大流程關系,有如下圖:

總結一下:

上面僅僅說明了單個布局Invalidate/RequestLayout聯系,那麼如果父布局調用了invalidate,那麼子布局會走重繪過程嗎?接下來列舉這些關系。

子布局Invalidate
如果是軟體繪制或者父布局開啟了軟體緩存繪制,父布局會走重繪過程(前提是WILL_NOT_DRAW標記沒設置)。

子布局RequestLayout
父布局會重走Measure、Layout過程。

父布局Invalidate
如果是軟體繪制,則子布局會走重繪過程。

父布局RequestLayout
如果父布局尺寸發生了改變,則會觸發子布局Measure過程、Layout過程。

在Activity onCreate里創建子線程並展示對話框:

答案是可以的,接下來分析為什麼可以。

在分析ViewRootImpl里requestLayout/invalidate過程中,發現其內部調用了checkThread()方法:

問題的關鍵是mThread是什麼?從哪裡來?

而創建ViewRootImpl對象是在調用WindowManager.addView(xx)過程中創建的。
關於WindowManager/Window 請移步: Window/WindowManager 不可不知之事

現在回過頭來看Dialog創建就比較明朗了:

實際上,"子線程不能更新ui" 更合理的表述應為:View只能被構建了ViewTree的線程操作。只是通常來說,Activity 構建ViewTree的線程被稱作UI(主)線程,因此才會有上述說法。

既然invalidate()只能主線程調用(硬體加速條件下,不調用checkThread()),那如果想在子線程調用呢?當然想到的是先通過Handler切換到主線程,再執行invalidate(),但是每次這么寫有點冗餘,幸好,View里提供了postInvalidate:

切到ViewRootImpl.java

發現了真相:

本文基於Android 10.0

❸ Android:一篇文章帶你完全梳理自定義View工作流程!

了解自定義View流程前,需了解一定的自定義View基礎,具體請看文章: (1)自定義View基礎 - 最易懂的自定義View原理系列

下面,我將詳細講解 View 繪制的三大流程: measure 過程、 layout 過程、 draw 過程

請看文章: 自定義View Layout過程 - 最易懂的自定義View原理系列(3)

至此,關於自定義 View 的工作流程講解完畢。

結合原理 & 實現步驟,若需實現1個自定義View,請看文章: 手把手教你寫一個完整的自定義View

❹ 《深入理解Android卷I》epub下載在線閱讀,求百度網盤雲資源

《深入理解Android:Wi-Fi、NFC和GPS卷》(鄧凡平)電子書網盤下載免費在線閱讀

資源鏈接:

鏈接: https://pan..com/s/1Ys1OZJ-Gt-NHzDbbr1P8WA

提取碼: edhg

書名:深入理解Android:Wi-Fi、NFC和GPS卷

作者:鄧凡平

豆瓣評分:8.7

出版社:機械工業出版社

出版年份:2014-4-15

頁數:575

內容簡介:

本書是經典暢銷書「深入理解Android」系列的新作,由資深Android系統專家鄧凡平先生撰寫,全志和高通等公司資深專家擔任技術審校並強烈推薦。從通信專業知識和Android系統代碼實現的角度,對Netd、Wi-Fi、NFC和GPS等模塊的代碼進行深入的剖析,旨在深刻揭示其實現原理和工作流程。其中涉及大量通信相關的專業知識,因此特意邀請全志和高通等著名晶元公司的資深專家擔任技術審校。本書從實際應用的需求出發,適合所有Android系統工程師、Android應用開發工程師和BSP開發工程師閱讀。

全書共9章。第1章介紹本書的內容組成、工具使用以及參考源碼的下載方法。第2章介紹Netd及相關的背景知識。第3~5章介紹Wi-Fi基礎知識,重點分析了wpa_supplicant的實現,以及Android平台中特有的Wi-Fi服務模塊WifiService。第6~7章講解了Wi-Fi聯盟推出的兩項重要技術Wi-Fi Simple Configuration和Wi-Fi P2P,以及它們在Android平台中的代碼實現。第8章詳細介紹了NFC基礎知識,以及NFC在Android平台中的代碼實現。第9章講解了GPS原理及Android平台中的位置管理服務架構。

本書主要內容及特色:

本書所講解的Wi-Fi、NFC以及GPS模塊的背後都涉及非常多的專業知識,例如與Wi-Fi相關的802.11協議、Wi-Fi Alliance(Wi-Fi聯盟)定義的Wi-Fi Simple Configuration和Wi-Fi P2P協議、NFC Forum定義的一整套與NFC相關的協議、與GPS相關的衛星導航原理、AGPS和OMA-SUPL協議等。顯然,如果不了解這些專業知識,就不可能真正掌握它們在Android平台中的代碼實現。

考慮到這些專業知識的重要性,本書在講解Android平台中Wi-Fi、NFC和GPS模塊的實現之前,先重點介紹與代碼相關的專業知識。當然,這些專業知識內容如此豐富,在一本書中無法全部涵蓋。為了方便讀者進一步深入學習,本書每章的最後都會列舉筆者在撰寫各章時所閱讀的參考資料。

以下是本書的內容概述。

第1章介紹本書的內容組成、使用的工具以及參考源碼的下載方法。

第2章介紹Netd以及相關的背景知識。

第3章介紹Wi-Fi基礎知識。Wi-Fi是本章的重點,而且也是當下最熱門的技術。

第4章介紹wpa_supplicant,它是Wi-Fi領域中最核心的軟體實現。

第5章介紹WifiService,它是Android平台中特有的Wi-Fi服務模塊。

第6章和第7章介紹Wi-Fi Alliance推出的兩項重要技術——Wi-Fi Simple Configuration和Wi-Fi P2P,以及它們在Android平台中的代碼實現。

第8章介紹NFC背景知識以及NFC在Android平台中的代碼實現。NFC也是歷史比較悠久的技術,希望它能隨著Android的普及而走向大眾。

第9章介紹GPS原理及Android平台中的位置管理服務架構。

附錄為筆者和審稿專家之一的吳勁良先生關於本書定位、學習方法等方面的討論。相信這些討論內容能引起讀者的共鳴。

本書通過理論和代碼相結合的方式進行講解,旨在引領讀者一步步了解Wi-Fi、NFC和GPS模塊的工作原理。總之,筆者希望讀者在閱讀完本書後能有以下收獲。

初步掌握Wi-Fi、NFC和GPS的專業知識。

根據其實現代碼,進一步加深對這些專業知識的理解。

讀者對象:

適合閱讀本書的讀者包括:

Android系統開發工程師

系統開發工程師常常需要深入理解系統的運轉過程,而本書所涉及的內容正是他們在工作和學習中最想了解的。對具體模塊感興趣的讀者也可單刀直入,閱讀相關章節。

Wi-Fi、NFC或GPS的BSP開發工程師

BSP開發工程師更需要對Android平台中這些模塊的工作原理及背景知識有深入的理解。雖然本書沒有介紹這些模塊在linux Kernel層的實現,但了解它們在用戶空間的工作流程也將極大幫助BSP開發工程師拓展自己的知識面。

對Wi-Fi、NFC和GPS感興趣的在校高年級本科生、研究生和其他讀者

在掌握理論的基礎上,如何在實際代碼中來實現或使用它們也許是眾多學子最想知道的。希望這本理論與代碼實現深度結合的書籍會助您一臂之力。

作者簡介:

鄧凡平 資深Android系統工程師,對Android系統的設計與實現有非常深入的研究,曾擔任Tieto公司高級軟體架構師。暢銷書「深入理解Android」系列的總策劃和主筆,出版有暢銷書《深入理解Android:卷I》和《深入理解Android:卷II》。喜歡鑽研,樂於分享,活躍於CSDN、51CTO和開源中國等專業技術社區,撰寫的Android Framework源碼分析的系列文章深受讀者歡迎。2013年榮獲51CTO讀書頻道評選的「最受讀者喜愛的IT圖書作者獎」。

❺ Android 自定義View之Draw過程(上)

Draw 過程系列文章

Android 展示之三部曲:

前邊我們已經分析了:

這倆最主要的任務是: 確定View/ViewGroup可繪制的矩形區域。
接下來將會分析,如何在這給定的區域內繪制想要的圖形。

通過本篇文章,你將了解到:

Android 提供了關於View最基礎的兩個類:

然而ViewGroup 並沒有約定其內部的子View是如何布局的,是疊加在一起呢?還是橫向擺放、縱向擺放等。同樣的View 也沒有約定其展示的內容是啥樣,是矩形、圓形、三角形、一張圖片、一段文字抑或是不規則的形狀?這些都要我們自己去實現嗎?
不盡然,值得高興的是Android已經考慮到上述需求了,為了開發方便已經預制了一些常用的ViewGroup、View。
如:
繼承自ViewGroup的子類

繼承自View的子類

雖然以上衍生的View/ViewGroup子類已經大大為我們提供了便利,但也僅僅是通用場景下的通用控制項,我們想實現一些較為復雜的效果,比如波浪形狀進度條、會發光的球體等,這些系統控制項就無能為力了,也沒必要去預制千奇百怪的控制項。想要達到此效果,我們需要自定義View/ViewGroup。
通常來說自定義View/ViewGroup有以下幾種:

3 一般不怎麼用,除非布局比較特殊。1、2、4 是我們常用的手段,對於我們常說的"自定義View" 一般指的是 4。
接下來我們來看看 4是怎麼實現的。

在xml里引用MyView

效果如下:

黑色部分為其父布局背景。
紅色矩形+黃色圓形即是MyView繪制的內容。
以上是最簡單的自定義View的實現,我們提取重點歸納如下:

由上述Demo可知,我們只需要在重寫的onDraw(xx)方法里繪制想要的圖形即可。
來看看View 默認的onDraw(xx)方法:

發現是個空實現,因此繼承自View的類必須重寫onDraw(xx)方法才能實現繪制。該方法傳入參數為:Canvas類型。
Canvas翻譯過來一般叫做畫布,在重寫的onDraw(xx)里拿到Canvas對象後,有了畫布我們還需要一支筆,這只筆即為Paint,翻譯過來一般稱作畫筆。兩者結合,就可以愉快的作畫(繪制)了。
你可能發現了,在Demo里調用

並沒有傳入Paint啊,是不是Paint不是必須的?實際上調用該方法後,底層會自動生成Paint對象。

可以看到,底層初始化了Paint,並且給其設置的顏色為在Java層設置的顏色。

onDraw(xx)比較簡單,開局一個Canvas,效果全靠畫。
試想,這個Canvas怎麼來的呢,換句話說是誰調用了onDraw(xx)。發揮一下聯想功能,在Measure、Layout 過程有提到過兩者套路很像:

那麼Draw過程是否也是如此套路呢?看見了onDraw(xx),那麼draw(xx)還遠嗎?
沒錯,還真有draw(xx)方法:

可以看出,draw(xx)主要分為兩個部分:

不管是A分支還是B分支,都進行了好幾步的繪制。
通常來說,單一一個View的層次分為:

後面繪制的可能會遮擋前邊繪制的。
對於一個ViewGroup來說,層次分為:

來看看A分支標注的4個點:
(1)
onDraw(canvas)
前面分析過,對於單一的View,onDraw(xx)是空實現,需要由我們自定義繪制。
而對於ViewGroup,也並沒有具體實現,如果在自定義ViewGroup里重寫onDraw(xx),它會執行嗎?默認是不會執行的,相關分析請移步:
Android ViewGroup onDraw為什麼沒調用

(2)
dispatchDraw(canvas),來看看在View.java里的實現:

發現是個空實現,再看看ViewGroup.java里的實現:

也即是說,對於單一View,因為沒有子布局,因此沒必要再分發Draw,而對於ViewGroup來說,需要觸發其子布局發起Draw過程(此過程後續分析),可以類比事件分發過程View、ViewGroup的處理。感興趣的請移步:
Android 輸入事件一擼到底之View接盤俠(3)

(3)
OverLay,顧名思義就是"蓋在某個東西上面",此處是在繪制內容之後,繪制前景之前。怎麼用呢?

以上是給一個ViewGroup設置overLay,效果如下:

你可能發現了,這和設置overLay差不多的嘛,實際還是有差別的。在onDrawForeground(xx)里會重新調整Drawable的尺寸,該尺寸與View大小一致,之前給Drawable設置的尺寸會失效。運行效果如下:

可以看出,ViewGroup都被前景蓋住了。
再來看看B分支的重點:邊緣漸變效果
先來看看TextView 邊緣漸變效果:

加上這倆參數。
實際上系統自帶的一些控制項也使用了該效果,如NumberPicker、YearPickerView

以上是NumberPicker 的效果,可以看出是垂直方向漸變的。

對於View.java 里的onDraw(xx)、draw(xx),ViewGroup.java里並沒有重寫。
而對於dispatchDraw(xx),在View.java里是空實現。在ViewGroup.java里發起對子布局的繪制。

來看看標記的2點:
(1)
設置padding的目的是為了讓子布局留出一定的空隙出來,因此當設置了padding後,子布局的canvas需要根據padding進行裁減。判斷標記為:

FLAG_CLIP_TO_PADDING 默認設置為true
FLAG_PADDING_NOT_NULL 只要有padding不為0,該標記就會打上。
也就是說:只要設置了padding 不為0,子布局顯示區域需要裁減。
能不能不讓子布局裁減顯示區域呢?
答案是可以的。
考慮到一種場景:使用RecyclerView的時候,我們需要設置paddingTop = 20px,效果是:RecyclerView Item展示時離頂部有20px,但是滾動的時候永遠滾不到頂部,看起來不是那麼友好。這就是上述的裁減起作用了,需要將此動作禁止。通過設置:

當然也可以在xml里設置:

(2)
drawChild(xx)

從方法名上看是調用子布局進行繪制。
child.draw(x1,x2,x3)里分兩種情況:

這兩者具體作用與區別會在下篇文章分析,不管是硬體加速繪制還是軟體加速繪制,最終都會調用View.draw(xx)方法,該方法上面已經分析過。
注意,draw(x1,x2,x3)與draw(xx)並不一樣,不要搞混了。

用圖表示:

View/ViewGroup Draw過程的聯系:

一般來說,我們通常會自定義View,並且重寫其onDraw(xx)方法,有沒有繪制內容的ViewGroup需求呢?
是有的,舉個例子,大家可以去看看RecyclerView ItemDecoration 的繪制,其中運用到了ViewGroup draw(xx)、ViewGroup onDraw(xx) 、View onDraw(xx)繪制的先後順序來實現分割線,分組頭部懸停等功能的。

本篇文章基於 Android 10.0

❻ android 系統簽名

也有提到怎麼單獨給一個apk簽名,這里補充一下android的簽名許可權控制機制。

android的標准簽名key有:

     testkey

     media

    latform

    hared

    以上的四種,可以在源碼的/build/target/proct/security裡面看到對應的密鑰,其中shared.pk8代表私鑰,shared.x509.pem公鑰,一定是成對出現的。

    其中testkey是作為android編譯的時候默認的簽名key,如果系統中的apk的android.mk中沒有設置LOCAL_CERTIFICATE的值,就默認使用testkey。

   而如果設置成:

   LOCAL_CERTIFICATE := platform

    就代表使用platform來簽名,這樣的話這個apk就擁有了和system相同的簽名,因為系統級別的簽名也是使用的platform來簽名,此時使用android:sharedUserId="android.uid.system"才有用!

     在/build/target/proct/security目錄下有個README,裡面有說怎麼製作這些key以及使用問題(android4.2):

     從上面可以看出來在源碼下的/development/tools目錄下有個make_key的腳本,通過傳入兩個參數就可以生成一對簽名用的key。

    其中第一個為key的名字,一般都默認成android本身有的,因為很多地方都默認使用了這些名字,我們自定義的話只需要對第二個參數動手腳,定義如下:

    C ---> Country Name (2 letter code) ST ---> State or Province Name (full name) L ---> Locality Name (eg, city) O ---> Organization Name (eg, company) OU ---> Organizational Unit Name (eg, section) CN ---> Common Name (eg, your name or your server』s hostname) emailAddress ---> Contact email addre

    另外在使用上面的make_key腳本生成key的過程中會提示輸入password,我的處理是不輸入,直接enter,不要密碼!後面解釋,用自定義的key替換/security下面的。

    可以看到android源碼裡面的key使用的第二個參數就是上面README裡面的,是公開的,所以要release版本的系統的話,肯定要有自己的簽名key才能起到一個安全控製作用。

    在上面提到如果apk中的編譯選項LOCAL_CERTIFICATE沒有設置的話,就會使用默認的testkey作為簽名key,我們可以修改成自己想要的key,按照上面的步驟製作一個releasekey,修改android配置在/build/core/config.mk中定義變數:

在主makefile文件裡面:

ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/proct/security/releasekey)

  BUILD_VERSION_TAGS += release-key

這樣的話默認的所有簽名將會使用releasekey。

修改完之後就要編譯了,如果上面的這些key在製作的時候輸入了password就會出現如下錯誤:

我在網上找到了合理的解釋:

其實會出現這個錯誤的最根本的原因是多線程的問題。在編譯的時候為了加速一般都會執行make -jxxx,這樣本來需要手動輸入密碼的時候,由於其它線程的運行,就會導致影響當前的輸入終端,所以就會導緻密碼無法輸入的情況!

再編譯完成之後也可以在build.prop中查看到變數:

這樣處理了之後編譯出來的都是簽名過的了,系統才算是release版本

我發現我這樣處理之後,整個系統的算是全部按照我的要求簽名了。

網上看到還有另外的簽名release辦法,但是應該是針對另外的版本的,借用學習一下:

make -j4 PRODUCT-proct_mol-user dist

這個怎麼跟平時的編譯不一樣,後面多了兩個參數PRODUCT-proct_mol-user 和 dist. 編譯完成之後回在源碼/out/dist/目錄內生成個proct_mol-target_files開頭的zip文件.這就是我們需要進行簽名的文件系統.

我的proct_mol 是full_gotechcn,後面加「-user」代表的是最終用戶版本,關於這個命名以及proct_mol等可參考http://blog.csdn.net/jscese/article/details/23931159

編譯出需要簽名的zip壓縮包之後,就是利用/security下面的准備的key進行簽名了:

./build/tools/releasetools/sign_target_files_apks -d /build/target/proct/security  out/dist/full_gotechcn-target_files.zip   out/dist/signed_target_files.zi

簽名目標文件 輸出成signed_target_files.zi

如果出現某些apk出錯,可以通過在full_gotechcn-target_files.zip前面加參數"-e =" 來過濾這些apk.

然後再通過image的腳本生成imag的zip文件,這種方式不適用與我目前的工程源碼,沒有做過多驗證!

Android簽名機制可劃分為兩部分:(1)ROM簽名機制;(2)第三方APK簽名機制。

Android APK實際上是一個jar包,而jar包又是一個zip包。APK包的簽名實際上使用的是jar包的簽名機制:在zip中添加一個META的子目錄,其中存放簽名信息;而簽名方法是為zip包中的每個文件計算其HASH值,得到簽名文件(*.sf),然後對簽名文件(.sf)進行簽名並把簽名保存在簽名塊文件(*.dsa)中。

在編譯Android源碼生成ROM的過程中,會使用build/target/proct/security目錄中的4個key(media, platform, shared, testkey)來對apk進行簽名。其中,*.pk8是二進制形式(DER)的私鑰,*.x509.pem是對應的X509公鑰證書(BASE64編碼)。build/target/proct/security目錄中的這幾個默認key是沒有密碼保護的,只能用於debug版本的ROM。

要生成Release版本的ROM,可先生成TargetFiles,再使用帶密碼的key對TargetFiles重新簽名,最後由重簽名的TargetFiles來生成最終的ROM。

可以使用Android源碼樹中自帶的工具「development/tools/make_key」來生成帶密碼的RSA公私鑰對(實際上是通過openssl來生成的): $ development/tools/make_key media 『/C=CN/ST=Sichuan/L=Cheng/O=MyOrg/OU=MyDepartment/CN=MyName』 上面的命令將生成一個二進制形式(DER)的私鑰文件「media.pk8」和一個對應的X509公鑰證書文件「media.x509.pem」。其中,/C表示「Country Code」,/ST表示「State or Province」,/L表示「City or Locality」,/O表示「Organization」,/OU表示「Organizational Unit」,/CN表示「Name」。前面的命令生成的RSA公鑰的e值為3,可以修改development/tools/make_key腳本來使用F4 (0×10001)作為e值(openssl genrsa的-3參數改為-f4)。

也可以使用JDK中的keytool來生成公私鑰對,第三方APK簽名一般都是通過keytool來生成公私鑰對的。

可以使用openssl x509命令來查看公鑰證書的詳細信息: $ openssl x509 -in media.x509.pem -text -noout or, $ openssl x509 -in media.x509.pem -inform PEM -text -noout

還可以使用JDK中的keytool來查看公鑰證書內容,但其輸出內容沒有openssl x509全面: $ keytool -printcert -v -file media.x509.pem

有了key之後,可以使用工具「build/tools/releasetools/sign_target_files」來對TargetFiles重新簽名: $ build/tools/releasetools/sign_target_files_apks -d new_keys_dir -o target_files.zip target_files_resigned.zip 其中,new_keys_dir目錄中需要有四個key(media, platform, shared, releasekey)。注意:這里的releasekey將代替默認的testkey(請參考build/tools/releasetools/sign_target_files腳本實現),也就是說,如果某個apk的Android.mk文件中的LOCAL_CERTIFICATE為testkey,那麼在生成TargetFiles時是使用的build/target/proct/security/testkey來簽名的,這里重新簽名時將使用new_keys_dir/releasekey來簽名。

uild/tools/releasetools/sign_target_files_apks是通過host/linux-x86/framework/signapk.jar來完成簽名的。也可以直接使用host/linux-x86/framework/signapk.jar來對某個apk進行簽名: $ java -jar signapk [-w] publickey.x509[.pem] privatekey.pk8 input.jar output.jar 其中,」-w」表示還對整個apk包(zip包)進行簽名,並把簽名放在zip包的comment中。

對於第三方應用開發者而言,對APK簽名相對要簡單得多。第三方應用開發一般採用JDK中的keytool和jarsigner來完成簽名密鑰的管理和APK的簽名。

使用keytool來生成存儲有公私鑰對的keystore: $ keytool -genkey -v -keystore my-release-key.keystore -alias mykey -keyalg RSA -keysize 2048 -validity 10000

查看生成的密鑰信息: $ keytool -list -keystore my-release-key.keystore -alias mykey -v or, $ keytool -list -keystore my-release-key.keystore -alias mykey -rfc (註:獲取Base64格式的公鑰證書,RFC 1421)

導出公鑰證書: $ keytool -export -keystore mystore -alias mykey -file my.der (註:二進制格式公鑰證書,DER) $ keytool -export -keystore mystore -alias mykey -file my.pem -rfc (註:Base64格式公鑰證書,PEM)

對APK進行簽名: $ jarsigner -verbose -keystore my-release-key.keystore my_application.apk mykey

驗證簽名: $ jarsigner -verify -verbose -certs my_application.apk

在進行Android二次開發時,有時需要把build/target/proct/security下面的公私鑰對轉換為keystore的形式,可以參考這篇文章:把Android源碼中的密碼對轉換為keystore的方法。

❼ android一篇文章怎麼顯示

裝個看文章的閱讀軟體,可以在APP商店裡搜索個OFFICE看看

❽ Android11 Notification功能解析

       我們知道,當手機有通知時,下拉通知中心中會顯示所有的通知,該功能是在SystemUI中實現的,接著上篇文章 Android11 SystemUI解析 ,本文對通知相關的功能邏輯進行分析,基於Android11 CarSystemUI的通知功能邏輯展開分析。
       關於通知功能邏輯,簡單來說,無非就是四步,注冊、發送、接收、顯示,那麼接下來針對以上四步進行源碼詳細分析。

       關於CarSystemUI啟動及相關邏輯可以參考文章 Android11 SystemUI解析 ,本文就不贅述了,直接以類為入口進行分析:

       從構造方法來看:

       可以看到,在創建以上實例時,會通過Inject的方式來創造對應參數的實例,該功能是通過dagger2來實現,具體對應的Mole為CarNotificationMole類,看一下CarNotificationListener實例創造時的實現,關於NotificationViewController後面再分析:

       可以看到,在()提供CarNotificationListener實例時,還執行了registerAsSystemService()方法,接下來看一下CarNotificationListener類:

       CarNotificationListener繼承了NotificationListenerService類,該類繼承了Service,是framework內部的組成部分,通過registerAsSystemService()來看一下該類的實現:

       該方法內部主要執行了兩項操作:
       a.創建了NotificationListenerWrapper對象,該類繼承了INotificationListener.Stub,主要用來接收回調,後面在顯示環節進行詳細分析;

       b.將以上對象作為參數通過INotificationManager的registerListener進行注冊;

       通過getNotificationInterface()的是實現可以知道,registerListener()執行到了NotificationManagerService裡面去了,接下來一起看一下:

       mListeners是NotificationListeners實例,是在init()中進行初始化的,NotificationListeners是其內部類,看一下具體實現:

       NotificationListeners繼承了ManagedServices,registerSystemService方法是在ManagedServices裡面實現的,看一下:

       根據調用關系,registerServiceImpl()方法內先將前面創建的INotificationListener(mWrapper)作為參數創建了ManagedServiceInfo實例info,然後執行linkToDeath進行死亡監測,最後將info加入mServices中進行管理,執行完後再執行onServiceAdded(info),該方法是在NotificationListeners類內部實現的,再回去看一下該方法,最終會調用到CarNotificationListener.java裡面的onListenerConnected()方法。
       以上注冊過程邏輯比較繞,用一張圖來總結一下:

       發送過程比較簡單,按照系統提供的方法來發送即可,主要涉及NotificationChannel、Notification、NotificationManager這三個類,簡單看一下:
       首先某個應用在發送通知前需要創建該應用對應的NotificationChannel,然後在通知中傳入對應channel ID就可以了,創建如下:

       在創建NotificationChannel時需要傳入唯一的id、name和importance,創建如下:

       創建完NotificationChannel後,再創建Notification,Notification創建採用的是Builder模式,主要涉及的內容比較多,創建如下:

       Notification涉及的內容比較多,可以根據需要進行設定;

       創建完Notification後,通過NotificationManger來進行發送就可以了:

       執行完notify後續的邏輯處理過程,在接收環節進行分析;

       發送時會調用到notify()方法:

       跟隨調用關系,會調用到notifyAsUser()方法:

       在notifyAsUser()會調用到NotificationManagerService中的enqueueNotificationWithTag()方法,先看一下fixNotification()方法:

       需要注意一下:當應用targetSdkVersion大於22時,在創建Notification時需要傳入smallIcon,否則會拋異常導致發送不成功;接下來看一下enqueueNotificationWithTag()方法:

       NotificationManagerService繼承了SystemService,在SystemServer中會進行啟動,在start()方法內部會執行publishBinderService來進行Binder注冊提供服務:

       可以看到,enqueueNotificationWithTag()會調用到enqueueNotificationInternal(),該方法是真正的邏輯實現:

       該方法中主要做了以下幾件事:
       1.進行各種check來確保notification的合法性;
       2.將Notification作為參數創建StatusBarNotification;
       3.獲取Notification對應的channel id,根據channel id 來獲取應用對應的NotificationChannel,如果為空的話,就直接返回了,因此應用在發送notification前需要先創建對應NotificationChannel;
       4.通過Handler post EnqueueNotificationRunnable來執行後續邏輯;

       在EnqueueNotificationRunnable內部會將r(NotificationRecord)加入mEnqueuedNotifications進行管理,然後判斷該NotificationRecord是否已經存在過,最後執行PostNotificationRunnable;

       PostNotificationRunnable的run()中主要處理邏輯如下:
       1.從mEnqueuedNotifications中找到跟key對應的NotificationRecord;
       2.通過indexOfNotificationLocked()看mNotificationList裡面是否已經包含該NotificationRecord,如果不存在,說明是新的record,需要加入mNotificationList進行管理;否則的話,將mNotificationList中index對應的NotificationRecord進行更新;
       3.將要處理的NotificationRecord放入mNotificationsByKey進行管理;
       4.執行mListeners.notifyPostedLocked(r, old)來進行通知;
       5.在finally裡面將要處理的NotificationRecord從mEnqueuedNotifications裡面移除;
       6.如果在加入後有取消操作,需要立刻執行取消操作,並將NotificationRecord從mDelayedCancelations中移除;

       前面講到,在PostNotificationRunnable中會執行mListeners.notifyPostedLocked(r, old)進行通知,mListeners是NotificationListeners實例:

       跟隨調用關系:

       通過getServices()來找到已經注冊過的ManagedServiceInfo列表,最後執行notifyPosted();

       在notifyPosted()內部通過info.service來找到對應的INotificationListener實例,對應NotificationListenerService內部的NotificationListenerWrapper,然後將StatusBarNotification封裝成StatusBarNotificationHolder,最後執行NotificationListenerWrapper的onNotificationPosted()方法:

       最終會通過Handler來發送消息來進行處理;

       onNotificationPosted(sbn, rankingMap)是在CarNotificationListener內部實現的;

       在CarNotificationListener內部會將StatusBarNotification封裝成AlertEntry,然後執行notifyNotificationPosted():

       一步一步調用:

       先將alertEntry存入mActiveNotifications進行管理;然後執行發送NOTIFY_NOTIFICATION_POSTED消息;

       該Handler是通過setHandler來賦值的,具體是在什麼地方呢?
       這個需要回到最前面裡面了,前面說到NotificationViewController是在顯示環節進行分析,輪到NotificationViewController登場了;

       NotificationViewController是在實例化,並執行enable()方法,先看一下構造方法:

       在構造方法內部,會傳入CarNotificationView、CarNotificationListener、PreprocessingManager等實例,都是跟顯示有關的核心類;
       1.CarNotificationView:負責處理通知顯示;
       2.PreprocessingManager:負責管理通知,通過CarNotificationListener來獲取通知;
       3.CarNotificationListener:跟NotificationManagerService間接交互的類;

       可以看到Handler是在NotificationViewController裡面實現的,當有消息到來時,如果CarNotificationView顯示時執行updateNotifications()來直接顯示通知;不顯示時執行resetNotifications()來對通知進行管理;

       以上就是Notification的整個工作過程,最後用一張流程圖來總結一下:

❾ 如果要在Android的activity中顯示一篇文章,這篇文章存儲在哪裡

那最好放在資料庫中,可以是本地資料庫,也可以是後台資料庫,需要的時候去取

❿ 介紹android的英文文章

Android is an open-source software stack created for mobile phones and other devices. The Android Open Source Project (AOSP), led by Google, is tasked with the maintenance and further development of Android. Many device manufacturers have brought to market devices running Android, and they are readibly available around the world.

熱點內容
雲伺服器可以通過遠程打游戲嗎 發布:2025-03-06 17:16:43 瀏覽:914
cs新版本要什麼配置 發布:2025-03-06 17:15:22 瀏覽:225
用戶登錄密碼設置規則是什麼 發布:2025-03-06 17:04:40 瀏覽:751
海外移動賬號訪問 發布:2025-03-06 17:00:09 瀏覽:815
samba在伺服器搭建 發布:2025-03-06 16:53:35 瀏覽:170
圖片管理資料庫 發布:2025-03-06 16:51:05 瀏覽:980
用舊電腦搭建家用伺服器 發布:2025-03-06 16:48:54 瀏覽:251
台式電腦如何與安卓手機連接藍牙 發布:2025-03-06 16:48:47 瀏覽:816
奇博源碼 發布:2025-03-06 16:47:43 瀏覽:986
原油存儲罐 發布:2025-03-06 16:12:21 瀏覽:55