Android網絡圖片加載內存溢出(OOM)解決方案

最近在做的項目在加載大量網絡圖片時遇到了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);
                    }
                });
            }
        }
    });
}
發佈了26 篇原創文章 · 獲贊 5 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章