HashMap复习时候的一些问题总结

在获取hash值的时候,为什么要对hashcode进行一个右移16位的操作

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

答: 目的是为了将hashcode的高位和低位进行混合,从而减少hash冲突的发生,也只能降低hash冲突的可能,增加性能.

为什么hash冲突会影响性能

因为hashmap最好的性能是实现O1的查找,插入,删除效率,但是由于会出现hash冲突,会导致多个值被装进同一个桶中,在hashmap里面桶里面的数据结果是用链表,红黑树来实现的,链表和红黑树的插入删除查找的复杂度增加了,就导致了hashmap的效率降低了.
为了减少hash冲突,应该选择合适的hash函数.调整hash表的大小,调整合适的装载因子.
hashmap里面默认的装载因子是0.75.

扩容发生在哪些情况

  1. 当hashmap里面的所有键值对的数量超过了设置的hashmap容量(默认大小16),时候会发生扩容

什么情况下会转换成红黑树

当单个桶的数量超过8的时候:

1
2
3
int TREEIFY_THRESHOLD = 8;
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);

线程安全的话使用谁比较好

首先需要知道HashMap不是线程安全的.
线程安全的有:

  • HashTable
  • ConcurrentHashMap

HashTable : 实现的线程安全很简单,他直接给 put get 方法进行上锁(synchronized).这就导致在插入获取数据的时候效率会降低.
ConcurrentHashMap : 他在获取数据的时候效率比HashTable高,因为 get操作是不加锁的,他用了 volatile 关键字进行修饰,这个关键字保证了有序性和可见性,保证了每次获取的数据都是最新的.然后在 put 的时候使用乐观锁实现同步问题.

Glide的自问自答

1. Glide的缓存有哪些

内存缓存和磁盘缓存

2. 缓存对应的实现类是什么

内存缓存:

  1. ActiveResource
  2. LruResourceCache
  • ActiveResource是使用HaspMap进行的数据存储.键值是用弱引用进行的包装.为什么要用弱引用,因为他缓存的是正在使用的图片,当页面被销毁了,你还拿着对象的引用,会造成内存泄漏,所以要用弱引用进行包装.
  • LruResourceCache他缓存的是所有曾经加载过的图片信息,他和ActiveResource的区别是,他底层使用了Lru算法,然后Lru算法的实现是通过LinkedHashmap.为什么LruResourceCache要用到Lru算法,然后还不用弱引用.因为他缓存的不是正在使用的图片,不会存在内存泄漏的问题.然后如果他也使用了弱引用,就会导致缓存的资源被频繁的清空,也就失去了缓存的意义了.当然,缓存的大小总是有个边界的,所以使用了Lru算法,在超过缓存大小后删除那些最近最少使用的资源.这个实现就是用到了LinkedHashMap.LinkedHashMap继承HashMap,他重写了newNode方法,键值封装到了LinkedHashMapEntry里面,维护了一个双向链表.这样我们就能得到我们的插入顺序了.新插入的值是插在链表的尾部的.默认情况下LinkedHashMap的插入顺序和遍历顺序是一样的,但是我们通过改变accessOrder值为true

3. 如何全局设置Glide的加载配置,并且他的原理是什么,为什么可以设置一个全局的加载配置

在Glide里,配置信息都是写入BaseRequesOptions里面.我们每次发起请求通过Glide.with(context)先获取当前页面的RequestManager,然后在调用load创建RequestBuilder对象,这个对象是每次调用load都会创建一个新的.我们可以看到RequestBuilder是继承BaseRequestOptions的.看下对象创建的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class RequestManager{
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
}
class RquestBuilder{
protected RequestBuilder(
@NonNull Glide glide,
RequestManager requestManager,
Class<TranscodeType> transcodeClass,
Context context) {
----
apply(requestManager.getDefaultRequestOptions());
}
}

其他都是浮云.重点是RequestBuilder的构造函数里面的apply方法.

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
public T apply(@NonNull BaseRequestOptions<?> o) {
BaseRequestOptions<?> other = o;

if (isSet(other.fields, SIZE_MULTIPLIER)) {
sizeMultiplier = other.sizeMultiplier;
}
if (isSet(other.fields, USE_UNLIMITED_SOURCE_GENERATORS_POOL)) {
useUnlimitedSourceGeneratorsPool = other.useUnlimitedSourceGeneratorsPool;
}
if (isSet(other.fields, USE_ANIMATION_POOL)) {
useAnimationPool = other.useAnimationPool;
}
if (isSet(other.fields, DISK_CACHE_STRATEGY)) {
diskCacheStrategy = other.diskCacheStrategy;
}
if (isSet(other.fields, PRIORITY)) {
priority = other.priority;
}
if (isSet(other.fields, ERROR_PLACEHOLDER)) {
errorPlaceholder = other.errorPlaceholder;
errorId = 0;
fields &= ~ERROR_ID;
}
if (isSet(other.fields, ERROR_ID)) {
errorId = other.errorId;
errorPlaceholder = null;
fields &= ~ERROR_PLACEHOLDER;
}
if (isSet(other.fields, PLACEHOLDER)) {
placeholderDrawable = other.placeholderDrawable;
placeholderId = 0;
fields &= ~PLACEHOLDER_ID;
}
if (isSet(other.fields, PLACEHOLDER_ID)) {
placeholderId = other.placeholderId;
placeholderDrawable = null;
fields &= ~PLACEHOLDER;
}
if (isSet(other.fields, IS_CACHEABLE)) {
isCacheable = other.isCacheable;
}
if (isSet(other.fields, OVERRIDE)) {
overrideWidth = other.overrideWidth;
overrideHeight = other.overrideHeight;
}
if (isSet(other.fields, SIGNATURE)) {
signature = other.signature;
}
if (isSet(other.fields, RESOURCE_CLASS)) {
resourceClass = other.resourceClass;
}
if (isSet(other.fields, FALLBACK)) {
fallbackDrawable = other.fallbackDrawable;
fallbackId = 0;
fields &= ~FALLBACK_ID;
}
if (isSet(other.fields, FALLBACK_ID)) {
fallbackId = other.fallbackId;
fallbackDrawable = null;
fields &= ~FALLBACK;
}
if (isSet(other.fields, THEME)) {
theme = other.theme;
}
if (isSet(other.fields, TRANSFORMATION_ALLOWED)) {
isTransformationAllowed = other.isTransformationAllowed;
}
if (isSet(other.fields, TRANSFORMATION_REQUIRED)) {
isTransformationRequired = other.isTransformationRequired;
}
if (isSet(other.fields, TRANSFORMATION)) {
transformations.putAll(other.transformations);
isScaleOnlyOrNoTransform = other.isScaleOnlyOrNoTransform;
}
if (isSet(other.fields, ONLY_RETRIEVE_FROM_CACHE)) {
onlyRetrieveFromCache = other.onlyRetrieveFromCache;
}

// Applying options with dontTransform() is expected to clear our transformations.
if (!isTransformationAllowed) {
transformations.clear();
fields &= ~TRANSFORMATION;
isTransformationRequired = false;
fields &= ~TRANSFORMATION_REQUIRED;
isScaleOnlyOrNoTransform = true;
}

fields |= other.fields;
options.putAll(other.options);

return selfOrThrowIfLocked();
}

