Android-图片加载优化

Android应用中常常有加载图片资源的操作,随着Android手机平板的分辨率越来越高,图片资源越来越大,在加载高清图片的时候,由于瞬间产生大量的内存消耗,有时java GC来不及进行垃圾回收,就很容易发生OOM现象,怎么优化加载图片呢?

方法一:BitmapFactory.Options的两个参数inPurgeable、inNativeAlloc

public Bitmap decodeFile(String filePath) {
        Bitmap bitmap = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPurgeable = true;//1 :inPurgeable
        try {
            BitmapFactory.Options.class.getField("inNativeAlloc").setBoolean(options, true);//2:inNativeAlloc
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        if (mFilePath != null) {
            bitmap = BitmapFactory.decodeFile(mFilePath, options);
        }
        return bitmap;
    }

关于inPurgeable:处理过位图加载的人可能对BitmapFactory.Options的inPurgeable参数比较熟悉,当inPurgeable==true时,可以让java系统内存不足时先行回收部分的内存,这个方法其实已经解决大部分的问题了。
关于inNativeAlloc:那么try-catch里面做了什么呢?在看了source code 之后,我发现在BitmapFactory.Options里竟然有一个inNativeAlloc的public变量,可以直接不把使用的内存算到VM里,相应的,inPurgeable生出来的内存还是算在java 的VM里。需要注意的这个变量是个隐藏的变量,不能直接用,需要用反射将这个变量设成true。如此一来bitmap OOM的问题发生的机率又更低了.

方法二:使用BitmapFactory.decodeStream

public Bitmap ReadBitMap(Context context, int resId){    
       BitmapFactory.Options opt = new BitmapFactory.Options();    
       opt.inPreferredConfig = Bitmap.Config.RGB_565;     
       opt.inPurgeable = true;    
       opt.inInputShareable = true;      
      InputStream is = context.getResources().openRawResource(resId);    
       return BitmapFactory.decodeStream(is,null,opt);    
}

关于BitmapFactory.decodeStream:当我们给view设置图片资源时,使用像 setBackgroundResource,setImageResource,或者 BitmapFactory.decodeResource 这样的方法来设置一张高清图片的时候,这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。而改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的source。decodeStream的优势在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。如果在读取时加上图片的Config参数,可以更有效减少加载的内存,从而跟有效阻止抛out of Memory异常。
另外,需要特别注意:decodeStream是直接读取图片资料的字节码了, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。

方法三:使用内存缓存

1.内存缓存很多人都用,但是内存缓存设置需要得当,太小了会导致缓存不够用,太大了会导致其他应用可用内存减小,也容易造成内存溢出。

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // 获取到虚拟机的可用最大内存
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // 只使用1/8的空闲内存作为缓存空间.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

2.设置好内存缓存区,我们就可以在需要的时候从内存缓存区直接拿到图片了

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.default_drawable);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}

3.当然了,在bitmap是新的图片时,需要把图片放到缓存区中去:

class BitmapWorkerTask extends AsyncTask {  
    ...  
    @Override  
    protected Bitmap doInBackground(Integer... params) {  
        final Bitmap bitmap = decodeSampledBitmapFromResource(  
                getResources(), params[0], 100, 100));  
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
        return bitmap;  
    }  
    ...  
}

*1.针对大图的加载,比较常用的方法是进行 DownSampling(向下采样)。
2.避免同一时间加载大量的图片,也可以为我们的内存优化提供不小的收益。比如,在一个 ScrollView 中有非常多的 ImageView,这时候,占用的内存往往非常客观,因为就算一些 View 我们在屏幕视野里面看不到,它还是持续占用内存。我们可以通过 RecyclerView 或者 ListView 来予以替换,从而达到内存优化的效果。
3.一般不要静态缓存图片,就算有缓存,也可以结合 LRU 机制来保证缓存图片的个数和占用内存。Android SDK 已经提供了 LruCache 类来实现 LRU 机制。
4.采用开源库加载图片,这些开源库一般来说,对内存的优化已经比较全面了,比我们自己手工管理内存来的好。

三级缓存-UIL中的内存缓存策略

  1. 只使用的是强引用缓存
    LruMemoryCache(这个类就是这个开源框架默认的内存缓存类,缓存的是bitmap的强引用,下面我会从源码上面分析这个类)

    2.使用强引用和弱引用相结合的缓存有
    (1)UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap)
    (2)LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)
    (3)FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap)
    (4)LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)
    (5)LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)

    3.只使用弱引用缓存
    WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)

三级缓存-UIL中的磁盘缓存策略

像新浪微博、花瓣这种应用需要加载很多图片,本来图片的加载就慢了,如果下次打开的时候还需要再一次下载上次已经有过的图片,相信用户的流量会让他们的叫骂声很响亮。对于图片很多的应用,一个好的磁盘缓存直接决定了应用在用户手机的留存时间。我们自己实现磁盘缓存,要考虑的太多,幸好UIL提供了几种常见的磁盘缓存策略,当然如果你觉得都不符合你的要求,你也可以自己去扩展。

(1)FileCountLimitedDiscCache(可以设定缓存图片的个数,当超过设定值,删除掉最先加入到硬盘的文件)
(2)LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件)
(3)TotalSizeLimitedDiscCache(设定缓存bitmap的最大值,当超过这个值,删除最先加入到硬盘的文件)
(4)UnlimitedDiscCache(这个缓存类没有任何的限制)
在UIL中有着比较完整的存储策略,根据预先指定的空间大小,使用频率(生命周期),文件个数的约束条件,都有着对应的实现策略。最基础的接口DiscCacheAware和抽象类BaseDiscCache

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值