Android知识点三

1. RecycleView 如何设置分割线的?

它是读取系统自带的属性 R.attr.listDivider 属性,来设置分割线的,支持横向和纵向。

2. RecycleView 分割线的样式如何修改?

  • 第一种办法:因为 R.attr.listDivider 定义在系统样式中,那么我们可以重写这个属性修改,如下:

    styles.xml 中添加该属性:

    1
    2
    3
    4
    5
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    ....
    <item name="android:listDivider">@drawable/drawable_bg</item>
    </style>

    其中 drawable_bg 为自定义分割线的drawable.

  • 第二种办法:可以通过自带的 setDrawable() 改变,如下:

    1
    2
    3
    4
    DividerItemDecoration itemDecoration = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
    Drawable drawable = getResources().getDrawable(R.drawable.drawable_bg);
    itemDecoration.setDrawable(drawable);
    recyclerView.addItemDecoration(itemDecoration);

    3. RecycleView 缓存

    缓存涉及三个类,分别是:

    • Recycler

    • RecycledViewPool

    • ViewCachedExtension

  • Recycler : 用于管理已经废弃或者与 RecyclerView 分离的 ViewHolder

    • mChangedScrap : 与 RecyclerView 分离的 ViewHolder 列表
    • mAttachedScrap : 未与 RecyclerView 分离的 ViewHolder 列表
    • mCachedViews : ViewHolder 缓存列表
  • RecycledViewPool : ViewHolder 缓存池

  • ViewCachedExtension : 开发者可以控制的 ViewHolder 缓存的帮助类

获取缓存里的 ViewHolder 的关键方法是: tryGetViewHolderForPositionByDeadline 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount()
+ exceptionLabel());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 0) 如果需要改变废弃的view,从这里查找
if (mState.isPreLayout()) {
//这里是 mChangedScrap 集合中取出
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
// 1) 这是从 mAttachedScrap 集合中查找
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}

final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
// 2)从 mCachedViews 集合中查找缓存
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
// 3 从mViewCacheExtension 获取
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
// 4)从缓存池中获取 recycledViewPool
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
//如果都没有 就重新创建 createViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
.....
}
}
}

总结缓存的机制如下:

  • 1.从 mChangedScrapmAttachScrap 中获取
  • 2.从 mCacheViews
  • 3.从 mRecycledViewPool 缓存池中获取

4. RecycleView 刷新闪屏

一般刷新:

1
adapter.notifyItemChanged(0); 参数:position

实际上会全部刷新,局部刷新应使用:

1
adapter.notifyItemChanged(0,"q"); 参数: position,payload

并且在 adapter 中重写方法: `onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)方法`

payloadssize 始终为1.

1
2
3
4
5
6
7
8
9
@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
if(payloads.isEmpty()){
onBindViewHolder(holder,position);
}else{
String type = payloads.get(0);//获取为 q
holder.name.setText(list.get(position));
}
}

5. RecycleView 和 listView

//https://zhuanlan.zhihu.com/p/23339185

  • 缓存层级不同

    recycleView 比 listView 多两级缓存,支持开发者自定义缓存处理,支持多个recycleView共用同一个 recycleViewPool(缓存池)

listView 缓存(两级缓存):

是否需要回调createView 是否需要回调bindVIew 生命周期 备注
mActiveViews onLayout函数周期内 用于屏幕内itemView快速重用
mScrapViews 与mAdapter一致,当Adapter被更换时,mScrapViews即被清空

RecycrleView 缓存( 四级缓存):

是否回调createView 回调bindVIew 生命周期 备注
mAttachedScrap onLayout 用于屏幕内itemView快速重用
mCacheViews 与mAdapter一致,当Adapter被更换时,mCacheVIews被缓存到mRecyclerPool 默认缓存大小是2
mViewCacheExtension 用户自定义实现
mRecyclerPool 与自身生命周期一致,不再引用时被释放 默认大小是5

ListViewRecyclerView 缓存机制:

  • mActiveViewsmAttachedScrap 功能相似,意义在于快速重用屏幕上可见的列表项 itemView,而不需要重新createViewbindView
  • mScrapViewmCachedViews + mRecyclerViewPool 功能相似,意义在于缓存离开屏幕的itemView,目的是让即将进入屏幕的 itemView 重用
  • RecycleView 的优势在于 mCacheVIews 的使用,可以做到屏幕外的列表项itemView 进入屏幕内时也无须bindView 快速重用; mRecycleViewPool 可以供多个recycleView 共同使用,在特定场景下,如viewpager 加多个列表页有优势。

6. RecycleView 的回收复用机制的内部实现都是哪个类完成的?

RecycleView 的回收复用机制都是由内部类 Recycler 类,核心方法:tryGetViewHolderForPositionByDeadline() 方法中完成的。

随笔源码记录:

复用机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//复用机制的原理
- 1.mCacheView
- 2.recyclerViewPool
- 3.扩展
- 4.都没有,重新创建一个ViewHolder(createViewHolder)
...
}

回收机制

也是 Recycler 类中的,核心方法:recycleViewHolderInternal(holder)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void recycleView(View view) {
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//回收的内部实现
recycleViewHolderInternal(holder);
}