可以看到apply方法就是一个拷贝的操作,他将requestManager.getDefaultRequestOptions()默认配置赋值给了新的RequestBuilder.所以我们可以操作的点出来了.
我们通过修改getDefultRequestoptions就可以实现修改全局的GLide加载属性了. 这里我拿修改Glide的编码格式为DecodeFormat.PREFER_RGB_565来举例.(应用场景:在手机内存太小的手机上,用来减少图片的内存占用).

1
2
3
4
5
6
7
8
9
10
11
12
@GlideModule
class MyGlideModule : AppGlideModule(){
override fun applyOptions(context: Context, builder: GlideBuilder) {
super.applyOptions(context, builder)
val activityManager:ActivityManager = context.getSystemService(Context.ALARM_SERVICE) as ActivityManager
if(activityManager!=null){
var memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
}
}
}
1
2
3
<meta-data
android:name="com.adventure.live.MyGlideModule"
android:value="GlideModule" />

实现方法就是继承AppGlideModule重写applyOptions方法.

rebase命令学习

reword

修改你的提交信息.
演示
第一步:

1
git rebase -i HEAD~2

captrue.png
第二步:
将提交7前面的pick改成reword

captrue.png
然后键盘按 ctrl + x

captrue.png
键盘按Y.然后再按 回车

captrue.png
修改成:提交8,然后键盘输入 ctrl+x 在 按 y 在 按回车.

captrue.png
修改完成.

edit

看名字就是修改的意思.你可以修改任意位置已经提交的内容.
演示
首先提交两个:

captrue.png
内容分别为:
第一次提交:

1
2
3
4
class Test {
fun main(){
}
}

第二次提交:

1
2
3
4
5
class Test {
fun main(){
print("111111")
}
}

执行命令:git rebase -i HEAD~2

captrue.png
比如我要修改第一次提交的内容.那就需要将第一个的pick改成edit:

captrue.png
然后按 ctrl+x y 回车
这时候我的工作取回到了第一次提交的时候的样子:

captrue.png
这时候我们修改下:
captrue.png
修改完成后,就需要执行git add .表示保存这次修改.
然后执行commit操作.commit有两种方式

  • –amend: 表示这次的提交内容还是会被保存在当前这个提交信息上
  • -m : 会新开一个提交

这里我执行 git commit --amend

captrue.png
现在就需要执行git rebase --continue,表示继续下一个指令操作:

captrue.png
可以看到出现了合并冲突.我们需要解决下.
解决完继续执行git add . git commit --amend

captrue.png
解决完成了合并冲突:
继续执行git rebase --continue

captrue.png
可以看到我们成功在第一次提交信息里面增加了信息.
如果将中途的git commit --amend换成git commit -m 'cha'会发现:

captrue.png

pick

这个代表着一次提交如果你删除了那行pick,那么你本地的全部提交都会被删除.谨慎删除.

为什么onMeasure会被执行两次

什么情况下会onMeasure会执行?

