Android bitmap高效显示和优化

时间:2023-12-23 13:33:44

第一部分:Bitmap高效显示

应用场景:
有时候我们想在界面上显示一个网络图片或者显示一张本地的图片,
但是图片本身是很大的有几兆,但是显示的位置很小或者说我们可以用更小
的图片来满足这样的需求,如果把整个图片都显示出来会非常的耗内存,甚至可以导致
内存溢出,这就需要我们来处理,如何高效的显示图片,减少内存消耗。

 BitmapFactory.Options options = new BitmapFactory.Options();

 options.inJustDecodeBounds = true;

 BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

 int imageHeight = options.outHeight;
int imageWidth = options.outWidth; String imageType = options.outMimeType;

设置 inJustDecodeBounds 属性为true可以在decoding的时候避免内存的分配,
它会返回一个null的bitmap,
但是 outWidth, outHeight 与 outMimeType 还是可以获取。
这个技术可以允许你在构造bitmap之前优先读图片的尺寸与类型。

将本地一张大图片显示到页面,为了节省内存对图片进行压缩
下面的代码是计算压缩的比例:

 public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
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; // Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
} return inSampleSize;
}

设置inSampleSize为2的幂是因为decoder最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。

 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {

     // First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}

为了使用这个方法,首先需要设置 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缓存

     private LruCache<String, Bitmap> mMemoryCache;  

     @Override
protected void onCreate(Bundle savedInstanceState) {
... final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); //获取系统内存大小 final int cacheSize = maxMemory / 8; //设置缓存为内存大小的8分之1 //初始化缓存
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);
}

加载缓存中的图片:
    当加载一个bitmap到ImageView中的时候,先检查LruCache
如果找到了一个实体,那就马上更新到ImageView上面,否则使用一个后台线程来处理这张图片:

  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.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
task.execute(resId);
}
}

BitmapWorkerTask也需要更新来以便加实体到内存缓存中

 //缓存中没有图片就需重新加载
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
...
}

使用磁盘缓存

创建一个磁盘缓存

  private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override
protected void onCreate(Bundle savedInstanceState) {
...
// Initialize memory cache
...
// Initialize disk cache on background thread
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR); //创建缓存目录
new InitDiskCacheTask().execute(cacheDir); //创建硬盘缓存
...
} //创建硬盘缓存的线程
class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
@Override
protected Void doInBackground(File... params) {
synchronized (mDiskCacheLock) {
File cacheDir = params[0];
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
mDiskCacheStarting = false; // Finished initialization
mDiskCacheLock.notifyAll(); // Wake any waiting threads
}
return null;
}
} //如果缓存中没有图片就从硬盘加载图片
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread
Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache
// Process as normal
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
} // Add final bitmap to caches
addBitmapToCache(imageKey, bitmap); return bitmap;
}
...
} //添加bitmap到缓存
public void addBitmapToCache(String key, Bitmap bitmap) {
// Add to memory cache as before
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
} // Also add to disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
mDiskLruCache.put(key, bitmap);
}
}
} //获取缓存的bitmap
public Bitmap getBitmapFromDiskCache(String key) {
synchronized (mDiskCacheLock) {
// Wait while disk cache is started from background thread
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
return mDiskLruCache.get(key);
}
}
return null;
} //创建缓存目录的方法
// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName);
}