有效地处理较大的位图
图像有各种不同的形状和大小,在许多情况下,他们往往比一个典型应用程序的用户界面(UI)所需要的资源更大。
现在的图像尺寸都是已知的,他们可以被用来决定是否应该加载完整的图片到内存或者是否用一个缩小的版本去代替加载。
以下是一些值得考虑的因素:
- 估计加载完整图像所需要的内存;
- 你承诺加载这个图片所需空间带给你的程序的其他内存需求;
- 准备加载图像的目标ImageView或UI组件尺寸;
- 当前设备的屏幕尺寸和密度;
- 当前设备的屏幕尺寸和密度;
例如,如果1024768像素的图像最终被缩略地显示在一个12696像素的ImageView中,就不值得加载到内存中去。
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView)findViewById(R.id.imageView);
}
public void showClick(View view){
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),R.mipmap.a,102,68);
iv.setImageBitmap(bitmap);
}
//位图重新采样
public Bitmap decodeSampledBitmapFromResource(Resources res,int resid,int reqwidth,int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resid);
options.inSampleSize = calculateInSampleSize(options,reqwidth,reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resid);
}
//计算位图的采样比例
public int calculateInSampleSize(BitmapFactory.Options options,int reqwidth,int reqHeight){
//获取宽高
int w = options.outWidth;
int h = options.outHeight;
int inSampleSize = 1;
if (w>reqwidth || h>reqHeight){
if (w>h){
inSampleSize = Math.round((float)h/(float)reqHeight);
}else {
inSampleSize = Math.round((float)w/(float)reqwidth);
}
}
System.out.println("inSampleSize:"+inSampleSize);
return inSampleSize;
}
}
缓存位图
(1)内存缓冲
参考博客:https://jykenan.iteye.com/blog/1828086
内存缓冲提供了可以快速访问位图LruCache类用于缓存位图的任务,最近被引用的对象保存在一个强引用LinkedHashMap中,以及在缓存超过了其指定的大小之前释放最近很少使用的对象的内存.
注意:在过去,一个常用的内存缓存实现是一个SoftReference或WeakReference的位图缓存,然而从android2.3(API 级别9)开始,垃圾回收器更加注重于回收软/弱引用,这使得使用以上引用很大程度上无效.此外,之前的android3.0(API级别11),位图的备份数据存储在本地那些在一种可预测的情况下没有被释放的内存中,很有可能会导致应用程序内存溢出和崩溃.
注:LRU是Least Recently Used近期最少使用算法
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private ImageView iv;
private LruCache<String,Bitmap> LruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView)findViewById(R.id.imageView);
//获取当前Activity内存大小
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
final int cacheSize = memoryClass/8 * 1024 * 1024;//字节 1/8的内存作为缓存大小
LruCache = new LruCache<>(cacheSize);
}
//添加缓存
public void addBitmapToCache(String key,Bitmap bitmap){
if (getBitmapFromCache(key)==null){
LruCache.put(key,bitmap);
}
}
//从缓存中读取对象
public Bitmap getBitmapFromCache(String key){
return LruCache.get(key);
}
public void showClick(View view){
String key = String.valueOf(R.mipmap.a);
Bitmap bitmap = getBitmapFromCache(key);
if (bitmap==null){
bitmap = decodeSampledBitmapFromResource(getResources(),R.mipmap.a,511,341);
addBitmapToCache(key,bitmap);
}else {
System.out.println("lruCache中有位图");
}
iv.setImageBitmap(bitmap);
}
//位图重新采样
public Bitmap decodeSampledBitmapFromResource(Resources res,int resid,int reqwidth,int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resid,options);
options.inSampleSize = calculateInSampleSize(options,reqwidth,reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resid,options);
}
//计算位图的采样比例
public int calculateInSampleSize(BitmapFactory.Options options,int reqwidth,int reqHeight){
//获取宽高
int w = options.outWidth;
int h = options.outHeight;
int inSampleSize = 1;
if (w>reqwidth || h>reqHeight){
if (w>h){
inSampleSize = Math.round((float)h/(float)reqHeight);
}else {
inSampleSize = Math.round((float)w/(float)reqwidth);
}
}
System.out.println("inSampleSize:"+inSampleSize);
return inSampleSize;
}
}
(2)磁盘缓存
参考博客:https://blog.youkuaiyun.com/xuewater/article/details/41870839
使用磁盘缓存来持续处理位图,并且有助于在图片在内存缓存中不再可用时缩短加载时间.当然,从磁盘获取图片比从内存加载更慢并且应当在后台线程中处理,因为磁盘读取的时间是不可预知的。
注意:如果它们被更频繁地访问,那么一个ContentProvider可能是一个更合适的地方来存储缓存中的图像,例如在一个图片库应用程序里。
导入disklrucache.jar
http://www.java2s.com/Code/Jar/d/Downloaddisklrucache202jar.htm
双缓存案例实现
添加权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
创建LruCacheUtils工具类
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.LruCache;
import com.jakewharton.disklrucache.DiskLruCache;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class LruCacheUtils {
private static LruCacheUtils lruCacheUtils;
private DiskLruCache diskLruCache;
private LruCache<String, Bitmap> lruCache;
private Context context;
private LruCacheUtils(){}
public static LruCacheUtils getInstance(){
if (lruCacheUtils==null){
lruCacheUtils = new LruCacheUtils();
}
return lruCacheUtils;
}
//打开磁盘缓存
public void open(Context context,String disk_cache_subdir,int disk_cache_size){
try {
this.context = context;
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
lruCache = new LruCache<>(memoryClass/8 * 1024 * 1024);
/**
*open()方法四个参数:
* 第一个参数:缓存文件文件的位置。
* 第二个参数:应用程序的版本号(在manifest中设置android:versionCode="1")。
* 第三个参数:表示同一个key可以对应多少个缓存文件,一般情况下我们都是传1,这样key和缓存文件一一对应,查找和移除都会比较方便。
* 第四个参数:最大可以缓存多少字节的数据。
*/
diskLruCache = DiskLruCache.open(getCacheDir(disk_cache_subdir),getAppVersion(),1,disk_cache_size);
} catch (IOException e) {
e.printStackTrace();
}
}
public InputStream getDiskCache(String url){
String key = hashKeyForDisk(url);
System.out.println("getDiskCache="+key);
try {
DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
System.out.println(snapshot);
if (snapshot!=null){
return snapshot.getInputStream(0);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void putCache(final String url, final CallBack callBack){
new AsyncTask<String,Void,Bitmap>(){
@Override
protected Bitmap doInBackground(String... strings) {
String key = hashKeyForDisk(strings[0]);
System.out.println("key="+key);
DiskLruCache.Editor editor = null;
Bitmap bitmap = null;
try {
URL url = new URL(strings[0]);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setReadTimeout(1000 * 30);
conn.setConnectTimeout(1000 * 30);
ByteArrayOutputStream baos = null;
if (conn.getResponseCode()==HttpURLConnection.HTTP_OK){
BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len = -1;
while ((len=bis.read(bytes))!=-1){
baos.write(bytes,0,len);
}
bis.close();
baos.close();
conn.disconnect();
}
if (baos!=null){
bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),512,341);
addBitmapToCache(strings[0],bitmap);
editor = diskLruCache.edit(key);
System.out.println(url.getFile());
bitmap.compress(Bitmap.CompressFormat.JPEG,100,editor.newOutputStream(0));
editor.commit();
}
} catch (IOException e) {
try {
editor.abort();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
callBack.response(bitmap);
}
}.execute(url);
}
//关闭磁盘缓存
public void close(){
if (diskLruCache!=null && !diskLruCache.isClosed()){
try {
diskLruCache.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//刷新磁盘缓存
public void flush(){
if (diskLruCache!=null){
try {
diskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//回调接口,把下载完成的图片传进来
public interface CallBack<T>{
public void response(T entity);
}
//添加缓存
public void addBitmapToCache(String url,Bitmap bitmap){
String key = hashKeyForDisk(url);
if (getBitmapFromCache(key)==null){
System.out.println("key===="+key);
System.out.println("bitmap===="+bitmap);
lruCache.put(key,bitmap);
}
}
//从缓存中读取对象
public Bitmap getBitmapFromCache(String url){
String key = hashKeyForDisk(url);
return lruCache.get(key);
}
//位图重新采样
public Bitmap decodeSampledBitmapFromStream(byte[] bytes, int reqwidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes,0,bytes.length,options);
options.inSampleSize = calculateInSampleSize(options,reqwidth,reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes,0,bytes.length,options);
}
//计算位图的采样比例
public int calculateInSampleSize(BitmapFactory.Options options, int reqwidth, int reqHeight){
//获取宽高
int w = options.outWidth;
int h = options.outHeight;
int inSampleSize = 1;
if (w>reqwidth || h>reqHeight){
if (w>h){
inSampleSize = Math.round((float)h/(float)reqHeight);
}else {
inSampleSize = Math.round((float)w/(float)reqwidth);
}
}
System.out.println("inSampleSize:"+inSampleSize);
return inSampleSize;
}
//字符串计算摘要
public String hashKeyForDisk(String key){
String cacheKey = null;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
private int getAppVersion(){
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(),0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
private File getCacheDir(String name){
String cachePath =
Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || //判断是不是SD卡
!Environment.isExternalStorageRemovable()?//是否有外部存储
context.getExternalCacheDir().getPath():context.getCacheDir().getPath();
return new File(cachePath + File.separator + name);
}
}
运行
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import java.io.InputStream;
public class Main2Activity extends AppCompatActivity {
private ImageView iv;
private LruCacheUtils lruCacheUtils;
private static final String DISK_CACHE_SUBDIR = "temp";
private static final int DISK_CACHE_SIZE = 1024*1024*10;//10MB
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
iv = (ImageView)findViewById(R.id.imageView);
}
public void showClick(View view){
String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555663463088&di=4b6117d1cea1a7d39fd03b77b765dd48&imgtype=0&src=http%3A%2F%2Fpic.qiantucdn.com%2F58pic%2F25%2F56%2F29%2F58396c9c1a3a4_1024.jpg";
loadBitmap(url,iv);
}
@Override
protected void onResume() {
super.onResume();
lruCacheUtils = LruCacheUtils.getInstance();
lruCacheUtils.open(this,DISK_CACHE_SUBDIR,DISK_CACHE_SIZE);
}
@Override
protected void onPause() {
super.onPause();
lruCacheUtils.flush();
}
@Override
protected void onStop() {
super.onStop();
lruCacheUtils.close();
}
//加载图片
public void loadBitmap(String url,final ImageView imageView){
//从内存缓存中取得图片
Bitmap bitmap = lruCacheUtils.getBitmapFromCache(url);
if (bitmap == null){
//再从磁盘缓存中取图片
InputStream in = lruCacheUtils.getDiskCache(url);
if (in == null){
lruCacheUtils.putCache(url, new LruCacheUtils.CallBack<Bitmap>() {
@Override
public void response(Bitmap entity) {
System.out.println("http load");
imageView.setImageBitmap(entity);
}
});
}else {
System.out.println("disk cache");
bitmap = BitmapFactory.decodeStream(in);
lruCacheUtils.addBitmapToCache(url,bitmap);
imageView.setImageBitmap(bitmap);
}
}else {
System.out.println("memory cache");
imageView.setImageBitmap(bitmap);
}
}
}