进入Viewmeasure方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void measure(){
boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
boolean isSepcExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if(forceLayout || needLayout){
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
}

什么时候forceLayout=true:

  1. 调用requestLayout
  2. 调用forceRequestLayout

什么时候needsLayout=true:

  1. 当长宽发生改变

什么时候调用了onMeasure>方法:

  1. forceLayouy=true
  2. 或者mMeasureCache没有当前的缓存

所以总结:当调用了requestLayout一定会测发重测过程.当forceLayout=false的时候会去判断mMeasureCache值.现在研究下这个mMeasureCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class View{
LongSparseLongArray mMeasureCache;
void measure(widthSpec,heightSpec){
---
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if(cacheIndex<0){
onMeasure(widthSpec,heightSpec);
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key,widhSpec|heightSpec);
---
}
}

这里可以看到oldWidthMeasureSpecmMeasureCache都是缓存上一次的值,那他们有什么不同呢?不同点就是,oldWidthMeasureSpec>不仅仅缓存了测量的spec模式而且缓存了size.但是mMeasureCache只缓存了size.从这行代码可以看出:

1
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;

这里一同运算就为了排除掉spec造成的影响.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//不信你可以试下下面的代码
public class Test {
public static void main(String[] args) {
long widthMeasureSpec = makeMeasureSpec(10,0);
long heightMeasureSpec = makeMeasureSpec(20,0);
long ss = widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
System.out.println("=========="+ss);
}

private static final int MODE_MASK = 0x3 << 30;

public static int makeMeasureSpec(int size,
int mode) {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//42949672980
//42949672980
//42949672980

什么时候mPrivateFlags会被赋值PFLAG_FORCE_LAYOUT.

view viewGrouup的构造函数里面会主动赋值一次,然后在ViewGroup.addView时候会给当前ViewmProvateFlags赋值PFLAG_FORCE_LAYOUT.


为什么onMeasure会被执行两次?

1
2
3
4
5
6
7
8
9
10
11
12
13
void measure(int widthMeasureSpec,int heightMeasureSpec){
----
boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if(forceLayout | needsLayout){
onMeasure()
}
----
}
public void layout(int l, int t, int r, int b){
---
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
---
}

在第一次触发到measure方法时,forceLayoyt=true needsLayout=true,但是layout方法还没触发到.
在第二次触发到measure>方法时,forceLayout=true needsLayout=false,所以还是会进入onMeasure方法.这次会执行layout方法.然后我们在下次的时候forceLayout就等于false了.上面的这一段分析是分析的measure内部如何防止多次调用onMeasure.

现在分析外部是如何多次调用measure方法的:
Activity执行到onResume生命周期的时候,会执行WindowManager.addView操作,WindowManager的具体实现类是WindowManagerImpl然后addView操作交给了代理类WindowManagerGlobal,然后在WindowManagerGlobaladdView里面执行了ViewRootImpl.setView操作(ViewRootImpl对象也是在这个时候创建的),在ViewRootImpl会主动调用一次requestLayout,也就开启了第一次的视图 测量 布局 绘制.

setView的时候主动调用了一次ViewRootImpl.requestLayout,注意这个requestLayoutViewRootImpl的内部方法,和view viewGroup那些requestLayout不一样.在ViewRootImpl.requestLayout内部调用了performTraversals方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ViewRootImpl{
void performTraversals(){
if(layoutResuested){
//标记1
windowSizeMayChanged |= measureHierarchy(host,lp,res,desiredWindowWidth,desiredWindowHeight);
}
//标记2
performMeasure()
performLayout()
}
void measureHierarchy(){
performMeasure()
}
}

ViewRootImpl的执行逻辑你可以看出,在执行performLayout之前,他自己就已经调用了两次performMeasure方法.所以你现在就知道为啥了.

下载编译查看Android12源码

  1. 安装repo
    1
    2
    3
    4
    mkdir ~/bin
    PATH=~/bin:$PATH
    curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
    chmod a+x ~/bin/repo
    这个时候的repo镜像还是国外的下载会很慢,需要换成国内的.打开 /home/yu/bin/repo文件修改字段REPO_URL值:https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/

image.png

  1. 下载
    新建一个放源码的目录,然后在目录下执行这个命令:

    1
    repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-11.0.0_r1

    (选择要下载的源码分支可以查看这个网站)

  2. 同步源码树:

    1
    repo sync

    下载完成后的样子:

captrue.png

  1. 编译
    1. 初始化编译环境
      1
      source build/envsetup.sh

        4.2. 选择产品
(由于我这的产品没有sdk_phone_x86_64-eng,我需要自己添加个,修改文件build/make/target/product/AndroidProducts.mk,添加sdk_phone_x86_64-eng)

captrue.png
选择产品:

1
lunch sdk_phone_x86_64-eng
  1. 启动虚拟机:

    1
    emulator
  2. AndroidStudio导入源码:

  3. 1: 首先执行命令
    mmm development/tools/idegen
    会生成idegen.jar文件.

  4. 2: 执行命令./development/tools/idegen/idegen.sh这时候会生成文件android.ipr

captrue.png
我们用As打开这个文件就可以看Andoid源码了.

Recycleview的自问自答

RecycledView四级缓存的生命周期

  1. mAttachedScrap,他的数据随着一次layout从无到有再到无.

从无到有 ->

执行layoutChildren时候,会执行detachAndScrapAttachViews()方法,当viewholder.flag!=INVALID && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()的时候,当前的ViewHodler就会被添加进入mAttachedScrap

从有到无 ->

layoutChunk阶段,从mAttachedScrap拿到对应的ViewHolder后,执行addView操作完成后,当前ViewHodler就会从mAttachedScrao里面进行移除.

所以mAttachedScrap的值随着一次布局从存在到消失.

  1. mCachedView生命周期.在滑动的是动态添加和移除.然后在调用notifyDataSetChanged时候会被清空.

  2. RecycledViewPool生命周期.添加进去就会一直存在.


Recycleview获取ViewHolder顺序:

  1. 如果当前Recycleview.State.isPreLayout()==true则先从mChangedScrap数组里面获取,然后会将获取到的ViewHolderFlag赋值为FLAG_RETURNED_FROM_SCRAP.
    1
    2
    3
    4
    5
    6
    ViewHolder tryGetViewHolderForPositionByDeadline(int position){
    if(mState.isPreLayout()){
    holder = mChangedScrap.get(position);
    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
    }
    }
  2. mAttachedScrap数组获取,还会将获取到的ViewHolderflag赋值为FLAG_RETURNED_FROM_SCRAP
    1
    2
    3
    4
    5
    6
    ViewHolder tryGetViewHolderForPositionByDeadline(int position){
    ----
    holder = mAttachedScrap.get(position)
    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
    ----
    }
  3. ChildHelper.mHiddenViews数组获取
  4. mCachedViews获取,获取完后会从mCachedViews里面移除当前对他的缓存
    1
    2
    3
    4
    5
    ViewHolder tryGetViewHolderForPositionByDeadline(int position){
    ----
    holder = mCachedViews.get(position);
    ----
    }
  5. mViewCacheExtenstion获取,这个需要开发者自行实现
  6. recycledViewPool获取,需要注意,从这里获取的viewHolder会进行一个重置操作,重置内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class ViewHolder{
    void resetInternal() {
    mFlags = 0;
    mPosition = NO_POSITION;
    mOldPosition = NO_POSITION;
    mItemId = NO_ID;
    mPreLayoutPosition = NO_POSITION;
    mIsRecyclableCount = 0;
    mShadowedHolder = null;
    mShadowingHolder = null;
    clearPayload();
    mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
    mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
    clearNestedRecyclerViewIfNotNested(this);
    }
    }
    所有的缓存里面,只有从RecycledViewPool里面获取的ViewHolder需要重新执行bind操作.
    1
    2
    3
    4
    5
    6
    ViewHolder tryGetViewHolderForPositionByDeadline(int position){
    ----
    holder = getRecycledViewPool.getRecycledView(type);
    holder.resetInternal();
    ----
    }
  7. 缓存里面还没拿到ViewHolder的话就去执行createViewHolder操作.

Recycleview什么时候会执行bindViewHolder操作:

在执行bindViewHolder方法前有个判断:

1
2
3
if(!holder.isBound() || holder.needsUpdate() || holder.isInvalid()){
bindViewHolder();
}

holder.isBound() = true是在执行过一次bindViewHodler后进行的赋值.在第一次布局的时候,所有的ViewHolder都会执行bindViewHolder操作.然后执行完bindViewHodler操作后,当前的viewHodlerflag会被赋值FLAG_BOUND.

1
2
3
4
5
6
void bindViewHolder(ViewHodler holder,int position){
holder.position = positon;
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
}

所以:

  1. 第一次布局的时候会执行bindViewHolder
  2. RecycledViewPool里面获取到的ViewHolder会重新执行bind,因为RecycledViewPool厘面的viewHodlerflag会被重置掉.
  3. 调用notify方法标记了需要更新的viewHodler他们的flag会带有FLAG_UPDATE.所以会重新执行bind操作.

为什么执行notifyDataSetChaged()所有的ViewHodler会重新执行bind操作,而执行notifyItemChanged()不会导致全部的viewHolder执行bind操作:

首先需要搞清楚哪些ViewHolder是需要执行bind操作的:

  1. flag值没有FLAG_BOUND
  2. flag值有UPDATE
  3. flag值有INVALID

那哪些情况下flag会符合上面三种情况?

  1. RecycledViewPool里获取到的ViewHodler,他们的Flag值都为0
  2. 调用了notifyDataSetChanged方法,导致在布局的时候,获取到的ViewHodler是从RecycledViewPool里面获取到的.
  3. 调用了nofifyItemChanged,他会将flag标记为UPDATE

所以上面这个问题就好回答了,因为调用notifyDataSetChanged会导致获取viewHolder的操作都从RecycledViewPool里面获取,所以所有的viewHolder都会重新执行bind操作.而调用notifyItemChanged方法,所有的ViewHodler都是从mAttachedScrap里面获取的,然后只有需要更新的viewHodlerflag等于FLAG_UPDATE.所以只有需要更新的才会执行bind操作.


为什么调用notifyDataSetChanged更新视图的时候,会导致视图全部重新执行bind操作.

要探究这个问题就需要去看detachAndScrapAttachdViews()方法.这个方法是在layoutChildren阶段执行的.这个方法在初学阶段你是看不懂的,更不会用了, 现在我来讲明白.
这个方法做的事情就是分离视图缓存已经存在的viewHodler.

分离视图有两种选择:

  1. removeView
  2. detachViewFromParent

缓存viewHodler有四种方法选择:

  1. 存进mCachedViews
  2. 存进recycledViewPool
  3. 存进mAttachedScrap
  4. 存进mChangedScrap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void detachAndScrapAttachdViews(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//情况1
removeViewAt(index);
addViewHolderToRecycledViewPool(holder)
} else {
// 情况2
detachViewAt(index);
if(flag == removed || falg == Invalid || !flag==update){
mAttachedScrap.add(hodler)
}else{
mChangedScrap.add(hodler)
}
}
}

我们在调用notifyDataSetChanged的时候,他会将我们的所有的viewHodlerflag设置为invalid 和 update并且清空了mCachedViews缓存,就导致在方法detachedAndScrapAttachViews时候符合情况1,将当前的全部ViewHodler缓存进了RecycledViewPool里面.所以就导致执行更新时,会重新都走一遍bind操作.
所以我们要少用notifyDataSetChanged方法,他在回收的时候执行的是removeView操作,是一个很重的操作,然后所有的ViewHodler也会被保存进RecycledViewPool里面,而不是mRecycledScrap里面,就导致还会重新走一遍bind操作,增加了页面更新的时间.


Recycleview里面分割线的实现原理

Recycleview的分割线是单独画上去的,在draw阶段画的,所以他的层次要高于ViewHolder,因为他是晚于ViewHolder绘制.

看下测量阶段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Recycleview{
class LayoutManager{
void measureChildWithMargins(View child,int widthUsed,int heightUsed){
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//标记1
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;

final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
}
}

在标记1的地方,可以看到去获取了我们分割线的大小.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}

if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//标记2
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}

在标记2的位置可以看到调用了ItemDirectiongetItemOffsets方法,去获取每个viewHolder对应的边距.所以自定义ItemDirection的时候,需要你重写getItemOffsets方法.设置完的值会被保存在layoutParams.mDecorInsets里面.

大小获取完成了.现在就是布局了.再来看下布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
class LinearLayoutManager{
void layoutChunk(){
layoutDecoratedWidthMargins(view,l,t,r,b);
}
public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
}

可以看到通过layoutParams去获取了mDecorInsets值.这是我们在measure设置进去的.

综上所述,分析了Recycleview的分割线实现原理.


recycleview获取ViewHolder的方法:

  1. Recycleview的成员方法findViewHolderForPosition(postion)
  2. Recycleview的成员方法findViewHolderForLayoutPosition(position)
  3. Recycleview的成员方法findViewHolderforAdapterPosition(position)
  4. Recycleview的成员方法findViewHolderForItemId(id)

区别:1 和 2 是一样子的. 2 和 3是不一样的.
findViewHolderForLayoutPosition获取的是holder.layoutPosition值.findViewHolderforAdapterPosition获取的是holder.position值.
那现在问题来了,ViewHolder里的position和mPreLayoutPosition他们都表示位置信息,那有什么区别呢?
区别在,mPreLayoutPosition获取的是当前此刻viewHolder在的位置.而position代表这次onLayout结束后这个viewholder的位置.这个值会更准确.

我们可以来看下源码:
首先看findViewHolderForLayoutPosition:

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
public ViewHolder findViewHolderForLayoutPosition(int position) {
return findViewHolderForPosition(position, false);
}
ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
final int childCount = mChildHelper.getUnfilteredChildCount();
ViewHolder hidden = null;
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.isRemoved()) {
if (checkNewPosition) {
if (holder.mPosition != position) {
continue;
}
} else if (holder.getLayoutPosition() != position) {
continue;
}
if (mChildHelper.isHidden(holder.itemView)) {
hidden = holder;
} else {
return holder;
}
}
}
return hidden;
}

