1. 程式人生 > >圓形圖片CircleImageView(解決記憶體溢位)

圓形圖片CircleImageView(解決記憶體溢位)

一、前言

專案已經做完了,目前測試那邊還沒有提交什麼bug,所以目前對程式碼進行重構和優化。有一個很頭疼的問題就是圓形影象引起GC記憶體溢位,logcat老列印:Grow heap (frag case) to 20.982MB for 1542416-byte allocation 和 GC_FOR_ALLOC freed 407K, 20% free 17919K/22332K, paused 16ms, total 16ms。這個大家應該都瞭解,專案中也經常遇到會引起介面卡頓嚴重的甚至會導致記憶體溢位。下面我談談我的解決辦法和思路,如果有更好的點子或是本文有紕漏請及時指正。

二、引起的原因

專案中有一個GridView列表:

這裡寫圖片描述

專案比較趕,為了方便自己就在github上面下載了一個開源專案BorderCircularImageView,但是一開啟這個介面就各種GC各種開闢大記憶體,後來用記憶體分析軟體才發現在onDraw方法中有這樣一段程式碼:

@Override
    protected void onDraw(Canvas canvas) {
        ...
        ...

        Bitmap scaledSrcBmp;
        int diameter = radius * 2;
        // 為了防止寬高不相等,造成圓形圖片變形,因此擷取長方形中處於中間位置最大的正方形圖片
        int bmpWidth = bmp.getWidth();
        int bmpHeight = bmp.getHeight();
        int squareWidth = 0
, squareHeight = 0; int x = 0, y = 0; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大於寬 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 擷取正方形圖片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else
if (bmpHeight < bmpWidth) {// 寬大於高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); bmp = null; squareBitmap.recycle(); scaledSrcBmp.recycle(); squareBitmap = null; scaledSrcBmp = null; ... ... }

網上說在寫專案的時候往往最好不要在ondraw()方法中做過多的操作,像初始化畫筆開闢物件等等最好放在建構函式中,其實有時候實在要寫也沒什麼,一般不會引起介面卡頓GC,在這裡引起卡頓的問題有兩個,第一:GridView的複用機制,反覆拖動就會不斷的複用子條目介面,而導致不斷的呼叫圓形圖片的onDraw()方法;最重要的是第二個原因:裡面有這樣一句程式碼Bitmap.createBitmap(bmp, x, y, squareWidth,squareHeight) 。這句程式碼就是罪魁禍首開闢了大記憶體導致GC,就像自己如果不用第三方庫自己載入記憶體卡中大圖片往往會用BitmapFactory.(String pathName, Options opts);有時會導致GC是一樣的。

二、解決辦法

1.現在咋們最好不要在onDraw()中做過多的操作,還有就是咋們不能直接用Bitmap.createBitmap(…)這個方法。但是又必須將圖片變成圓形,所以有一種方式就是:1.不再Override onDraw()這個方法,2.可以從Drawable下手,因為Drawable其實有很多高效用法,這裡就不闡述了,大家可以去鴻大神的部落格看看。新建一個CircleImageDrawable繼承Drawable

    public class CircleImageDrawable extends Drawable {

    private Paint mPaint;
    private int mWidth;
    private Bitmap mBitmap;

    public CircleImageDrawable(Bitmap bitmap) {
        mBitmap = bitmap;
        BitmapShader bitmapShader = new BitmapShader(bitmap,
                TileMode.CLAMP, TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitmapShader);

        mWidth = Math.min(mBitmap.getWidth(), mBitmap.getHeight());
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, mPaint);
    }

    @Override
    public int getIntrinsicWidth() {
        return mWidth;
    }

    @Override
    public int getIntrinsicHeight() {
        return mWidth;
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

}

2.自定義一個CircleImageView繼承ImageView,這時我們必須拿bitmap轉為圓形Drawable,所以只要Override setImageBitmap()即可,但是有時我們不只是載入網路資料還必須載入本地資源圖片,所以還得覆蓋setImageResource(int resId)和setBackgroundResource(int resId)方法:

    /**
 * 
 * ============================================================
 * 
 * project name : 
 * 
 * copyright ZENG HUI (c) 2015
 * 
 * author : HUI
 * 
 * version : 1.0
 * 
 * date created : On October, 2015
 * 
 * description : 
 * 
 * revision history :
 * 
 * ============================================================
 * 
 */
public class CircleImageView extends ImageView {

    public CircleImageView(Context context) {
        super(context);
    }

    public CircleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        // 將Bitmap轉為圓形drawable
        CircleImageDrawable drawable = new CircleImageDrawable(bm);
        setImageDrawable(drawable);
    }

    @Override
    public void setImageResource(int resId) {
        // 這種載入方式同下
        ImageUtil.getInstance(getContext()).display(this, resId + "");
    }

    @Override
    public void setBackgroundResource(int resId) {
        // 可以用下面這種方式   , 但是這樣如果是大的圖片資原始檔, 同樣可以使記憶體高達 20M 甚至更高 
        /*
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.mv);
        setImageDrawable(new CircleImageDrawable(bitmap)); 
        */

        // 所以利用第三方Xutils讀取drawable目錄下的圖片檔案,這個庫裡面沒有這樣的一種方式,但可以對原始碼進行修改即可
        /*
        程式碼如下 : 利用流的方式轉為bitmap設定
        int drawableId = Integer.parseInt(uri); // 還原原始的 id
        InputStream inputStream = this.getContext().getResources()
                .openRawResource(drawableId);
        fileLen = inputStream.available();
        bis = new BufferedInputStream(inputStream); 
        result = Long.MAX_VALUE;
        */

        // BitmapUtils內部最終會呼叫   ((ImageView) container).setImageBitmap(bitmap);
        ImageUtil.getInstance(getContext()).display(this, resId + "");
    }

    @Override
    public void setBackground(Drawable background) {
        // 如果想在佈局xml程式碼中設定方形背景圖片,可以在這裡做處理,可以先將drawable轉為bitmap,但是也沒必要處理,直接放一個圓形的背景圖片即可
        super.setBackground(background);
    }
}

二、總結測試

最後放在自己的專案中就沒有GC和開闢大記憶體的問題了,目前還沒有bug待以後發現了,再做修改,下面是單獨的效果:
這裡寫圖片描述