回收的逻辑:由 LayoutManager 来遍历移出屏幕的卡位,然后对每个卡位进行回收操作,回收时,都是把 viewHolder 放在 mCachedViews 里面,如果mCachedViews 满了,那就在mCachedViews 里拿一个ViewHolder 扔到 ViewPool 缓存里,然后mCachedViews 就可以空出位置来放新回收的ViewHolder 了。

mCachedViews 里存放的 ViewHolder 只有原本位置的卡位才能复用

参考链接:https://blog.csdn.net/xJ032w2j4cCjhOW8s8/article/details/79946425

===================================================================================

  • updateViewCacheSize()
1
2
3
4
5
void updateViewCacheSize() {
int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
mViewCacheMax = mRequestedCacheMax + extraCache;
....
}

在第一次向上滑动

7. RecycleView 性能优化

7.1 数据处理和视图加载适度分离

什么意思呢?就是我们异步从服务端拉取数据,拿到数据之后就把数据丢给 ViewHolder 进行处理,当然有些数据需要单独处理,那么其实这些处理逻辑也应该异步执行,这样的话就可以让 adapter 在 notifyDatasetChanged 之后,ViewHolder 就可以简单处理视图与数据的绑定工作。

7.2 数据刷新

如果是个别数据发生改变,调用局部刷新的方法

7.3 布局优化

减少 GPU 过度绘制;

7.4 杂七杂八

  1. 升级 RecycleView 版本到 25.1.0 以上使用 Prefetch 功能,开启这个功能之后对页面复杂度的敏感性降低,在高复杂度情况下,Prefetch 可以显著提高页面流畅度。Android 5.0 以后引入 Render Thread 提升动画流畅性。
  2. 如果item 高度固定的话,可以使用 RecyclerView.setHasFixSize(true) 来避免 requestLayout 浪费资源;
  3. 共用 RecycleViewPool ;
  4. 通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间,默认是一页数据的数量

8. 自定义属性获取完属性后为什么调用TypeArray.recycle()?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Recycles the TypedArray, to be re-used by a later caller. After calling
* this function you must not ever touch the typed array again.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
//大概意思是说回收这个TypedArray, 将会被复用当再次调用的时候,当你调用这个方法之后你就不能再操作not touch the typed
public void recycle() {
if (mRecycled) {
//如果已经被回收,就会抛出异常
throw new RuntimeException(toString() + " recycled twice!");
}

mRecycled = true;

// These may have been set by the client.
mXml = null;
mTheme = null;
mAssets = null;
//看下面
mResources.mTypedArrayPool.release(this);
}
1
2
3
4
public class Resources{
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
}

看上面的那个大概意思就是从一个同步池子里获取到的,默认大小是5. 平常我们获取的属性是通过:

1
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.xx);

context 的一个静态方法,点进去看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//最终到了这里
public class TypedArray {

static TypedArray obtain(Resources res, int len) {
//这里 就是从上述一个SynchronizedPool 中获取的
final TypedArray attrs = res.mTypedArrayPool.acquire();
if (attrs != null) {
attrs.mLength = len;
attrs.mRecycled = false;

// Reset the assets, which may have changed due to configuration changes
// or further resource loading.
attrs.mAssets = res.getAssets();

final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
if (attrs.mData.length >= fullLen) {
return attrs;
}

attrs.mData = new int[fullLen];
attrs.mIndices = new int[1 + len];
return attrs;
}

return new TypedArray(res,
new int[len*AssetManager.STYLE_NUM_ENTRIES],
new int[1+len], len);
}

SynchronizedPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* Simple (non-synchronized) pool of objects.
*
* @param <T> The pooled type.
*/
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool;

private int mPoolSize;

/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mPool = new Object[maxPoolSize];
}

@Override
@SuppressWarnings("unchecked")
//从pool 中获取
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}

@Override
public boolean release(T instance) {
if (isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}

private boolean isInPool(T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}

维护一个同步栈结构的对象池,避免重复频繁创建属性值 TypeArray 对象,使用时记得调用 Recycle() 方法将不用的对象返回至对象池来达到重用的目的,如果不调用 recycle() 方法将会随着 Activity的每一次创建而创建,因此系统频繁创建 array, 对内存和性能是一个不小的开销,如果不使用池,每次都让 GC 来回收,很可能就会造成 OOM.

9. 自定义属性

1
2
3
4
5
6
7
8
9
10
11
12
//自定义属性一般我们会在values-attr.xml 中定义,如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--第一种方式,在declare-styleable标签中-->
<declare-styleable name="customerView">
<attr name="colors" format="color" />
</declare-styleable>

<!--第二种方式,单独定义属性-->
<attr name="AloneAttr" format="color" />

</resources>

两种方式有什么区别呢?

第一种方式会把有关这个控件的属性包裹起来,达到一个分组的概念,不零散,属性的使用范围更加明确,

第一种方式获取属性值:

1
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.customerView);

第二种方式获取属性值:

1
2
int[] array = {R.attr.AloneAttr};
TypedArray ta = context.obtainStyledAttributes(attrs,array);
-------------本文结束感谢您的阅读-------------