web-dev-qa-db-ja.com

Android app for REST APIの結果にキャッシュを実装する方法は?

私の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

16

Google I/O 2013でリリースされた素晴らしいライブラリVolleyは、REST API:

Volleyはライブラリです 、それはAndroid devチームからのVolleyと呼ばれるライブラリです。これにより、Androidアプリがより簡単に、そして最も重要なことに、より高速になります。ネットワークリクエストの処理とキャッシングとこれにより、開発者は同じネットワーク呼び出し/キャッシュコードを何度も作成する手間を省くことができます。また、コードを減らすことのもう1つの利点は、バグの数が少なくなることです。これは、開発者が望んでいることです。

ボレーの例: technotalkative

10
LOG_TAG

最良の方法の1つは、MatthiasKäpplerの点火されたライブラリを使用して、応答をメモリ(弱参照)およびファイルにキャッシュするhttp要求を行うことです。どちらか一方、または両方を実行するように実際に構成可能です。

ライブラリはここにあります: https://github.com/mttkay/ignition ここに例があります: https://github.com/mttkay/ignition/wiki/Sample-applications

個人的には、Droidfuと呼ばれていた頃からこのlibが大好きです

これが私と同じくらいあなたに役立つことを願っていますAjay!

4
petey

まず、デバイスがインターネットから接続されているかどうかを確認します。

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();}
3
Tanvi Agarwal