平台:
android-28(android 9.0)
我有个需求, 需要改动LruCache, 当我从android 9.0 SDK源码(从AndrodiStudio SDK manager下载)拷贝到本地目录后, 发现一个bug.
其TrimToSize 函数会淘汰最新元素(链尾), 而不是最老元素(链头), 和链接5 提到的android5.0 中的bug一致. 奇怪的是, 直接调用系统的LruCache(android.util)不会有问题, 调用我从SDK源码复制出来的代码会出现上述问题.
测试代码:
SerializableLruCache<Integer, Integer> cache = new SerializableLruCache<Integer, Integer>(10);
LruCache<Integer, Integer> lruCache = new LruCache<Integer, Integer>(10);
for (int i = 0; i < 20; i++) {
cache.put(i,i);
lruCache.put(i,i);
}
for (Map.Entry entry : cache.snapshot().entrySet()) {
System.out.print("," + entry.getValue());
}
System.out.println();
for (Map.Entry entry : lruCache.snapshot().entrySet()) {
System.out.print("," + entry.getValue());
}
前者打印10~ 19, 后者打印0 ~9
当调试跳入SDK的LruCache源码时会提示 Source code does not match the bytecode. 而我的真机, 工程的compileSDK, SDK源码 都是android-28. 为什么还会提示源码不匹配呢?
主要问题是trimToSize函数找待删除元素的时候有差异;
在android 27 是这样的:
Map.Entry<K, V> toEvict = map.eldest();
//...
//eldest是被标记隐藏的. 实现如下:
public Entry<K, V> eldest() {
LinkedEntry<K, V> eldest = header.nxt;
return eldest != header ? eldest : null;
}
而android 28 源码
// BEGIN LAYOUTLIB CHANGE
// get the last item in the linked list.
// This is not efficient, the goal here is to minimize the changes
// compared to the platform version.
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
问题是, 在android9真机上对应的源码是什么. 使用jd-gui工具反编译android9.0.jar看到具体实现是这样的:
public void trimToSize(int maxSize) { throw new RuntimeException("Stub!"); }
这里意思是androidSDK没有实现此函数, 具体实现参考framework. SDK源码注释也说明需要对比平台版本, 但是并没有细说.
链接4是Android9.0 framework版本的LruCache实现, trimTosSize函数使用 map.eldest();
原因总结
原来是Framework实现和SDK源码不一致, 而我复制了一份带bug版本的LruCache.
由于 map.eldest();是隐藏调用, 无法直接使用. 用 LinkedEntry
代替 代码片段2即可;
AndroidStudio debug source code和运行代码不匹配
阅读Android源码的一些姿势 - 中二病也要开发ANDROID - SegmentFault 思否\
framework java
lruCache- pie-release
link