可以看到默认checkNewPostion传的就是false,表示不检查新位置,直接返回当前mPreLayoutPosition等于目标值的viewHolder

再来看看findViewHolderForAdapterPosition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ViewHolder findViewHolderForAdapterPosition(int position) {
if (mDataSetHasChangedAfterLayout) {
return null;
}
final int childCount = mChildHelper.getUnfilteredChildCount();
ViewHolder hidden = null;
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.isRemoved()
&& getAdapterPositionFor(holder) == position) {
if (mChildHelper.isHidden(holder.itemView)) {
hidden = holder;
} else {
return holder;
}
}
}
return hidden;
}

可以看到当mDataSetHasChangedAfterLayouttrue的时候会返回null,那什么时候这个值会为true呢,在adapter调用方法notifyDataSetHasChanged的时候,会赋值他为true.然后在看方法getAdapterPositionfor(viewHolder)

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
int getAdapterPositionFor(ViewHolder viewHolder) {
mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
}

public int applyPendingUpdatesToPosition(int position) {
final int size = mPendingUpdates.size();
for (int i = 0; i < size; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
if (op.positionStart <= position) {
position += op.itemCount;
}
break;
case UpdateOp.REMOVE:
if (op.positionStart <= position) {
final int end = op.positionStart + op.itemCount;
if (end > position) {
return RecyclerView.NO_POSITION;
}
position -= op.itemCount;
}
break;
case UpdateOp.MOVE:
if (op.positionStart == position) {
position = op.itemCount; //position end
} else {
if (op.positionStart < position) {
position -= 1;
}
if (op.itemCount <= position) {
position += 1;
}
}
break;
}
}
return position;
}

