如何在Android中高效存储位图?

时间:2012-07-02 13:37:40

标签: android caching bitmap jpeg store

我正在构建一个相对基本的新闻阅读器应用程序,该应用程序涉及在自定义列表视图中显示新闻(图像+标题+每个列表元素的简短描述)。

我的问题是如何存储我从服务器下载的图像,然后将它们附加到列表视图?图像相对较小,通常为200 X 200,格式为.jpeg。

这与“如何有效地完成”的问题并不是一个问题,因为在使用默认的“ic_launcher”图标而不是位图时,我已经注意到了低端手机的延迟。

当应用程序启动并同步新闻或缓存新闻时,将它们作为文件或新闻数据库与其他新闻数据一起存储会更快吗?

我应该怎么做?

2 个答案:

答案 0 :(得分:2)

更好的是你可以通过ImageManager类使用SoftReference。

在ListAdpater中,getView()方法调用ImageManager的displayImage()方法。

ImageManager编码实例:

public class ImageManagerExemple {

private static final String LOG_TAG = "ImageManager";

private static ImageManagerExemple instance = null;

public static ImageManagerExemple getInstance(Context context) {
    if (instance == null) {
        instance = new ImageManagerExemple(context);
    } 
    return instance;        
}   

private HashMap<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();

private Context context;
private File cacheDir;

private ImageManagerExemple(Context context) {
    this.context = context;
    // Find the dir to save cached images
    String sdState = android.os.Environment.getExternalStorageState();
    if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
        File sdDir = android.os.Environment.getExternalStorageDirectory();      
        cacheDir = new File(sdDir,"data/yourappname");
    } else {
        cacheDir = context.getCacheDir();
    }
    if(!cacheDir.exists()) {
        cacheDir.mkdirs();
    }
}


/**
 * Display web Image loading thread
 * @param imageUrl picture web url
 * @param imageView target
 * @param imageWaitRef picture during loading
 */
public void displayImage(String imageUrl, ImageView imageView, Integer imageWaitRef) {
    String imageKey = imageUrl;     
    imageView.setTag(imageKey);
    if(imageMap.containsKey(imageKey) && imageMap.get(imageKey).get() != null) {
        Bitmap bmp = imageMap.get(imageKey).get();
        imageView.setImageBitmap(bmp);
    } else {
        queueImage(imageUrl, imageView);
        if(imageWaitRef != null)
            imageView.setImageResource(imageWaitRef);
    }
}

private void queueImage(String url, ImageView imageView) {
    ImageRef imgRef=new ImageRef(url, imageView);
    // Start thread
    Thread imageLoaderThread = new Thread(new ImageQueueManager(imgRef));
    // Make background thread low priority, to avoid affecting UI performance
    imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
    imageLoaderThread.start();
}

private Bitmap getBitmap(String url) {
    String filename = String.valueOf(url.hashCode());
    File f = new File(cacheDir, filename);
    try {
        // Is the bitmap in our cache?
        Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
        if(bitmap != null) return bitmap;
        // Nope, have to download it
        bitmap = ImageServerUtils.pictureUrlToBitmap(url);
        // save bitmap to cache for later
        writeFile(bitmap, f);
        return bitmap;
    } catch (Exception ex) {
        ex.printStackTrace();
        Log.e(LOG_TAG, ""+ex.getLocalizedMessage());
        return null;
    }  catch (OutOfMemoryError e) {
        Log.e(LOG_TAG, "OutOfMemoryError : "+e.getLocalizedMessage());
        e.printStackTrace();
        return null;
    }
}

private void writeFile(Bitmap bmp, File f) {
    if (bmp != null && f != null) {
        FileOutputStream out = null;

        try {
            out = new FileOutputStream(f);
            //bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
            bmp.compress(Bitmap.CompressFormat.JPEG, 80, out);
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally { 
            try { if (out != null ) out.close(); }
            catch(Exception ex) {} 
        }
    }
}


private class ImageRef {
    public String imageUrl;
    public ImageView imageView;

    public ImageRef(String imageUrl, ImageView i) {
        this.imageUrl=imageUrl;
        this.imageView=i;
    }
}

private class ImageQueueManager implements Runnable {
    private ImageRef imageRef;
    public ImageQueueManager(ImageRef imageRef) {
        super();
        this.imageRef = imageRef;
    }
    @Override
    public void run() {
        ImageRef imageToLoad = this.imageRef;
        if (imageToLoad != null) {
            Bitmap bmp = getBitmap(imageToLoad.imageUrl);
            String imageKey = imageToLoad.imageUrl;
            imageMap.put(imageKey, new SoftReference<Bitmap>(bmp));
            Object tag = imageToLoad.imageView.getTag();

            // Make sure we have the right view - thread safety defender
            if (tag != null && ((String)tag).equals(imageKey)) {
                BitmapDisplayer bmpDisplayer = new BitmapDisplayer(bmp, imageToLoad.imageView);                         
                Activity a = (Activity)imageToLoad.imageView.getContext();                          
                a.runOnUiThread(bmpDisplayer);
            } 
        } 
    }
}

//Used to display bitmap in the UI thread
private class BitmapDisplayer implements Runnable {
    Bitmap bitmap;
    ImageView imageView;

    public BitmapDisplayer(Bitmap b, ImageView i) {
        bitmap=b;
        imageView=i;
    }
    @Override
    public void run() {
        if(bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } 
    }
}

答案 1 :(得分:1)

在没有口吃的情况下获得平滑ListView滚动的技巧是在用户滚动时不以任何方式,形状或形式更新它。 Afaik,这实际上是iOS设法让ListViews顺利进行的方式:当用户有手指时,它不允许对它进行任何更改(以及一般的UI)。

只需注释掉任何更改ListView的代码,同时保留所有位图加载代码不变,您将看到后台实际加载位图并不会真正影响性能。问题是UI线程无法跟上视图更新和同时滚动。

使用OnScrollListener可以在用户滚动时阻止对ListView的所有更新,从而实现相同的功能。一旦用户停止,您就可以潜入所有待处理的更新。 为了提高性能,请尽量不要使用notifyDataSetChanged,而是迭代ListView的视图,只更新实际更改的视图。