如何在Android应用程序中为REST API结果实现缓存?

18

我的Android应用程序使用REST API获取数据。我想实现客户端缓存。我们有没有内置的类来实现这个功能?

如果没有,是否有任何可重用的代码?我记得之前看到过这样的代码。但是我现在找不到了。

如果没有其他办法,我将自己编写。以下是基本结构:

public class MyCacheManager {

static Map<String, Object> mycache;

public static Object getData(String cacheid) {
    return mycache.get(cacheid);
}

public static void putData(String cacheid, Object obj, int time) {
    mycache.put(cacheid, obj);
}

}

如何为缓存对象启用时间?还有,最佳序列化方式是什么?即使应用程序关闭后重新打开(如果时间未过期),缓存也应保持完好无损。

谢谢。 Ajay

3个回答

10

现在,2013年的Google I/O发布了一个非常棒的库——Volley,它有助于解决调用REST API时出现的所有问题:

Volley是一个库,是由Android dev团队开发的网络库。它可以让Android应用程序的网络通信更加简便和快速。它管理网络请求的处理和缓存,并且可以帮助开发者节省很多时间,不再需要重复编写相同的网络调用/缓存代码。而且,代码越少就意味着出错的可能性也越小,这正是所有开发者所追求和期望的。

Volley的示例:technotalkative


太好了!你还可以在这里找到一些使用 Volley 的好例子:https://github.com/stormzhang/AndroidVolley - Sam003

4

首先要检查设备是否连接到了互联网。

public class Reachability {

private final ConnectivityManager mConnectivityManager;


public Reachability(Context context) {
    mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}

public boolean isConnected() {
    NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
    return networkInfo != null && networkInfo.isConnectedOrConnecting();
}}

如果设备从互联网连接,则从API获取数据并将其缓存,否则从缓存中获取数据。
public class CacheManager {

Cache<String, String> mCache;
private DiskLruCache mDiskLruCache;
private final Context mContext;

public CacheManager(Context context) throws IOException {
    mContext = context;
    setUp();
    mCache = DiskCache.getInstanceUsingDoubleLocking(mDiskLruCache);
}

public void setUp() throws IOException {
    File cacheInFiles = mContext.getFilesDir();
    int version = BuildConfig.VERSION_CODE;

    int KB = 1024;
    int MB = 1024 * KB;
    int cacheSize = 400 * MB;

    mDiskLruCache = DiskLruCache.open(cacheInFiles, version, 1, cacheSize);
}

public Cache<String, String> getCache() {
    return mCache;
}

public static class DiskCache implements Cache<String, String> {

    private static DiskLruCache mDiskLruCache;
    private static DiskCache instance = null;

    public static DiskCache getInstanceUsingDoubleLocking(DiskLruCache diskLruCache){
        mDiskLruCache = diskLruCache;
        if(instance == null){
            synchronized (DiskCache.class) {
                if(instance == null){
                    instance = new DiskCache();
                }
            }
        }
        return instance;
    }

