我們啟動一個空殼app,所謂空殼app 就是裡面實際上啥都沒有,一張圖片都沒有的app,在我的小米Note 4x上看看記憶體佔用多少
我曹 怎麼會有 36mb? wtf? 這個graphics裡面幾乎都是bitmap,為何這裡bitmap佔用記憶體這麼多呀?
我明明一張圖都沒載入啊。
開啟記憶體dump,匯出以後 用hprof工具轉換成mat可以解析的格式:
最後用mat開啟,搜下bitmap 看看是啥?
嗯?怎麼有這麼多bitmap物件?我沒載入過任何圖片啊?
查檢視都是誰引用的?
去原始碼裡面查查這是個sPreloadedDrawables啥東西?
既然是跟資源有關的東西 肯定繞不開ResourcesImpl這個類了,很快就能搜到
實際上解釋起來,就是android系統啟動的時候肯定是先啟動zygote程式,有了這個程式以後,其他app程式啟動 就只要fork他即可。所以實際上當zygote程式啟動的時候會把一些系統的資源優先載入到記憶體裡,注意 這個framework本身包含的資源是很多的,但是隻有很有限的資源才有資格到這預載入記憶體裡面
所以當我們zygote程式啟動完畢以後,這個程式的記憶體裡面就有這些 系統資源在裡面了,我們這裡只關注bitmap資源 也就是drawable,然後其他app程式啟動的時候既然是fork了zygote程式,自然而然的這裡 也會包含這些圖片資源。
這就是為什麼我們一個空殼app也會載入那麼多bitmap在記憶體裡的原因,這樣做的好處當然是如果你的app用到了這些預載入 的資源,那麼可以省略decode的過程,直接從記憶體裡面取,速度快,缺點就是這個東西佔用的記憶體著實有一些大了。。。 動不動20mb左右的空間。
我們來看下系統loadDrawable的過程就可以加深這個理解了
@Nullable
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
boolean useCache) throws NotFoundException {
try {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);
if (name != null) {
Log.d("PreloadDrawable", name);
}
}
}
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme. Skip the cache if
// we're currently preloading or we're not using the cache.
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
// Next, check preloaded drawables. Preloaded drawables may contain
// unresolved theme attributes.
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
//這裡看到沒有 取系統快取了
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
//取不到就重新decode
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(wrapper, value, id, null);
}
// Determine if the drawable has unresolved theme attributes. If it
// does, we'll need to apply a theme and store it in a theme-specific
// cache.
final boolean canApplyTheme = dr != null && dr.canApplyTheme();
if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
}
// If we were able to obtain a drawable, store it in the appropriate
// cache: preload, not themed, null theme, or theme-specific. Don't
// pollute the cache with drawables loaded from a foreign density.
if (dr != null && useCache) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
} catch (Exception e) {
String name;
try {
name = getResourceName(id);
} catch (NotFoundException e2) {
name = "(missing name)";
}
// The target drawable might fail to load for any number of
// reasons, but we always want to include the resource name.
// Since the client already expects this method to throw a
// NotFoundException, just throw one of those.
final NotFoundException nfe = new NotFoundException("Drawable " + name
+ " with resource ID #0x" + Integer.toHexString(id), e);
nfe.setStackTrace(new StackTraceElement[0]);
throw nfe;
}
}
複製程式碼
所以這裡就給我們提供了一種優化思路:
當我們的app剛開始執行時,記憶體富餘空間很大,所以我們可以不關心這個系統快取帶來的問題,但是如果當我們發現 記憶體壓力變大的時候,就可以考慮在合適的時機,手動的釋放掉這20mb左右的系統快取了,釋放的方法也很簡單 反射clear一下即可:
Resources resource = getApplicationContext().getResources();
try {
//注意這個地方 有些rom會有獨特的名字,需要你們線上監控到不同rom以後 遍歷下他們的field看看這個變數
//的名字是什麼 才能hook成功
Field field = Resources.class.getDeclaredField("sPreloadedDrawables");
field.setAccessible(true);
LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables = (LongSparseArray<Drawable.ConstantState>[]) field
.get(resource);
for (LongSparseArray<Drawable.ConstantState> s : sPreloadedDrawables) {
s.clear();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
複製程式碼
當然這樣的開銷也有弊病,就是如果你釋放掉這部分記憶體以後,如果要載入這些資源,那麼會多一步decode的過程。
當然現在外掛框架很多了,很多人都對resources這部分有了解,其實這個地方我們完全可以利用動態代理的方法 hook掉 系統的這個取快取的sPreloadedDrawables 過程,讓他們取 我們自己圖片載入框架裡的快取即可。 但是注意這個hook的過程要考慮不同rom的相容性的地方就更多了,有興趣的同學可以自行研究下。