可以看到内部调用了方法applyPendingUpdatesToPosition翻译下:执行内部位置更新操作.里面涉及到了数组mPendingUpdates,这个数组在我们执行notifyItemInserted notifyItemMoved notifyItemRangeChanged方法的时候会生成操作对象然后存入到mPendingUpdates数组里面….然后在我们获取viewHolder.position的时候,这时候会考虑到在执行更新操作的时候是否影响到了当前我们的viewHolder如果影响到了,就根具具体的操作去获取这次操作结束后的位置. 这就印证了我上面讲的,findViewHodlerforAdapterPostion获取的位置是这次onLayout结束后的位置.findViewHolderForLayoutPosition获取的是当前viewHolder的位置.

分析下Recycleview的dispatchLayout1

关于Recycleview的布局分为三个阶段:dispatchLayout1() dispatchLayout2 dispatchLayout3().先分析下dispatchLayout1():

  • 看下processAdapterUpdatesAndSetAnimationFlags方法:
    这个方法会遍历数组mPendingUpdates数组,(这个数组存的是我们在执行nofityItemInserted等更新方法时候的操作),比如我们执行了一个插入更新操作,那么在dispatchLayout1()阶段遍历这个数组的目的就是更新所有因为这个插入操作而导致的位置更新的viewHolder
    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
    private void postponeAndUpdateViewHolders(UpdateOp op) {
    mPostponedList.add(op);
    switch (op.cmd) {
    case UpdateOp.ADD:
    mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
    break;
    case UpdateOp.MOVE:
    mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
    break;
    case UpdateOp.REMOVE:
    mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
    op.itemCount);
    break;
    case UpdateOp.UPDATE:
    mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
    break;
    default:
    throw new IllegalArgumentException("Unknown update op type for " + op);
    }
    }


    public void offsetPositionsForAdd(int positionStart, int itemCount) {
    offsetPositionRecordsForInsert(positionStart, itemCount);
    mItemsAddedOrRemoved = true;
    }
    void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
    if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
    //去更新viewHolder的position值
    holder.offsetPosition(itemCount, false);
    mState.mStructureChanged = true;
    }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
    }
  • 看下方法findMinMaxChildLayoutPositons(int info):
    Recycleview里有一个成员属性int[] mMinMaxLayoutPosition这个属性保存了Recycleview在屏幕上可以显示的最小和最大的item值.帮助Recycleview在滑动的时候确定要显示哪些item,以提高滑动的流畅性.这个值在dispatchLayout1阶段会去修改:

    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
    private void findMinMaxChildLayoutPositions(int[] into) {
    final int count = mChildHelper.getChildCount();
    if (count == 0) {
    into[0] = NO_POSITION;
    into[1] = NO_POSITION;
    return;
    }
    int minPositionPreLayout = Integer.MAX_VALUE;
    int maxPositionPreLayout = Integer.MIN_VALUE;
    for (int i = 0; i < count; ++i) {
    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
    if (holder.shouldIgnore()) {
    continue;
    }
    final int pos = holder.getLayoutPosition();
    if (pos < minPositionPreLayout) {
    minPositionPreLayout = pos;
    }
    if (pos > maxPositionPreLayout) {
    maxPositionPreLayout = pos;
    }
    }
    into[0] = minPositionPreLayout;
    into[1] = maxPositionPreLayout;
    }

    可以看到遍历了当前屏幕上的所有孩子,找到他们的最小值和最大值.

  • 继续看dispatchLayout1方法,现在看的是Recycleview如何标记哪些是需要执行动画操作的view.

    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
    class Recycleview{
    void dispatchLayout1(){
    ***
    if(mState.mRunSimpleAnimatioms){
    //情况1
    int count = mChildHelper.getChildCount();
    for(int i = 0; i < count; ++i){
    ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
    ***
    ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(mState,holder,flag,holder.getImodifiedPayloads());
    mViewInfoStore.addToPreLayout(holder,animationInfo);
    ***
    }
    }
    if(mState.mRunPredictiveAnimations){
    //情况2
    saveOldPositions();
    mLayout.onLayoutChildren(mRecycler,mState);

    for(int i = 0; i < mChildHelper.getChildCount(); ++i){
    View child = mChildHelper.getChildAt(i);
    ViewHolder viewHolder.getChildViewHolderInt(child);

    //在刚才的预布局里面没找到这个viewholder说明这是新建的viewholder,是需要执行动画的viewholder
    if(!mViewInfoStore.isInPreLayout(viewHolder)){
    mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder,animationInfo);
    }
    }

    }
    ***
    }
    }

    首先先看情况1:,他遍历了当前页面上的所有孩子,然后给每个viewHolder创建了ItemHolderInfo对象,并且保存在了viewInfoStoremLayoutHolderMap里面,键名是holder键值是InfoRecord,这个时候每个InfoRecordflag等于FLAG_PRE(从名字也可以看出是预布局的意思)

