安卓 - 存储从网络下载的图片

14

我有一个关于从网上加载图片并可能存储的问题,与此相关的方法应该怎么选择。假设我从我的Android应用程序中调用了一个Web服务,在这个Web服务中,我获得了一张网上的图片URL。我会下载并在ListView的列表项左侧显示这个图片。我的问题是,应该使用什么方法来存储图片?

  1. 将其保存到SD卡中,并在创建ListView时检查它是否存在(在后续请求中),必要时重新下载(同时定期更新图片,以防其更改)。
  2. 使用Context.getCacheDir()将其存储在缓存中,但由于无法保证图片在缓存中保留,可能需要更频繁地重新下载。
  3. 始终下载它,而不存储图像。

这些图像文件本身相当小,但我希望有些用户可以下载/存储几十个这样的小图像。哪种方法最好,或者说哪种方法是首选的呢?

另外一个问题是,我应该先加载ListView中的所有图像(可能会锁定UI一段时间),还是异步加载它们,同时在此期间显示一个占位符图像(可能会更“丑”)?在这里,标准是什么?


还要考虑https://dev59.com/cnI-5IYBdhLWcg3wQV8Z - QED
4个回答

21

关于存储位置: 答案取决于下载的内容和大小。然后你可以做出选择。

例如:如果你要下载的内容是临时的、数量少(较少的远程获取)和大小小(占用较少内存),而且只在活动中使用,那么你应该考虑使用SoftReferences将图像保存在内存中。SoftReferences可能会导致重新获取,但由于项目数量很小,所以应该是可以接受的。

但是,如果要下载的项目数量超过了某个阈值(意味着更多的获取和内存),则应考虑通过缓存来减少获取和运行时内存消耗。在这里,你可以选择将它们保存在Sdcard上或者在临时存储(应用程序本地缓存目录)上。对于那些小型、仅在应用程序上下文中具有意义的项目(例如缩略图),用户大多数情况下不会在应用程序之外使用它们。因此,你可以将这些内容存储在缓存目录中。使用它们最好的部分是你不必清理它们,它会自动处理。但也可能会导致重新获取。

但是,如果下载的项目体积很大,可以单独存在于应用程序的上下文之外,例如图片、视频、音频剪辑,则SD卡应该是你的选择。 你还应该阅读:防止BitmapFactory.decodeStream(..)期间OOM错误的大型位图处理方法

请注意,你还可以检查是否可以使用数据库来帮助存储。请参阅此

在ListView中进行懒加载时考虑的一些问题:你应该在后台加载而不阻塞UI线程。在项目下载时,你应考虑显示临时图像。许多原生应用程序都使用这种方式。有关懒加载的示例实现,请参见此处。此外,对于大型列表,你可以实现SlowAdapter模式(检查API演示)。它基本上在列表滚动时停止下载。

可以帮助您的示例项目:

Romain Guy的书架项目使用了两级缓存,其中他使用了内存缓存(包含软引用的HashMap)和存储在SD卡中的缓存。浏览源代码

此外,还有一些由Mark Murphy编写(CWAC)和DroidFu编写的开源库可供使用。

祝好运!


3
关于你的“附加问题”——我认为异步加载是首选行为,尤其是考虑到网络事务可能不仅是“锁定UI一段时间”,而是“永久锁定UI”,如果它永远无法加载。
如果你认为这种行为很丑陋,你可以设置一个计时器(1或2秒),让一些图像有机会加载,如果它们全部加载完成或计时器已经过期,那么就显示UI,并使用占位符图像让其余的异步加载。这是防止永久锁定的方法。
至于你问题的第一部分,我认为它在某种程度上取决于上下文和你所展示的图像类型。但对于大多数Web资源来说,我认为第二种方法是首选,因为你肯定不想在同一会话中重复下载,但你也不想占用永久存储空间。

我期望能够很好地处理图像,当它们不再需要或用户删除与图像相关联的项目时将其删除。对于一个非常繁忙的用户,我预计最多不会超过100张图片。在异步加载图片之前使用计时器是个好主意。这实际上让我想到了另一种思路:加载足够的图片以使列表中的第一批项目正确显示,然后异步加载其余部分(甚至在滚动时也可以)。谢谢你。 - Bara
1
@Bara 这就是我所说的上下文。如果这些图像与类似“浏览”之类的活动有关,那么您只需要暂时缓存它们。如果它们与用户在使用您的应用程序时可能始终查看的永久项目相关(听起来是这样),那么使用#1或#2可能不会有害。很高兴能帮助到您。 - Nicole
在决策语句中加入上下文和类型检查,得到加1的好处! :) - Samuh

1
关于你的“附加问题”——我认为异步加载是首选行为,特别是考虑到网络事务可能不仅仅是“锁定UI一段时间”,而是“永久锁定UI”,如果它永远无法加载。
为了避免这种情况,如果我们谈论的是droid-fu的WebImageView,您可以将ImageLoader.java initialize()函数更改为以下内容。
    static int alive=-1;
   public static synchronized void initialize(Context context) {

        if (executor == null) {        
           executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);           
       }
        else if(alive==executor.getActiveCount()){
               executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
           }
       alive=executor.getActiveCount();
       if (imageCache == null) {
           imageCache = new ImageCacheHope(context, 25, 5);
       }
   }

0

它需要太多的外部库。我想使用它,但决定自己来做。 - Makalele

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接