Android bitmap高效显示和优化
第一部分:Bitmap高效显示
应用场景:
有时候我们想在界面上显示一个网络图片或者显示一张本地的图片,
但是图片本身是很大的有几兆,但是显示的位置很小或者说我们可以用更小
的图片来满足这样的需求,如果把整个图片都显示出来会非常的耗内存,甚至可以导致
内存溢出,这就需要我们来处理,如何高效的显示图片,减少内存消耗。
1 BitmapFactory.Options options = new BitmapFactory.Options(); 2 3 options.inJustDecodeBounds = true; 4 5 BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 6 7 int imageHeight = options.outHeight; 8 int imageWidth = options.outWidth; 9 10 String imageType = options.outMimeType;
设置 inJustDecodeBounds 属性为true可以在decoding的时候避免内存的分配,
它会返回一个null的bitmap,
但是 outWidth, outHeight 与 outMimeType 还是可以获取。
这个技术可以允许你在构造bitmap之前优先读图片的尺寸与类型。
将本地一张大图片显示到页面,为了节省内存对图片进行压缩
下面的代码是计算压缩的比例:
1 public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 2 // Raw height and width of image 3 final int height = options.outHeight; 4 final int width = options.outWidth; 5 int inSampleSize = 1; 6 7 if (height > reqHeight || width > reqWidth) { 8 9 final int halfHeight = height / 2; 10 final int halfWidth = width / 2; 11 12 // Calculate the largest inSampleSize value that is a power of 2 and keeps both 13 // height and width larger than the requested height and width. 14 while ((halfHeight / inSampleSize) > reqHeight&& (halfWidth / inSampleSize) > reqWidth) { 15 inSampleSize *= 2; 16 } 17 } 18 19 return inSampleSize; 20 }
设置inSampleSize为2的幂是因为decoder最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。
1 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { 2 3 // First decode with inJustDecodeBounds=true to check dimensions 4 final BitmapFactory.Options options = new BitmapFactory.Options(); 5 options.inJustDecodeBounds = true; 6 BitmapFactory.decodeResource(res, resId, options); 7 8 // Calculate inSampleSize 9 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 10 11 // Decode bitmap with inSampleSize set 12 options.inJustDecodeBounds = false; 13 return BitmapFactory.decodeResource(res, resId, options); 14 }
为了使用这个方法,首先需要设置 inJustDecodeBounds 为 true,
把options的值传递过来,然后使用 inSampleSize 的值并设置
inJustDecodeBounds 为 false 来重新Decode一遍。
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
以上就是将一张图片压缩成100*100来显示的,大大的降低了显示原图片所占的内存。
注意:千万要记得,在退出程序,或者退出该界面的时候一定要对生成的bitmap进行回收
// 先判断是否已经回收
if(bitmap != null && !bitmap.isRecycled()){
// 回收并且置为null
bitmap.recycle();
bitmap = null;
}
System.gc();
第二部分:Bitmap缓存
内存缓存:LruCache类特别适合缓存bitmap的任务,保持最近引用的对象在一个强引用的LinkedHashMap中
,在缓存扩张到指定大小之前,移除最近最少使用的成员
创建LruCache缓存
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 10 final int cacheSize = maxMemory / 8; //设置缓存为内存大小的8分之1 11 12 //初始化缓存 13 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 14 @Override 15 protected int sizeOf(String key, Bitmap bitmap) { 16 17 return bitmap.getByteCount() / 1024; 18 } 19 }; 20 ... 21 } 22 23 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 24 if (getBitmapFromMemCache(key) == null) { 25 mMemoryCache.put(key, bitmap); 26 } 27 } 28 29 public Bitmap getBitmapFromMemCache(String key) { 30 return mMemoryCache.get(key); 31 }
加载缓存中的图片:
当加载一个bitmap到ImageView中的时候,先检查LruCache
如果找到了一个实体,那就马上更新到ImageView上面,否则使用一个后台线程来处理这张图片:
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.image_placeholder); 9 BitmapWorkerTask task = new BitmapWorkerTask(mImageView); 10 task.execute(resId); 11 } 12 }
BitmapWorkerTask也需要更新来以便加实体到内存缓存中
1 //缓存中没有图片就需重新加载 2 class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 3 ... 4 // Decode image in background. 5 @Override 6 protected Bitmap doInBackground(Integer... params) { 7 final Bitmap bitmap = decodeSampledBitmapFromResource( 8 getResources(), params[0], 100, 100)); 9 addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 10 return bitmap; 11 } 12 ... 13 }
使用磁盘缓存
创建一个磁盘缓存
1 private DiskLruCache mDiskLruCache; 2 private final Object mDiskCacheLock = new Object(); 3 private boolean mDiskCacheStarting = true; 4 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB 5 private static final String DISK_CACHE_SUBDIR = "thumbnails"; 6 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 ... 10 // Initialize memory cache 11 ... 12 // Initialize disk cache on background thread 13 File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR); //创建缓存目录 14 new InitDiskCacheTask().execute(cacheDir); //创建硬盘缓存 15 ... 16 } 17 18 //创建硬盘缓存的线程 19 class InitDiskCacheTask extends AsyncTask<File, Void, Void> { 20 @Override 21 protected Void doInBackground(File... params) { 22 synchronized (mDiskCacheLock) { 23 File cacheDir = params[0]; 24 mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE); 25 mDiskCacheStarting = false; // Finished initialization 26 mDiskCacheLock.notifyAll(); // Wake any waiting threads 27 } 28 return null; 29 } 30 } 31 32 //如果缓存中没有图片就从硬盘加载图片 33 class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 34 ... 35 // Decode image in background. 36 @Override 37 protected Bitmap doInBackground(Integer... params) { 38 final String imageKey = String.valueOf(params[0]); 39 40 // Check disk cache in background thread 41 Bitmap bitmap = getBitmapFromDiskCache(imageKey); 42 43 if (bitmap == null) { // Not found in disk cache 44 // Process as normal 45 final Bitmap bitmap = decodeSampledBitmapFromResource( 46 getResources(), params[0], 100, 100)); 47 } 48 49 // Add final bitmap to caches 50 addBitmapToCache(imageKey, bitmap); 51 52 return bitmap; 53 } 54 ... 55 } 56 57 //添加bitmap到缓存 58 public void addBitmapToCache(String key, Bitmap bitmap) { 59 // Add to memory cache as before 60 if (getBitmapFromMemCache(key) == null) { 61 mMemoryCache.put(key, bitmap); 62 } 63 64 // Also add to disk cache 65 synchronized (mDiskCacheLock) { 66 if (mDiskLruCache != null && mDiskLruCache.get(key) == null) { 67 mDiskLruCache.put(key, bitmap); 68 } 69 } 70 } 71 72 //获取缓存的bitmap 73 public Bitmap getBitmapFromDiskCache(String key) { 74 synchronized (mDiskCacheLock) { 75 // Wait while disk cache is started from background thread 76 while (mDiskCacheStarting) { 77 try { 78 mDiskCacheLock.wait(); 79 } catch (InterruptedException e) {} 80 } 81 if (mDiskLruCache != null) { 82 return mDiskLruCache.get(key); 83 } 84 } 85 return null; 86 } 87 88 //创建缓存目录的方法 89 // Creates a unique subdirectory of the designated app cache directory. Tries to use external 90 // but if not mounted, falls back on internal storage. 91 public static File getDiskCacheDir(Context context, String uniqueName) { 92 // Check if media is mounted or storage is built-in, if so, try and use external cache dir 93 // otherwise use internal cache dir 94 final String cachePath = 95 Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || 96 !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() : 97 context.getCacheDir().getPath(); 98 99 return new File(cachePath + File.separator + uniqueName); 100 } 101