再看情况2,他会去执行layout.onLayoutChildren进行一次布局,然后对比情况1看看多了哪些ViewHolder,这些多出来的viewHolder会被添加到ViewInfoStore里面然后flag会被赋值为FLAG_APPEAR

到这里dispatchLayout1就分析完成了,他干了五件大事.

  • 第一件大事:
    执行processAdapterUpdatesAndSetAnimationFlags()方法,遍历mPendingUpdates数组,修改页面上所有viewHolderposition数据,是增是减.然后给是否执行动画标志位进行赋值.

  • 第二件大事:
    给成员属性mMinMaxLayoutPosition赋值,找到目前显示的最大和最小position

  • 第三件大事:
    将当前页面的所有viewHolder添加进了viewInfoStore里面然后设置flag等于FLAG_PRE

  • 第四件大事调用layoutManager.onLayoutChildren(),进去一次布局

  • 第五件大事,遍历页面所有孩子,对比第三件大事里面的mViewInfoStore没有的viewHolder,然后将没有的viewHodler添加到viewInfoStore里面.并且设置flag等于FLAG_APPEAR

分析下Recycleview的dispatchLayoutSetp2()

Recycleview中AnchorInfo的作用

AnchorInfo用于记录Recycleview的滚动状态,

关于Adapter进行数据更新的分析

调用notifyDataSetChanged

这是一个全局更新:

  1. 他会将所有的ViewHolderflag设为ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_INVALID,
  2. 他会将所有的View.LayoutParamsmInstesDirty设置为true,代表这些数据都脏了.
  3. 将所有的Recycler.mCachedViews缓存的ViewHodler对应的View.LayoutParamsmInsetsDirty赋值为true.
  4. mCachedViews缓存的所有ViewHolderflag赋值为ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_INVALID
  5. 如果mAdapter.mHasStableIds等于false(默认都是false,除非你自己去改了),还会移除mCachedViews所有的缓存数据,移除的数据会被添加到RecycledViewPool里面.

上述所有事情做完会调用requestLayout重新开始布局.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Adapter{
void notifyDataSetChanged(){
mState.mStructureChanged = true;
mDispatchItemsChangedEvent = true;
mDataSetHasChangedAfterLayout = true;
//遍历所有的viewholder,将他们的flag设置为update和invalid
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
}
}
----还有很多代码----
----具体代码详见Adapter.processDataSetCompletelyChanged----
}
}

这里有一点很重要,在执行notifyDataSetChanged方法的时候,会去修改成员变量

  • mState.mStructureChanged
  • mDispatchItemChangedEvent
  • mDataSetHasChangedAfterLayout
    这几个参数在执行layout的时候很重要,这几个值也只有在执行方法notifyDataSetChanged的时候才会被赋值为true. 当mDataSetHasChangedAfterLayouttrue的时候,recycleview将不会执行简单动画和预加载动画.

notifyDataSetChangednotifyItemChanged区别就是:

  • notifyDataSetChanged会立马将所有的viewHolder设置为更新状态和无效状态,并且删除了所有mCachedViews数组里面的数据
  • notifyItemChanged会设置mAdapterUpdateDuringMeasure为true.然后在onMeasure阶段将变化的viewHolder设置为更新或者移除或者无用等等状态.实现原理就是会将操作保存到mPendingUpdates数组里面,然后在onMeasure阶段遍历数组进行对ViewHolder的flag修改.
  • notifyDataSetChanged不会有动画效果
  • notifyItemChanged会有动画效果

在调用全局更新的时候不会用到AdapterHelper.mPendingUpdates数组.
现在继续看调用notifyDataSetChanged时触发的requestLayout.
进入onMeasure方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Recycleview{
void onMeasure(int widthSpec,int heightSpec){
//这个参数在调用notifyDataSetChanged()时是false
//调用其他的更新方法比如notifyItemChanged(int position)的时候会被赋值为true
if(mAdapterUpdateDuringMeasure){
//所以这里为false不会进来
//我就不放代码了
}else{
//会进这里
mLayout.onMeasure(mRecycler,mState,widthSpec,heightSpec)
}
}
}

关于notifyDatasetChanged触发的onMeasure没有什么可以讲的,重点的是在通过notifyItemChanged(int position)触发的重新测量,这个下面讲,很重要.涉及到了AdapterHelper.mPendingUpdates数组的使用.

