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

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

  先来看一段神奇的代码



1 public Bitmap decodeFile(String filePath) {
 2         Bitmap bitmap = null;
 3         BitmapFactory.Options options = new BitmapFactory.Options();
 4         options.inPurgeable = true;
 5         try {
 6             BitmapFactory.Options.class.getField("inNativeAlloc").setBoolean(
 7                     options, true);
 8         } catch (IllegalArgumentException e) {
 9             e.printStackTrace();
10         } catch (SecurityException e) {
11             e.printStackTrace();
12         } catch (IllegalAccessException e) {
13             e.printStackTrace();
14         } catch (NoSuchFieldException e) {
15             e.printStackTrace();
16         }
17         if (mFilePath != null) {
18             bitmap = BitmapFactory.decodeFile(mFilePath, options);
19         }
20         return bitmap;
21     }



  关于inPurgeable

  处理过位图加载的人可能对BitmapFactory.Options的inPurgeable参数比较熟悉,当inPurgeable==true时,可以让java系统内存不足时先行回收部分的内存,这个方法其实已经解决大部分的问题了。

  关于inNativeAlloc

在看了source code 之后,我发现在BitmapFactory.Options里竟然有一个inNativeAlloc的public变量,可以直接不把使用的内存算到VM里,相应的,inPurgeable生出来的内存还是算在java 的VM里。需要注意的这个变量是个隐藏的变量,不能直接用,需要用反射将这个变量设成true。如此一来bitmap OOM的问题发生的机率又更低了,有需要的人可以参考一下。

  方法二:使用BitmapFactory.decodeStream

  同样先show code

 



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



  关于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;
 2 
 3 @Override
 4 protected void onCreate(Bundle savedInstanceState) {
 5     ...
 6     // 获取到虚拟机的可用最大内存
 7     final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
 8 
 9     // 只使用1/8的空闲内存作为缓存空间.
10     final int cacheSize = maxMemory / 8;
11 
12     mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
13         @Override
14         protected int sizeOf(String key, Bitmap bitmap) {
15             return bitmap.getByteCount() / 1024;
16         }
17     };
18     ...
19 }
20 
21 public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
22     if (getBitmapFromMemCache(key) == null) {
23         mMemoryCache.put(key, bitmap);
24     }
25 }
26 
27 public Bitmap getBitmapFromMemCache(String key) {
28     return mMemoryCache.get(key);
29 }



 

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



1 public void loadBitmap(int resId, ImageView imageView) {
 2     final String imageKey = String.valueOf(resId);
 3 
 4     final Bitmap bitmap = getBitmapFromMemCache(imageKey);
 5     if (bitmap != null) {
 6         mImageView.setImageBitmap(bitmap);
 7     } else {
 8         mImageView.setImageResource(R.drawable.default_drawable);
 9         BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
10         task.execute(resId);
11     }
12 }



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



1 class BitmapWorkerTask extends AsyncTask {  
 2     ...  
 3     @Override  
 4     protected Bitmap doInBackground(Integer... params) {  
 5         final Bitmap bitmap = decodeSampledBitmapFromResource(  
 6                 getResources(), params[0], 100, 100));  
 7         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
 8         return bitmap;  
 9     }  
10     ...  
11 }



  附:别的一些优化思想

  总结了下别的一些优化思想,有的是对图片进行压缩,比如说图片宽高像素大于空间时,就先把图片进行压缩,再decodeResource,不过一般应用开发时较少遇到这样的情况。还有的是使用SDcard卡进行缓存,这样即使应用被意外退出了,也不会丢失缓存,但是这中间需要应用到IO操作,IO操作向来是比较慢的,也不是很推荐。