對(duì)于Android開發(fā)者來說,我們或多或少有了解過Android圖像顯示的知識(shí)點(diǎn),剛剛學(xué)習(xí)Android開發(fā)的人會(huì)知道,在Actvity的onCreate方法中設(shè)置我們的View后,再經(jīng)過onMeasure,onLayout,onDraw的流程,界面就顯示出來了;對(duì)Android比較熟悉的開發(fā)者會(huì)知道,onDraw流程分為軟件繪制和硬件繪制兩種模式,軟繪是通過調(diào)用Skia來操作,硬繪是通過調(diào)用Opengl ES來操作;對(duì)Android非常熟悉的開發(fā)者會(huì)知道繪制出來的圖形數(shù)據(jù)最終都通過GraphiBuffer內(nèi)共享內(nèi)存?zhèn)鬟f給SurfaceFlinger去做圖層混合,圖層混合完成后將圖形數(shù)據(jù)送到幀緩沖區(qū),于是,圖形就在我們的屏幕顯示出來了。
創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括武都網(wǎng)站建設(shè)、武都網(wǎng)站制作、武都網(wǎng)頁(yè)制作以及武都網(wǎng)絡(luò)營(yíng)銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,武都網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到武都省份的部分城市,未來相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
但我們所知道的Activity或者是應(yīng)用App界面的顯示,只屬于Android圖形顯示的一部分。同樣可以在Android系統(tǒng)上展示圖像的WebView,F(xiàn)lutter,或者是通過Unity開發(fā)的3D游戲,他們的界面又是如何被繪制和顯現(xiàn)出來的呢?他們和我們所熟悉的Acitvity的界面顯示又有什么異同點(diǎn)呢?我們可以不借助Activity的setView或者InflateView機(jī)制來實(shí)現(xiàn)在屏幕上顯示出我們想要的界面嗎?Android系統(tǒng)顯示界面的方式又和IOS,或者Windows等系統(tǒng)有什么區(qū)別呢?……
去探究這些問題,比僅僅知道Acitvity的界面是如何顯示出來更加的有價(jià)值,因?yàn)橄胍卮疬@些問題,就需要我們真正的掌握Android圖像顯示的底層原理,當(dāng)我們掌握了底層的顯示原理后,我們會(huì)發(fā)現(xiàn)WebView,F(xiàn)lutter或者未來會(huì)出現(xiàn)的各種新的圖形顯示技術(shù),原來都是大同小異。
我會(huì)花三篇文章的篇幅,去深入的講解Android圖形顯示的原理,OpenGL ES和Skia的繪制圖像的方式,他們?nèi)绾问褂茫约八麄冊(cè)贏ndroid中的使用場(chǎng)景,如開機(jī)動(dòng)畫,Activity界面的軟件繪制和硬件繪制,以及Flutter的界面繪制。那么,我們開始對(duì)Android圖像顯示原理的探索吧。
在講解Android圖像的顯示之前,我會(huì)先講一下屏幕圖像的顯示原理,畢竟我們圖像,最終都是在手機(jī)屏幕上顯示出來的,了解這一塊的知識(shí)會(huì)讓我們更容易的理解Android在圖像顯示上的機(jī)制。
圖像顯示的完整過程,分為下面幾個(gè)階段:
圖像數(shù)據(jù)→CPU→顯卡驅(qū)動(dòng)→顯卡(GPU)→顯存(幀緩沖)→顯示器
我詳細(xì)介紹一下這幾個(gè)階段:
實(shí)際上顯卡驅(qū)動(dòng),顯卡和顯存,包括數(shù)模轉(zhuǎn)換模塊都是屬于顯卡的模塊。但為了能能詳細(xì)的講解經(jīng)歷的步驟,這里做了拆分。
當(dāng)顯存中有數(shù)據(jù)后,顯示器又是怎么根據(jù)顯存里面的數(shù)據(jù)來進(jìn)行界面的顯示的呢?這里以LCD液晶屏為例,顯卡會(huì)將顯存里的數(shù)據(jù),按照從左至右,從上到下的順序同步到屏幕上的每一個(gè)像素晶體管,一個(gè)像素晶體管就代表了一個(gè)像素。
如果我們的屏幕分辨率是1080x1920像素,就表示有1080x1920個(gè)像素像素晶體管,每個(gè)橡素點(diǎn)的顏色越豐富,描述這個(gè)像素的數(shù)據(jù)就越大,比如單色,每個(gè)像素只需要1bit,16色時(shí),只需要4bit,256色時(shí),就需要一個(gè)字節(jié)。那么1080x1920的分辨率的屏幕下,如果要以256色顯示,顯卡至少需要1080x1920個(gè)字節(jié),也就是2M的大小。
剛剛說了,屏幕上的像素?cái)?shù)據(jù)是從左到右,從上到下進(jìn)行同步的,當(dāng)這個(gè)過程完成了,就表示一幀繪制完成了,于是會(huì)開始下一幀的繪制,大部分的顯示屏都是以60HZ的頻率在屏幕上繪制完一幀,也就是16ms,并且每次繪制新的一幀時(shí),都會(huì)發(fā)出一個(gè)垂直同步信號(hào)(VSync)。我們已經(jīng)知道,圖像數(shù)據(jù)都是放在幀緩沖中的,如果幀緩沖的緩沖區(qū)只有一個(gè),那么屏幕在繪制這一幀的時(shí)候,圖像數(shù)據(jù)便沒法放入幀緩沖中了,只能等待這一幀繪制完成,在這種情況下,會(huì)有很大了效率問題。所以為了解決這一問題,幀緩沖引入兩個(gè)緩沖區(qū),即 雙緩沖機(jī)制 。雙緩沖雖然能解決效率問題,但會(huì)引入一個(gè)新的問題。當(dāng)屏幕這一幀還沒繪制完成時(shí),即屏幕內(nèi)容剛顯示一半時(shí),GPU 將新的一幀內(nèi)容提交到幀緩沖區(qū)并把兩個(gè)緩沖區(qū)進(jìn)行交換后,顯卡的像素同步模塊就會(huì)把新的一幀數(shù)據(jù)的下半段顯示到屏幕上,造成畫面撕裂現(xiàn)象。
為了解決撕裂問題,就需要在收到垂直同步的時(shí)候才將幀緩沖中的兩個(gè)緩沖區(qū)進(jìn)行交換。Android4.1黃油計(jì)劃中有一個(gè)優(yōu)化點(diǎn),就是CPU和GPU都只有收到垂直同步的信號(hào)時(shí),才會(huì)開始進(jìn)行圖像的繪制操作,以及緩沖區(qū)的交換工作。
我們已經(jīng)了解了屏幕圖像顯示的原理了,那么接著開始對(duì)Android圖像顯示的學(xué)習(xí)。
從上一章已經(jīng)知道,計(jì)算機(jī)渲染界面必須要有GPU和幀緩沖。對(duì)于Linux系統(tǒng)來說,用戶進(jìn)程是沒法直接操作幀緩沖的,但我們想要顯示圖像就必須要操作幀緩沖,所以Linux系統(tǒng)設(shè)計(jì)了一個(gè)虛擬設(shè)備文件,來作為對(duì)幀緩沖的映射,通過對(duì)該文件的I/O讀寫,我們就可以實(shí)現(xiàn)讀寫屏操作。幀緩沖對(duì)應(yīng)的設(shè)備文件于/dev/fb* ,*表示對(duì)多個(gè)顯示設(shè)備的支持, 設(shè)備號(hào)從0到31,如/dev/fb0就表示第一塊顯示屏,/dev/fb1就表示第二塊顯示屏。對(duì)于Android系統(tǒng)來說,默認(rèn)使用/dev/fb0這一個(gè)設(shè)幀緩沖作為主屏幕,也就是我們的手機(jī)屏幕。我們Android手機(jī)屏幕上顯示的圖像數(shù)據(jù),都是存儲(chǔ)在/dev/fb0里,早期AndroidStuio中的DDMS工具實(shí)現(xiàn)截屏的原理就是直接讀取/dev/fb0設(shè)備文件。
我們知道了手機(jī)屏幕上的圖形數(shù)據(jù)都存儲(chǔ)在幀緩沖中,所以Android手機(jī)圖像界面的原理就是將我們的圖像數(shù)據(jù)寫入到幀緩沖內(nèi)。那么,寫入到幀緩沖的圖像數(shù)據(jù)是怎么生成的,又是怎樣加工的呢?圖形數(shù)據(jù)是怎樣送到幀緩沖去的,中間經(jīng)歷了哪些步驟和過程呢?了解了這幾個(gè)問題,我們就了解了Android圖形渲染的原理,那么帶著這幾個(gè)疑問,接著往下看。
想要知道圖像數(shù)據(jù)是怎么產(chǎn)生的,我們需要知道 圖像生產(chǎn)者 有哪些,他們分別是如何生成圖像的,想要知道圖像數(shù)據(jù)是怎么被消費(fèi)的,我們需要知道 圖像消費(fèi)者 有哪些,他們又分別是如何消費(fèi)圖像的,想要知道中間經(jīng)歷的步驟和過程,我們需要知道 圖像緩沖區(qū) 有哪些,他們是如何被創(chuàng)建,如何分配存儲(chǔ)空間,又是如何將數(shù)據(jù)從生產(chǎn)者傳遞到消費(fèi)者的,圖像顯示是一個(gè)很經(jīng)典的消費(fèi)者生產(chǎn)者的模型,只有對(duì)這個(gè)模型各個(gè)模塊的擊破,了解他們之間的流動(dòng)關(guān)系,我們才能找到一條更容易的路徑去掌握Android圖形顯示原理。我們看看谷歌提供的官方的架構(gòu)圖是怎樣描述這一模型的模塊及關(guān)系的。
如圖, 圖像的生產(chǎn)者 主要有MediaPlayer,CameraPrevier,NDK,OpenGl ES。MediaPlayer和Camera Previer是通過直接讀取圖像源來生成圖像數(shù)據(jù),NDK(Skia),OpenGL ES是通過自身的繪制能力生產(chǎn)的圖像數(shù)據(jù); 圖像的消費(fèi)者 有SurfaceFlinger,OpenGL ES Apps,以及HAL中的Hardware Composer。OpenGl ES既可以是圖像的生產(chǎn)者,也可以是圖像的消費(fèi)者,所以它也放在了圖像消費(fèi)模塊中; 圖像緩沖區(qū) 主要有Surface以及前面提到幀緩沖。
Android圖像顯示的原理,會(huì)僅僅圍繞 圖像的生產(chǎn)者 , 圖像的消費(fèi)者 , 圖像緩沖區(qū) 來展開,在這一篇文章中,我們先看看Android系統(tǒng)中的圖像消費(fèi)者。
SurfaceFlinger是Android系統(tǒng)中最重要的一個(gè)圖像消費(fèi)者,Activity繪制的界面圖像,都會(huì)傳遞到SurfaceFlinger來,SurfaceFlinger的作用主要是接收?qǐng)D像緩沖區(qū)數(shù)據(jù),然后交給HWComposer或者OpenGL做合成,合成完成后,SurfaceFlinger會(huì)把最終的數(shù)據(jù)提交給幀緩沖。
那么SurfaceFlinger是如何接收?qǐng)D像緩沖區(qū)的數(shù)據(jù)的呢?我們需要先了解一下Layer(層)的概念,一個(gè)Layer包含了一個(gè)Surface,一個(gè)Surface對(duì)應(yīng)了一塊圖形緩沖區(qū),而一個(gè)界面是由多個(gè)Surface組成的,所以他們會(huì)一一對(duì)應(yīng)到SurfaceFlinger的Layer中。SurfaceFlinger通過讀取Layer中的緩沖數(shù)據(jù),就相當(dāng)于讀取界面上Surface的圖像數(shù)據(jù)。Layer本質(zhì)上是 Surface和SurfaceControl的組合 ,Surface是圖形生產(chǎn)者和圖像消費(fèi)之間傳遞數(shù)據(jù)的緩沖區(qū),SurfaceControl是Surface的控制類。
前面在屏幕圖像顯示原理中講到,為了防止圖像的撕裂,Android系統(tǒng)會(huì)在收到VSync垂直同步時(shí)才會(huì)開始處理圖像的繪制和合成工作,而Surfaceflinger作為一個(gè)圖像的消費(fèi)者,同樣也是遵守這一規(guī)則,所以我們通過源碼來看看SurfaceFlinger是如何在這一規(guī)則下,消費(fèi)圖像數(shù)據(jù)的。
SurfaceFlinger專門創(chuàng)建了一個(gè)EventThread線程用來接收VSync。EventThread通過Socket將VSync信號(hào)同步到EventQueue中,而EventQueue又通過回調(diào)的方式,將VSync信號(hào)同步到SurfaceFlinger內(nèi)。我們看一下源碼實(shí)現(xiàn)。
上面主要是SurfaceFlinger初始化接收VSYNC垂直同步信號(hào)的操作,主要有這幾個(gè)過程:
經(jīng)過上面幾個(gè)步驟,我們接收VSync的初始化工作都準(zhǔn)備好了,EventThread也開始運(yùn)轉(zhuǎn)了,接著看一下EventThread的運(yùn)轉(zhuǎn)函數(shù)threadLoop做的事情。
threadLoop主要是兩件事情
mConditon又是怎么接收VSync的呢?我們來看一下
可以看到,mCondition的VSync信號(hào)實(shí)際是DispSyncSource通過onVSyncEvent回調(diào)傳入的,但是DispSyncSource的VSync又是怎么接收的呢?在上面講到的SurfaceFlinger的init函數(shù),在創(chuàng)建EventThread的實(shí)現(xiàn)中,我們可以發(fā)現(xiàn)答案—— mPrimaryDispSync 。
DispSyncSource的構(gòu)造方法傳入了mPrimaryDispSync,mPrimaryDispSync實(shí)際是一個(gè)DispSyncThread線程,我們看看這個(gè)線程的threadLoop方法
DispSyncThread的threadLoop會(huì)通過mPeriod來判斷是否進(jìn)行阻塞或者進(jìn)行VSync回調(diào),那么mPeriod又是哪兒被設(shè)置的呢?這里又回到SurfaceFlinger了,我們可以發(fā)現(xiàn)在SurfaceFlinger的 resyncToHardwareVsync 函數(shù)中有對(duì)mPeriod的賦值。
可以看到,這里最終通過HWComposer,也就是硬件層拿到了period。終于追蹤到了VSync的最終來源了, 它從HWCompser產(chǎn)生,回調(diào)至DispSync線程,然后DispSync線程回調(diào)到DispSyncSource,DispSyncSource又回調(diào)到EventThread,EventThread再通過Socket分發(fā)到MessageQueue中 。
我們已經(jīng)知道了VSync信號(hào)來自于HWCompser,但SurfaceFlinger并不會(huì)一直監(jiān)聽VSync信號(hào),監(jiān)聽VSync的線程大部分時(shí)間都是休眠狀態(tài),只有需要做合成工作時(shí),才會(huì)監(jiān)聽VSync,這樣即保證圖像合成的操作能和VSync保持一致,也節(jié)省了性能。SurfaceFlinger提供了一些主動(dòng)注冊(cè)監(jiān)聽VSync的操作函數(shù)。
可以看到,只有當(dāng)SurfaceFlinger調(diào)用 signalTransaction 或者 signalLayerUpdate 函數(shù)時(shí),才會(huì)注冊(cè)監(jiān)聽VSync信號(hào)。那么signalTransaction或者signalLayerUpdate什么時(shí)候被調(diào)用呢?它可以由圖像的生產(chǎn)者通知調(diào)用,也可以由SurfaceFlinger根據(jù)自己的邏輯來判斷是否調(diào)用。
現(xiàn)在假設(shè)App層已經(jīng)生成了我們界面的圖像數(shù)據(jù),并調(diào)用了 signalTransaction 通知SurfaceFlinger注冊(cè)監(jiān)聽VSync,于是VSync信號(hào)便會(huì)傳遞到了MessageQueue中了,我們接著看看MessageQueue又是怎么處理VSync的吧。
MessageQueue收到VSync信號(hào)后,最終回調(diào)到了SurfaceFlinger的 onMessageReceived 中,當(dāng)SurfaceFlinger接收到VSync后,便開始以一個(gè)圖像消費(fèi)者的角色來處理圖像數(shù)據(jù)了。我們接著看SurfaceFlinger是以什么樣的方式消費(fèi)圖像數(shù)據(jù)的。
VSync信號(hào)最終被SurfaceFlinger的onMessageReceived函數(shù)中的INVALIDATE模塊處理。
INVALIDATE的流程如下:
handleMessageTransaction的處理比較長(zhǎng),處理的事情也比較多,它主要做的事情有這些
handleMessageRefresh函數(shù),便是SurfaceFlinger真正處理圖層合成的地方,它主要下面五個(gè)步驟。
我會(huì)詳細(xì)介紹每一個(gè)步驟的具體操作
合成前預(yù)處理會(huì)判斷Layer是否發(fā)生變化,當(dāng)Layer中有新的待處理的Buffer幀(mQueuedFrames0),或者mSidebandStreamChanged發(fā)生了變化, 都表示Layer發(fā)生了變化,如果變化了,就調(diào)用signalLayerUpdate,注冊(cè)下一次的VSync信號(hào)。如果Layer沒有發(fā)生變化,便只會(huì)做這一次的合成工作,不會(huì)注冊(cè)下一次VSync了。
重建Layer棧會(huì)遍歷Layer,計(jì)算和存儲(chǔ)每個(gè)Layer的臟區(qū), 然后和當(dāng)前的顯示設(shè)備進(jìn)行比較,看Layer的臟區(qū)域是否在顯示設(shè)備的顯示區(qū)域內(nèi),如果在顯示區(qū)域內(nèi)的話說明該layer是需要繪制的,則更新到顯示設(shè)備的VisibleLayersSortedByZ列表中,等待被合成
rebuildLayerStacks中最重要的一步是 computeVisibleRegions ,也就是對(duì)Layer的變化區(qū)域和非透明區(qū)域的計(jì)算,為什么要對(duì)變化區(qū)域做計(jì)算呢?我們先看看SurfaceFlinger對(duì)界面顯示區(qū)域的分類:
還是以這張圖做例子,可以看到我們的狀態(tài)欄是半透明的,所以它是一個(gè)opaqueRegion區(qū)域,微信界面和虛擬按鍵是完全不透明的,他是一個(gè)visibleRegion,除了這三個(gè)Layer外,還有一個(gè)我們看不到的Layer——壁紙,它被上方visibleRegion遮擋了,所以是coveredRegion
對(duì)這幾個(gè)區(qū)域的概念清楚了,我們就可以去了解computeVisibleRegions中做的事情了,它主要是這幾步操作:
HelloGitHub 分享 GitHub 上有趣、入門級(jí)的開源項(xiàng)目。
這里有實(shí)戰(zhàn)項(xiàng)目、入門教程、黑 科技 、開源書籍、大廠開源項(xiàng)目等,涵蓋多種編程語(yǔ)言 Python、Java、Go、C/C++、Swift...讓你在短時(shí)間內(nèi)感受到開源的魅力,對(duì)編程產(chǎn)生興趣!
1、 toybox :該項(xiàng)目將 200 多個(gè)常用的 Linux 命令行工具,做成一個(gè)可執(zhí)行文件。從而可以讓 Android 這種原本不支持 Linux 命令的系統(tǒng),也得以用上 ls、find、ps 等命令。還可以用于快速構(gòu)建最小的 Linux 環(huán)境
2、 the_silver_searcher :比 ack 更快的命令行搜索工具。速度快、功能強(qiáng)大、使用簡(jiǎn)單,支持 Linux、Windows、macOS 操作系統(tǒng),還能夠整合進(jìn) Vim 和 Emacs 等編輯器
3、 WindTerm :支持 SSH/Telnet/Serial/Shell/Sftp 的終端工具。雖然該軟件完全免費(fèi),但部分代碼尚未完全開源,對(duì)安全敏感的同學(xué)可以再觀望下
4、 wavefunctioncollapse :基于波函數(shù)坍縮 (WFC) 算法,實(shí)現(xiàn)的無限城市示例。城市里有房子、樓梯、樹木、連接房屋的通道,你可以在城市中自由移動(dòng)、跳躍、飛行,但不論你怎么移動(dòng)都找不到盡頭,因?yàn)檫@座城市會(huì)無限延伸
5、 NETworkManager :管理和解決網(wǎng)絡(luò)問題的工具。它集成了 IP 和端口掃描、WiFi 分析器、跟蹤路由、DNS 查詢等工具
6、 ppsspp :能夠運(yùn)行在 Android 和 PC 上的開源 PSP 模擬器
7、 leocad :用來創(chuàng)建虛擬樂高模型的 CAD 工具。適用于 Windows、Linux 和 macOS 系統(tǒng)
8、 csshake :用 CSS 實(shí)現(xiàn)抖動(dòng)效果
9、 MangoDB :真正開源的 MongoDB 替代品。它底層采用 PostgreSQL 作為存儲(chǔ)引擎,用 Go 語(yǔ)言實(shí)現(xiàn)了 MongoDB 協(xié)議,所以幾乎兼容所有的 MongoDB 庫(kù),遷移起來毫無負(fù)擔(dān)。如果你用不到 MongoDB 的高級(jí)功能,還受限于它的開源協(xié)議,那么這個(gè)項(xiàng)目可作為 MongoDB 的開源替代方案。它才剛剛起步,建議觀望一段時(shí)間或做足測(cè)試再用于生產(chǎn)環(huán)境
10、 caddy :用 Go 編寫的輕量級(jí) Web 服務(wù)器。它相較于 Apache、Nginx 這些知名 Web 服務(wù)器,獨(dú)特點(diǎn)在于提供了編譯好的可執(zhí)行文件,實(shí)現(xiàn)了真正的開箱即用。無需任何配置即可擁有免費(fèi)的 HTTPS、自動(dòng)把 Markdown 文件轉(zhuǎn)化成 HTML 等人性化的功能。如果是搭建中小型的 Web 服務(wù),它完全夠用而且省時(shí)省心
11、 croc :可以讓任意兩臺(tái)計(jì)算機(jī),安全方便地傳輸文件和文件夾的工具。輕松實(shí)現(xiàn)端到端加密的跨平臺(tái)文件傳輸,還支持多文件傳輸、傳輸中斷和恢復(fù)等功能
12、 jnativehook :獲取鍵盤和鼠標(biāo)事件的 Java 庫(kù)。輕松監(jiān)聽按鍵、鼠標(biāo)移動(dòng)、點(diǎn)擊等事件
13、 spider-flow :用流程圖的方式編寫爬蟲的平臺(tái)。無需寫代碼就可以快速完成一個(gè)簡(jiǎn)單的爬蟲
14、 greenDAO :高性能的 Android ORM 庫(kù)。擁有體積小、易于使用、支持?jǐn)?shù)據(jù)庫(kù)加密等特點(diǎn),通過它 Android 開發(fā)者可以采用面向?qū)ο蟮姆绞讲僮鲾?shù)據(jù)庫(kù),不需要再手寫和拼接 SQL 啦
15、 vue-color-avatar :純前端實(shí)現(xiàn)的矢量風(fēng)格頭像生成網(wǎng)站。可以通過搭配不同的素材,生成個(gè)性化頭像。該項(xiàng)目使用 Vite + Vue3 開發(fā),能夠幫助前端初學(xué)者熟悉 Vue3 語(yǔ)法并掌握項(xiàng)目搭建的相關(guān)知識(shí)
16、 colorfu :自動(dòng)生成由文字/顏色/圖片/紋理元素組成的壁紙
17、 pm2 :Node.js 的進(jìn)程管理工具。它容易上手操作簡(jiǎn)單,可以有效地提高 Node.js 程序運(yùn)行的穩(wěn)定性,支持自動(dòng)重啟、負(fù)載均衡、不停服務(wù)重啟、性能監(jiān)控等功能,多用于生產(chǎn)環(huán)境中管理、監(jiān)控 Node.js 進(jìn)程
18、 automa :通過圖形化界面拖拽功能模塊,實(shí)現(xiàn)瀏覽器自動(dòng)操作的擴(kuò)展工具。輕松實(shí)現(xiàn)自動(dòng)填表、截圖、定時(shí)執(zhí)行等操作。讓瀏覽器自動(dòng)完成預(yù)設(shè)工作流的插件,從而減少重復(fù)性操作提高效率
19、 PyWebIO :快速構(gòu)建 Web 應(yīng)用的 Python 工具。通過該項(xiàng)目你可在不寫 HTML、CSS、JS 代碼的前提下,僅用 Python 快速完成一個(gè)包含數(shù)據(jù)展示、表單的小型 Web 應(yīng)用頁(yè)面
20、 pottery :以 Python 的方式操作 Redis 的庫(kù)。忘記那些 Redis 命令吧,只要你知道如何使用 Python 字典,那么你就會(huì)用這個(gè)庫(kù)操作 Redis
21、 zulip :完全開源的企業(yè)級(jí)即時(shí)通訊項(xiàng)目。后端采用 Python 語(yǔ)言實(shí)現(xiàn)性能足夠強(qiáng)大,功能齊全相當(dāng)于開源、免費(fèi)的 Slack,擁有拖拽上傳文件、代碼高亮、Markdown 語(yǔ)法、應(yīng)用整合、容易接入的 API 等功能,還支持 Web、PC、iOS 和 Android 主流平臺(tái),眾多知名企業(yè)都在用,能夠有效地提高團(tuán)隊(duì)溝通和辦公效率。同時(shí)該項(xiàng)目對(duì)新手用戶友好,如果你想加入一個(gè)不錯(cuò)的 Python 開源項(xiàng)目,推薦你花時(shí)間研究下它一定會(huì)有所收獲
22、 webssh :簡(jiǎn)單的 SSH 連接服務(wù)器的 Python Web 應(yīng)用。該項(xiàng)目后端采用 Tornado Web 框架和 Python SSH 庫(kù) paramiko,前端是 TypeScript 寫的命令行前端組件 Xterm.js 實(shí)現(xiàn)。整個(gè)項(xiàng)目簡(jiǎn)單還具有實(shí)用價(jià)值,可作為 Python Web 的實(shí)戰(zhàn)項(xiàng)目學(xué)習(xí)
23、 django-debug-toolbar :Django 的調(diào)試工具欄??娠@示當(dāng)前請(qǐng)求和響應(yīng)有關(guān)的各種調(diào)試信息,包括耗時(shí)、SQL、配置、性能等信息
24、 hyperfine :命令行基準(zhǔn)測(cè)試工具。可用來查看和對(duì)比命令的耗時(shí),支持多次運(yùn)行的統(tǒng)計(jì)分析、結(jié)果導(dǎo)出等功能
25、 xcode-dev-cleaner :用于清理各種 Xcode 的緩存數(shù)據(jù),釋放存儲(chǔ)空間。注意是清除 Xcode 緩存數(shù)據(jù),不是卸載 Xcode 哈
26、 toml :更易讀和易于維護(hù)的配置文件格式。如果你厭倦了 INI 的局限性、層層嵌套的 JSON 和 YAML 令人心驚膽戰(zhàn)的縮進(jìn)語(yǔ)法,不防給 TOML 一個(gè)機(jī)會(huì),它支持多種數(shù)據(jù)類型、拋棄了縮進(jìn)和嵌套,而且眾多流行編程語(yǔ)言都有對(duì)應(yīng)的庫(kù)。TOML 已經(jīng)足夠成熟,絕對(duì)值得一試
27、 waka-readme-stats :自動(dòng)在 GitHub 個(gè)人首頁(yè)展示編程時(shí)長(zhǎng)的工具。該項(xiàng)目通過 WakaTime 記錄用戶在 IDE 的使用時(shí)間,統(tǒng)計(jì)編程時(shí)長(zhǎng)和數(shù)據(jù),然后采用 GitHub Action 自動(dòng)獲取并動(dòng)態(tài)更新到 GitHub 個(gè)人首頁(yè)。輕而易舉地展示自己的編程時(shí)長(zhǎng)
28、 PathPlanning :常見的路徑規(guī)劃算法集合。項(xiàng)目包含了 Python 代碼實(shí)現(xiàn)、運(yùn)行過程動(dòng)畫以及相關(guān)論文
29、 howdy :為 Linux 系統(tǒng)提供人臉識(shí)別解鎖電腦的工具。通過電腦內(nèi)置的攝像頭和紅外設(shè)備,實(shí)現(xiàn)了類似 Windows Hello 風(fēng)格的身份認(rèn)證,可用于登陸、鎖屏、sudo 等任何需要輸入密碼的地方
30、 The-Open-Book :開源的電子水墨屏閱讀設(shè)備。動(dòng)手能力強(qiáng)的同學(xué)可跟著這個(gè)項(xiàng)目,從焊電路板開始親手制作出一個(gè)類 Kindle 的 4.2 英寸閱讀設(shè)備
31、 fl_chart :Flutter 圖表庫(kù)。它支持折線圖、條形圖、餅圖、散點(diǎn)圖和雷達(dá)圖
32、 ugo-compiler-book :《從頭開發(fā)一個(gè)迷你 Go 語(yǔ)言》該書教你從頭實(shí)現(xiàn)迷你 Go 語(yǔ)言,內(nèi)容包含了詞法解析、語(yǔ)法樹構(gòu)建、函數(shù)閉包、接口、CGO 的實(shí)現(xiàn)等內(nèi)容
33、 archbase :教科書《計(jì)算機(jī)體系結(jié)構(gòu)基礎(chǔ)》第三版
34、 spring-in-action-v6-translate :《Spring 實(shí)戰(zhàn)第 6 版》中文翻譯
35、 best_AI_papers_2021 :2021 年必看的人工智能論文列表。該項(xiàng)目不是簡(jiǎn)單的羅列論文,它不僅包含相關(guān)論文的代碼、效果展示,還有深入的文章和講解視頻。通過學(xué)習(xí)這些前沿的人工智能論文,提前了解 AI 在未來更多可能性
36、 AnimeGANv2 :可以將圖片和視頻轉(zhuǎn)換成漫畫風(fēng)格的工具。采用的是神經(jīng)風(fēng)格遷移+生成對(duì)抗網(wǎng)絡(luò)(GAN)的組合,轉(zhuǎn)換速度快
感謝您的閱讀,如果覺得內(nèi)容還不錯(cuò)的話 求贊、求分享 ,您的每一次支持都將讓 HelloGitHub 變得更好!
用來控制controller對(duì)應(yīng)widget的各種各樣交互行為以及狀態(tài)變化的控制(類似于widget本身只是一個(gè)靜態(tài)的物品,而通過對(duì)controller的操作控制讓這個(gè)widget活了起來)
APP 啟動(dòng)頁(yè)在國(guó)內(nèi)是最常見也是必備的場(chǎng)景,其中啟動(dòng)頁(yè)在 iOS 上算是強(qiáng)制性的要求,其實(shí)配置啟動(dòng)頁(yè)挺簡(jiǎn)單,因?yàn)樵?Flutter 里現(xiàn)在只需要:
一般只要配置無誤并且圖片尺寸匹配,基本上就不會(huì)有什么問題, 那既然這樣,還有什么需要適配的呢?
事實(shí)上大部分時(shí)候 iOS 是不會(huì)有什么問題, 因?yàn)? LaunchScreen.storyboard 的流程本就是 iOS 官方用來做應(yīng)用啟動(dòng)的過渡;而對(duì)于 Andorid 而言,直到 12 之前 windowBackground 這種其實(shí)只能算“民間”野路子 ,所以對(duì)于 Andorid 來說,這其中就涉及到一個(gè)點(diǎn):
所以下面主要介紹 Flutter 在 Android 上為了這個(gè)啟動(dòng)圖做了哪些騷操作~
在已經(jīng)忘記版本的“遠(yuǎn)古時(shí)期” , FlutterActivity 還在 io.flutter.app.FlutterActivity 路徑下的時(shí)候,那時(shí)啟動(dòng)頁(yè)的邏輯相對(duì)簡(jiǎn)單,主要是通過 App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 來進(jìn)行判斷。
在 FlutterActivity 內(nèi)部 FlutterView 被創(chuàng)建的時(shí)候,會(huì)通過讀取 meta-data 來判斷是否需要使用 createLaunchView 邏輯 :
是不是很簡(jiǎn)單,那就會(huì)有人疑問為什么要這樣做?我直接配置 Activity 的 android:windowBackground 不就完成了嗎?
這就是上面提到的時(shí)間差問題, 因?yàn)閱?dòng)頁(yè)到 Flutter 渲染完第一幀畫面中間,會(huì)出現(xiàn)概率出現(xiàn)黑屏的情況,所以才需要這個(gè)行為來實(shí)現(xiàn)過渡 。
經(jīng)歷了“遠(yuǎn)古時(shí)代”之后, FlutterActivity 來到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本發(fā)布之前,F(xiàn)lutter 又針對(duì)這個(gè)啟動(dòng)過程做了不少調(diào)整和優(yōu)化,其中主要就是 SplashScreen 。
自從開始進(jìn)入 embedding 階段后, FlutterActivity 主要用于實(shí)現(xiàn)了一個(gè)叫 Host 的 interface ,其中和我們有關(guān)系的就是 provideSplashScreen 。
默認(rèn)情況下它會(huì)從 AndroidManifest 文件里是否配置了 SplashScreenDrawable 來進(jìn)行判斷 。
默認(rèn)情況下當(dāng) AndroidManifest 文件里配置了 SplashScreenDrawable ,那么這個(gè) Drawable 就會(huì)在 FlutterActivity 創(chuàng)建 FlutterView 時(shí)被構(gòu)建成 DrawableSplashScreen 。
DrawableSplashScreen 其實(shí)就是一個(gè)實(shí)現(xiàn)了 io.flutter.embedding.android.SplashScreen 接口的類,它的作用就是:
之后 FlutterActivity 內(nèi)會(huì)創(chuàng)建出 FlutterSplashView ,它是個(gè) FrameLayout。
FlutterSplashView 將 FlutterView 和 ImageView 添加到一起, 然后通過 transitionToFlutter 的方法來執(zhí)行動(dòng)畫,最后動(dòng)畫結(jié)束時(shí)通過 onTransitionComplete 移除 splashScreenView 。
所以整體邏輯就是:
當(dāng)然這里也是分狀態(tài):
當(dāng)然這個(gè)階段的 FlutterActivity 也可以通過 override provideSplashScreen 方法來自定義 SplashScreen 。
看到?jīng)]有,做了這么多其實(shí)也就是為了彌補(bǔ)啟動(dòng)頁(yè)和 Flutter 渲染之間, 另外還有一個(gè)優(yōu)化,叫 NormalTheme 。
通過該配置 NormalTheme ,在 Activity 啟動(dòng)時(shí),就會(huì)首先執(zhí)行 switchLaunchThemeForNormalTheme(); 方法將主題從 LaunchTheme 切換到 NormalTheme 。
大概配置完就是如下樣子, 前面分析那么多其實(shí)就是為了告訴你,如果出現(xiàn)問題了,你可以從哪個(gè)地方去找到對(duì)應(yīng)的點(diǎn) 。
講了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被棄用了,驚不喜驚喜,意不意外,開不開心 ?
通過源碼你會(huì)發(fā)現(xiàn),當(dāng)你設(shè)置了 splashScreen 的時(shí)候,會(huì)看到一個(gè) log 警告:
為什么會(huì)棄用?
其實(shí)這個(gè)提議是在 這個(gè) issue 上,然后通過 這個(gè) pr 完成調(diào)整。
大概意思就是: 原本的設(shè)計(jì)搞復(fù)雜了,用 OnPreDrawListener 更精準(zhǔn),而且不需要為了后面 Andorid12 的啟動(dòng)支持做其他兼容,只需要給 FlutterActivity 等類增加接口開關(guān)即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 來實(shí)現(xiàn)延遲直到加載出 Flutter 的第一幀。
為什么說默認(rèn)情況? 因?yàn)檫@個(gè)行為在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才會(huì)被調(diào)用,而 RenderMode 又和 BackgroundMode 有關(guān)心 。
所以在 2.5 版本后, FlutterActivity 內(nèi)部創(chuàng)建完 FlutterView 后就會(huì)執(zhí)行一個(gè) delayFirstAndroidViewDraw 的操作。
這里主要注意一個(gè)參數(shù): isFlutterUiDisplayed 。
當(dāng) Flutter 被完成展示的時(shí)候, isFlutterUiDisplayed 就會(huì)被設(shè)置為 true。
所以當(dāng) Flutter 沒有執(zhí)行完成之前, FlutterView 的 onPreDraw 就會(huì)一直返回 false ,這也是 Flutter 2.5 開始之后適配啟動(dòng)頁(yè)的新調(diào)整。
看了這么多,大概可以看到其實(shí)開源項(xiàng)目的推進(jìn)并不是一帆風(fēng)順的,沒有什么是一開始就是最優(yōu)解,而是經(jīng)過多方嘗試和交流,才有了現(xiàn)在的版本,事實(shí)上開源項(xiàng)目里,類似這樣的經(jīng)歷數(shù)不勝數(shù):
對(duì)于滾動(dòng)的視圖,我們經(jīng)常需要監(jiān)聽它的一些滾動(dòng)事件,在監(jiān)聽到的時(shí)候去做對(duì)應(yīng)的一些事情。
比如視圖滾動(dòng)到底部時(shí),我們可能希望做上拉加載更多;
比如滾動(dòng)到一定位置時(shí)顯示一個(gè)回到頂部的按鈕,點(diǎn)擊回到頂部的按鈕,回到頂部;
比如監(jiān)聽滾動(dòng)什么時(shí)候開始,什么時(shí)候結(jié)束;
在Flutter中監(jiān)聽滾動(dòng)相關(guān)的內(nèi)容由兩部分組成:ScrollController和ScrollNotification。
ScrollController
在Flutter中,Widget并不是最終渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常這種監(jiān)聽事件以及相關(guān)的信息并不能直接從Widget中獲取,而是必須通過對(duì)應(yīng)的Widget的Controller來實(shí)現(xiàn)。
ListView、GridView的組件控制器是ScrollController,我們可以通過它來獲取視圖的滾動(dòng)信息,并且可以調(diào)用里面的方法來更新視圖的滾動(dòng)位置。
另外,通常情況下,我們會(huì)根據(jù)滾動(dòng)的位置來改變一些Widget的狀態(tài)信息,所以ScrollController通常會(huì)和StatefulWidget一起來使用,并且會(huì)在其中控制它的初始化、監(jiān)聽、銷毀等事件。
我們來做一個(gè)案例,當(dāng)滾動(dòng)到1000位置的時(shí)候,顯示一個(gè)回到頂部的按鈕:
jumpTo(double offset)、animateTo(double offset,...):這兩個(gè)方法用于跳轉(zhuǎn)到指定的位置,它們不同之處在于,后者在跳轉(zhuǎn)時(shí)會(huì)執(zhí)行一個(gè)動(dòng)畫,而前者不會(huì)。
ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動(dòng)事件。
當(dāng)前文章:flutter做壁紙,flutter設(shè)置背景圖片
轉(zhuǎn)載來源:http://m.rwnh.cn/article0/phghoo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動(dòng)態(tài)網(wǎng)站、商城網(wǎng)站、關(guān)鍵詞優(yōu)化、用戶體驗(yàn)、品牌網(wǎng)站制作、微信公眾號(hào)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)