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 網路請求列表返回邏輯處理
我們在網路請求時,總有分頁載入等,處理業務邏輯也是比較混亂的,容易出現各種Bug,下面我這種模式用了很久,記錄一下,有任何問題,歡迎指正。
採用 SmartRefreshLayout框架,下拉刷新採用autoRefresh(),上拉載入更多是根據addOnScrollListener()自定義寫的。
注意:什麼時候載入更多,完全可以自定義。 (本文是滑動到數據一半,開始載入更多。)
1、開始請求數據
2、載入更多請求
3、數據請求完成處理:
4、布局的顯示和隱藏:
採用 SmartRefreshLayout框架,下拉刷新採用autoRefresh(),上拉載入更多採用setEnableAutoLoadMore()。
注意:setEnableAutoLoadMore()只有滑到底部才會載入第二頁。
『叄』 [Android] DataBinding 布局裡include的databinding刷新不生效
xml布局中,通過include嵌入其他綁定ViewModel的databinding布局,如:
通過刷新headerVM中的ObservableField的方式刷新布局,發現更新未生效。
普通的控制項,通過綁定ViewModel的ObservableField的方式,可以實時刷新,如:
更新數據後,重新綁定include的布局和ViewModel並執行databinding的executePendingBindings()方法。
『肆』 Android性能優化總結
常用的Android性能優化方法:
一、布局優化:
1)盡量減少布局文件的層級。
層級少了,繪制的工作量也就少了,性能自然提高。
2)布局重用 <include標簽>
3)按需載入:使用ViewStub,它繼承自View,一種輕量級控制項,本身不參與任何的布局和繪制過程。他的layout參數里添加一個替換的布局文件,當它通過setVisibility或者inflate方法載入後,它就會被內部布局替換掉。
二、繪制優化:
基於onDraw會被調用多次,該方法內要避免兩類操作:
1)創建新的局部對象,導致大量垃圾對象的產生,從而導致頻繁的gc,降低程序的執行效率。
2)不要做耗時操作,搶CPU時間片,造成繪制很卡不流暢。
三、內存泄漏優化:
1)靜態變數導致內存泄漏 比較明顯
2)單例模式導致的內存泄漏 單例無法被垃圾回收,它持有的任何對象的引用都會導致該對象不會被gc。
3)屬性動畫導致內存泄漏 無限循環動畫,在activity中播放,但是onDestroy時沒有停止的話,動畫會一直播放下去,view被動畫持有,activity又被view持有,導致activity無法被回收。
四、響應速度優化:
1)避免在主線程做耗時操作 包括四大組件,因為四大組件都是運行在主線程的。
2)把一些創建大量對象等的初始化工作放在頁面回到前台之後,而不應該放到創建的時候。
五、ListView的優化:
1)使用convertView,走listView子View回收的一套:RecycleBin 機制
主要是維護了兩個數組,一個是mActiveViews,當前可見的view,一個是mScrapViews,當前不可見的view。當觸摸ListView並向上滑動時,ListView上部的一些OnScreen的View位置上移,並移除了ListView的屏幕范圍,此時這些OnScreen的View就變得不可見了,不可見的View叫做OffScreen的View,即這些View已經不在屏幕可見范圍內了,也可以叫做ScrapView,Scrap表示廢棄的意思,ScrapView的意思是這些OffScreen的View不再處於可以交互的Active狀態了。ListView會把那些ScrapView(即OffScreen的View)刪除,這樣就不用繪制這些本來就不可見的View了,同時,ListView會把這些刪除的ScrapView放入到RecycleBin中存起來,就像把暫時無用的資源放到回收站一樣。
當ListView的底部需要顯示新的View的時候,會從RecycleBin中取出一個ScrapView,將其作為convertView參數傳遞給Adapter的getView方法,從而達到View復用的目的,這樣就不必在Adapter的getView方法中執行LayoutInflater.inflate()方法了。
RecycleBin中有兩個重要的View數組,分別是mActiveViews和mScrapViews。這兩個數組中所存儲的View都是用來復用的,只不過mActiveViews中存儲的是OnScreen的View,這些View很有可能被直接復用;而mScrapViews中存儲的是OffScreen的View,這些View主要是用來間接復用的。
2)使用ViewHolder避免重復地findViewById
3)快速滑動不適合做大量非同步任務,結合滑動監聽,等滑動結束之後載入當前顯示在屏幕范圍的內容。
4)getView中避免做耗時操作,主要針對圖片:ImageLoader來處理(原理:三級緩存)
5)對於一個列表,如果刷新數據只是某一個item的數據,可以使用局部刷新,在列表數據量比較大的情況下,節省不少性能開銷。
六、Bitmap優化:
1)減少內存開支:圖片過大,超過控制項需要的大小的情況下,不要直接載入原圖,而是對圖片進行尺寸壓縮,方式是BitmapFactroy.Options 采樣,inSampleSize 轉成需要的尺寸的圖片。
2)減少流量開銷:對圖片進行質量壓縮,再上傳伺服器。圖片有三種存在形式:硬碟上時是file,網路傳輸時是stream,內存中是stream或bitmap,所謂的質量壓縮,它其實只能實現對file的影響,你可以把一個file轉成bitmap再轉成file,或者直接將一個bitmap轉成file時,這個最終的file是被壓縮過的,但是中間的bitmap並沒有被壓縮。bitmap.compress(Bitmap.CompressFormat.PNG,100,bos);
七、線程優化:
使用線程池。為什麼要用線程池?
1、從「為每個任務分配一個線程」轉換到「在線程池中執行任務」
2、通過重用現有的線程而不是創建新線程,可以處理多個請求在創建銷毀過程中產生的巨大開銷
3、當使用線程池時,在請求到來時間 ,不用等待系統重新創建新的線程,而是直接復用線程池中的線程,這樣可以提高響應性。
4、通過和適當調整線程池的大小 ,可以創建足夠多的線程以使處理器能夠保持忙碌狀態,同時還可以防止過多線程相互競爭資源而使應用程序耗盡內存或者失敗。
5、一個App裡面所有的任務都放在線程池中執行後,可以統一管理 ,當應用退出時,可以把程序中所有的線程統一關閉,避免了內存和CPU的消耗。
6、如果這個任務是一個循環調度任務,你則必須在這個界面onDetach方法把這個任務給cancel掉,如果是一個普通任務則可cancel,可不cancel,但是最好cancel
7、整個APP的總開關會在應用退出的時間把整個線程池全部關閉。
八、一些性能優化建議:
1)避免創建過多對象,造成頻繁的gc
2)不要過多使用枚舉,枚舉佔用的空間比整型大很多
3)字元串的拼接使用StringBuffer、StringBuilder來替代直接使用String,因為使用String會創建多個String對象,參考第一條。
4)適當使用軟引用,(弱引用就不太推薦了)
5)使用內存緩存和磁碟緩存。
『伍』 android上拉刷新下拉載入 通用框架怎麼用
1. 關於下拉刷新
下拉刷新這種用戶交互最早由twitter創始人洛倫•布里切特(Loren Brichter)發明,有理論認為,下拉刷新是一種適用於按照從新到舊的時間順序排列feeds的應用,在這種應用場景中看完舊的內容時,用戶會很自然地下拉查找更新的內容,因此下拉刷新就顯得非常合理。大家可以參考這篇文章:有趣的下拉刷新,下面我貼出一個有趣的下拉刷新的案例。
2. 實現原理
上面這些例子,外觀做得再好看,他的本質上都一樣,那就是一個下拉刷新控制項通常由以下幾部分組成:
【1】Header
Header通常有下拉箭頭,文字,進度條等元素,根據下拉的距離來改變它的狀態,從而顯示不同的樣式
【2】Content
這部分是內容區域,網上有很多例子都是直接在ListView裡面添加Header,但這就有局限性,因為好多情況下並不一定是用ListView來顯示數據。我們把要顯示內容的View放置在我們的一個容器中,如果你想實現一個用ListView顯示數據的下拉刷新,你需要創建一個ListView旋轉到我的容器中。我們處理這個容器的事件(down, move, up),如果向下拉,則把整個布局向下滑動,從而把header顯示出來。
【3】Footer
Footer可以用來顯示向上拉的箭頭,自動載入更多的進度條等。