在上一篇专题Android开发之图片处理专题(一):利用软引用构建图片高速缓存中我们讲述了如何利用软引用技术构建高速缓存。那么想要用到图片,首先得有图片的来源。一般而言,一个应用的图片资源都是从服务器处获得的。今天,我们利用Android开发之网络请求通信专题(二):基于HttpClient的文件上传下载里面封装好的httpUtils来实现图片的下载,然后加载到本地配合软引用缓存使用,以一个listView为例子来说明。
一、准备工作
我们需要准备以下几个类(图片对象和软引用缓存类请参考上一篇专题):
1、图片下载类ImageDownload
2、图片下载工具FreedomHttpClientUtil(参考:
Android开发之网络请求通信专题(二):基于HttpClient的文件上传下载)
3、图片下载异步处理任务LoadTask
4、回调接口FreedomCallBack
二、工具类的编写
1、回调接口的准备
public interface FreedomCallback {
/**
* @Title: imageLoaded
* @Description: TODO
* @param imageDrawable 传回的bitmap对象
* @param tag 用于listView查找控件
* @throws
*/
public void imageLoaded(Bitmap imageDrawable, Object tag);
}
2、图片下载类和异步处理任务
图片下载异步处理任务我们将其做成是图片下载类的内部类来实现。
public class ImageDownload {
private static final String TAG = "ImageDownload";
private static String path;
//构建缓存地址
static {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
File externalStorageDirectory = Environment
.getExternalStorageDirectory();
path = externalStorageDirectory.getAbsolutePath()
+ ConstantValue.IMAGE_PATH;
File directory = new File(path);
if (!directory.exists()) {
directory.mkdirs();
}
} else {
path = null;
Log.e(TAG, "no sdcard.");
}
}
/**
* @Title: loadDrawable
* @Description: 下载图片
* @param imageUrl
* @param simpleName
* @param imageCallback
* @throws
*/
public void loadDrawable(String imageUrl, String simpleName,
FreedomCallback imageCallback) {
new FreedomLoadTask(imageCallback).execute(imageUrl, simpleName);
}
/**
* @Title: loadImageFromUrl
* @Description: 根据地址下载图片
* @param url
* @return
* @throws
*/
public Bitmap loadImageFromUrl(String url) {
InputStream i = null;
try {
FreedomHttpClientUtil util = new FreedomHttpClientUtil();
util.open(url, FreedomHttpClientUtil.GET);
util.post();
i = util.openInputStream();
//拿到流后创建bitmap
Bitmap bitmap = BitmapFactory.decodeStream(i);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @Title: saveImage2SDCard
* @Description: 将图片存放到SD卡中
* @param simpleName
* @param bitmap
* @throws
*/
public void saveImage2SDCard(String simpleName, Bitmap bitmap) {
if (StringUtils.isNotBlank(path)) {
File file = new File(new File(path), simpleName + ".png");
FileOutputStream fOut = null;
try {
fOut = new FileOutputStream(file);
//将bitmap写入流中
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
} catch (Exception e) {
org.cao.optimization.util.Log.info("文件无法创建");
e.printStackTrace();
} finally {
if (fOut != null)
try {
fOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* @ClassName: FreedomLoadTask
* @author victor_freedom (x_freedom_reddevil@126.com)
* @createddate 2015-1-31 下午11:39:18
* @Description: 图片下载,返回一个bitmap对象
*/
class FreedomLoadTask extends AsyncTask<String, Integer, Bitmap> {
private FreedomCallback imageCallback;
public FreedomLoadTask(FreedomCallback imageCallback) {
this.imageCallback = imageCallback;
}
@Override
protected Bitmap doInBackground(String... params) {
// 传入两个 参数,一个是下载路径,一个是文件名称
Bitmap bitmap = loadImageFromUrl(params[0]);
if (bitmap != null) {
// 保存到SD卡中
saveImage2SDCard(params[1], bitmap);
// 加入缓存中
ImageCache.getInstance().cacheImage(
new Image(params[1], bitmap));
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
// 下载成功后执行回调接口
imageCallback.imageLoaded(result, null);
super.onPostExecute(result);
}
}
}
三、界面处理
1、Adapter处getView处理
我们假设adater只有一个ImageView,利用ViewHolder缓存,那么在getView方法里面,我们这样处理:
//拿到图片的名字
final String id = infos.getName();
//给此处ImageView空间设置id
continer.image.setTag(id);
//根据名字首先从软应用中拿到bitmap
Image image = ImageCache.getInstance().getImage(id);
Bitmap bitmap = image.getBitmap();
//判断bitmap是否为空
if (bitmap == null) {
// 判断本地SDCard中是否有相关文件
if (Image.hasFile(id)) {
InputStream is;
try {
is = new FileInputStream(Image.getFileFromSDCard(id));
bitmap = BitmapFactory.decodeStream(is);
ImageCache.getInstance().cacheImage(new Image(id, bitmap));
continer.image.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
//本地也没有,就从服务器下载
ImageDownload download = new ImageDownload();
download.loadDrawable(news.getImgUrl(), id + "",
new FreedomCallback() {
@Override
public void imageLoaded(Bitmap imageDrawable,
Object tag) {
if (imageDrawable != null) {
//构建adapter时传入的回调接口,如果下载图片成功,回调此方法
listCallback.imageLoaded(imageDrawable, id);
}
}
});
continer.image.setImageResource(R.drawable.ic_launcher);
}
} else {
//如果软引用还存在,则直接拿来用
continer.image.setImageBitmap(bitmap);
}
2、ListView初始化相关处理
adapter = new NewsAdapter(context, new ListCallback() {
@Override
public void imageLoaded(Bitmap bitmap, Object tag) {
//根据回调传回的tag找到对应的ImageView
ImageView imageView = (ImageView) listView.findViewWithTag(tag);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
});
listView = new ListView(context);
listView.setAdapter(adapter);
四、图片压缩
在上述过程中,我们还可以优化一下下。上述操作过程中,我们默认了下载下来的图片的大小是已经设定好的。当然,在实际开发中,我们下载再来的图片,可能也是和服务端沟通好设定好了。但是,有时候,图片下载下来很大,这个时候,就需要对图片进行压缩了。一般而言,图片的压缩有两种,质量压缩和宽高压缩。我们来看一个工具类。这是博主以前不知道从哪里找来的了。
//图片质量压缩
public static Bitmap compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
while (baos.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();// 重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中
options -= 10;// 每次都减少10
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
return bitmap;
}
//图片宽高压缩
public static Bitmap getimage(String srcPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
// 开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 这个设置图片的宽高,根据实际需要设定
float hh = 800f;// 这里设置高度为800f
float ww = 480f;// 这里设置宽度为480f
// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;// be=1表示不缩放
if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;// 设置缩放比例
// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
}
有时候,我们还可以让系统自动的给我们压缩,只要确保不会出现OOM错误又能让图片以最大性能去展示
File file = Image.getFileFromSDCard(name, "pure");
Options opts = new Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), opts);
opts.inJustDecodeBounds = false;
// 根据设定的值让系统自动压缩,这行代码的参数是第一个是BitmapFactory.Options,第二个参数是调整后图片最小的宽或者高,第三个参数是调整后图片的内存占用量上限。
opts.inSampleSize = Util.computeSampleSize(opts, 600,(int) (1 * 1024 * 1024));
opts.inDither = false;
<span style="white-space:pre"> </span>opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), opts);
图片压缩的步骤一般设定在图片下载完之后。
至此,图片从下载到显示的所有流程,我们已经实现。当然,这只是一种实现方式,图片的异步加载还可以有其他的实现方式。今天我们回顾了Android开发之网络请求通信专题(二):基于HttpClient的文件上传下载的内容里封装的httputils,下一篇我们利用之前所学的线程池ThreadPoolExcutor配合 Android开发之网络请求通信专题(一):基于HttpURLConnection的请求通信里所讲的httpurlconection来实现listview的大量图片的异步加载。