进入onLayout方法.

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
class Recycleview{
void onLayout(boolean changed,int l,int t,int r,int b){
dispatchLayout()
}
void dispatchLayout(){
dispatchLayout1()
dispatchLayout2()
dispatchLayout3()
}
void dispatchLayout1(){
processAdapterUpdatesAndSetAnimationFlags();
mState.mInPreLayout = mState.mRunPredictiveAnimations;
if(mState.mRunSimpleAnimations){

}
if(mState.mRunPredictiveAnimations){
mLayout.onLayoutChildren(mRecycler,mState)
}
mState.mLayoutStep = State.STEP_LAYOUT;
}
void processAdaterUpdatesAndSetAnimationFlags(){
if(mDataSetHasChangedAfterLayout){
//这里是一个重点,只有调用notifyDataSetChanged()方法的时候这个值才会被设置为true,调用notifyItemChanged()时这个值不会被修改为true.
mAdapterHelper.reset();//清空mPendingUpdates和mPostponedList数组的数据
if(mDispatchItemChangedEvent(){
//是否告诉Layout数据发生了改变,意思也就是开发者可以监听onItemsChanged方法来知道数据发生了改变
mLayout.onItemsChanged(this);
}
}
//Recycleview是否设置了动画
//由于这里走的是notifyDataSetChanged这条线,上面执行了reset操作,导致这里的mAdapterHelper的数组是空的
if(predictiveItemAnimationsEnabled()){
//这方法其实就是在遍历AdapterHelper.mPendingUpdates数组,给viewHolder设置对应的flag值.
mAdapterHelper.preProcess();
}else{
//遍历AdapetHelper.mPendingUpdates数组,给对应的viewHolder设置对应的flag
mAdapterHelper.consumeUpdatesInOnePass();
}
//由于走的是全局更新所以mItemsAddedOrRemoved为false mItemsChanged也为false
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
//mFirstLayoutComplete这个值只会在第一次完整的布局结束后才会赋值为true
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout//这个值我们看到了.他是在notifyDataSetChanged方法里面被赋值为了true
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
}

由于这里探究的是notifyDataSetChanged方法,也就没有继续探究mAdapterHelper.preprocess和mAdapterHepler.consumeUpdatesInOnepass方法的必要了.因为进去了也是空的数组.为什么空,上面已经分析了.

上面用了很大的篇幅介绍了:processAdapterUpdatesAndSetAnimationFlags()方法,这个方法很重要:

  1. 他遍历了数组mPendingUpdates也就是我们在执行更新操作notify时候存入的事件,遍历完的结果就是给对应的ViewHolder设置对应的Flag信息,并且这个时候我们也能在LayoutManager监听到ViewHolder的变化.
  2. 判断当前的Layout需不需要动画.需要动画的话会给mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations赋值为true

分析完了dispatchLayout1()方法,你会发现这个方法就做了一件事情,预布局,全部都是为了动画.如果你没有动画的需求,dispatchLayout1()直接可以跳过了.并且在调用notifyDataSetChanged方法的情况下我们的dispatchLayout1()方法也没干什么事情,因为全局更新不需要动画.

这里探究过头了,dispatchLayout1 displayLayout2 displayLayout3不是我这里需要探究的.我现在需要探究的是,在调用notifyDataSetChanged的时候,他将所有的ViewHolderflag值设置为了ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_INVALID,现在我需要找到他用的地方.
进入LinearLayoutManageronLayoutChildren()

1
2
3
4
5
class LinearLayoutManager{
void onLayoutChildren(){
detachAndScrapAttachedViews();
}
}

第一个用到的地方就在detachAndScrapAttachedViews().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//情况1
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
//情况2
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

上面是原代码,下面我进行精简:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void scrapOrRecycleView(){
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
Recycleview.removeViewAt(index);
if(!holder.hasAnyOfTheFlags(INVALID | REMOVED | UPDATE)){
//只要不符合上面的任何一个情况,当前的ViewHolder都可以被缓存到mCachedView数组里面.
}else{
addViewHolderToRecycledViewPool();
}
} else {
Recycleview.detachViewFromParent(index)
if(holder.hasAnyOfTheFlags(REMOVED | !UPDATE){
mAttachedScap.add(holder)
}else{
mChangedScrap.add(holder)
}
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

看到了精简的你会发现,只有当不是INVALIDE REMOVE UPDATE是可以被放进mCachedViews.否则当是REMOVE INVALID会被放进mAttachedScrap里面.如果是UPDATE会被放进mChangedScrap

调用notifyItemChanged(int position)

这是专门更新某一个ViewHolder.
这么一个更新操作会被保存在AdapterHelper.mPendingUpdates数组里面:

1
2
3
4
5
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
return mPendingUpdates.size() == 1;
}

构建了操作对象UpdateOp操作名字是UPDATE.关于操作有以下四种,对应着不同的更新方式:

  1. ADD
  2. REMOV
  3. UPDATE
  4. MOVE

这个操作被记录在mPendingUpdates数组里面.然后设置了Recycleview.mAdapterUpdateDuringMeasuretrue.关于这个数组什么时候会被用到后面讲.


关于Recycleview动画实现原理

ViewHolder的状态有以下几种,这也是动画的几种区别:

  1. PERSISTENT : 针对布局前和布局后都在手机界面上的View所做的动画.
  2. REMOVED : 在布局前对用户可见,但是数据已经从数据源中删除.
  3. ADDED : 新增数据到数据源,并且在布局后对用户可见.
  4. DISAPPEARING : 数据一直都存在于数据原中,但是布局后从可见变成了不可见.
  5. APPERAING : 数据一直在数据源中,但是布局后从不可见变成了可见.

groovy闭包

为什么会开始学习闭包,因为在看到这个代码的时候我总是看不懂是什么意思:

1
2
3
4
5
6
gradle.taskGraph.beforeTask {
println("任务之前执行")
}
gradle.taskGraph.afterTask {Task task,TaskState state->
println("任务之后执行")
}

这里你可以点开源码,比如这个afterTask:

1
2
3
4
5
6
class TaskExecutionGraph{
void beforeTask(Closure var1);
void beforeTask(Action<Task> var1);
void afterTask(Closure var1);
void afterTask(Action<Task> var1);
}

这里可以看到传入的参数是一个叫Closure的类,这个就是Groovy里面的闭包.
这里举个闭包的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//lala.gradle
class Person{
String getName(Closure closure){
closure("cc","dd")
}
}
def bibao = {
x , y ->
println("Hello Closure ,the param valus is: "+x+","+y)
}
new Persion().getName(bibao)

// 运行后输出:
Hello Closure ,the param valus is:cc,dd

上面的也可以这么写:

1
2
3
4
5
6
7
8
class Person{
String getName(Closure closure){
closure("cc","dd")
}
}
new Persion().getName{x,y->
println("Hello Closure ,the param valus is: "+x+","+y)
}

看了上面这个例子你对闭包应该了解了,和kotlin里面的闭包差不多. 闭包


再回到上面的代码:

1
2
3
gradle.taskGraph.afterTask {Task task,TaskState state->
println("任务之后执行")
}

我们在看到源码的时候,看到的是afterTask里面传的参数是Closure,也就是一个闭包,那我们怎么知道这个闭包到底该怎么写呢.这个时候就需要看官方文档了:https://docs.gradle.org/current/javadoc/org/gradle/api/execution/TaskExecutionGraph.html#beforeTask-groovy.lang.Closure-
找到afterTask方法:
captrue.png
可以看到官方文档写清楚了,他的参数有哪些.


现在看另外一个方法: (这个方法是用来配置远程仓库的)

1
2
3
repositories {
mavenCentral()
}
1
2
3
class Project{
void repositories(Closure var1);
}

可以看到这个方法是属于project对象的.
打开官方文档找下这个方法:

captrue.png

文档里面很清楚的讲了RepositoryHandler做为闭包的委托传递给闭包.

这里出现了一个新的知识点做为闭包的委托传递给闭包.
闭包委托:
groovy的闭包委托有thisObject,owner,delegate三个属性.当在闭包内调用方法时,由他们来确定使用哪个对象来处理.默认情况owner和delegeta是相等的.但是delegate是可以进行修改的.

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
//闭包委托
def method1(){
println "Context this :${this.getClass()} in root"
println "method1 in root"
}

class Delegate{
def method1(){
println "Delegate this : ${this.getClass()} in Delegate"
println "method1 in Delegate"
}

def test(Closure<Delegate> closure){
closure(this)
}
}

task helloDelegate <<{
new Delegate().test{
println "thisObject :${thisObject.getClass()}"
println "owner:${owner.getClass()}"
println "delegate:${delegate.getClass()}"
method1()
it.method1()
}
}
//输出
thisObject :class build_apnm7l2hrtoaa0bd51bpb5xcf
owner:class build_apnm7l2hrta
delegate:class build_apnm7
Context this :class build_apnm7l2hrtoaa0bd51bpb5xcf in root
method1 in root
Delegate this : class Delegate in Delegate
method1 in Delegate

可以看到thisObject优先级是最高的.
在DSL中,比如gradle,一般会指定delegate为当前it,这样我们就可以在闭包中对该it进行配置和方法调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
String personName
int personAge

def dumpPerson(){
println "name is ${personName}, age is ${personAge}"
}
}

def person(Closure<Person> closure){
Person p = new Person()
closure.delegate = p
//委托模式优先
closure.setResolveStrategy(Closure.DELEGATE_FIRST)
closure(p)
}

task configClosure << {
person{
personName="张三"
personAge = 20
dumpPerson()
}
}

在回到上面讲的方法:

1
2
3
repositories {
mavenCentral()
}

官方文档讲了RepositoryHandler做为了闭包的委托传递给了闭包,所以在{}里面可以用到的方法就都是在RepositoryHandler对象申明的方法了.不信你可以打开RepositoryHandler类看下:

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
public interface RepositoryHandler extends ArtifactRepositoryContainer {
FlatDirectoryArtifactRepository flatDir(Map<String, ?> var1);

FlatDirectoryArtifactRepository flatDir(Closure var1);

FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> var1);

ArtifactRepository gradlePluginPortal();

ArtifactRepository gradlePluginPortal(Action<? super ArtifactRepository> var1);

/** @deprecated */
@Deprecated
MavenArtifactRepository jcenter(Action<? super MavenArtifactRepository> var1);

/** @deprecated */
@Deprecated
MavenArtifactRepository jcenter();

MavenArtifactRepository mavenCentral(Map<String, ?> var1);

MavenArtifactRepository mavenCentral();

MavenArtifactRepository mavenCentral(Action<? super MavenArtifactRepository> var1);

MavenArtifactRepository mavenLocal();

MavenArtifactRepository mavenLocal(Action<? super MavenArtifactRepository> var1);

MavenArtifactRepository google();

MavenArtifactRepository google(Action<? super MavenArtifactRepository> var1);

MavenArtifactRepository maven(Closure var1);

MavenArtifactRepository maven(Action<? super MavenArtifactRepository> var1);

IvyArtifactRepository ivy(Closure var1);

IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> var1);

void exclusiveContent(Action<? super ExclusiveContentRepository> var1);
}

