Android View轉(zhuǎn)Bitmap引發(fā)的思考

前言

筆者在最近在做截屏分享的功能,采用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捕捉一下呢叼风?


https://www.processon.com/view/5ec74b817d9c08156c58f2b3#map

理論上取董,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可以參考這里估盘。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓷患,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子遣妥,更是在濱河造成了極大的恐慌擅编,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箫踩,死亡現(xiàn)場離奇詭異爱态,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)境钟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門肢藐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吱韭,你說我怎么就攤上這事吆豹。” “怎么了理盆?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵痘煤,是天一觀的道長。 經(jīng)常有香客問我猿规,道長衷快,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任姨俩,我火速辦了婚禮蘸拔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘环葵。我一直安慰自己调窍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布张遭。 她就那樣靜靜地躺著邓萨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪菊卷。 梳的紋絲不亂的頭發(fā)上缔恳,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音洁闰,去河邊找鬼歉甚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扑眉,可吹牛的內(nèi)容都是我干的纸泄。 我是一名探鬼主播雅镊,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刃滓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起耸弄,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤咧虎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后计呈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砰诵,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年捌显,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茁彭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扶歪,死狀恐怖理肺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情善镰,我是刑警寧澤妹萨,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站炫欺,受9級特大地震影響乎完,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜品洛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一树姨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桥状,春花似錦帽揪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至砾肺,卻和暖如春挽霉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背变汪。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工侠坎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裙盾。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓实胸,卻偏偏與公主長得像他嫡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子庐完,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容