轉載自:http://blog.csdn.net/mxm691292118/article/details/51028953
一、加載按顯示需要的比例縮小的圖片
1、先來說說屏幕密度
在Android中,Imageview控件的長寬單位一般設置爲dp/dip,而不用px。這樣做的原因,是因爲dp/dip與屏幕像素密度無關,px與屏幕密度有關。在Android中,規定以160dpi爲基準,1dip=1px,如果密度是320dpi,則1dip=2px。(所以,同一個imageview,在不同的設備上所顯示的長寬的像素是不同的,我們需要根據像素不同,來按比例壓縮大圖)
如果把一個大小爲1024x1024像素的圖片顯示到大小爲512x512像素的ImageView上嗎,就沒有必要加載整張原圖到Bitmap中。
爲了告訴解碼器去加載一個縮小比例是多少的圖片到內存中,需要在BitmapFactory.Options 中設置 inSampleSize 的值。例如, 一個分辨率爲2048x2048的圖片,如果設置 inSampleSize 爲4,那麼會產出一個大約512x512大小的Bitmap。
下面的代碼是動態獲取一個ImageView的長寬像素:
注意:返回的是像素值(px),而不是dp/dip
int reqHeight = iv.getLayoutParams().height;
int reqWidth = iv.getLayoutParams().width;
- 1
- 2
2、壓縮圖片
- 一般來說,加載本地資源圖片才需要壓縮,加載網絡圖片,應該讓服務器按需壓縮,一方面節省流量,另一方面提高加載流暢度
- 可以通過下面的代碼計算inSampleSize的值,就是需要壓縮的倍數:
//reqWidth和reqHeight是需要顯示的Imageview的width和height
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// height和width圖片長寬的像素
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 通過下面的方法可以獲得壓縮後的Bitmap:
注意:可以通過設置 inJustDecodeBounds 屬性爲true可以在解碼的時候避免內存的分配,它會返回一個null的Bitmap,但是可以獲取到 outWidth, outHeight 與 outMimeType。確定好壓縮比例後,再將inJustDecodeBounds設置爲false。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 計算inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 根據inSampleSize壓縮圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
二、Bitmap緩存
1、緩存類LruCache介紹
- 在Android3.0之後,一般使用LruCache來緩存Bitmap,它使用一個強引用的LinkedHashMap保存最近引用的對象,並且在緩存超出設置大小的時候剔除(evict)最少使用到的對象。
- LinkedHashMap會根據LRU算法來排列對象的順序,新加入的對象添加到頭部,剛被使用過的對象也被移動到頭部,所以在鏈表尾部的對象是最久沒有被使用過的,一旦鏈表滿了,有新對象加入,就會刪除鏈表尾部的對象。
2、如何給LruCache設置一個合適的大小?
- LruCache太大的話,容易造成OOM或者分配給應用的剩餘內存不夠用,LruCache大小的設置,應該考慮下面的因素:
- (1)應用允許的最大內存是多少?剩下了多少可用的內存?
- (2)多少張圖片會同時顯示在屏幕上?
- (3)設備的屏幕密度是多少?顯示圖片的像素是多少?
- (4)可以根據訪問頻率給Bitmap分組,爲不同的Bitmap組設置不同大小的LruCache對象。
3、一個LruCache使用的完整例子
- 代碼參考了一個大神的代碼,並修真了一些寫的不對和不好的地方。
public class PhotoAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
// 從network下載圖片的線程集合
private List<ImageDownloadTask> mDownloadTaskList;
private LruCache<String, Bitmap> mLruCache;
// 引用外部的變量
private WeakReference<GridView> mGridView;
private WeakReference<List<String>> urls;
private WeakReference<Context> mContext;
// 可見項的第一項的index
private int mFirstVisibleIndex;
// 可見項的個數
private int mVisibleItemCount;
// 是不是第一次打開Activity
private boolean isFirstOpen = true;
public PhotoAdapter(Context context, GridView mGridView, List<String> urls) {
this.mContext = new WeakReference<Context>(context);
this.urls = new WeakReference<List<String>>(urls);
this.mGridView = new WeakReference<GridView>(mGridView);
this.mGridView.get().setOnScrollListener(this);
mDownloadTaskList = new ArrayList<>();
// 初始化圖片緩存池
initCache();
}
private void initCache() {
// 獲取應用的max heap size
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Android官方教學文檔推薦LruCache的size爲heap size的1/8
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
if (bitmap != null) {
return bitmap.getByteCount() / 1024;
}
return 0;
}
};
}
@Override
public int getCount() {
return urls.get().size();
}
@Override
public Object getItem(int position) {
return urls.get().get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
viewHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(mContext.get()).inflate(R.layout.layout_item, parent, false);
holder = new viewHolder();
holder.mImageView = (ImageView) convertView.findViewById(R.id.imageView);
holder.mTextView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(holder);
} else {
holder = (viewHolder) convertView.getTag();
}
String url = urls.get().get(position);
//imageview與url綁定,防止錯亂顯示
holder.mImageView.setTag(MD5Tools.decodeString(url));
holder.mTextView.setText("第" + position + "項");
if (!holder.mImageView.getTag().equals(url)) {
showImageView(holder.mImageView, url);
}
return convertView;
}
/**
* convertView複用
*/
private class viewHolder {
ImageView mImageView;
TextView mTextView;
}
/**
* 給ImageView設置Bitmap
*/
private void showImageView(ImageView imageView, String url) {
// 對url進行md5編碼
String key = MD5Tools.decodeString(url);
// 先從cache中找bitmap緩存
Bitmap bitmap = get(key);
if (bitmap != null) {
// 如果緩存命中
imageView.setImageBitmap(bitmap);
} else {
// 如果cache miss
imageView.setBackgroundResource(R.color.color_five);
}
}
/**
* 將Bitmap put 到 cache中
*/
private void put(String key, Bitmap bitmap) {
if (get(key) == null) {
mLruCache.put(key, bitmap);
}
}
/**
* 在Cache中查找bitmap,如果miss則返回null
*/
private Bitmap get(String key) {
return mLruCache.get(key);
}
/**
* 從網絡下載圖片
*/
private Bitmap loadBitmap(String urlStr) {
HttpURLConnection connection = null;
Bitmap bitmap = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream mInputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(mInputStream);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return bitmap;
}
/**
* 取消所有的下載任務
*/
public void cancelAllTask() {
if (mDownloadTaskList != null) {
for (int i = 0; i < mDownloadTaskList.size(); i++) {
mDownloadTaskList.get(i).cancel(true);
}
}
}
/**
* 加載可見項的圖片
*/
private void loadVisibleBitmap(int mFirstVisibleItem, int mVisibleItemCount) {
for (int i = mFirstVisibleItem; i < mFirstVisibleItem + mVisibleItemCount; i++) {
final String url = urls.get().get(i);
String key = MD5Tools.decodeString(url);
Bitmap bitmap = get(key);
ImageView mImageView;
if (bitmap != null) {
//緩存中存在該圖片的話就設置給ImageView
mImageView = (ImageView) mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
if (mImageView != null) {
mImageView.setImageBitmap(bitmap);
}
} else {
//不存在的話就開啓一個異步線程去下載
ImageDownloadTask task = new ImageDownloadTask(this);
mDownloadTaskList.add(task);
task.execute(url);
}
}
}
/**
* 從網絡下載圖片的異步task
*/
static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private WeakReference<PhotoAdapter> photoAdapter;
public ImageDownloadTask(PhotoAdapter photoAdapter) {
this.photoAdapter = new WeakReference<PhotoAdapter>(photoAdapter);
}
@Override
protected Bitmap doInBackground(String... params) {
//在後臺開始下載圖片
url = params[0];
Bitmap bitmap = photoAdapter.get().loadBitmap(url);
if (bitmap != null) {
//把下載好的圖片放入LruCache中
String key = MD5Tools.decodeString(url);
photoAdapter.get().put(key, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//把下載好的圖片顯示出來
ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
if (mImageView != null && bitmap != null) {
mImageView.setImageBitmap(bitmap);
photoAdapter.get().mDownloadTaskList.remove(this);//把下載好的任務移除
}
}
}
/**
* 監聽GridView的滑動狀態
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//GridView停止滑動時,去加載可見項的圖片
if (scrollState == SCROLL_STATE_IDLE) {
loadVisibleBitmap(mFirstVisibleIndex, mVisibleItemCount);
} else {
//GridView開始滑動時,取消所有加載任務
cancelAllTask();
}
}
/**
* 監聽並更新GridView滑動過程中的可見項
*/
@Override
public void onScroll(AbsListView view, int firstVisibleIndex, int visibleItemCount, int totalItemCount) {
mFirstVisibleIndex = firstVisibleIndex;
mVisibleItemCount = visibleItemCount;
// 第一次打開,加載可見項
if (isFirstOpen && visibleItemCount > 0) {
loadVisibleBitmap(mFirstVisibleIndex, mVisibleItemCount);
isFirstOpen = false;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
4、Andorid3.0之前的Bitmap管理方法(參考自xiaanming的博客)
- 在Android3.0之前,Bitmap對象與數據是分開存儲的,Bitmap對象存儲在Java heap中,而像素數據存儲在Native Memory中,Java虛擬機的垃圾回收機制不會主動回收Native Memory中的對象,需要在Bitmap不需要使用的時候,主動調用recycle()方法來釋放,而在Android3.0之後,Bitmap的像素數據和Bitmap對象都存放在Java Heap中,所以不需要手動調用recycle()來釋放,垃圾收集器會處理。
- 應該在什麼時候去調用recycle()方法呢?可以用引用計數算法,用一個變量來記錄Bitmap顯示情況,如果Bitmap繪製在View上面displayRefCount加一, 否則就減一, 只有在displayResCount爲0且Bitmap不爲空且Bitmap沒有調用過recycle()的時候,才調用recycle(),下面用BitmapDrawable類來包裝下Bitmap對象,代碼如下:
public class RecycleBitmapDrawable extends BitmapDrawable {
private int displayResCount = 0;
private boolean mHasBeenDisplayed;
public RecycleBitmapDrawable(Resources res, Bitmap bitmap) {
super(res, bitmap);
}
/**
* @param isDisplay
*/
public void setIsDisplayed(boolean isDisplay){
synchronized (this) {
if(isDisplay){
mHasBeenDisplayed = true;
displayResCount ++;
}else{
displayResCount --;
}
}
checkState();
}
/**
* 檢查圖片的一些狀態,判斷是否需要調用recycle
*/
private synchronized void checkState() {
if (displayResCount <= 0 && mHasBeenDisplayed
&& hasValidBitmap()) {
getBitmap().recycle();
}
}
/**
* 判斷Bitmap是否爲空且是否調用過recycle()
* @return
*/
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 還需要一個自定義的ImageView,重寫了setImageDrawable()方法,在這個方法中我們先獲取ImageView上面的圖片,然後通知之前顯示在ImageView的Drawable不在顯示了,Drawable會判斷是否需要調用recycle(),代碼如下:
public class RecycleImageView extends ImageView {
public RecycleImageView(Context context) {
super(context);
}
public RecycleImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RecycleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setImageDrawable(Drawable drawable) {
Drawable previousDrawable = getDrawable();
super.setImageDrawable(drawable);
//顯示新的drawable
notifyDrawable(drawable, true);
//回收之前的圖片
notifyDrawable(previousDrawable, false);
}
@Override
protected void onDetachedFromWindow() {
//當View從窗口脫離的時候,清除drawable
setImageDrawable(null);
super.onDetachedFromWindow();
}
/**
* 通知該drawable顯示或者隱藏
*
* @param drawable
* @param isDisplayed
*/
public static void notifyDrawable(Drawable drawable, boolean isDisplayed) {
if (drawable instanceof RecycleBitmapDrawable) {
((RecycleBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
} else if (drawable instanceof LayerDrawable) {
LayerDrawable layerDrawable = (LayerDrawable) drawable;
for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 具體的使用方法如下
ImageView imageView = new ImageView(context);
imageView.setImageDrawable(new RecycleBitmapDrawable(context.getResource(), bitmap));
- 1
- 2