先贴出代码:
1 package com.cache; 2 3 import android.content.Context; 4 import android.text.TextUtils; 5 import android.util.Log; 6 7 import com.google.gson.Gson; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 import java.util.concurrent.CopyOnWriteArrayList; 12 import java.util.concurrent.ExecutorService; 13 import java.util.concurrent.Executors; 14 15 public class CacheHelper { 16 17 private static final String TAG = "CacheHelper"; 18 19 private static final String SHARED_PREFERENCES_NAME = "web_content_cache"; 20 private static final String STORE_KEY_XXX = "STORE_KEY_XXX"; 21 22 private static final boolean NEED_LIMIT_MAX_CACHE_LENGTH = true; 23 private static final int MAX_CACHE_LENGTH = 500000; 24 25 private static CacheHelper sInstance; 26 private ExecutorService mExecutorService; 27 28 public static synchronized CacheHelper instance() { 29 if (null == sInstance) { 30 sInstance = new CacheHelper(); 31 } 32 return sInstance; 33 } 34 35 private CacheHelper() { 36 mExecutorService = Executors.newSingleThreadExecutor(); 37 38 } 39 40 /** 41 * 缓存某个场景下的数据。 42 * 43 * @param cacheValue 44 */ 45 public synchronized void cacheXXXList(final List<XXXBean> cacheValue) { 46 runCache(cacheValue, STORE_KEY_XXX); 47 } 48 49 /** 50 * 获取某个场景下的数据缓存,回调在工作线程中执行。(如果有UI操作,请注意需要在post回UI线程执行。) 51 * 52 * @param callback 53 */ 54 public synchronized void getCacheXXXList(final CacheXXXCallback callback) { 55 if (null == callback) { 56 return; 57 } 58 // 通过某种全局的Utils得到Context,如果没有这种方法,需要在获取实例的时候传入Context 59 final Context appContext = AppUtils.getContext(); 60 if (null == appContext) { 61 return; 62 } 63 checkThreadPool(); 64 mExecutorService.execute(new Runnable() { 65 @Override 66 public void run() { 67 // 目前使用SharedPreference存储Json String,可以改成文件等其他方式 68 final String cacheStr = appContext.getSharedPreferences(SHARED_PREFERENCES_NAME, 69 Context.MODE_PRIVATE).getString(STORE_KEY_XXX, null); 70 final List<XXXBean> cacheValue = new CopyOnWriteArrayList<>(); 71 if (!TextUtils.isEmpty(cacheStr)) { 72 final Gson gson = new Gson(); 73 final List<String> jsons = gson.fromJson(cacheStr, List.class); 74 for (String item : jsons) { 75 cacheValue.add(gson.fromJson(item, XXXBean.class)); 76 } 77 } 78 callback.onGetXXXList(cacheValue); 79 } 80 }); 81 } 82 83 public static interface CacheXXXCallback { 84 public void onGetXXXList(List<XXXBean> cacheValue); 85 } 86 87 private <T> void runCache(final List<T> list, final String cacheKey) { 88 // 通过某种全局的Utils得到Context,如果没有这种方法,需要在获取实例的时候传入Context 89 final Context appContext = AppUtils.getContext(); 90 if (null == appContext) { 91 return; 92 } 93 if (null == list || list.isEmpty()) { 94 return; 95 } 96 final CopyOnWriteArrayList<T> copyList = new CopyOnWriteArrayList<>(); 97 copyList.addAll(list); 98 if (copyList.isEmpty()) { 99 return; 100 } 101 checkThreadPool(); 102 mExecutorService.execute(new Runnable() { 103 @Override 104 public void run() { 105 final Gson gson = new Gson(); 106 final ArrayList<String> jsons = new ArrayList<>(); 107 for (T t : copyList) { 108 jsons.add(gson.toJson(t)); 109 } 110 final String cacheStr = gson.toJson(jsons); 111 if (NEED_LIMIT_MAX_CACHE_LENGTH) { 112 if (cacheStr.length() > MAX_CACHE_LENGTH) { 113 Log.e(TAG, "runCache() : cache value length over max limit."); 114 return; 115 } 116 } 117 // 目前使用SharedPreference存储Json String,可以改成文件等其他方式 118 appContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) 119 .edit().putString(cacheKey, cacheStr).commit(); 120 } 121 }); 122 } 123 124 private void checkThreadPool() { 125 if (null == mExecutorService || mExecutorService.isShutdown() 126 || mExecutorService.isTerminated()) { 127 mExecutorService = Executors.newSingleThreadExecutor(); 128 } 129 } 130 }
说明几个地方:
(1)在helper类内部用线程池实现了异步。使用异步的原因第一,存储Json String的方式有可能是文件,有可能是SharedPreference,涉及到IO;第二,可能会频繁调用写cache和读cache,异步化可以降低调用线程的压力,另外在单一的工作线程(单一线程的线程池)中顺序执行读、写,实现同步。
(2)通过测试发现,对于数据实体对象,Gson无法将对象的List直接转成Json字符串,但大多数的应用场景都是List,所以采用先将List中的每一个对象转成Json String,组成一个List<String>,然后再将List<String>转成一个Json String,存储。在读缓存的时候,是以上过程的逆过程,先将存储的Json String转成List<String>,然后将List中的每一个String转成一个数据实体对象。
(3)存储的方式目前使用的是SharedPreference。关于SharedPreference在这个场景的使用,稍后有时间会有另一篇文章细聊。存储方式可以替换成文件等,只要将读写操作稍作抽象即可,不影响数据转化的逻辑。