【问题标题】:How to make Volley NetworkImageView worke offline如何使 Volley NetworkImageView 离线工作
【发布时间】:2014-10-25 08:22:46
【问题描述】:

我使用Volley NetworkImageView 从互联网下载图像并显示在我的listview 中。现在我想让Volley NetworkImageView 在没有可用网络时显示保存的图像。 Volley 已经缓存了 URL 的图像作为键,因为当我使用

Entry entry = SingletonRequestQueue.getInstance(context).getRequestQueue().getCache().get(imageURL);

entry.data 不为空。但我的问题是图像分辨率很高,我无法使用

Bitmap b = BitmapFactory.decodeByteArray(entry.data, 0, entry.data.length);

因为它会产生很多延迟,我必须重新发明轮子,因为我必须再次创建 asynctask 看看 listview 何时滚动取消解码,回收 bitmap,在内存缓存中创建,找到最佳 insample价值和...

更好的想法是做一些技巧,让Volley NetworkImageView 使用自己的 DiskLRUCache 在没有网络的情况下显示它们。

有什么想法吗?

我的代码:

public class SingletonRequestQueue {


    private static SingletonRequestQueue mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;
    private LruBitmapCache mLruBitmapCache;

    private SingletonRequestQueue(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();
        mLruBitmapCache = new LruBitmapCache(LruBitmapCache.getCacheSize(context));
        mImageLoader = new ImageLoader(mRequestQueue,mLruBitmapCache);

    }

    public static synchronized SingletonRequestQueue getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new SingletonRequestQueue(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {

            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.

            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(),new OkHttpStack());
//          mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    } 

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }

    public LruBitmapCache getLruBitmapCache() {
        return mLruBitmapCache;
    }

    public void setLruBitmapCache(LruBitmapCache lruBitmapCache) {
        mLruBitmapCache = lruBitmapCache;
    }


}

在我的适配器中:

public IssueListAdapter(Context context, int resource, List<Issue> objects) {
        super(context, resource, objects);
        this.context = context;
        this.mIssueList = objects;
        mImageLoader = SingletonRequestQueue.getInstance(context).getImageLoader();
}

public static class ViewHolder{

    public  NetworkImageView mNetworkImageView;
    public  TextView mFee;
    public  TextView mName;
}


@Override
public View getView(int position, View convertView, ViewGroup parent) {


    ViewHolder holder;

    if(convertView == null){
        holder = new ViewHolder();
        LayoutInflater inflater =    
                (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.gridview_issuelist_item, parent, false);

        holder.mNetworkImageView = (NetworkImageView)convertView.findViewById(R.id.NetworkImageView_MainActivity_issue_image);
        holder.mName = (TextView)convertView.findViewById(R.id.TextView_MainActivity_name);
        holder.mFee = (TextView)convertView.findViewById(R.id.TextView_MainActivity_fee);
        Utility.settingTypfaceFont(context, holder.mName);
        Utility.settingTypfaceFont(context, holder.mFee);

        convertView.setTag(holder);

    }else{
        holder = (ViewHolder)(convertView.getTag());
    }

    final Issue issue = mIssueList.get(position);
    holder.mName.setText(issue.getTitle());
    holder.mFee.setText(String.valueOf(issue.getFee()));
    String imageURL = issue.getPublicCover();

    holder.mNetworkImageView.setImageUrl(imageURL, mImageLoader);
    holder.mNetworkImageView.setDefaultImageResId(R.drawable.placeholder2);;

    /*
    Entry entry = SingletonRequestQueue.getInstance(context).getRequestQueue().getCache().get(imageURL);
    if(entry != null && entry.data != null){
        byte[] imageByte = entry.data;
        loadBitmap(imageByte, holder.mNetworkImageView,imageURL);
    }else{
        holder.mNetworkImageView.setImageUrl(imageURL, mImageLoader);
    }*/

    return convertView;
}

@Override
public int getCount() {
    if(mIssueList != null){
        return mIssueList.size();           
    }
    else{
        return 0;
    }
}

public List<Issue> getIssueList() {
    return mIssueList;
}

}

