How to make Volley NetworkImageView worke offline

2019-03-21 14:12发布

I use Volley NetworkImageView to download images from internet and show in my listview. Now I want to make Volley NetworkImageView show saved images when there is no network available. Volley has already cached images by URL as a key because when I use

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

the entry.data is not null. But my problem is that image resolutions are high and I can not use

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

because it creates a lot of lag and I have to reinvent the wheel because again I must create asynctask see when listview has scrolled to cancel decoding, recycling the bitmap, creating in memory cache, finding best insample value and ...

so better Idea is just do some tricks that make Volley NetworkImageView use its own DiskLRUCache to show them when there is no network.

Any idea?

My code:

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;
    }


}

and in my adapter:

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;
}

}

6条回答
\"骚年 ilove
2楼-- · 2019-03-21 14:49

I prefer to use Volley/retrofit with Android-Universal-Image-Loader /Picasso, picture loader libs have done a great job in loading and caching images indeed.

They handle everything with a single line of code by default:

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

Also you can animate, resize your images and add placeholder while they loading.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-03-21 14:49

Hello @mmlooloo I have created a project which use DiskLRUCache and Volley. Here's the link of my repository DiskLRUCache using Volley. Hope it will helps you to show saved image. Thanks.

查看更多
Melony?
4楼-- · 2019-03-21 14:50

When you restart your app in offline, the last thing you can rely on just the Disk Cache(i.e. DiskBasedCache). Volley's local cache consist of network data and the response headers. But in this situation, we just need to focusing on the Cache-Control header. For instance, if the server-side return that header is "Cache-Control: max-age=604800", that's tell Volley to cache the response resource for 604800 seconds( source at HttpHeaderParser.parseCacheHeaders() ). Then next time we retrieving the same url's data would checking if exceeded the cache expire time, finally decide retrieve from network or local.

Follow your describe, I suppose your server-side deliver you a value like Cache-Control:must-revalidate|proxy-revalidate|no-cache|no-store, that's why you can't reuse the last retrieved data when you were in offline.

Right now there is question came : once we can manipulate the cache expire time, we'll be capable of increase that time to a large enough value so we can ensure us use that data in offline.

Unfortunately, Volley does not support us to do this. So if you can make the server-side to delivering a viable max-age for this?

If not, I'd suggest you to change to another library which fulfill this desired. and there actually have one can be your friend, is Netroid. It's based on Volley and offered a few improvements, that won't make you change your current code very much. With it, control the expire time would be far easier, and more features would be come with.

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);
        }
    }
};

the full code was on the project's sample module, i hope this can be helpful.

查看更多
兄弟一词,经得起流年.
5楼-- · 2019-03-21 14:52
  1. Search internet for "android how to check internet connectivity"
  2. implement that and check it in your cache implementation (like LruCache). if(networkAvailable()){ getFromNetwork()} else { getFromCache()}

logic is ok? then just try.
It seems your cache impl class is LruBitmapCache.

then how about check connectivity in that class?

public Bitmap getBitmap(String url) {
  if(networkAvailable()/* this is your impl */){
    // dont use cache
    return null;
  }
  return getFromCache();  // or something like that;
}
查看更多
Rolldiameter
6楼-- · 2019-03-21 15:05

If I understand you correctly, you would benefit if the memory cache provided to the ImageLoader class that's used by your NetworkImageView will be persisted between app runs, without losing the fact that it's a memory cache.

That memory cache keeps the correctly sized bitmap in normal operation - which you would like available even if the network goes down.

So here's an idea: every time you're app is closed, persist on file the images from the cache. The next time you load your app, when you create the memory cache - check for a persisted version on the disk, and if it's available - populate the memory cache from the disk.

There are several approaches you can take to decide when to persist an image and when to delete it.

Here's one approach: create a hybrid memory / disk cache. It would work exactly the same as your memory cache works now with the following differences:

  1. Every time putBitmap() is called, along with your normal operation, save an encoded version of the bitmap to the disk in a background thread / AsyncTask.
  2. Every time a bitmap is removed from the cache (I'm assuming you have some sort of space constraint on the cache), delete that file from the disk on a background thread / AsyncTask.
  3. Create a "loadFromDisk" task, to be performed in the background every time a memory cache is created (once per app run) to populate your memory cache with the available images from the disk.

You can't avoid decoding the bitmaps, however you can cut the size and having to deal with resizing large bitmaps.

Does this help you?

查看更多
戒情不戒烟
7楼-- · 2019-03-21 15:12

Just add this line in BasicNetwork class

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);
        }

and for data request expiry you can change the Cached.Entry using using own HttpHeaderParser

Here is Link which explain thing in detail

查看更多
登录 后发表回答