圓形圖片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待以後發現了,再做修改,下面是單獨的效果: