最近在做的項目在加載大量網絡圖片時遇到了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);
}
});
}
}
});
}