【问题标题】:How to efficiently store bitmaps in Android?如何在 Android 中高效地存储位图?
【发布时间】:2012-07-02 22:29:23
【问题描述】:

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

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

与其说是“如何有效地做到这一点”,不如说是“如何有效地做到这一点”的问题,因为我已经注意到低端手机在使用默认的“ic_launcher”图标而不是位图时会出现延迟。

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

我该怎么办?

【问题讨论】:

  • “滞后”到底是什么意思?在显示图片之前,您的 ListView 卡顿或加载时间过长?
  • 您可以使用一些技术来提高性能。见this page in the docs
  • 使用 ic_launcher 图像而不是实际位图时列表视图中的卡顿。问题是如何在应用位图之前有效地存储它们?
  • @蒂姆。感谢您的页面,我会调查它...
  • @Eugen 不客气,希望对您有所帮助。请注意,我发现下载该页面右上角的 BitmapFun.zip 项目并重新使用 com.example.android.bitmapfun.util 包中的类而不是实现所有技术要容易得多我自己。

标签: android caching bitmap jpeg store


【解决方案1】:

你可以通过 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);
        } 
    }
}

【讨论】:

    【解决方案2】:

    在没有卡顿的情况下获得平滑的 ListView 滚动的诀窍是在用户滚动它时不要以任何方式、形状或形式对其进行更新。 Afaik,这本质上是 iOS 设法使其 ListViews 变得流畅的方式:它不允许用户对其进行任何更改(以及一般的 UI)。

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

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

    【讨论】:

    • 感谢您的回答。你说的真的很有趣,我不知道的事情。问题是我没有使用 notifyDataSetChanged() 或其他列表修饰符。图片是在启动 listview 活动之前加载的,而不是在后台加载。
    • 我还想知道如何通过存储图像来缩短加载时间。我想缓存它们是最快的方法......?
    • 我通过在本机代码内存空间中创建自己的图像缓存解决了我的成像应用程序中的这个问题。我的缩略图更小,每张图片允许 8K 内存,因此存储 2000 张图片没什么大不了的。
    • 从网络检索图像时,您通常应该使用 2 级缓存(文件系统 + 内存),是的。不过,我不完全确定这是否是您的问题,因为您说即使在使用 ic_launcer 作为图标之后它仍然滞后..?加载本地可绘制对象通常非常快,以至于您几乎不会注意到它。
    • @BitBank 是在解码图像之前还是之后?只是为了让阅读本文的每个人都知道,位图以每像素 24 位(取决于 BitmapConfig)未压缩的方式存储在内存中。这比实际文件大得多(例如 8K 相当于 18x18 的缩略图)。
    猜你喜欢
    • 2015-12-21
    • 2017-04-09
    • 2011-03-23
    • 2022-12-18
    • 2019-07-07
    • 1970-01-01
    • 2019-09-08
    • 1970-01-01
    • 2012-01-07
    相关资源
    最近更新 更多