简介
在Project中有大量数据显示需求时,数据缓存就会显得尤为重要。
特别是图片等特别消耗内存的数据。不过有非常多的优秀轮子可以实现图像的加载和缓存,例如
- Picasso Square公司
- Fresco Facebook
- Glide : 2014年google I/O大会上发布的官方推荐(Glide和Picasso对比)
今天这里主要来讨论LRU在普通数据上的使用,比如用于大批量的数据缓存。
解决方案:Global全局存储 + LruCache + DiskLruCache
案例需求说明
随机产生一组庞大的数据,首页启动时加载。
加载完毕后,存入内存缓存和Disk缓存。
点击按钮分别从不同的缓存中读取。
数据存储设计方案
缓存有三层
- GlobalManager 全局存储,多用于共享少量数据。比如config 配置,user info,状态等
- LruCacheManager 内存缓存,多用于大量数据存储,比如联络人信息,新闻数据等
- DiskCacheManager Disk缓存,多用于大量数据存储,比如联络人信息,新闻数据等
其中LruCacheManager 和 DiskCacheManager都是Lru思想。
方案实现:
Project采用MVP框架,所有的数据读取逻辑放在DownloadPresenter中,数据加载放在model的DataApi中。
-
UserApplication中初始化和释放缓存资源
public class UserApplication extends Application { /** * 在创建应用程序时调用,可以在这里实例化应用程序的单例, * 创建和实例化任何应用程序状态变量或共享资源等 */ @Override public void onCreate() { super.onCreate(); // init cache GlobalManager.INSTANCE.init(); CacheManager.INSTANCE.init(this); } /** * 程序终止时调用,如果需要及时的释放资源可以在各个Activity中。 * onTerminate只有在程序终止时才会调用,程序退到后台时有可能不会终止。 */ @Override public void onTerminate() { super.onTerminate(); GlobalManager.INSTANCE.release(); CacheManager.INSTANCE.release(); } ...... } -------------------------------------CacheManager public enum CacheManager { INSTANCE; public void init(Context context){ DiskCacheManager.INSTANCE.init(context); DiskLruCacheUtils.setDiskLruCache(DiskCacheManager.INSTANCE.getDiskLruCache(context)); } public void release(){ DiskCacheManager.INSTANCE.release(); } } ---------------------------------------GlobalManager public enum GlobalManager { INSTANCE; public int mDataCount; public void init(){ } public void release(){ } }
-
DownloadPresenter使用RxAndroid框架异步加载数据,并提供缓存读取,写入接口
public class DownloadPresenter { private static final String TAG = "DownloadPresenter"; private IDownloadListener mListener; // rx private CompositeDisposable compositeDisposable = new CompositeDisposable(); private Context mContext; public DownloadPresenter(Context context) { mContext = context; } public void setListener(IDownloadListener listener) { mListener = listener; } public void loadData() { Log.d(TAG, "loadData"); // load start notify if (mListener != null) { mListener.OnLoadStart(); } // load data Disposable dispose = Observable.fromCallable(DataApi::getData) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> { //Log.d(TAG, "loadData Success: " + StringUtils.join(data.toArray(), ",")); if (mListener != null) { mListener.OnLoadSuccess(data); } // add to disk cache cacheToDisk(data); // add to lru cache cacheToLru(data); GlobalManager.INSTANCE.mDataCount = data.size(); }, error -> { Log.w(TAG, "loadData error: ", error); if (mListener != null) { mListener.OnLoadFail(error.toString()); } }); compositeDisposable.add(dispose); } private void cacheToDisk(List<DataModel> list) { for (DataModel data : list) { DiskLruCacheUtils.set(data.id, data.description); } } private void cacheToLru(List<DataModel> list) { for (DataModel data : list) { LruCacheManager.INSTANCE.getLruCache(mContext).put(data.id, data.description); } } public String getFromDiskCache(String key) { return DiskLruCacheUtils.getString(key); } public String getFromLruCache(String key) { return LruCacheManager.INSTANCE.getLruCache(mContext).get(key); } public String getFromCache(String key) { String str = getFromLruCache(key); if (str != null) { Log.d(TAG,"getFromCache is from lru cache"); return str; } Log.d(TAG,"getFromCache is from disk lru cache"); return getFromDiskCache(key); } public void destroy() { // to avoid okhttp leak if (compositeDisposable != null && !compositeDisposable.isDisposed()) { compositeDisposable.dispose(); } } }
-
MainActivity负责显示缓存读取数据,以及UI交互逻辑
public class MainActivity extends BaseActivity implements IDownloadListener { private final static String TAG = "MainActivity"; // presenter of MVP private DownloadPresenter mPresenter; private Context mContext; private TextView mHint; // permission request code private final static int PERMISSION_REQUEST_CODE_RECORD = 10000; // if system request dialogue show private boolean mSystemPermissionShowing = false; private AlertDialog mNeverShowDlg; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void initData() { mContext = this; mPresenter = new DownloadPresenter(this); mPresenter.setListener(this); } @Override protected void initView(Bundle savedInstanceState) { setContentView(R.layout.activity_main); mHint = (TextView) findViewById(R.id.tv_hint); mHint.setMovementMethod(ScrollingMovementMethod.getInstance()); findViewById(R.id.tv_lru).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mHint.setText(mPresenter.getFromLruCache("a")); } }); findViewById(R.id.tv_disk).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mHint.setText(mPresenter.getFromDiskCache("c")); } }); findViewById(R.id.tv_common).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mHint.setText(GlobalManager.INSTANCE.mDataCount + ", b: " + mPresenter.getFromCache("b")); } }); } @Override protected void loadData() { } @Override protected void onPause() { super.onPause(); DiskCacheManager.INSTANCE.flushDiskLruCache(); } @Override public void OnLoadStart() { } @Override public void OnLoadSuccess(List<DataModel> data) { Toast.makeText(mContext, "OnLoadSuccess " + data.size(), Toast.LENGTH_LONG).show(); } @Override public void OnLoadFail(String error) { } @Override protected void onResume() { super.onResume(); dealWithPermission(); } @Override protected void onDestroy() { super.onDestroy(); if (mNeverShowDlg != null && mNeverShowDlg.isShowing()) { mNeverShowDlg.dismiss(); } mPresenter.destroy(); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE_RECORD) { mSystemPermissionShowing = false; int hasWriteStoragePermission = ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (hasWriteStoragePermission != PackageManager.PERMISSION_GRANTED) { // if user choose deny,exit app finish(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } private void dealWithPermission() { int hasWriteStoragePermission = ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (hasWriteStoragePermission != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "permission not granted"); // if user choose never show before, request system permission will not work boolean never_show = !ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (never_show) { Log.d(TAG, "user choose never show"); showNeverShowHintDialogue(); } else { if (!mSystemPermissionShowing) { Log.d(TAG, "show system permission dialogue"); // show system permission dialogue mSystemPermissionShowing = true; showSystemRequestDialog(); } } } else { mPresenter.loadData(); } } private void showSystemRequestDialog() { // show system permission dialogue ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE_RECORD); } private void showNeverShowHintDialogue() { mNeverShowDlg = new AlertDialog.Builder(mContext) .setCancelable(false) .setTitle(getString(R.string.grant_permission_title)) .setMessage(getString(R.string.grant_permission)) .setPositiveButton(getString(R.string.grant_setting), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // user choose never show,you could change your resolution here for your project gotoAppDetail(); } }) .setNegativeButton(getString(R.string.grant_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }) .show(); } private void gotoAppDetail() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getPackageName())); startActivity(intent); } }