博主将所有博文整理在Github上:https://github.com/miomin/AndroidDifficulty
Andorid学习过程中的重难点整理,包括个人的一些读书笔记和博客。
如果你觉得对你有帮助的话,希望可以star活follow一下哟,我会持续保持更新。
写在前面:笔者的上一篇博文有提到过,如果不恰当的使用Bitmap,很容易造成OOM。这篇博文就来谈谈应该如何正确的管理Bitmap的内存,以及优化策略。
参考: Google官方教程 – 《Android Training Ccourse》
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;
2、压缩图片
//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;
}
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、缓存类LruCache介绍
2、如何给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;
}
}
}
4、Andorid3.0之前的Bitmap管理方法(参考自xiaanming的博客)
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();
}
}
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);
}
}
}
}
ImageView imageView = new ImageView(context);
imageView.setImageDrawable(new RecycleBitmapDrawable(context.getResource(), bitmap));