【问题讨论】:

  • NetworkImageView 确实使用了 2 级缓存:开发人员创建的内存缓存和 Volley 自己创建的磁盘缓存。图像首先在内存缓存中检查,然后在磁盘缓存中检查,如果未找到则仅放置网络请求。因此,无需进行任何额外操作即可使其在离线模式下工作。能否分享一下你的代码,看看有没有问题?
  • @ManishMulimani 非常感谢您的帮助,我已经更新了我的问题。问题是当我关闭我的应用程序并再次打开它时,只显示图像的占位符,图像下方的文本全部正确加载,但图像没有。再次感谢。
  • ImageLoader 已经有isCached(String requestUrl, int maxWidth, int maxHeight),如果那里检查的缓存和getRequestQueue().getCache() 不一样,那么重新实现ImageLoader 添加一个Context成员,并使用第一个检查缓存的图像在你的问题中。 src代码ImageLoader:android.googlesource.com/platform/frameworks/volley/+/master/…
  • DiskBasedCache 默认最大缓存大小为 5MB。当您使用非常高分辨率的图像(3000x2500 即 7.5MB)时,您可以增加缓存大小。使用 DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) 构造函数指定磁盘大小,即 DiskBasedCache(cacheDir, 20 * 1024 * 1024) 用于 20MB 缓存。让我知道它是否有帮助。
  • @ManishMulimani 我已经将它设置为/** Default maximum disk usage in bytes.*/private static final int DEFAULT_DISK_USAGE_BYTES = 20 * 1024 * 1024;

标签: android android-volley


【解决方案1】:

我更喜欢将 Volley/retrofit 与 Android-Universal-Image-Loader /Picasso 一起使用,图片加载器库在加载和缓存图像方面确实做得很好。

默认情况下,他们用一行代码处理所有事情:

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

您还可以在加载时为图像设置动画、调整图像大小并添加占位符。

【讨论】:

  • 谢谢(+1) @Pedram 我已经知道所有这些,但现在我不想使用其他库,而且我的图像是 3000*2500,我认为这些库会遇到麻烦分辨率:-)
  • 我认为您需要两个版本的图像,一个作为缩略图显示在“ListView”中,一个以原始大小显示,我建议生成图像的缩略图并使用 Picasso 加载它们,并且根据需要使用 volley 下载真实大小的图像并使用 DiskLruCache github.com/JakeWharton/DiskLruCache 缓存它们,我查看了 Volley 的 DiskBasedCache 源但没有发现它有趣,阅读此线程 stackoverflow.com/questions/20916478/… 祝你好运
  • @mmlooloo 我发现这个顺便试一下stackoverflow.com/a/26565940/2970351
  • 感谢您的所有建议 :-)
【解决方案2】:

只需在 BasicNetwork 类中添加这一行

if (!ConnectivityUtils.isNetworkEnabled(CardApplication.getContext()) && request instanceof ImageRequest) {
            VolleyLog.e("Cached response", "No Network Connectivity for Url=", request.getUrl());
            return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                    request.getCacheEntry().data, responseHeaders, true);
        }

对于数据请求到期,您可以使用自己的 HttpHeaderParser 更改 Cached.Entry

这里是Link,详细解释一下

【讨论】:

  • 欢迎你 其实你不应该缓存 Data Request ,这就是为什么我添加了 request instanceof ImageRequest ,
【解决方案3】:

当你离线重启你的应用程序时,你可以依赖的最后一件事就是磁盘缓存(即DiskBasedCache)。 Volley 的本地缓存由网络数据和响应头组成。但在这种情况下,我们只需要关注Cache-Control 标头即可。例如,如果服务器端返回的标头是“Cache-Control: max-age=604800”,那就是告诉 Volley 将响应资源缓存 604800 秒(来源HttpHeaderParser.parseCacheHeaders())。然后下次我们检索相同的url的数据时会检查是否超过缓存过期时间,最终决定从网络或本地检索。

按照您的描述,我想您的服务器端会为您提供像Cache-Control:must-revalidate|proxy-revalidate|no-cache|no-store 这样的值,这就是您离线时无法重用上次检索到的数据的原因。

现在问题来了:一旦我们可以操纵缓存过期时间,我们就能够将该时间增加到足够大的值,这样我们就可以确保我们可以离线使用该数据。

很遗憾,Volley 不支持我们这样做。那么,如果您可以让服务器端为此提供一个可行的ma​​x-age

如果没有,我建议您更改为满足此要求的另一个库。实际上有一个可以成为你的朋友,是Netroid。它基于 Volley 并提供了一些改进,不会使您对当前代码进行太多更改。有了它,控制过期时间会容易得多,而且会带来更多的功能。

mImageLoader = new SelfImageLoader(mRequestQueue, mLruBitmapCache) {
    @Override
    public void makeRequest(ImageRequest request) {
        // you can manipulate the cache expire time with one line code.
        request.setCacheExpireTime(TimeUnit.DAYS, 10);

        // you can even according to the different request to
        // set up the corresponding expire time.
        if (request.getUrl().contains("/for_one_day/")) {
            request.setCacheExpireTime(TimeUnit.DAYS, 1);
        } else {
            request.setCacheExpireTime(TimeUnit.DAYS, 10);
        }
    }
};

