android緩存池
『壹』 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)使用內存緩存和磁碟緩存。
『貳』 談談RecyclerView中的緩存
Android深入理解RecyclerView的緩存機制
RecyclerView在項目中的使用已經很普遍了,可以說是項目中最高頻使用的一個控制項了。除了布局靈活性、豐富的動畫,RecyclerView還有優秀的緩存機制,本文嘗試通過源碼深入了解一下RecyclerView中的緩存機制。
RecyclerView做性能優化要說復雜也復雜,比如說布局優化,緩存,預載入等等。其優化的點很多,在這些看似獨立的點之間,其實存在一個樞紐:Adapter。因為所有的ViewHolder的創建和內容的綁定都需要經過Adaper的兩個函數onCreateViewHolder和onBindViewHolder。
因此我們性能優化的本質就是要減少這兩個函數的調用時間和調用的次數。如果我們想對RecyclerView做性能優化,必須清楚的了解到我們的每一步操作背後,onCreateViewHolder和onBindViewHolder調用了多少次。因此,了解RecyclerView的緩存機制是RecyclerView性能優化的基礎。
為了理解緩存的應用場景,本文首先會簡單介紹一下RecyclerView的繪制原理,然後再分析其緩存實現原理。
RecyclerView滑動時會觸發onTouchEvent#onMove,回收及復用ViewHolder在這里就會開始。我們知道設置RecyclerView時需要設置LayoutManager,LayoutManager負責RecyclerView的布局,包含對ItemView的獲取與復用。以LinearLayoutManager為例,當RecyclerView重新布局時會依次執行下面幾個方法:
上述的整個調用鏈:onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(),getViewForPosition()即是是從RecyclerView的回收機制實現類Recycler中獲取合適的View,下面主要就來從看這個Recycler#getViewForPosition()的實現。
上述邏輯用流程圖表示:
RecyclerView在Recyler裡面實現ViewHolder的緩存,Recycler裡面的實現緩存的主要包含以下5個對象:
public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
RecyclerView在設計的時候講上述5個緩存對象分為了3級。每次創建ViewHolder的時候,會按照優先順序依次查詢緩存創建ViewHolder。每次講ViewHolder緩存到Recycler緩存的時候,也會按照優先順序依次緩存進去。三級緩存分別是:
使用自定義ViewCacheExtension後,view離屏後再回來不會走onBindViewHolder()方法。
holder.setIsRecyclable(false),這樣的話每次都會走onCreateViewHolder()和onBindViewHolder()方法
1.提前初始化viewHolder,放到緩存池中
viewPool.putRecycledView(adapter.onCreateViewHolder(recyclerView, 1))
2.提前初始化view,在onCreateViewHolder的時候去取view
3.自定義ViewCacheExtension
4.適當的增加cacheSize
4.公用緩存池,比如多個viewPager+fragment場景使用,或者全局單利緩存池,感覺用戶不大。
有2中做法有值
第一種
第二種
不會,因為prefetch(GapWorker中的一個方法)之後mViewCacheMax會變成mRequestedCacheMax + extraCache
有2種方式可以讓緩存失效
第一種
recyclerView.setItemViewCacheSize(-1)
第二種
recyclerView.setItemViewCacheSize(0)
layoutManager.isItemPrefetchEnabled = false
設置不緩存後,來回滑動讓view進入屏幕離開屏幕,viewHolder的item時會多次走onBindViewHolder()方法。
『叄』 RecyclerView緩存原理及優化方向
Android新增的Recyclerview主要用於代替ListView。Recyclerview可擴展性強。
RecyclerView做性能優化要說復雜也復雜,比如說布局優化,緩存,預載入等等。
其優化的點很多,在這些看似獨立的點之間,其實存在一個樞紐:Adapter。
因為所有的ViewHolder的創建和內容的綁定都需要經過Adaper的兩個函數 onCreateViewHolder和onBindViewHolder 。
因此我們性能優化的本質就是要 **減少這兩個函數的調用時間和調用的次數** 。
如果我們想對RecyclerView做性能優化,必須清楚的了解到我們的每一步操作背後,onCreateViewHolder和onBindViewHolder調用了多少次。
因此,了解RecyclerView的緩存機制是RecyclerView性能優化的基礎。
為了簡化問題,繪制原理介紹提供以下假設:
(1)類的職責介紹
LayoutManager:接管RecyclerView的Measure,Layout,Draw的過程
Recycler:緩存池
Adapter:ViewHolder的生成器和內容綁定器。
(2)繪制過程簡介
RecyclerView緩存基本上是通過三個內部類管理的, Recycler 、 RecycledViewPool 和 ViewCacheExtension 。
Recycler
用於管理已經廢棄或者與RecyclerView分離的ViewHolder,為了方便理解這個類,整理了下面的資料,內部類的成員變數和他們的含義:
RecycledViewPool
RecycledViewPool類是用來緩存Item用,是一個ViewHolder的緩存池,如果多個RecyclerView之間用 setRecycledViewPool(RecycledViewPool) 設置同一個RecycledViewPool,他們就可以共享Item。
其實RecycledViewPool的內部維護了一個Map,裡面以不同的viewType為Key存儲了各自對應的ViewHolder集合。可以通過提供的方法來修改內部緩存的Viewholder。
ViewCacheExtension
開發者可自定義的一層緩存,是虛擬類ViewCacheExtension的一個實例,開發者可實現方法getViewForPositionAndType(Recycler recycler, int position, int type)來實現自己的緩存。
屏幕內緩存指在屏幕中顯示的ViewHolder,這些ViewHolder會緩存在mAttachedScrap、mChangedScrap中 :
當列表滑動出了屏幕時,ViewHolder會被緩存在 mCachedViews ,其大小由mViewCacheMax決定,默認DEFAULT_CACHE_SIZE為2,可通過Recyclerview.setItemViewCacheSize()動態設置。
可以自己實現ViewCacheExtension類實現自定義緩存,可通過Recyclerview.setViewCacheExtension()設置。
ViewHolder在首先會緩存在 mCachedViews 中,當超過了個數(比如默認為2), 就會添加到 RecycledViewPool 中。
在有限的mCachedViews中如果存不下ViewHolder時,就會把ViewHolder存入RecyclerViewPool中。
* 按照Type來查找ViewHolder
* 每個Type默認最多緩存5個
RecycledViewPool 會根據每個ViewType把ViewHolder分別存儲在不同的列表中,每個ViewType最多緩存DEFAULT_MAX_SCRAP = 5 個ViewHolder,如果RecycledViewPool沒有被多個RecycledView共享,對於線性布局,每個ViewType最多隻有一個緩存,如果是網格有多少行就緩存多少個。
Recyclerview在獲取ViewHolder時按四級緩存的順序查找,如果沒找到就創建。其中只有RecycledViewPool找到時才會調用 bindViewHolder,其它緩存不會重新bindViewHolder 。
通過了解RecyclerView的四級緩存,我們可以知道,RecyclerView最多可以緩存 N(屏幕最多可顯示的item數) + 2 (屏幕外的緩存) + 5*M (M代表M個ViewType,緩存池的緩存),只有RecycledViewPool找到時才會重新調用 bindViewHolder。
RecyclerView在Recyler裡面實現ViewHolder的緩存,Recycler裡面的實現緩存的主要包含以下5個對象:
RecyclerView在設計的時候講上述5個緩存對象分為了3級。
每次創建ViewHolder的時候,會按照優先順序依次查詢緩存創建ViewHolder 。
三級緩存分別是:
RecyclerView緩存原理,有圖有真相
關於Recyclerview的緩存機制的理解
『肆』 android開發三大框架
XUtil框架、volley、ImageLoader框架。XUtil框架:Android中的orm框架,一行代碼就可以進行增刪改查;支持事務,默認關閉;可通過註解自定型行譽義表名,列名,外鍵,唯一性約束,NOT NULL約束,CHECK約束等(需要混淆的時候請註解表名和列名)。
XUtil框架、volley、ImageLoader框架。
1、XUtil框架:
主要有四大模塊:
(1) 資料庫模塊:Android中的orm框架,一行代碼就可以進行增刪改查;支持事務,默認關閉;可通過註解自定義表名,列名,外鍵,唯一性約束,NOT NULL約束,CHECK約束等(需要混淆的時候請註解表名和列名);支持綁定外鍵,保存實體時外鍵關聯實體自動保存或更新;自動載入外鍵關聯實體,支持延時載入;支持鏈式表達查詢,更直觀的查詢語義,參考下面的介紹或sample中的例子。
(2)註解模塊卜段:android中的ioc框架,完全註解帶做方式就可以進行UI,資源和事件綁定;新的事件綁定方式,使用混淆工具混淆後仍可正常工作;目前支持常用的20種事件綁定,參見ViewCommonEventListener類和包com.lidroid.xutils.view.annotation.event。
(3)網路模塊:支持同步,非同步方式的請求;支持大文件上傳,上傳大文件不會oom;支持GET,POST,PUT,MOVE,COPY,DELETE,HEAD,OPTIONS,TRACE,CONNECT請求;下載支持301/302重定向,支持設置是否根據Content-Disposition重命名下載的文件;返迴文本內容的請求(默認只啟用了GET請求)支持緩存,可設置默認過期時間和針對當前請求的過期時間。
(4)圖片緩存模塊:載入bitmap的時候無需考慮bitmap載入過程中出現的oom和android容器快速滑動時候出現的圖片錯位等現象;支持載入網路圖片和本地圖片;內存管理使用lru演算法,更好的管理bitmap內存;可配置線程載入線程數量,緩存大小,緩存路徑,載入顯示動畫等…
2、volley:JSON,圖像等的非同步下載;網路請求的排序(scheling)網路請求的優先順序處理緩存多級別取消請求和Activity和生命周期的聯動(Activity結束時同時取消所有網路請求)。
3、ImageLoader框架:支持多線程圖片載入。提供豐富的細節配置,比如線程池大小,HTPP請求項,內存和磁碟緩存,圖片顯示時的參數配置等等;提供雙緩存,支持載入過程的監聽;提供圖片的個性化顯示配置介面。
『伍』 Android 【手撕Glide】--Glide緩存機制(面試)
本文源碼解析基於Glide 4.6.1
系列文章
Android 【手撕Glide】--Glide緩存機制
Android 【手撕Glide】--Glide緩存機制(面試)
Android 【手撕Glide】--Glide是如何關聯生命周期的?
Glide緩存分為內存緩存和磁碟緩存,其中內存緩存是由弱引用+LruCache組成。
取的順序是:弱引用、LruCache、磁碟
存的順序是:磁碟、弱引用、LruCache
這張親手製作的圖片,方便大家更直觀的理解緩存機制的整體流程,結合文末總結效果更佳。喜歡的記得點贊!
概述
1、弱引用是由這樣一個HashMap維護,key是緩存的key,這個key由圖片url、width、height等10來個參數組成;value是圖片資源對象的弱引用形式。
2、LruCache是由一個LinkedHashMap維護,根據Lru演算法來管理圖片。大致的原理是利用linkHashMap鏈表的特性,把最近使用過的文件插入到列表頭部,沒使用的圖片放在尾部;然後當圖片大小到達預先設置的一個閥值的時候 ,按演算法刪除列表尾部的部分數據。由於篇幅有限,這里不講解LruCache和DiskLruCache的底層原理,這里推薦一篇 圖解LinkedHashMap原理
這是Glide自定義的LruCache
存取原理
取數據
在內存緩存中有一個概念叫圖片引用計數器 ,具體來說是在 EngineResource 中定義一個 acquired 變數用來記錄圖襪指正片被引用的次數,調用 acquire() 方法會讓變數加1,調用 release() 方法會讓變數減1。
獲取圖片資源是先從弱引用取緩存,拿到的話,引用計數+1;沒有的話從LruCache中拿緩存,拿到的話,引用計數逗枝也是+1,同時把圖片從LruCache緩存轉移到弱應用緩存池中;再沒有的話就通過 EngineJob 開啟線程池去載入圖片,拿到的話,引用計數也是+1,會把圖片放到弱引用。
存數據
很明顯,這是載入圖片之後的事情。通過 EngineJob 開啟線程池去載入圖片,取到數據之後,會回調到主線程,把圖片存到弱引用。當圖片不再使用的時候,比如說暫停請求或者載入完畢或者清除資源時,就會將其從弱引用中轉移到告悔 LruCache 緩存池中。 總結一下,就是正在使用中的圖片使用 弱引用 來進行緩存,暫時不用的圖片使用 LruCache 來進行緩存的功能;同一張圖片只會出現在 弱引用 和 LruCache 中的一個。
為什麼要引入軟引用?
1、分壓策略,減少Lrucache 中 trimToSize 的概率。如果正在remove的是張大圖,lrucache正好處在臨界點,此時remove操作,將延緩Lrucache的 trimToSize 操作;
2 提高效率:弱引用用的是 HashMap ,Lrucache用的是 LinkedHashMap ,從訪問效率而言,肯定是 HashMap 更高。
Glide磁碟緩存策略(4.x)
如果在內存緩存中沒獲取到數據會通過 EngineJob 開啟線程池去載入圖片,這里有2個關鍵類: DecodeJob 和 EngineJob 。 EngineJob 內部維護了線程池,用來管理資源載入,當資源載入完畢的時候通知回調; DecodeJob 是線程池中的一個任務。
磁碟緩存是通過 DiskLruCache 來管理的,根據緩存策略,會有2種類型的圖片, DATA (原始圖片)和 RESOURCE (轉換後的圖片)。磁碟緩存依次通過 ResourcesCacheGenerator 、 SourceGenerator 、 DataCacheGenerator 來獲取緩存數據。 ResourcesCacheGenerator 獲取的是轉換過的緩存數據; SourceGenerator 獲取的是未經轉換的原始的緩存數據; DataCacheGenerator 是通過網路獲取圖片數據再按照按照緩存策略的不同去緩存不同的圖片到磁碟上。
Glide緩存分為 弱引用+ LruCache+ DiskLruCache ,其中讀取數據的順序是:弱引用 > LruCache > DiskLruCache>網路;寫入緩存的順序是:網路 --> DiskLruCache--> LruCache-->弱引用
內存緩存分為弱引用的和 LruCache ,其中正在使用的圖片使用弱引用緩存,暫時不使用的圖片用 LruCache緩存,這一點是通過 圖片引用計數器(acquired變數)來實現的,詳情可以看內存緩存的小結。
磁碟緩存就是通過DiskLruCache實現的,根據緩存策略的不同會獲取到不同類型的緩存圖片。它的邏輯是:先從轉換後的緩存中取;沒有的話再從原始的(沒有轉換過的)緩存中拿數據;再沒有的話就從網路載入圖片數據,獲取到數據之後,再依次緩存到磁碟和弱引用。
參考:
面試官:簡歷上最好不要寫Glide,不是問源碼那麼簡單
原來面試的時候寫精通Glide,這樣問我這樣答
『陸』 Android線程池ThreadPoolExecutor詳解
傳統的多線程是通過繼承Thread類及實現Runnable介面來實現的,每次創建及銷毀線程都會消耗資源、響應速度慢,且線程缺乏統一管理,容易出現阻塞的情況,針對以上缺點,線程池就出現了。
線程池是一個創建使用線程並能保存使用過的線程以達到復用的對象,簡單的說就是一塊緩存了一定數量線程的區域。
1.復用線程:線程執行完不會立刻退出,繼續執行其他線程;
2.管理線程:統一分配、管理、控制最大並發數;
1.降低因頻繁創建&銷毀線程帶來的性能開銷,復用緩存在線程池中的線程;
2.提高線程執行效率&響應速度,復用線程:響應速度;管理線程:優化線程執行順序,避免大量線程搶占資源導致阻塞現象;
3.提高對線程的管理度;
線程池的使用也比較簡單,流程如下:
接下來通過源碼來介紹一下ThreadPoolExecutor內部實現及工作原理。
線程池的最終實現類是ThreadPoolExecutor,通過實現可以一步一步的看到,父介面為Executor:
其他的繼承及實現關系就不一一列舉了,直接通過以下圖來看一下:
從構造方法開始看:
通過以上可以看到,在創建ThreadPoolExecutor時,對傳入的參數是有要求的:corePoolSize不能小於0;maximumPoolSize需要大於0,且需要大於等於corePoolSize;keepAliveTime大於0;workQueue、threadFactory都不能為null。
在創建完後就需要執行Runnable了,看以下execute()方法:
在execute()內部主要執行的邏輯如下:
分析點1:如果當前線程數未超過核心線程數,則將runnable作為參數執行addWorker(),true表示核心線程,false表示非核心線程;
分析點2:核心線程滿了,如果線程池處於運行狀態則往workQueue隊列中添加任務,接下來判斷是否需要拒絕或者執行addWorker();
分析點3:以上都不滿足時 [corePoolSize=0且沒有運行的線程,或workQueue已經滿了] ,執行addWorker()添加runnable,失敗則執行拒絕策略;
總結一下:線程池對線程創建的管理,流程圖如下:
在執行addWorker時,主要做了以下兩件事:
分析點1:將runnable作為參數創建Worker對象w,然後獲取w內部的變數thread;
分析點2:調用start()來啟動thread;
在addWorker()內部會將runnable作為參數傳給Worker,然後從Worker內部讀取變數thread,看一下Worker類的實現:
Worker實現了Runnable介面,在Worker內部,進行了賦值及創建操作,先將execute()時傳入的runnable賦值給內部變數firstTask,然後通過ThreadFactory.newThread(this)創建Thread,上面講到在addWorker內部執行t.start()後,會執行到Worker內部的run()方法,接著會執行runWorker(this),一起看一下:
前面可以看到,runWorker是執行在子線程內部,主要執行了三件事:
分析1:獲取當前線程,當執行shutdown()時需要將線程interrupt(),接下來從Worker內部取到firstTask,即execute傳入的runnable,接下來會執行;
分析2:while循環,task不空直接執行;否則執行getTask()去獲取,不為空直接執行;
分析3:對有效的task執行run(),由於是在子線程中執行,因此直接run()即可,不需要start();
前面看到,在while內部有執行getTask(),一起看一下:
getTask()是從workQueue內部獲取接下來需要執行的runnable,內部主要做了兩件事:
分析1:先獲取到當前正在執行工作的線程數量wc,通過判斷allowCoreThreadTimeOut[在創建ThreadPoolExecutor時可以進行設置]及wc > corePoolSize來確定timed值;
分析2:通過timed值來決定執行poll()或者take(),如果WorkQueue中有未執行的線程時,兩者作用是相同的,立刻返回線程;如果WorkQueue中沒有線程時,poll()有超時返回,take()會一直阻塞;如果allowCoreThreadTimeOut為true,則核心線程在超時時間沒有使用的話,是需要退出的;wc > corePoolSize時,非核心線程在超時時間沒有使用的話,是需要退出的;
allowCoreThreadTimeOut是可以通過以下方式進行設置的:
如果沒有進行設置,那麼corePoolSize數量的核心線程會一直存在。
總結一下:ThreadPoolExecutor內部的核心線程如何確保一直存在,不退出?
上面分析已經回答了這個問題,每個線程在執行時會執行runWorker(),而在runWorker()內部有while()循環會判斷getTask(),在getTask()內部會對當前執行的線程數量及allowCoreThreadTimeOut進行實時判斷,如果工作數量大於corePoolSize且workQueue中沒有未執行的線程時,會執行poll()超時退出;如果工作數量不大於corePoolSize且workQueue中沒有未執行的線程時,會執行take()進行阻塞,確保有corePoolSize數量的線程阻塞在runWorker()內部的while()循環不退出。
如果需要關閉線程池,需要如何操作呢,看一下shutdown()方法:
以上可以看到,關閉線程池的原理:a. 遍歷線程池中的所有工作線程;b. 逐個調用線程的interrupt()中斷線程(註:無法響應中斷的任務可能永遠無法終止)
也可調用shutdownNow()來關閉線程池,二者區別:
shutdown():設置線程池的狀態為SHUTDOWN,然後中斷所有沒有正在執行任務的線程;
shutdownNow():設置線程池的狀態為STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表;
使用建議:一般調用shutdown()關閉線程池;若任務不一定要執行完,則調用shutdownNow();
總結一下:ThreadPoolExecutor在執行execute()及shutdown()時的調用關系,流程圖如下:
線程池可以通過Executors來進行不同類型的創建,具體分為四種不同的類型,如下:
可緩存線程池:不固定線程數量,且支持最大為Integer.MAX_VALUE的線程數量:
1、線程數無限制
2、有空閑線程則復用空閑線程,若無空閑線程則新建線程
3、一定程度上減少頻繁創建/銷毀線程,減少系統開銷
固定線程數量的線程池:定長線程池
1、可控制線程最大並發數(同時執行的線程數)
2、超出的線程會在隊列中等待。
單線程化的線程池:可以理解為線程數量為1的FixedThreadPool
1、有且僅有一個工作線程執行任務
2、所有任務按照指定順序執行,即遵循隊列的入隊出隊規則
定時以指定周期循環執行任務
一般來說,等待隊列 BlockingQueue 有: ArrayBlockingQueue 、 LinkedBlockingQueue 與 SynchronousQueue 。
假設向線程池提交任務時,核心線程都被佔用的情況下:
ArrayBlockingQueue :基於數組的阻塞隊列,初始化需要指定固定大小。
當使用此隊列時,向線程池提交任務,會首先加入到等待隊列中,當等待隊列滿了之後,再次提交任務,嘗試加入隊列就會失敗,這時就會檢查如果當前線程池中的線程數未達到最大線程,則會新建線程執行新提交的任務。所以最終可能出現後提交的任務先執行,而先提交的任務一直在等待。
LinkedBlockingQueue :基於鏈表實現的阻塞隊列,初始化可以指定大小,也可以不指定。
當指定大小後,行為就和 ArrayBlockingQueue一致。而如果未指定大小,則會使用默認的 Integer.MAX_VALUE 作為隊列大小。這時候就會出現線程池的最大線程數參數無用,因為無論如何,向線程池提交任務加入等待隊列都會成功。最終意味著所有任務都是在核心線程執行。如果核心線程一直被占,那就一直等待。
SynchronousQueue :無容量的隊列。
使用此隊列意味著希望獲得最大並發量。因為無論如何,向線程池提交任務,往隊列提交任務都會失敗。而失敗後如果沒有空閑的非核心線程,就會檢查如果當前線程池中的線程數未達到最大線程,則會新建線程執行新提交的任務。完全沒有任何等待,唯一制約它的就是最大線程數的個數。因此一般配合Integer.MAX_VALUE就實現了真正的無等待。
但是需要注意的是, 進程的內存是存在限制的,而每一個線程都需要分配一定的內存。所以線程並不能無限個。
『柒』 Recyclerview多種場景下的優化
因為APP設計的原因,Recyclerview是我在Android中最常用的組件,我們公司的APP幾乎每一個頁面都會包含至少一個Recyclerview,本篇文章主要介紹一些我個人在工作中總結、收集的recyclerview優化經驗。
1.不要在onBindViewHolder中設置點擊事件和耗時操作
Recyclerview的onBindViewHoler主要負責將數據與holder綁定,它在列表滑動時會不停的被調用。如果在onBindViewHolder中設定監聽操作,會導致已經的綁定點擊事件的view,被重復綁定監聽操作。
點擊事件的監聽可以在onCreateViewHolder中設定。一些會創建新對象的操作,也需要根據實際情況考慮從onBindViewHolder中遷移到onCreateViewHolder。
注意: onBindViewHolder運行在UI線程中,如果進行了耗時操作,會導致頁面卡頓。並且onBindViewHolder中只應該進行數據的綁定,而不應該進行數據的處理和計算等操作。
2.Recyclerview嵌套Recyclerview的優化
Recyclerview嵌套Recyclerview最經典的運用就是,一個縱向滑動的列表內部的每個item是一個可以橫向滑動的Recyclerview,比如說GooglePlay。
這種情況可以使用LinearLayoutManager.setInitialPrefetchItemCount()設定
橫向列表初次顯示時可見item的個數。如果橫向滑動的View中數據量很少,並且不需要橫向刷新時,也可以考慮使用HorizontalScrollView實現。
關於這個API的官方文檔翻譯如下:
如果將此值設置為大於此視圖中可見的視圖數可能會導致不必要的綁定工作,並且會增加創建和活動使用的視圖數。
3.多個RecycerView共用RecycledViewPool
在大多數APP中都有這樣一種場景,一個ViewPager中包含多個Fragment,而Fragment中主體是Recyclerview,並且Recyclerview中item view的布局是相同的。例如 微博等
這種情況下,Recyclerview可以設定統一的緩存池用來提高性能。
新建緩存池:
設定緩存池:
4.精確的更新數據使用DiffUtil
在Recyclerview中提供了多種數據數據刷新方式
雖然有了這些刷新方式,但是實際開發中,存在這樣一種情況,新數據集與舊數據集僅有一部分數據存在差異。
例如:刷新一個聯系人列表,聯系人列表中部分聯系人的頭像有變化,但是姓名和手機號碼等信息未發生變化。這種情況以往都是使用notifyDataChanged方法刷新全部數據,但是刷新全部數據的會導致整個布局重繪,Recyclerview中針對這種情況還提供了另一種粒度更小的刷新方式 DiffUti
這里不打算去講DiffUtil的具體用法,只需要記住DiffUtil的使用場景即可: 列表中存在多個Item的數據需要刷新,但是新數據集與舊數據集存在重復的情況
5.靈活設定setHasFixedSize
在Recyclerview中使用以下方法時,會觸發requestLayout()
requestLayout()會重新計算item的大小,如果item的布局文件已經將寬高設為固定大小,可以設定setHasFixedSize(true),來避免Recyclerview重新計算item的大小。
6.優化Item的布局
布局優化是個老生長談的問題,本質上就是減少嵌套,ConstraintLayout是google推出的一個用於減少布局嵌套的新layout,但是在Recyclerview中使用ConstraintLayout會導致cpu使用率上升,暫時不推薦使用,不過ConstraintLayout 2.0版本已經進入beta測試,期待後續會有優化。
7.數據非常少時,使用ListView
不知道你有沒有考慮過這樣的問題,RecyclerView用已經如此強大,用得人也越來越多,為什麼最新的Android系統中ListView依然沒有被標注為"過時"。
RecyclerView重新設計了緩存機制,新的緩存機制更加強大,更適合處理大量數據的顯示,但是這種緩存機制會占據更多的內存,當一個列表頁面實際數據項非常少的時候,ListView往往比RecyclerView更合適。