A. Android:深入剖析圖片載入庫Glide緩存功能(源碼分析)
Glide 需要緩存的 圖片資源 分為兩類:
Glide 的緩存機制使得 Glide 具備非常好的圖片緩存效果,從而使得具備較高的圖片載入效率。
下面,我將根據 Glide 緩存流程中的每個步驟 進行源碼分析。
至此, Glide 的圖片緩存 Key 生成完畢。
至此,創建好了緩存對象 LruResourceCache
即:
源碼分析如下:
若上述兩個方法都沒獲取到緩存圖片時(即內存緩存里沒有該圖片的緩存),就開啟新線程載入圖片。
若無法從 內存緩存 里 獲得緩存的圖片, Glide 就會採用第2級緩存:磁碟緩存 去獲取緩存圖片
寫入 內存緩存分為:寫入 弱引用緩存 & LruCache 演算法的緩存
寫入 LruCache 演算法 內存緩存的原理:包含圖片資源 resource 的 EngineResource 對象的一個引用機制:
所以:
至此,實現了:
至此, Glide 的圖片緩存流程解析完畢。
Android圖片載入的那些事:為什麼你的Glide 緩存沒有起作用?
不定期分享關於 安卓開發 的干貨,追求 短、平、快 ,但 卻不缺深度 。
B. 關於VC++雙緩沖的問題
在圖形圖象處理編程過程中,雙緩沖是一種基本的技術。我們知道,如果窗體在響應WM_PAINT消息的時候要進行復雜的圖形處理,那麼窗體在重繪時由於過頻的刷新而引起閃爍現象。解決這一問題的有效方法就是雙緩沖技術。
因為窗體在刷新時,總要有一個擦除原來圖象的過程OnEraseBkgnd,它利用背景色填充窗體繪圖區,然後在調用新的繪圖代碼進行重繪,這樣一擦一寫造成了圖象顏色的反差。當WM_PAINT的響應很頻繁的時候,這種反差也就越發明顯。於是我們就看到了閃爍現象。
我們會很自然的想到,避免背景色的填充是最直接的辦法。但是那樣的話,窗體上會變的一團糟。因為每次繪制圖象的時候都沒有將原來的圖象清除,造 成了圖象的殘留,於是窗體重繪時,畫面往往會變的亂七八糟。所以單純的禁止背景重繪是不夠的。我們還要進行重新繪圖,但要求速度很快,於是我們想到了使用 BitBlt函數。它可以支持圖形塊的復制,速度很快。我們可以先在內存中作圖,然後用此函數將做好的圖復制到前台,同時禁止背景刷新,這樣就消除了閃 爍。以上也就是雙緩沖繪圖的基本的思路。
先按普通做圖的方法進行編程。即在視類的OnDraw函數中添加繪圖代碼。在此我們繪制若干同心圓,代碼如下:
CBCDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CPoint ptCenter;
CRect rect,ellipseRect;
GetClientRect(&rect);
ptCenter = rect.CenterPoint();
for(int i=20;i>0;i--)
{
ellipseRect.SetRect(ptCenter,ptCenter);
ellipseRect.InflateRect(i*10,i*10);
pDC->Ellipse(ellipseRect);
}
編譯運行程序,嘗試改變窗口大小,可以發現閃爍現象。
在雙緩沖方法中,首先要做的是屏蔽背景刷新。背景刷新其實是在響應WM_ERASEBKGND消息。我們在視類中添加對這個消息的響應,可以看到預設的代碼如下:
BOOL CMYView::OnEraseBkgnd(CDC* pDC)
{
return CView::OnEraseBkgnd(pDC);
}
是調用父類的OnEraseBkgnd函數,我們屏蔽此調用,只須直接return TRUE;即可。
下面是內存緩沖作圖的步驟.
CPoint ptCenter;
CRect rect,ellipseRect;
GetClientRect(&rect);
ptCenter = rect.CenterPoint();
CDC dcMem; //用於緩沖作圖的內存DC
CBitmap bmp; //內存中承載臨時圖象的點陣圖
dcMem.CreateCompatibleDC(pDC); //依附窗口DC創建兼容內存DC
bmp.CreateCompatibleBitmap(&dcMem,rect.Width(),rect.Height());//創建兼容點陣圖
dcMem.SelectObject(&bmp); //將點陣圖選擇進內存DC
dcMem.FillSolidRect(rect,pDC->GetBkColor());//按原來背景填充客戶區,不然會是黑色
for(int i=20;i>0;i--) //在內存DC上做同樣同心圓圖象
{
ellipseRect.SetRect(ptCenter,ptCenter);
ellipseRect.InflateRect(i*10,i*10);
dcMem.Ellipse(ellipseRect);
}
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&dcMem,0,0,SRCCOPY);//將內存DC上的圖象拷貝到前台
dcMem.DeleteDC(); //刪除DC
bm.DeleteObject(); //刪除點陣圖
由於復雜的畫圖操作轉入後台,我們看到的是速度很快的復制操作,自然也就消除了閃爍現象。
C. Vue—KeepAlive源碼探究,適時清理頁面緩存
使用過 Vue 的小夥伴們肯定都知道,Vue 的內部組件 keep-alive 是用來緩存我們不活動的組件的。
但是在某些情況下,我們需要緩存,某些情況下希望及時釋放掉緩存,那我們應該怎麼做呢?
有個場景是,希望模仿App的方式,每次push到當前頁面的時候,觸發 mounted 進行組件初始化,而從其他頁面返回到當前頁面時,希望可以保留當前組件的狀態。
舉個例子:
移動端的分頁列表頁面,載入了幾頁,並且有滑動記錄,但是希望進詳情頁後,返回列表頁時,可以保持分頁的狀態以及滑動的軌跡。
需求如上,這就需要 keep-alive 幫助我們緩存組件了。
但是用過 keep-alive 組件的小夥伴肯定明白,如果 include 當前列表頁面,雖然可以做到返回列表頁保持狀態,但是從其他頁面前往列表頁時,依然會載入緩存的狀態,我們不得不採用 activated 鉤子方法來處理,但這樣總歸是不優雅的。
看過一些文字有人說讓include的數組變成動態的是否能達到類似的效果,這種方式也非常好,可以很容易的達到我們想要的效果。
閱讀過源碼之後發現, keep-alive 內部實現是將組件緩存在一個 caches 數組中的,如果我們可以操作這個數組,是否可以達到靈活控制緩存組件的效果呢?
方法一比較繁瑣,但是用起來還是很直接的,比較靈活,可以應付瀏覽器刷新的場景
方法二比較簡單,適合在移動app中使用,自己維護頁面棧,不會有瀏覽器刷新等操作,否則include的內容可能會由於刷新而出現棧異常
D. 微信公眾號緩存的源代碼在哪裡找到
1、首先在你的電腦上打開微信登錄網頁版,掃碼登錄。<br>2、其次把你想看的網頁鏈接發送到微信網頁上面。<br>3、然後點擊鏈接把左上角用默認瀏覽器打開。<br>4、最後右擊查看源代碼。
E. Android圖形渲染原理上
對於Android開發者來說,我們或多或少有了解過Android圖像顯示的知識點,剛剛學習Android開發的人會知道,在Actvity的onCreate方法中設置我們的View後,再經過onMeasure,onLayout,onDraw的流程,界面就顯示出來了;對Android比較熟悉的開發者會知道,onDraw流程分為軟體繪制和硬體繪制兩種模式,軟繪是通過調用Skia來操作,硬繪是通過調用Opengl ES來操作;對Android非常熟悉的開發者會知道繪制出來的圖形數據最終都通過GraphiBuffer內共享內存傳遞給SurfaceFlinger去做圖層混合,圖層混合完成後將圖形數據送到幀緩沖區,於是,圖形就在我們的屏幕顯示出來了。
但我們所知道的Activity或者是應用App界面的顯示,只屬於Android圖形顯示的一部分。同樣可以在Android系統上展示圖像的WebView,Flutter,或者是通過Unity開發的3D游戲,他們的界面又是如何被繪制和顯現出來的呢?他們和我們所熟悉的Acitvity的界面顯示又有什麼異同點呢?我們可以不藉助Activity的setView或者InflateView機制來實現在屏幕上顯示出我們想要的界面嗎?Android系統顯示界面的方式又和IOS,或者Windows等系統有什麼區別呢?……
去探究這些問題,比僅僅知道Acitvity的界面是如何顯示出來更加的有價值,因為想要回答這些問題,就需要我們真正的掌握Android圖像顯示的底層原理,當我們掌握了底層的顯示原理後,我們會發現WebView,Flutter或者未來會出現的各種新的圖形顯示技術,原來都是大同小異。
我會花三篇文章的篇幅,去深入的講解Android圖形顯示的原理,OpenGL ES和Skia的繪制圖像的方式,他們如何使用,以及他們在Android中的使用場景,如開機動畫,Activity界面的軟體繪制和硬體繪制,以及Flutter的界面繪制。那麼,我們開始對Android圖像顯示原理的探索吧。
在講解Android圖像的顯示之前,我會先講一下屏幕圖像的顯示原理,畢竟我們圖像,最終都是在手機屏幕上顯示出來的,了解這一塊的知識會讓我們更容易的理解Android在圖像顯示上的機制。
圖像顯示的完整過程,分為下面幾個階段:
圖像數據→CPU→顯卡驅動→顯卡(GPU)→顯存(幀緩沖)→顯示器
我詳細介紹一下這幾個階段:
實際上顯卡驅動,顯卡和顯存,包括數模轉換模塊都是屬於顯卡的模塊。但為了能能詳細的講解經歷的步驟,這里做了拆分。
當顯存中有數據後,顯示器又是怎麼根據顯存裡面的數據來進行界面的顯示的呢?這里以LCD液晶屏為例,顯卡會將顯存里的數據,按照從左至右,從上到下的順序同步到屏幕上的每一個像素晶體管,一個像素晶體管就代表了一個像素。
如果我們的屏幕解析度是1080x1920像素,就表示有1080x1920個像素像素晶體管,每個橡素點的顏色越豐富,描述這個像素的數據就越大,比如單色,每個像素只需要1bit,16色時,只需要4bit,256色時,就需要一個位元組。那麼1080x1920的解析度的屏幕下,如果要以256色顯示,顯卡至少需要1080x1920個位元組,也就是2M的大小。
剛剛說了,屏幕上的像素數據是從左到右,從上到下進行同步的,當這個過程完成了,就表示一幀繪制完成了,於是會開始下一幀的繪制,大部分的顯示屏都是以60HZ的頻率在屏幕上繪制完一幀,也就是16ms,並且每次繪制新的一幀時,都會發出一個垂直同步信號(VSync)。我們已經知道,圖像數據都是放在幀緩沖中的,如果幀緩沖的緩沖區只有一個,那麼屏幕在繪制這一幀的時候,圖像數據便沒法放入幀緩沖中了,只能等待這一幀繪制完成,在這種情況下,會有很大了效率問題。所以為了解決這一問題,幀緩沖引入兩個緩沖區,即 雙緩沖機制 。雙緩沖雖然能解決效率問題,但會引入一個新的問題。當屏幕這一幀還沒繪制完成時,即屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩沖區並把兩個緩沖區進行交換後,顯卡的像素同步模塊就會把新的一幀數據的下半段顯示到屏幕上,造成畫面撕裂現象。
為了解決撕裂問題,就需要在收到垂直同步的時候才將幀緩沖中的兩個緩沖區進行交換。Android4.1黃油計劃中有一個優化點,就是CPU和GPU都只有收到垂直同步的信號時,才會開始進行圖像的繪制操作,以及緩沖區的交換工作。
我們已經了解了屏幕圖像顯示的原理了,那麼接著開始對Android圖像顯示的學習。
從上一章已經知道,計算機渲染界面必須要有GPU和幀緩沖。對於Linux系統來說,用戶進程是沒法直接操作幀緩沖的,但我們想要顯示圖像就必須要操作幀緩沖,所以Linux系統設計了一個虛擬設備文件,來作為對幀緩沖的映射,通過對該文件的I/O讀寫,我們就可以實現讀寫屏操作。幀緩沖對應的設備文件於/dev/fb* ,*表示對多個顯示設備的支持, 設備號從0到31,如/dev/fb0就表示第一塊顯示屏,/dev/fb1就表示第二塊顯示屏。對於Android系統來說,默認使用/dev/fb0這一個設幀緩沖作為主屏幕,也就是我們的手機屏幕。我們Android手機屏幕上顯示的圖像數據,都是存儲在/dev/fb0里,早期AndroidStuio中的DDMS工具實現截屏的原理就是直接讀取/dev/fb0設備文件。
我們知道了手機屏幕上的圖形數據都存儲在幀緩沖中,所以Android手機圖像界面的原理就是將我們的圖像數據寫入到幀緩沖內。那麼,寫入到幀緩沖的圖像數據是怎麼生成的,又是怎樣加工的呢?圖形數據是怎樣送到幀緩沖去的,中間經歷了哪些步驟和過程呢?了解了這幾個問題,我們就了解了Android圖形渲染的原理,那麼帶著這幾個疑問,接著往下看。
想要知道圖像數據是怎麼產生的,我們需要知道 圖像生產者 有哪些,他們分別是如何生成圖像的,想要知道圖像數據是怎麼被消費的,我們需要知道 圖像消費者 有哪些,他們又分別是如何消費圖像的,想要知道中間經歷的步驟和過程,我們需要知道 圖像緩沖區 有哪些,他們是如何被創建,如何分配存儲空間,又是如何將數據從生產者傳遞到消費者的,圖像顯示是一個很經典的消費者生產者的模型,只有對這個模型各個模塊的擊破,了解他們之間的流動關系,我們才能找到一條更容易的路徑去掌握Android圖形顯示原理。我們看看谷歌提供的官方的架構圖是怎樣描述這一模型的模塊及關系的。
如圖, 圖像的生產者 主要有MediaPlayer,CameraPrevier,NDK,OpenGl ES。MediaPlayer和Camera Previer是通過直接讀取圖像源來生成圖像數據,NDK(Skia),OpenGL ES是通過自身的繪制能力生產的圖像數據; 圖像的消費者 有SurfaceFlinger,OpenGL ES Apps,以及HAL中的Hardware Composer。OpenGl ES既可以是圖像的生產者,也可以是圖像的消費者,所以它也放在了圖像消費模塊中; 圖像緩沖區 主要有Surface以及前面提到幀緩沖。
Android圖像顯示的原理,會僅僅圍繞 圖像的生產者 , 圖像的消費者 , 圖像緩沖區 來展開,在這一篇文章中,我們先看看Android系統中的圖像消費者。
SurfaceFlinger是Android系統中最重要的一個圖像消費者,Activity繪制的界面圖像,都會傳遞到SurfaceFlinger來,SurfaceFlinger的作用主要是接收圖像緩沖區數據,然後交給HWComposer或者OpenGL做合成,合成完成後,SurfaceFlinger會把最終的數據提交給幀緩沖。
那麼SurfaceFlinger是如何接收圖像緩沖區的數據的呢?我們需要先了解一下Layer(層)的概念,一個Layer包含了一個Surface,一個Surface對應了一塊圖形緩沖區,而一個界面是由多個Surface組成的,所以他們會一一對應到SurfaceFlinger的Layer中。SurfaceFlinger通過讀取Layer中的緩沖數據,就相當於讀取界面上Surface的圖像數據。Layer本質上是 Surface和SurfaceControl的組合 ,Surface是圖形生產者和圖像消費之間傳遞數據的緩沖區,SurfaceControl是Surface的控制類。
前面在屏幕圖像顯示原理中講到,為了防止圖像的撕裂,Android系統會在收到VSync垂直同步時才會開始處理圖像的繪制和合成工作,而Surfaceflinger作為一個圖像的消費者,同樣也是遵守這一規則,所以我們通過源碼來看看SurfaceFlinger是如何在這一規則下,消費圖像數據的。
SurfaceFlinger專門創建了一個EventThread線程用來接收VSync。EventThread通過Socket將VSync信號同步到EventQueue中,而EventQueue又通過回調的方式,將VSync信號同步到SurfaceFlinger內。我們看一下源碼實現。
上面主要是SurfaceFlinger初始化接收VSYNC垂直同步信號的操作,主要有這幾個過程:
經過上面幾個步驟,我們接收VSync的初始化工作都准備好了,EventThread也開始運轉了,接著看一下EventThread的運轉函數threadLoop做的事情。
threadLoop主要是兩件事情
mConditon又是怎麼接收VSync的呢?我們來看一下
可以看到,mCondition的VSync信號實際是DispSyncSource通過onVSyncEvent回調傳入的,但是DispSyncSource的VSync又是怎麼接收的呢?在上面講到的SurfaceFlinger的init函數,在創建EventThread的實現中,我們可以發現答案—— mPrimaryDispSync 。
DispSyncSource的構造方法傳入了mPrimaryDispSync,mPrimaryDispSync實際是一個DispSyncThread線程,我們看看這個線程的threadLoop方法
DispSyncThread的threadLoop會通過mPeriod來判斷是否進行阻塞或者進行VSync回調,那麼mPeriod又是哪兒被設置的呢?這里又回到SurfaceFlinger了,我們可以發現在SurfaceFlinger的 resyncToHardwareVsync 函數中有對mPeriod的賦值。
可以看到,這里最終通過HWComposer,也就是硬體層拿到了period。終於追蹤到了VSync的最終來源了, 它從HWCompser產生,回調至DispSync線程,然後DispSync線程回調到DispSyncSource,DispSyncSource又回調到EventThread,EventThread再通過Socket分發到MessageQueue中 。
我們已經知道了VSync信號來自於HWCompser,但SurfaceFlinger並不會一直監聽VSync信號,監聽VSync的線程大部分時間都是休眠狀態,只有需要做合成工作時,才會監聽VSync,這樣即保證圖像合成的操作能和VSync保持一致,也節省了性能。SurfaceFlinger提供了一些主動注冊監聽VSync的操作函數。
可以看到,只有當SurfaceFlinger調用 signalTransaction 或者 signalLayerUpdate 函數時,才會注冊監聽VSync信號。那麼signalTransaction或者signalLayerUpdate什麼時候被調用呢?它可以由圖像的生產者通知調用,也可以由SurfaceFlinger根據自己的邏輯來判斷是否調用。
現在假設App層已經生成了我們界面的圖像數據,並調用了 signalTransaction 通知SurfaceFlinger注冊監聽VSync,於是VSync信號便會傳遞到了MessageQueue中了,我們接著看看MessageQueue又是怎麼處理VSync的吧。
MessageQueue收到VSync信號後,最終回調到了SurfaceFlinger的 onMessageReceived 中,當SurfaceFlinger接收到VSync後,便開始以一個圖像消費者的角色來處理圖像數據了。我們接著看SurfaceFlinger是以什麼樣的方式消費圖像數據的。
VSync信號最終被SurfaceFlinger的onMessageReceived函數中的INVALIDATE模塊處理。
INVALIDATE的流程如下:
handleMessageTransaction的處理比較長,處理的事情也比較多,它主要做的事情有這些
handleMessageRefresh函數,便是SurfaceFlinger真正處理圖層合成的地方,它主要下面五個步驟。
我會詳細介紹每一個步驟的具體操作
合成前預處理會判斷Layer是否發生變化,當Layer中有新的待處理的Buffer幀(mQueuedFrames>0),或者mSidebandStreamChanged發生了變化, 都表示Layer發生了變化,如果變化了,就調用signalLayerUpdate,注冊下一次的VSync信號。如果Layer沒有發生變化,便只會做這一次的合成工作,不會注冊下一次VSync了。
重建Layer棧會遍歷Layer,計算和存儲每個Layer的臟區, 然後和當前的顯示設備進行比較,看Layer的臟區域是否在顯示設備的顯示區域內,如果在顯示區域內的話說明該layer是需要繪制的,則更新到顯示設備的VisibleLayersSortedByZ列表中,等待被合成
rebuildLayerStacks中最重要的一步是 computeVisibleRegions ,也就是對Layer的變化區域和非透明區域的計算,為什麼要對變化區域做計算呢?我們先看看SurfaceFlinger對界面顯示區域的分類:
還是以這張圖做例子,可以看到我們的狀態欄是半透明的,所以它是一個opaqueRegion區域,微信界面和虛擬按鍵是完全不透明的,他是一個visibleRegion,除了這三個Layer外,還有一個我們看不到的Layer——壁紙,它被上方visibleRegion遮擋了,所以是coveredRegion
對這幾個區域的概念清楚了,我們就可以去了解computeVisibleRegions中做的事情了,它主要是這幾步操作:
F. redis源碼解讀:單線程的redis是如何實現高速緩存的
redis可能是最近幾年最火的緩存資料庫方案了,在各個高並發領域都有應用。
這篇文章,我們將從源代碼的角度來分析一下,為何如此一個高性能,高應用的緩存,會是單線程的方案,當然一個方案的高性能,高並發是多方面的綜合因素,其它的因素我們將在後續解讀。後續分析主要以LINUX操作系統為基礎,這也是redis應用最廣的平台。
單線程最大的受限是什麼?就是CPU,現在伺服器一般已經是多CPU,而單線程只能使用到其中的一個核。
redis作為一個網路內存緩存資料庫,在實現高性能時,主要有4個點。
1.網路高並發,高流量的數據處理。
一個非同步,高效,且對CPU要求不高的網路模型,這個模型主要是由OS來提供的,目前在LINUX最主流使用的是EPOLL,這個網上介紹很多,主要是基於事件驅動的一個非同步模型。
2.程序內部的合理構架,調用邏輯,內存管理。
redis在採用純C實現時,整體調用邏輯很短,但在內存方面,適當的合並了一些對象和對齊,比如sds等,在底層使用了內存池,在不同情況下使用的不太一樣。
但整體處理上沒有NGINX的內池設計巧妙,當然二者不太一樣,NGINX是基於請求釋放的邏輯來設計的,因此針對請求,可以一次申請大塊,分量使用,再最後統一釋放。
3.數據復制的代價,不管是讀取數據或是寫入數據,一般都是需要有數據復制的過程。
數據復制其實就是一次內存,真正的代價是在於存在大VALUE,當value值長度超過16KB時,性能會開始下降。因為單線程的原因,如果存在一個超大VALUE,比如20MB,則會因為這個請求卡住整個線程,導致後續的請求進不來,雖然後面的請求是能快速處理的小請求。
4.redis中數據結構中演算法的代價,有些結構在大數據量時,代價是很高的。
很多時間,大家忽略了演算法的運算代碼,因為像memcached等這類是完全的KV緩存,不存在什麼演算法,除了一個KEY的查找定位HASH演算法。
而redis不一樣,提供了不少高階的數據對象,這些對象具有上層的一些演算法能力,而這些能力是需要比如GEO模塊。