是不是看到了我们熟知的方法:jcenter mavenCentral.所以上面的代码完整点可以这么写:

1
2
3
repositories {RepositoryHandler->
RepositoryHandler.mavenCentral()
}

JavaPoet学习

这是一个帮助我们动态生成代码的框架.

这里先给一个最简单的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
public static void main(String[] args) throws IOException {
buildAjavaFile();
}

static void buildAjavaFile() throws IOException {
MethodSpec methodSpec = MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC,Modifier.STATIC)
.returns(void.class)
.addParameter(Integer.class,"loop")
.addCode("System.out.print("生成的代码");\n")
.addCode("$T a = $L",Tea.class,"new Tea()")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("TestCode")
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addMethod(methodSpec)
.build();
JavaFile javaFile = JavaFile.builder("com.haha.hah",typeSpec)
.build();
// 将java文件内容写入文件中
File file = new File("./javapoet");
javaFile.writeTo(file);
}
}

生成的代码:

1
2
3
4
5
6
7
8
9
10
11
package com.haha.hah;

import ddd.Tea;
import java.lang.Integer;

public final class TestCode {
public static void test(Integer loop) {
System.out.print("生成的代码");
Tea a = new Tea()
}
}

可以看到需要import的内容他也帮我们自动添加了.

更多详细的使用文档可以参考这个博客:

  1. https://blog.csdn.net/IO_Field/article/details/89355941
  2. https://blog.csdn.net/chennai1101/article/details/103975423

captrue.png

java之apt学习环境搭建

新建modue: java_apt

captrue.png.jpg

在build.gradle添加下面内容:

1
2
implementation 'com.google.auto.service:auto-service:1.0.1'
annotationProcessor 'com.google.auto.service:auto-service:1.0.1'

新建类BaseProcessor继承AbstractProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.google.auto.service.AutoService;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;
@AutoService(Processor.class)
@SupportedAnnotationTypes("sunrise.annotation.Hello")
public class BaseProcessor extends AbstractProcessor {

@Override
public synchronized void init(ProcessingEnvironment env) {
System.out.println("sadasdasdsad");
}

@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
System.out.println("sadasdasdsadasdasdasddsad");

return true;
}
}

然后在最外层的build.gradle添加下面内容:

1
annotationProcessor(project(":java_apt"))

这个时候build下我们的工程:会看到生成下面这些内容:

captrue.png

这就是使用这种方法的好处,他会自动帮你生成这些文件.