您的位置:首页 > 编程语言 > Java开发

如何使用java的ResponseCache建立本地缓冲

2011-08-12 08:00 295 查看
Using ResponseCache in an Android App

I’m currently working on an Android app and I’ve decided to use the java.net package for making HTTP requests. While the java.net package includes support for caching requests it does not include a default caching implementation like some other libraries
— apparently Android developers are left to roll their own. I looked into it, and it seemed pretty straightforward to implement the ResponseCache, CacheRequest and CacheResponse classes, but there was a surprise waiting for when I tested my work. It was annoying
so I thought I’d share. What follows is a simple example of how to implement ResponseCache, the surprise I found, and how I dealt with it.

Implementing the Cache

There are three classes to implement to create a cache that can be used by URLConnection in the java.net package. They are ResponseCache, CacheRequest, and CacheResponse. You will create your own implementation of these three classes then you will register
your ResponseCache class to be used as the cache for URLConnections.

First extend the request and result classes:

CacheRequest is very simple. It has one member which is a ByteArrayOutputStream which is returned by its getBody() method. The ByteArrayOutputStream will eventually be passed to a companion CacheResponse instance.

package com.codebycoffee.net;

import java.io.ByteArrayOutputStream;

import java.io.OutputStream;

import java.net.CacheRequest;

public class CCCacheRequest extends CacheRequest {

final ByteArrayOutputStream body = new ByteArrayOutputStream();

public CCCacheRequest() {

}

public OutputStream getBody() {

return body;

}

public void abort() {

}

}

CacheResponse is also very simple. Its constructor will accept a Map of HTTP headers, and an OutputStream that we’ll get from a CacheRequest instance but cast to a ByteArrayOutputStream. The ByteArrayOutputStream is converted to a byte array and passed to the
ByteArrayInputStream returned by the CacheRequest’s getBody() method.

package com.codebycoffee.net;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.CacheResponse;

import java.util.List;

import java.util.Map;

public class CCCacheResponse extends CacheResponse {

Map<String, List<String>> headers;

ByteArrayOutputStream body;

public CCCacheResponse(Map<String, List<String>> headers, OutputStream body) {

this.headers = headers;

// should be the output stream defined in a companion CacheRequest.

this.body = (ByteArrayOutputStream)body;

}

@Override

public InputStream getBody() {

return new ByteArrayInputStream(body.toByteArray());

}

@Override

public Map<String, List<String>> getHeaders() {

return headers;

}

}

RequestCache defines the HashMap that actually stores the HTTP responses, and creates instances of the CacheRequest and CacheResponse classes.

package com.codebycoffee.net;

import java.io.IOException;

import java.net.CacheRequest;

import java.net.CacheResponse;

import java.net.ResponseCache;

import java.net.URI;

import java.net.URISyntaxException;

import java.net.URLConnection;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

public class CCResponseCache extends ResponseCache {

Map<URI, CacheResponse> mCache = new HashMap<URI, CacheResponse>();

@Override

public CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders) {

CacheResponse resp = mCache.get(uri);

return resp;

}

@Override

public CacheRequest put(URI uri, URLConnection conn) {

CacheRequest req = (CacheRequest)new CCCacheRequest();

Map<String, List<String>> headers = conn.getHeaderFields();

CacheResponse resp = (CacheResponse) new CCCacheResponse(headers, req.getBody());

mCache.put(uri, resp);

return req;

}

}

One thing to note about this implementation is that the cache is going to be stored in memory. This is fine so long as you are careful about how much data you store in the cache. If you expect to cache a large amount of data, it would be better to incorporate
a database or some kind of file IO into the cache solution.

The last step is to create an instance of the ResponseCache and set it as the default cache via the static setDefault() method.

ResponseCache.setDefault(new CCResponseCache());

Now any URLConnection that is set to useCache(true) will use your cache when making HTTP requests. We’re all done right? Well… maybe not.

Surprise, Surprise

The sample code above is pretty close to what I started with for the cache for my app. It looked good, and I was felt pretty good about it so I wired it into my app and went to test.

Guess what… it didn’t work. How annoying.

To make sure that the cache was doing its job I included some logging to report the size of the HashMap and the value of the URIs passed to the ResponseCache.put method. Every time an HTTP request was made the values were logged. Immediately I noticed two problems.
First, the size of the cache never changed, and second, the URI was always the same value.

This sort of behavior would be expected if the app was making the same call over and over again, but there was over a half-dozen distinct HTTP requests being made. What was really weird was the value of the URI. A URI has several properties including the HTTP
host name, and the path to the resource being requested. In every case the path property was an empty string making the value of the URI basically the root of the webserver. For a while I thought maybe there was some property or flag I forgot to set that could
account for the strange value of the URIs but no. Everything seemed to have been done correctly.

I added logging to the ResponseCache.get method to see if the value of the URI passed to it was also limited to the host name. It wasn’t, the URI’s being passed to the ResponeCache.get method were correct and included the path property. This was really weird,
but it was good because it gave me the work around I needed.

The Fix

The ResponseCache.put method accepts a URI as its first argument but it accepts a URLConnection as its second argument. Since the ResponseCache.get method was getting the correct URI values, I could ignore the value of the URI passed to the put method and instead
derive the value from the URLConnection by calling its getURL() method and then calling toURI() on the URL instance. I made the following change to and everything worked:

public class CCResponseCache extends ResponseCache {

Map<URI, CacheResponse> mCache = new HashMap<URI, CacheResponse>();

@Override

public CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders) {

CacheResponse resp = mCache.get(uri);

return resp;

}

@Override

public CacheRequest put(URI uri, URLConnection conn) {

CacheRequest req = (CacheRequest)new CCCacheRequest();

Map<String, List<String>> headers = conn.getHeaderFields();

CacheResponse resp = (CacheResponse) new CCCacheResponse(headers, req.getBody());

// For some reason the path of the URI being passed is an empty string.

// Get a good URI from the connection object.

try {

uri = conn.getURL().toURI();

} catch (URISyntaxException e) {

e.printStackTrace();

}

mCache.put(uri, resp);

return req;

}

}

Enjoy.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: