前言
筆者在最近在做截屏分享的功能,采用getDrawingCache()發(fā)現(xiàn)了兩個(gè)問題,特此記錄一下。
View生成Bitmap的兩種方式矢炼。
- 利用Canvas繪制出bitmap (測量后)
public static Bitmap getBitmapFromView(View view) {
final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
- 利用Canvas繪制出bitmap (測量前 不需要展示布局)
public static Bitmap getBitmap(View view) {
final int screenWidth = ScreenUtil.getScreenWidth(view.getContext());
final int screenHeight = ScreenUtil.getScreenHeight(view.getContext());
final int widthSpec = View.MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(screenHeight, MeasureSpec.EXACTLY);
view.measure(widthSpec, heightSpec);
view.layout(0, 0, screenWidth, screenHeight);
Bitmap bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Config.RGB_565);
final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
- View # getDrawingCache()
public static Bitmap getBitmapFromView(View view) {
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.setDrawingCacheEnabled(true);
return view.getDrawingCache();
}
public static Bitmap getBitmapFromView(View view) {
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
return view.getDrawingCache();
}
最終采用了第一種方案,沒什么可說的阿纤,不過第二種方案有一定學(xué)習(xí)價(jià)值句灌。筆者當(dāng)時(shí)在使用如下寫法時(shí),發(fā)現(xiàn)最終返回的Bitmap結(jié)果為null欠拾。
public static Bitmap getBitmapFromView(View view) {
view.setDrawingCacheEnabled(true);
return view.getDrawingCache();
}
不知道為什么胰锌,所以就看下源碼。
public void setDrawingCacheEnabled(boolean enabled) {
mCachingFailed = false;
setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
}
設(shè)置標(biāo)記位清蚀,沒啥說的匕荸。getDrawingCache()最終會走到buildDrawingCacheImpl(boolean autoSize)方法中爹谭。
private void buildDrawingCacheImpl(boolean autoScale) {
// 省略...
final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
final long drawingCacheSize = ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
if (width > 0 && height > 0) {
Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
+ " too large to fit into a software layer (or drawing cache), needs "
+ projectedBitmapSize + " bytes, only "
+ drawingCacheSize + " available");
}
destroyDrawingCache();
mCachingFailed = true;
return;
}
}
這里if判斷中會判斷當(dāng)前要生成Bitmap的目標(biāo)View的位圖大小與系統(tǒng)默認(rèn)支持的最大可生成的緩存大小做對比若目標(biāo)Bitmap大小大于系統(tǒng)的最大緩存大小枷邪,則直接返回Null。我們看下系統(tǒng)最大的支持的緩存Bitmap大小是如何計(jì)算的。
final Display display = win.getDefaultDisplay();
final Point size = new Point();
display.getRealSize(size);
mMaximumDrawingCacheSize = 4 * size.x * size.y;
從上述代碼上講东揣,最大緩存大小是 4 * 屏幕寬高 得出践惑。
因此,我們第一個(gè)思考嘶卧,如何去修改最終得目標(biāo)Bitmap的最終大小呢尔觉?使得這個(gè)大小小于系統(tǒng)所支持的大小呢?
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
結(jié)論就是提前將測量的尺寸設(shè)置為零芥吟,然后重新布局一下即可侦铜,就繞過去了判斷大小的限制。
那么引發(fā)的第二個(gè)思考就來了钟鸵,既然有大小判斷钉稍,Google肯定考慮到OOM的問題。那么我們繼續(xù)往下讀源碼棺耍。
private void buildDrawingCacheImpl(boolean autoScale) {
boolean clear = true;
Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCac
if (bitmap == null || bitmap.getWidth() != width || bitmap.getH
Bitmap.Config quality;
if (!opaque) {
switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
case DRAWING_CACHE_QUALITY_AUTO:
case DRAWING_CACHE_QUALITY_LOW:
case DRAWING_CACHE_QUALITY_HIGH:
default:
quality = Bitmap.Config.ARGB_8888;
break;
}
} else {
quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bit
}
if (bitmap != null) {
bitmap.recycle();
}
try {
// 1
bitmap = Bitmap.createBitmap(mResources.getDisplayMetri
bitmap.setDensity(getResources().getDisplayMetrics().de
if (autoScale) {
mDrawingCache = bitmap;
} else {
mUnscaledDrawingCache = bitmap;
}
if (opaque && use32BitCache) {
bitmap.setHasAlpha(false);
}
} catch (OutOfMemoryError e) {
if (autoScale) {
mDrawingCache = null;
} else {
mUnscaledDrawingCache = null;
}
mCachingFailed = true;
return;
}
clear = drawingCacheBackgroundColor != 0;
}
}
代碼1 處發(fā)現(xiàn)在創(chuàng)建Bitmap的時(shí)候贡未,try了創(chuàng)建Bitmap過程,然后catch了OutOfMemoryError這個(gè)錯(cuò)誤蒙袍,筆者在工作中俊卤,基本沒有對OOME進(jìn)行catch過,認(rèn)為OOM是無法被捕捉的害幅,因?yàn)樗鶎僖环N錯(cuò)誤消恍,繼承于VirtualMachineError這個(gè)父類。然后就寫了一個(gè)Demo矫限,發(fā)現(xiàn)OOM是可以被捕捉的哺哼。
try{
varbyte = ByteArray( 10000000* 1024* 1024)
} catch(ignore: OutOfMemoryError) {
// 主動釋放一些內(nèi)存資源
}
因此就引發(fā)了一個(gè)思考,什么時(shí)候需要將OOM捕捉一下呢叼风?
理論上取董,Java若拋出了Error的異常,那就說明它的執(zhí)行狀態(tài)已經(jīng)無法恢復(fù)了无宿,此時(shí)需要終止線程甚至是終止虛擬機(jī)茵汰。這是一種不應(yīng)該被我們應(yīng)用層去捕獲的異常。
那么產(chǎn)生OOM孽鸡,原因之一結(jié)合我們本文來說蹂午,就是Bitmap過大導(dǎo)致,結(jié)合需求彬碱,若發(fā)現(xiàn)View生成的Bitmap過大豆胸,那么就不進(jìn)行將View轉(zhuǎn)換成Bitmap的操作即可。所以是否需要捕捉OOM巷疼,有兩個(gè)先決條件:
- 觸發(fā) OOM 的代碼是開發(fā)者可控的晚胡。
- 在Try包圍的代碼塊中需要進(jìn)行大段的內(nèi)存分配,并且有可能導(dǎo)致OOM。
有關(guān)于VitualMachineError可以參考這里估盘。