完整的代码在项目的示例模块上,我希望这会有所帮助。

【讨论】:

  • 谢谢,但我认为你没有仔细阅读我的问题,数据已经在我的disckcache 中,因为我可以通过Bitmap b = BitmapFactory.decodeByteArray(entry.data, 0, entry.data.length); 获取图像
  • 哦,是的,我很抱歉,希望你得到正确的答案;-)
【解决方案4】:

你好@mmlooloo 我创建了一个使用DiskLRUCacheVolley 的项目。这是我的存储库DiskLRUCache using Volley 的链接。希望它能帮助您显示保存的图像。谢谢。

【讨论】:

  • 感谢您的帮助(+1),但 Volley 已经缓存了我的图像,我只想让它从 its DiskLRUCache 中获取图像。我正在使用NetworkImageView,它处理我所有的列表视图和解码问题......但是当我离线时它不起作用。
  • Volley 仅使用内存缓存。所以如果我们想离线显示它,我们需要实现我们自己的 DiskLRU 缓存。检查我的解决方案代码。
  • 不,不是,它有DiskLRU查看源代码:-)
  • 据我所知 Volley 不支持 DiskCache。好的,我会检查 Volley 的来源。
  • @Sharif 你错了。 Volley 将每个响应缓存在磁盘上。它将图像缓存在您提供 ImageLoader 的缓存内存中。顺便说一句 - 像您在项目中所做的那样为 ImageLoader 类提供磁盘缓存是一个错误。您应该提供内存缓存。由于 Volley 已经在磁盘上缓存了每个响应,因此您正在减慢您的应用程序并创建磁盘冗余。
【解决方案5】:
  1. 在互联网上搜索“android 如何检查互联网连接”
  2. 实现它并在你的缓存实现中检查它(如LruCache)。 if(networkAvailable()){ getFromNetwork()} else { getFromCache()}

逻辑没问题?那就试试吧。
看来您的缓存 impl 类是 LruBitmapCache

那么如何检查该类中的连接性?

public Bitmap getBitmap(String url) {
  if(networkAvailable()/* this is your impl */){
    // dont use cache
    return null;
  }
  return getFromCache();  // or something like that;
}

【讨论】:

  • 你不要仅仅因为你的互联网没有打开就使用缓存......缓存是为了提高性能并尽可能减少慢速网络调用。
【解决方案6】:

如果我对您的理解正确,如果您的NetworkImageView 使用的ImageLoader 类提供的内存缓存将在应用程序运行之间保持不变,而不会丢失它是一个内存 缓存。

内存缓存在正常操作中保持正确大小的位图 - 即使网络出现故障,您也希望它可用。

所以这是一个想法:每次关闭应用程序时,将缓存中的图像保存在文件中。下次加载应用时,创建内存缓存时 - 检查磁盘上的持久版本,如果可用 - 从磁盘填充内存缓存。

您可以采用多种方法来决定何时保留图像以及何时删除它。

这是一种方法:创建混合内存/磁盘缓存。它的工作方式与您的内存缓存现在的工作方式完全相同,但有以下区别:

  1. 每次调用putBitmap() 时,连同您的正常操作,在后台线程/AsyncTask 中将位图的编码版本保存到磁盘。
  2. 每次从缓存中删除位图时(我假设您的缓存有某种空间限制),在后台线程/AsyncTask 上从磁盘中删除该文件。
  3. 创建一个“loadFromDisk”任务,每次创建内存缓存时在后台执行(每次应用运行一次)以使用磁盘中的可用图像填充内存缓存。

您无法避免对位图进行解码,但是您可以削减尺寸并不得不处理调整大位图的大小。

这对你有帮助吗?

【讨论】:

  • “这对你有帮助吗?”不,因为您没有仔细阅读我的问题,Volley 使用 DiskLRUCache 和内存缓存。我的数据在 DiskLRUCache 中。
  • 我确实阅读了您的问题。您写的图像保存在磁盘缓存上或太大,您不想麻烦解码和调整它们的大小。我建议您跳过调整大小部分的方法。我很遗憾地说,保存位图的唯一方法是对其进行编码——所以如果你想要持久性,解码是你无法逃避的。可能你没看懂我的回答。
  • 可能是我评论不好,但我不想自己解码,我想让 volley 解码。
猜你喜欢
  • 2015-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多