平臺:
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<K, V> toEvict =map.nxt;
代替 代碼片段2即可;
參考
AndroidStudio debug source code和運行代碼不匹配