1.RecycledPool的重用
場景以及使用:
多個RecyclerView出現,并且他們的item布局結構一致,這時候可以進行重用,
在進行RecyclerView的初始化設定時候進行RecycledPool的設定,
//每個單元的視頻串列的RecycledPool
private var mRecycledViewPool: RecyclerView.RecycledViewPool? = null
unitVideoListContentRv.run {
layoutManager = GridLayoutManager(itemView.context, 3)
if (mRecycledViewPool != null) {
setRecycledViewPool(mRecycledViewPool)
} else {
mRecycledViewPool = recycledViewPool
}
...........
}
重用前后的對比:
本次展示的是長串列中的item嵌套串列,進行多item的加載然后上下滑動,同時檢測記憶體的開銷占用,

串列的規模是13個item,每個item中有4個視頻item,規模不算特別大,
重用RecycledPool之前:

資料加載完畢之后,最后上下滑動的記憶體趨于平穩在48.4m
重用RecycledPool之后:

資料加載完畢之后,最后上下滑動的記憶體趨于平穩在40m,
對比總結:
其實很明顯可以看到記憶體開銷減少,而記憶體開銷減少能提升串列的流暢度,效果是顯而易見的,這次的串列資料規模還不算大,后續如果規模增加到很大,那么對比將會更加明顯,
2.setHasFixedSize(boolean)的使用
方法的名字就表明了,設定是否有固定的尺寸,就是說RecyclerView是否有固定的尺寸,如果設定了true,那么會在以下的情景用到:
onMeasure---測量
如果設定了true,那么RecyclerView的mHasFixedSize變數為true,
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//是否允許自動測量
if (mLayout.isAutoMeasureEnabled()) {
.....
} else {
if (mHasFixedSize) { //是否有固定的尺寸
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
.........
}
}
// mLayout.onMeasure
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
void defaultOnMeasure(int widthSpec, int heightSpec) {
//直接設定固定的寬度和高度,沒有進行再次的測量
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
所以設定了這個值的時候,RecyclerView在測量的時候會有性能上的提升,
3.setHasStableIds(boolean)的使用
方法的名稱意思是設定是否有穩定的id,設定了該值為true后,ViewHolder中的mHasStableIds就為true,
StableId有三種模式:NO_STABLE_IDS、ISOLATED_STABLE_IDS、SHARED_STABLE_IDS
RecyclerView在進行Item的Remove,Insert,Change的時候會呼叫到,
如果設定了這個屬性,那么需要在Adapter中重寫getItemId(int position)方法,
這樣子在進行串列的更新時候,Adapter會根據getItemId方法回傳的long型別的id進行判斷,決定當前的item是否需要重繪,因此取代以往的全部重繪的情況,從而提高效率,
class Album{
String coverUrl;
String title;
}
@Override
public long getItemId(int position){
Album album = mListOfAlbums.get(position);
//如果回傳的id和上次不一樣,那就代表這個item發生了資料變化,則進行重繪
//如果回傳的id和上次一致,那么這個item就沒有改變,就無需重繪了,
return (album.coverUrl + album.title).hashcode();
}
4.ViewCacheExtension的使用
它是一個靜態抽象類,看類名就能大概知道view快取擴展,類中包括方法:
/**
回傳一個能系結到配接器position位置上的view
* <p>
* 此方法不應該創建新的視圖, 相反,它期望回傳一個已經創建的View,該View可以針對給定的型別和位置重 新使用, 如果將視圖示記為已忽略,則應先呼叫{@link LayoutManager#
stopIgnoringView(View)},然后再回傳視圖,
* RecyclerView將在必要時將回傳的View重新系結到該位置
*
* @param recycler The Recycler that can be used to bind the View
* @param position The adapter position
* @param type The type of the View, defined by adapter
* @return 系結到給定位置的View;如果沒有可重用的View,則為NULL
* @see LayoutManager#ignoreView(View)
*/
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
該快取為RecyclerView的第二級快取,即如果開發者設定了該快取,那么串列從CacheView中獲取不到holder,就會從ViewCacheExtension從獲取,
適用場景則為,串列有固定的數量條目和寬高,這樣子,串列初始化的時候就能直接從這級快取拿到ViewHolder,不需要再創建ViewHolder,大大節省時間,提高效率,
5.預加載
預加載功能在RecyclerView中是默認開啟的,
public boolean onTouchEvent(MotionEvent e) {
switch (action) {
case MotionEvent.ACTION_MOVE: {
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (mScrollState == SCROLL_STATE_DRAGGING) { //處于拖動狀態
........
if (mGapWorker != null && (dx != 0 || dy != 0)) { //滑動距離不等于0,
mGapWorker.postFromTraversal(this, dx, dy); //進行預取任務
}
}
}
break;
}
}
/**
* 在當前遍歷之后立即安排預取,
*/
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
if (recyclerView.isAttachedToWindow()) {
........
//第一次觸發拖動的是否將該runnable提交到Mainhandler里面,
//等待UI thread執行完成再執行預取任務
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();//獲取當前時間,記錄改次任務的開始
recyclerView.post(this); //提交當前任務
}
}
//設定預加載的坐標
recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
}
/**
* 獲取當前系統的時間,單位為納秒
*/
long getNanoTime() {
if (ALLOW_THREAD_GAP_WORK) { //在Android5.0及以上的系統中
return System.nanoTime(); //回傳正在運行的Java虛擬機的高解析度時間源的當前值,以納秒為單位
} else {
return 0; //5.0以下的系統直接回傳0
}
}
/**
在L +上,使用RenderThread,UI執行緒在將一幀傳遞給RenderThread之后但在下一幀開始之前具有空閑時間,我們 在此視窗中安排預取作業,
*/
static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
我們可以看下預加載程式的Runnable的run方法實作了什么操作,
@Override
public void run() {
try {
TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
//recyclerview嵌套的情況
if (mRecyclerViews.isEmpty()) {
// abort - no work to do
return;
}
//查詢最新的vsync,以便于我們預測下一個
//繪制時間在影片和輸入的回呼中未生效,所以在這里進行vsync的查詢是安全的
final int size = mRecyclerViews.size();
long latestFrameVsyncMs = 0;
//獲取RecyclerView最近一次開始RenderThread的時間
for (int i = 0; i < size; i++) {
RecyclerView view = mRecyclerViews.get(i);
if (view.getWindowVisibility() == View.VISIBLE) {
latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
}
}
if (latestFrameVsyncMs == 0) {
//終止,沒有任何視圖可見,或者無法獲得最新的vsync用于估計下一個
return;
}
//計算下一幀的時間,等于最新一幀的時間加上幀間隔的時間
//事實上,這是預加載作業的最后期限時間,如果不能在這個時間之前完成,那就意味著預加載失敗
long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
//進行預加載
prefetch(nextFrameNs);
// TODO: consider rescheduling self, if there's more work to do
} finally {
mPostTimeNs = 0;
TraceCompat.endSection();
}
}
void prefetch(long deadlineNs) {
//建立任務串列
buildTaskList();
//在deadlineNs這個時間前執行并完成任務
flushTasksWithDeadline(deadlineNs);
}
private void flushTasksWithDeadline(long deadlineNs) {
for (int i = 0; i < mTasks.size(); i++) {
final Task task = mTasks.get(i);
if (task.view == null) {
break; // done with populated tasks
}
flushTaskWithDeadline(task, deadlineNs);
task.clear();
}
}
6.更新串列的方式
item區域更新
單項item更新
- notifyItemChanged(position)
- notifyItemInserted(position)
- notifyItemRemoved(position)
- notifyItemMoved(fromPosition, toPosition)
整體串列更新
- notifyDataSetChanged(慎用)
- notifyItemRangeRemoved(positionStart, itemCount)
- notifyItemRangeChanged(positionStart, itemCount)
- notifyItemRangeInserted(positionStart, itemCount)
其它的優化點
過度繪制
如果串列中的一個Item存在過度繪制,那么串列所有的item都過度繪制,就到存在不必要的渲染作業,消耗系統資源,
防止過度繪制,可以打開開發者選項中的《除錯GPU過度繪制》,查看頁面中的顏色磁區,然后進行對應的優化,
Android 將按如下方式為界面元素著色,以確定過度繪制的次數:
- 真彩色:沒有過度繪制
- 藍色:過度繪制 1 次
- 綠色:過度繪制 2 次
- 粉色:過度繪制 3 次
- 紅色:過度繪制 4 次或更多次
因為在布局中同一幀多次繪制相同的像素就會發生繪制過度,因此修復過度繪制可以減少不必要的渲染作業,以此來提高性能,特別是對于大型,多串列的布局來說,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/390658.html
標籤:其他
上一篇:Android登陸注冊
