最近遇到一个很闹心的Android开发问题。现在越来越多的应用喜欢用图片代替文字,这个可能也是在这快节奏的时代所必须的产物,图片的生动、传达信息的快速使得在一款App中显示图片尤其重要。但是,在内存有限的移动设备上如何高效的显示图片一直是个另很多开发者蛋疼的问题。
一张100K的jpg格式的图片,在Android中生成Bitmap(位图)时所要占据的空间可能达到500k+。这是为什么呢?特地我问了一位研究图像的研究生:jpg格式的图片本身就对其压缩了,而Bitmap并没有对其压缩,所以两者的大小会有所差距。 好了,不多说了,接下来就是解决多图导致的内存溢出OOM的问题,
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
/**
* @author Warkey.Song
* @version 创建时间:2014年8月4日 上午10:06:26
* 类说明
*/
public class ImageDownLoader {
/**
* 缓存Image的类,当存储Image的大小大于LruCache设定的值,系统自动释放内存
*/
private LruCache<String, Bitmap> mMemoryCache;
/**
* 操作文件相关类对象的引用
*/
private FileUtils fileUtils;
/**
* 下载Image的线程池
*/
private ExecutorService mImageThreadPool = null;
public ImageDownLoader(Context context){
//获取系统分配给每个应用程序的最大内存,每个应用系统分配16M
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int mCacheSize = maxMemory / 8;
//给LruCache分配1/8 4M
mMemoryCache = new LruCache<String, Bitmap>(mCacheSize){
//必须重写此方法,来测量Bitmap的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
fileUtils = new FileUtils(context);
}
/**
* 获取线程池的方法,因为涉及到并发的问题,我们加上同步锁
* @return
*/
public ExecutorService getThreadPool(){
if(mImageThreadPool == null){
synchronized(ExecutorService.class){
if(mImageThreadPool == null){
//为了下载图片更加的流畅,我们用了2个线程来下载图片
mImageThreadPool = Executors.newFixedThreadPool(2);
}
}
}
return mImageThreadPool;
}
/**
* 添加Bitmap到内存缓存
* @param key
* @param bitmap
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null && bitmap != null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 从内存缓存中获取一个Bitmap
* @param key
* @return
*/
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
/**
* 先从内存缓存中获取Bitmap,如果没有就从SD卡或者手机缓存中获取,SD卡或者手机缓存
* 没有就去下载
* @param url
* @param listener
* @param isSave 是否保存到sd卡 true---保存
* @return
*/
public Bitmap downloadImage(final String url, final onImageLoaderListener listener, final Boolean isSave){
//替换Url中非字母和非数字的字符,这里比较重要,因为我们用Url作为文件名,比如我们的Url
//是Http://xiaanming/abc.jpg;用这个作为图片名称,系统会认为xiaanming为一个目录,
//我们没有创建此目录保存文件就会报错
if (url != null && !url.equals("")) {
final String subUrl = url.replaceAll("[^\\w]", "");
Bitmap bitmap = showCacheBitmap(subUrl);
if(bitmap != null){
return bitmap;
} else {
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
listener.onImageLoader((Bitmap) msg.obj, url);
}
};
getThreadPool().execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap = getBitmapFormUrl(url);
Message msg = handler.obtainMessage();
msg.obj = bitmap;
handler.sendMessage(msg);
try {
// 保存在SD卡或者手机目录
if (!isSave) {
return;
}
fileUtils.savaBitmap(subUrl, bitmap);
} catch (IOException e) {
e.printStackTrace();
}
// 将Bitmap 加入内存缓存
// addBitmapToMemoryCache(subUrl, bitmap);
}
});
}
}
return null;
}
/**
* 获取Bitmap, 内存中没有就去手机或者sd卡中获取
* @param url
* @return
*/
public Bitmap showCacheBitmap(String url){
try {
if(getBitmapFromMemCache(url) != null){
return getBitmapFromMemCache(url);
}else if(fileUtils.isFileExists(url) && fileUtils.getFileSize(url) != 0){
//从SD卡获取手机里面获取Bitmap
Bitmap bitmap = fileUtils.getBitmap(url);
//将Bitmap 加入内存缓存
//addBitmapToMemoryCache(url, bitmap);
return bitmap;
}
} catch(OutOfMemoryError e){
}
catch (Exception e) {
// TODO: handle exception
}
return null;
}
/**
* 从Url中获取Bitmap
* @param url
* @return
*/
private Bitmap getBitmapFormUrl(String url) {
Bitmap bitmap = null;
HttpURLConnection con = null;
try {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//对图片进行压缩,这里我将inSampleSize设置为3,这个值可以动态计算的,这里就不介绍了,网上方法忒多
//这是从网络获取的图片就是其压缩的图片
opt.inSampleSize = 3;
opt.inJustDecodeBounds = false;
URL mImageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) mImageUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
//没有直接用下面的这个方法解码bitmap而是用decodeSampledBitmapFromResourceMemOpt(is);
//bitmap = BitmapFactory.decodeStream(is, null, opt);
bitmap = decodeSampledBitmapFromResourceMemOpt(is);
is.close();
} catch (OutOfMemoryError e) {
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
}
}
return bitmap;
}
/**
* 取消正在下载的任务
*/
public synchronized void cancelTask() {
if(mImageThreadPool != null){
mImageThreadPool.shutdownNow();
mImageThreadPool = null;
}
}
/**
* 异步下载图片的回调接口
* @author len
*
*/
public interface onImageLoaderListener{
void onImageLoader(Bitmap bitmap, String url);
}
/**
* 字节生成bitmap
* @param inputStream
* @return
*/
public Bitmap decodeSampledBitmapFromResourceMemOpt(InputStream inputStream) {
byte[] byteArr = new byte[0];
byte[] buffer = new byte[1024];
int len;
int count = 0;
try {
while ((len = inputStream.read(buffer)) > -1) {
if (len != 0) {
if (count + len > byteArr.length) {
byte[] newbuf = new byte[(count + len) * 2];
System.arraycopy(byteArr, 0, newbuf, 0, count);
byteArr = newbuf;
}
System.arraycopy(buffer, 0, byteArr, count, len);
count += len;
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 3;
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
下面的一个类是对图片的保存
package com.movitech.xcfc.image;
/**
* @author Warkey.Song
* @version 创建时间:2014年8月4日 上午10:05:40
* 类说明
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.EBean.Scope;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
@EBean(scope = Scope.Singleton)
public class FileUtils {
/**
* sd卡的根目录
*/
private static String mSdRootPath = Environment
.getExternalStorageDirectory().getPath();
/**
* 手机的缓存根目录
*/
private static String mDataRootPath = null;
/**
* 保存Image的目录名
*/
private final static String FOLDER_NAME = "/AndroidImage";
public FileUtils(Context context) {
// mDataRootPath = context.getCacheDir().getPath();
mDataRootPath = context.getFilesDir().getAbsolutePath();
}
/**
* 获取储存Image的目录
*
* @return
*/
private String getStorageDirectory() {
return Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED) ? mSdRootPath + FOLDER_NAME
: mDataRootPath + FOLDER_NAME;
}
/**
* 保存Image的方法,有sd卡存储到sd卡,没有就存储到手机目录
*
* @param fileName
* @param bitmap
* @throws IOException
*/
public void savaBitmap(String fileName, Bitmap bitmap) throws IOException {
if (bitmap == null) {
return;
}
String path = getStorageDirectory();
File folderFile = new File(path);
if (!folderFile.exists()) {
folderFile.mkdir();
}
File file = new File(path + File.separator + fileName);
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
}
/**
* 从手机或者sd卡获取Bitmap
*
* @param fileName
* @return
*/
public Bitmap getBitmap(String fileName) {
String filePath = getStorageDirectory() + File.separator + fileName;
FileInputStream inputStream;
File file = new File(filePath);
Bitmap mBitmap = null;
try {
inputStream = new FileInputStream(file);
// mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream);
/* return BitmapFactory.decodeFile(getStorageDirectory()
+ File.separator + fileName);*/
return mBitmap;
} catch (Exception e) {
}
return mBitmap;
}
/**
* 判断文件是否存在
*
* @param fileName
* @return
*/
public boolean isFileExists(String fileName) {
return new File(getStorageDirectory() + File.separator + fileName)
.exists();
}
/**
* 获取文件的大小
*
* @param fileName
* @return
*/
public long getFileSize(String fileName) {
return new File(getStorageDirectory() + File.separator + fileName)
.length();
}
/**
* 删除SD卡或者手机的缓存图片和目录
*/
public void deleteFile() {
File dirFile = new File(getStorageDirectory());
if (!dirFile.exists()) {
return;
}
if (dirFile.isDirectory()) {
String[] children = dirFile.list();
for (int i = 0; i < children.length; i++) {
new File(dirFile, children[i]).delete();
}
}
dirFile.delete();
}
/**
* 字节生成bitmap
* @param inputStream
* @return
*/
public Bitmap decodeSampledBitmapFromResourceMemOpt(FileInputStream inputStream) {
byte[] byteArr = new byte[0];
byte[] buffer = new byte[1024];
int len;
int count = 0;
try {
while ((len = inputStream.read(buffer)) > -1) {
if (len != 0) {
if (count + len > byteArr.length) {
byte[] newbuf = new byte[(count + len) * 2];
System.arraycopy(byteArr, 0, newbuf, 0, count);
byteArr = newbuf;
}
System.arraycopy(buffer, 0, byteArr, count, len);
count += len;
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
在使用的时候其实很简单,附图一张