    @Override
    public synchronized void put(String key, String value) {
        try {
            if (mDiskLruCache != null) {
                DiskLruCache.Editor edit = mDiskLruCache.edit(getMd5Hash(key));
                if (edit != null) {
                    edit.set(0, value);
                    edit.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public synchronized String get(String key) {
        try {
            if (mDiskLruCache != null) {
                DiskLruCache.Snapshot snapshot = mDiskLruCache.get(getMd5Hash(key));

                if (snapshot == null) {
                    // if there is a cache miss simply return null;
                    return null;
                }

                return snapshot.getString(0);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // in case of error in reading return null;
        return null;
    }

    @Override
    public String remove(String key) {
        // TODO: implement
        return null;
    }

    @Override
    public void clear() {
        // TODO: implement
    }
}

public static String getMd5Hash(String input) {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] messageDigest = md.digest(input.getBytes());
        BigInteger number = new BigInteger(1, messageDigest);
        String md5 = number.toString(16);

        while (md5.length() < 32)
            md5 = "0" + md5;

        return md5;
    } catch (NoSuchAlgorithmException e) {
        Log.e("MD5", e.getLocalizedMessage());
        return null;
    }
}}

创建CacheInterceptor类以缓存网络响应并处理错误。
public class CacheInterceptor implements Interceptor{
private final CacheManager mCacheManager;
private final Reachability mReachability;

public CacheInterceptor(CacheManager cacheManager, Reachability reachability) {
    mCacheManager = cacheManager;
    mReachability = reachability;
}

@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    String key = request.url().toString();

    Response response;
    if (mReachability.isConnected()) {
        try {
            response = chain.proceed(request);
            Response newResponse = response.newBuilder().build();

            if (response.isSuccessful()) {
                if (response.code() == 204) {
                    return response;
                }
                // save to cache this success model.
                mCacheManager.getCache().put(key, newResponse.body().string());

                // now we know that we definitely have a cache hit.
                return getCachedResponse(key, request);
            }else if (response.code() >= 500) { // accommodate all server errors

                // check if there is a cache hit or miss.
                if (isCacheHit(key)) {
                    // if data is in cache, the return the data from cache.
                    return getCachedResponse(key, request);
                }else {
                    // if it's a miss, we can't do much but return the server state.
                    return response;
                }

            }else { // if there is any client side error
                // forward the response as it is to the business layers to handle.
                return response;
            }
        } catch (ConnectException | UnknownHostException e) {
            // Internet connection exception.
            e.printStackTrace();
        }
    }

    // if somehow there is an internet connection error
    // check if the data is already cached.
    if (isCacheHit(key)) {
        return getCachedResponse(key, request);
    }else {
        // if the data is not in the cache we'll throw an internet connection error.
        throw new UnknownHostException();
    }
}

private Response getCachedResponse(String url, Request request) {
    String cachedData = mCacheManager.getCache().get(url);

    return new Response.Builder().code(200)
            .body(ResponseBody.create(MediaType.parse("application/json"), cachedData))
            .request(request)
            .protocol(Protocol.HTTP_1_1)
            .build();
}

public boolean isCacheHit(String key) {
    return mCacheManager.getCache().get(key) != null;
}}

现在使用Retrofit创建服务时,在OkHttpClient中添加此拦截器。

public final class ServiceManager {
private static ServiceManager mServiceManager;

public static ServiceManager get() {
    if (mServiceManager == null) {
        mServiceManager = new ServiceManager();
    }
    return mServiceManager;
}

public <T> T createService(Class<T> clazz, CacheManager cacheManager, Reachability reachability) {
    return createService(clazz, HttpUrl.parse(ServiceApiEndpoint.SERVICE_ENDPOINT), cacheManager, reachability);
}

private <T> T createService(Class<T> clazz, HttpUrl parse, CacheManager cacheManager, Reachability reachability) {
    Retrofit retrofit = getRetrofit(parse, cacheManager, reachability);
    return retrofit.create(clazz);
}

public <T> T createService(Class<T> clazz) {
    return createService(clazz, HttpUrl.parse(ServiceApiEndpoint.SERVICE_ENDPOINT));
}

private <T> T createService(Class<T> clazz, HttpUrl parse) {
    Retrofit retrofit = getRetrofit(parse);
    return retrofit.create(clazz);
}

private <T> T createService(Class<T> clazz, Retrofit retrofit) {
    return retrofit.create(clazz);
}

private Retrofit getRetrofit(HttpUrl httpUrl, CacheManager cacheManager, Reachability reachability) {
    return new Retrofit.Builder()
            .baseUrl(httpUrl)
            .client(createClient(cacheManager, reachability))
            .addConverterFactory(getConverterFactory())
            .build();
}

private OkHttpClient createClient(CacheManager cacheManager, Reachability reachability) {
    return new OkHttpClient.Builder().addInterceptor(new CacheInterceptor(cacheManager, reachability)).build();
}

private Retrofit getRetrofit(HttpUrl parse) {
    return new Retrofit.Builder()
            .baseUrl(parse)
            .client(createClient())
            .addConverterFactory(getConverterFactory()).build();
}

private Retrofit getPlainRetrofit(HttpUrl httpUrl) {
    return new Retrofit.Builder()
            .baseUrl(httpUrl)
            .client(new OkHttpClient.Builder().build())
            .addConverterFactory(getConverterFactory())
            .build();
}

private Converter.Factory getConverterFactory() {
    return GsonConverterFactory.create();
}

private OkHttpClient createClient() {
    return new OkHttpClient.Builder().build();
}}

缓存接口
public interface Cache<K, V> {

void put(K key, V value);

V get(K key);

V remove(K key);

void clear();}

4
使用Matthias Käppler的Ignited库,最好的方法之一是进行http请求并将响应缓存到内存(弱引用)和文件中。它非常可配置,可以选择其中一个或两个同时使用。
该库位于这里:https://github.com/mttkay/ignition,示例位于这里:https://github.com/mttkay/ignition/wiki/Sample-applications
就我个人而言,我非常喜欢这个库,从它被称为Droidfu时起就开始使用。
希望这对您有所帮助,Ajay!

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