最近在做的项目在加载大量网络图片时遇到了OOM,在网上找了一些资料和请教公司高级工程师,对代码进行了优化。将大量图片直接加载到内存中,是造成OOM的主要原因。
解决方法:
添加本地缓存,不直接从网络加载图片到内存。将图片缓存到本地,每次都从本地获取图片,如果本地没有,再从网络获取。
本地缓存
开启线程,将网络图片下载到本地SD卡。
/**
*下载文件到SD卡指定路径
*/
private static void downLoadImage2SDCard(final String urlString, final String filepath) {
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(urlString);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5000);
con.setRequestMethod("POST");
int code = con.getResponseCode();
// 请求成功
if (code != 200) {
Log.e(TAG, "Connection fail : " + code);
} else {
InputStream is = con.getInputStream();
FileOutputStream fos = new FileOutputStream(filepath);
byte[] b = new byte[1024];
while (is.read(b) != -1) {
fos.write(b);
}
is.close();
fos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
自定义Adapter
convertView + ViewHolder实现ListView的性能优化
1. 复用convertView,减少对象的创建。
2. 使用ViewHolder类,避免反复使用findViewById的耗时操作。
public View getView(int position, View convertView, ViewGroup parent) {
RankListItem listItem = null;
if (convertView == null) {
listItem = new RankListItem();
LayoutInflater layoutInflater = LayoutInflater.from(ctxt);
convertView = layoutInflater.inflate(R.layout.list_item_layout_moments_rank, null);
listItem.avatar = (ImageView) convertView.findViewById(R.id.rank_item_im_avatar);
listItem.username = (TextView) convertView.findViewById(R.id.rank_item_tv_username);
convertView.setTag(listItem);
} else {
listItem = (RankListItem) convertView.getTag();
}
// 绑定数据
String imageName = url.substring(url.lastIndexOf("/"));
String filePath = FileUtils.getImgFolderPath() + imageName;
File imageFile = new File(filePath);
if (!imageFile.exists()) {
String imageUrl = BaseUrls.ROOT_URL + url;
downLoadImage2SDCard(imageUrl, imageFile);
} else {
Log.i(TAG, "filePath = " + filePath);
Bitmap bitmap = FileUtils.getLocalImage(filePath);
if (bitmap == null) {
listItem.avatar.setImageResource(R.drawable.my_icon_user);
} else {
listItem.avatar.setImageBitmap(bitmap);
}
}
listItem.username.setText(jsonObject.getString("username"));
return convertView;
}
public final class RankListItem {
public ImageView avatar;
public TextView username;
}
图片压缩
压缩图片到指定大小
/**
* 获取指定大小的本地图片
*/
public static Bitmap getLocalImage(String filePath) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
int reqHeight = 600;
int reqWidth = 600;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
运行效果:
这是本人解决OOM的几个策略,在此记录学习。
性能优化:
使用线程池管理线程,防止无限制的创建线程。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
private void getLocalImage(final String filePath, final ImageView imgView) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
final Bitmap bitmap = FileUtils.getLocalImage(filePath);
if (bitmap != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
imgView.setImageBitmap(bitmap);
}
});
}
}
});
}
private void downLoadImage2SDCard(final String iamgeUrl) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
InputStream is = HttpClientHelp.getFileFromServerByHttpClient(iamgeUrl);
String filepath = FileUtils.getImgFolderPath() + iamgeUrl.substring(iamgeUrl.lastIndexOf("/"));
if (is != null) {
try {
FileOutputStream fos = new FileOutputStream(filepath);
byte[] b = new byte[1024];
while (is.read(b) != -1) {
fos.write(b);
}
is.close();
fos.close();
mHandler.obtainMessage(MSG_DOWNLOAD_IMAGE);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
newFixedThreadPool(5);
创建一个定长为5的线程池,可控制线程最大并发数为5,超出的线程会在队列中等待。
对线程进行管理,重用存在的线程,减少对象创建、销毁,效控制最大并发线程数,提高系统资源的使用率。
最后,封装成一个dispaly方法,参数为网络图片路径url和要设置的ImageView
private void displayImage(final String iamgeUrl, final ImageView imageView) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
String imageName = iamgeUrl.substring(iamgeUrl.lastIndexOf("/"));
String filepath = FileUtils.getImgFolderPath() + imageName;
File imageFile = new File(filepath);
if (!imageFile.exists()) {
// 获取网络图片输入流
InputStream is = HttpClientHelp.getFileFromServerByHttpClient(iamgeUrl);
if (is != null) {
try {
FileOutputStream fos = new FileOutputStream(filepath);
byte[] b = new byte[1024];
while (is.read(b) != -1) {
fos.write(b);
}
is.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 获取本地图片的Bitmap
final Bitmap bitmap = FileUtils.getLocalImage(filepath);
if (bitmap != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
}
});
}