Android开发 ListView,Gallery,GridView等图片性能优化

ListView,Gallery,GridView等控件,在载入大量图片时,很容易会产生OutOfMemoryError异常,即内存溢出.因为每个应用可用内存是有限的,但是图片却很占内存,JPG,PNG本身就是压缩格式,如果分辨率高,很可能保存到磁盘中只有几百K,但是读到内存中会占用十几M内存.图片一多,自然就OutOfMemoryError了. 解决这个问题,需要考虑两个方面. 一是按需载入图片,即读图片的缩略图.比如,每个ImageView在屏幕上的面积是120dp*120dp,但是这个图片是高清的,分辨率2560*1600,如果直接载入整个图片,占用的内存是2560*1600*4/1024/1024=15.625M,如果一屏显示5个Item,占用的内存就是78.125M!而Galaxy Nexus给每个应用分配的内存只有64M,低配机型更少.所以,这里必须根据需要的分辨率载入图片.BitmapFactory解析图片时可以传入一个BitmapFactory.Options参数.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

这里最重要的是inSampleSize,这是一个int值,等于1时,返回原图,大于1时,返回宽高为原图1/inSampleSize的图片, 所以,我们需要先根据需要的大小,计算inSampleSize.

BitmapFactory.Options options = new BitmapFactory.Options();
        //设为true,不会去解析图片,只解析边界,即宽高
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(jpgPath, options);
         //读出图片真实的宽高
int[] size = new int[2];
size[0] = options.outWidth;
size[1] = options.outHeight;

float realWidth = size[0];
float realHeight = size[1];

// 如果图片尺寸比最大值小,直接返回
if (maxWidth > realWidth && maxHeight > realHeight) {
    return 1;
}
// 计算宽高比
float target_ratio = (float) maxWidth / maxHeight;
float real_ratio = realWidth / realHeight;
int inSampleSize = 1;
if (real_ratio > target_ratio) {
    // 如果width太大,height太小,以width为基准,把realWidth设为maxWidth,realHeight缩放
    inSampleSize = (int) realWidth / maxWidth;
} else {
    inSampleSize = (int) realHeight / maxHeight;
}

虽然经过上面的处理,图片占用的内存已经大幅减少,放个百来个Item也不会OutOfMemoryError了,但很多应用都是下拉刷新,增加Item,Item的数量不可控,如果所有的图片都放内存里,溢出也是早晚的事.这里就需要做个图片缓存,内存里只保留需要用到的,没用到的,释放.我这里做了二级缓存,第一级放内存,第二级放SD卡. 说到图片内存缓存,网上大部分文章推荐软引用(SoftReference) 和弱引用(WeakReference),但是从Android 2.3开始,所有软引用和弱引用的对象在GC时都会被回收,根本起不到缓存的作用,Google推荐使用LruCache(http://developer.android.com/reference/android/util/LruCache.html),该类可以分配指定大小的缓存空间,当达到临界值时,最先保存的对象会被挤出,释放内存. 下面是一个使用LruCache的适配器:

package com.pocketdigi.googleimage;

import java.util.List;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.pocketdigi.googleimage.mode.ApiResult.ImageData;
import com.pocketdigi.utils.ImageUtils;
import com.pocketdigi.utils.ImageUtils.DownBitmapListener;
import com.pocketdigi.views.ImageViewer;

public class GoogleImageAdapter extends BaseAdapter{
    Context mContext;
    List mImageList;
    LayoutInflater mInflater;
    boolean mBusy = false;
    // 图片缓存,Key是url,Value就是Bitmap
    LruCache mImageCache;
    public GoogleImageAdapter(Context context, List imageList) {
        mContext = context;
        mImageList = imageList;
        mInflater = LayoutInflater.from(mContext);
        //读取应用可用内存大小,这里读出来,单位是兆,Galaxy Nexus是64M
        final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        //取1/8作为图片缓存
        final int maxSize = 1024 * 1024 * memClass / 8;
        mImageCache = new LruCache(maxSize) {
            // 必须覆写sizeOf方法,因为默认返回的是1,即统计的是对象的数量,而不是占用的内存
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO 自动生成的方法存根
                return value.getByteCount();
            }
        };

    }

    @Override
    public int getCount() {
        // TODO 自动生成的方法存根
        return mImageList.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO 自动生成的方法存根
        return mImageList.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO 自动生成的方法存根
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO 自动生成的方法存根
        final ImageData imageData = mImageList.get(position);
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.item_listview, null);
            holder = new ViewHolder();
            holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
            holder.iv_thumbnail = (ImageView) convertView.findViewById(R.id.iv_thumbnail);
            holder.thumbnail_url = imageData.getThumbnail_url();
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
            if (!holder.thumbnail_url.equals(imageData.getThumbnail_url())) {
                holder.iv_thumbnail.setImageResource(R.drawable.loading);
            }
        }

        holder.tv_title.setText(imageData.getAbs());
        if (!isBusy()) {
            final String imgUrl = imageData.getThumbnail_url();
            Bitmap bmp = mImageCache.get(imgUrl);
            if (bmp != null) {
                holder.iv_thumbnail.setImageBitmap(bmp);
            } else {
                loadBmpFromNetWork(imgUrl);
            }

        }

        holder.iv_thumbnail.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                //点击ImageView显示大图
                ImageViewer imageViewer = new ImageViewer(mContext);
                imageViewer.setSourceView((ImageView) v);
                imageViewer.setJpgUrl(imageData.getImage_url());
                imageViewer.show();
            }
        });

        return convertView;
    }
    /**
     * 从网络下载图片
     * @param imgUrl
     */
    private void loadBmpFromNetWork(final String imgUrl) {
        //异步从网络下载图片,这个异步下载方法,是有作磁盘缓存的
        //缓存原理,根据传入的url,计算md5,作缓存文件名,下载前先判断该文件存不存在,如果存在,从SD卡读,不存在,再去下载
        //参数120,120,是返回120*120的bitmap,避免在载入大图时浪费内存
        ImageUtils.asynchronousDownloadBitmap(imgUrl, 120, 120, new DownBitmapListener() {
            @Override
            public void onComplete(Bitmap bmp) {
                // TODO 自动生成的方法存根
                if(imgUrl!=null&&bmp!=null)
                {
                    mImageCache.put(imgUrl, bmp);
                    notifyDataSetChanged();
                }
            }

            @Override
            public void onProgressChanged(long total, long downloaded) {
                // TODO 自动生成的方法存根

            }
        });
    }

    @Override
    public int getItemViewType(int position) {
        // TODO 自动生成的方法存根
        return super.getItemViewType(position);
    }

    @Override
    public int getViewTypeCount() {
        // TODO 自动生成的方法存根
        return super.getViewTypeCount();
    }
    //用来保存各个控件的引用
    static class ViewHolder {
        TextView tv_title;
        ImageView iv_thumbnail;
        String thumbnail_url;
    }



    public boolean isBusy() {
        return mBusy;
    }

    public void setBusy(boolean busy) {
        this.mBusy = busy;
    }

}