- Android中的图片加载所出现的问题
- 在Android的开发中,经常需要去加载图片,但是图片的尺寸有时候往往会很大,而我们的内存是有限的,加载进来的时候很有可能会造成内存溢出,这种结果也是我们不想看到的,所以我们为了避免这种情况的发生,就要采取一些有效的措施了。
- 当我们去加载图片的时候可以先去获取图片的大小,然后对图片进行压缩,来展示在我们的控件上,可以使用BitmapFactory来完成
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
然后使用控件展示图片就可以了
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 宽, 高));
- 当只是简单的加载几张图片,不用进行压缩也是可以的,但是当加载很多图片的时候,比如使用listview或者gridview来展示图片,屏幕不断的滑动,不断的加载,就会出现OOM,这就需要使用缓存技术了
- Android中提供了一个缓存的核心类LruCache,它的Lru算法是通过LinkedHashMap来实现的,LinkedHashMap是一个 双向链表的结构,是HashMap的子类,它里面的操作是,当调用缓存中的图片,会把图片放在尾端,插入的对象也是存储在链表的尾端,这样,当内存达到设定的最大值的时候,它会去remove掉头部的对象,也就是最近最少使用的,这样操作就可以有效的减少加载图片时的OOM了
- 知道LruCache的原理就可以自己写一个图片的三级缓存,加载图片的时候先在内存中查找,如果内存中没有,再去本地,本地没有,再去请求网络,从网络中请求成功后,再把图片存储到本地和内存中
/**
* 图片三级缓存操作
* @author Administrator
* */
public class ImageCacheUtil {
private LruCache<String, Bitmap> lruCache;
private File cacheDir;
private ExecutorService newFixedThreadPool;
private Handler mHandler;
public ImageCacheUtil(Context context,Handler handler){
int maxSize = (int) (Runtime.getRuntime().maxMemory()/8);
lruCache = new LruCache<String, Bitmap>(maxSize){
//获取移出图片的大小
@Override
protected int sizeOf(String key, Bitmap value) {
//getRowBytes() : 获取图片一行占用的大小
//getHeight() : 图片的高度,图片占用的行数
return value.getRowBytes()*value.getHeight();
}
};
cacheDir = context.getCacheDir();
newFixedThreadPool = Executors.newFixedThreadPool(5);
this.mHandler = handler;
}
/**
* 根据图片的路径获取图片
* @param url
* @return
*/
public Bitmap display(String url,int position){
//1.先从内存中获取图片.
//LruCache<K, V>;//key:图片的名称,一般图片的路径,v:value:图片
//缓存到内存中:lruCache.put(url, bitmap);//key:图片的名称 value:图片
//获取缓存的图片
Bitmap bitmap = lruCache.get(url);//key:图片的名称
if (bitmap!=null) {
return bitmap;
}
//2.如果内存中没有,查看本地缓存文件中是否有图片,有,使用,并且再次保存到内存中.
bitmap = getBitmapFromLocal(url);
if (bitmap!=null) {
return bitmap;
}
//3.如果本地缓存文件也没有,只能从网络重新下载,重新保存到本地缓存文件中和内存中.
getBitmapFromNet(url,position);
return null;
}
/**
* 从网络下载图片
* @param url
*/
private void getBitmapFromNet(String url,int position) {
newFixedThreadPool.execute(new RunnableTask(url,position));
}
private class RunnableTask implements Runnable{
private String mUrl;
private int mPosition;
public RunnableTask(String url,int position) {
this.mUrl = url;
this.mPosition = position;
}
@Override
public void run() {
Message message = Message.obtain();
try {
//从网络下载图片
URL url = new URL(mUrl);//spec:请求路径
//获取连接操作
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5000);//连接超时时间
con.setReadTimeout(5000);//读取超时时间
InputStream inputStream = con.getInputStream();//获取服务器返回的数据
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//将下载的图片保存到缓存文件中和内存中
lruCache.put(mUrl, bitmap);
setBitmapToLocal(mUrl, bitmap);
//通过Handler将数据传递给MenuPhotospager进行显示
message.what = MenuPhotosPager.SUCCESS;
message.obj = bitmap;
message.arg1=mPosition;
mHandler.sendMessage(message);
return;//通知线程池,线程已经使用完毕,可以回收
} catch (Exception e) {
e.printStackTrace();
//return;
}
message.what = MenuPhotosPager.FAIL;
mHandler.sendMessage(message);
}
}
/**
* 获取本地缓存的图片
* @param url
*/
private Bitmap getBitmapFromLocal(String url) {
try {
File file = new File(cacheDir, MD5Util.Md5(url).substring(10));
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
//将图片保存内存中
lruCache.put(url, bitmap);
return bitmap;
} catch (Exception e) {
// TODO: handle exception
}
return null;
}
/**
* 保存图片到本地文件中
*/
public void setBitmapToLocal(String url,Bitmap bitmap){
try {
File file = new File(cacheDir, MD5Util.Md5(url).substring(10));
FileOutputStream stream = new FileOutputStream(file);
//format : 图片的格式
//quality : 图片的质量
//stream : 写入流
//PNG : 图片质量不可变
bitmap.compress(CompressFormat.JPEG, 100, stream);//设置图片的格式、质量,并保存到相应的文件中
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
- 常见图片加载的开源框架
- 常见的图片加载开源框架有很多,像Glide,Picasso,Universal-Image-Loader等
- 其实Glide和Picasso差不多,但是Glide比Picasso更易用
Glide和Picasso
Glide.with(context)
.load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
.into(ivImg);
- Gilde的with里面可以传context,activity,Fragment的好处,图片的加载将会和activity和Fragment的生命周期保持一致,如:activity执行onPause的时候,图片回去暂停加载,当activity执行onResumed的时候,图片又会重新去加载。
- Picasso的with里面只能传context
- Glide默认图片的BitMap格式是RGB_565,而Picasso默认的是ARGB_8888,要比Picasso内存开销小一半,当然如果对这样的效果不满意,这还是可以设置的
public class GlideConfiguration implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Apply options to the builder here.
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
@Override
public void registerComponents(Context context, Glide glide) {
// register ModelLoaders here.
}
}
同时在AndroidManifest.xml中将GlideModule定义为meta-data
<meta-data android:name="com.inthecheesefactory.lab.glidepicasso.GlideConfiguration"
android:value="GlideModule"/>
- Glide另一个主要的特点就是可以加载gif动画,而Picasso却不能,Glide还可以把本地的视频 解码成一张静态的图片
- Glide和Picasso加载图片的方式也不一样,Glide缓存图片是和ImageView的大小相同的,而Picasso是全尺寸缓存的,从这里可以看出来,Glide更节省内存,并且速度也要比Picasso快
- 我比较喜欢使用Glide,毕竟速度快,占用内存少,但是如果少量图片,并且想要高质量的图片,可以使用Picasso,当然萝卜白菜各有所爱,就看个人喜好了
ImageLoader的使用
- 新建一个MyApplication继承Application,并在onCreate()中创建ImageLoader的配置参数
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//创建默认的ImageLoader配置参数
ImageLoaderConfiguration configuration = ImageLoaderConfiguration
.createDefault(this);
//Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(configuration);
}
}
- 在开发中使用ImageLoader的 DisplayImageOptions进行加载图片,它可以配置一些图片显示的选项,比如图片在加载中ImageView显示的图片,是否需要使用内存缓存,是否需要使用文件缓存等等
//显示图片的配置
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);
- ImageLoader加载图片的时候,还有设置进度的功能,在参数里面添加一个ImageLoadingProgressListener的监听
imageLoader.displayImage(imageUrl, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current,
int total) {
}
});
- 使用ImageLoader并不能一定去避免OOM产生,如果经常产生要怎样的去避免呢?
- 减少线程池中线程的个数,在ImageLoaderConfiguration中的(.threadPoolSize)中配置,推荐配置1-5
- 默认的图片是ARGB_8888,改成RGB_565,这样就可以节省一半的 内存
- ImageLoaderConfiguration中配置图片的内存缓存为memoryCache(new WeakMemoryCache()) 或者不使用内存缓存