feat(cache): 重构缓存架构并新增多级缓存及统计接口
This commit is contained in:
@@ -502,6 +502,12 @@ dependencies {
|
|||||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.2'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-runtime:2.6.2'
|
||||||
|
// 可选:RxJava3 Observable → LiveData 的桥接
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.6.2'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
@@ -533,6 +539,8 @@ dependencies {
|
|||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
|
||||||
//磁盘缓存
|
//磁盘缓存
|
||||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
implementation 'com.jakewharton:disklrucache:2.0.2'
|
||||||
|
//图表
|
||||||
|
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||||
//Aria
|
//Aria
|
||||||
implementation 'com.arialyy.aria:core:3.8.15'
|
implementation 'com.arialyy.aria:core:3.8.15'
|
||||||
annotationProcessor 'com.arialyy.aria:compiler:3.8.15'
|
annotationProcessor 'com.arialyy.aria:compiler:3.8.15'
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fuying.sn.config.CommonConfig;
|
|
||||||
import com.jakewharton.disklrucache.DiskLruCache;
|
import com.jakewharton.disklrucache.DiskLruCache;
|
||||||
import com.tencent.mmkv.MMKV;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@@ -19,20 +17,21 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 磁盘缓存帮助类
|
* 磁盘缓存帮助类
|
||||||
|
* https://www.cnblogs.com/aademeng/articles/6817058.html
|
||||||
*/
|
*/
|
||||||
public class CacheHelper {
|
public class CacheHelper {
|
||||||
private static final String TAG = "DiskLruCacheHelper";
|
private static final String TAG = "DiskLruCacheHelper";
|
||||||
|
|
||||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
|
||||||
|
|
||||||
private static final String DIR_NAME = "diskCache";
|
private static final String DIR_NAME = "diskCache";
|
||||||
private static final int MAX_COUNT = 1024 * 1024 * 1024;
|
private static final int MAX_COUNT = 1024 * 1024 * 1024;
|
||||||
private static final int DEFAULT_APP_VERSION = 1;
|
private static final int DEFAULT_APP_VERSION = 1;
|
||||||
@@ -72,9 +71,8 @@ public class CacheHelper {
|
|||||||
|
|
||||||
int appVersion = context == null ? DEFAULT_APP_VERSION : Utils.getAppVersion(context);
|
int appVersion = context == null ? DEFAULT_APP_VERSION : Utils.getAppVersion(context);
|
||||||
|
|
||||||
DiskLruCache diskLruCache = null;
|
|
||||||
try {
|
try {
|
||||||
diskLruCache = DiskLruCache.open(
|
return DiskLruCache.open(
|
||||||
dir,
|
dir,
|
||||||
appVersion,
|
appVersion,
|
||||||
DEFAULT_APP_VERSION,
|
DEFAULT_APP_VERSION,
|
||||||
@@ -82,13 +80,12 @@ public class CacheHelper {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return diskLruCache;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DiskLruCache generateCache(Context context, String dirName, int maxCount) {
|
private DiskLruCache generateCache(Context context, String dirName, int maxCount) {
|
||||||
DiskLruCache diskLruCache = null;
|
|
||||||
try {
|
try {
|
||||||
diskLruCache = DiskLruCache.open(
|
return DiskLruCache.open(
|
||||||
getDiskCacheDir(context, dirName),
|
getDiskCacheDir(context, dirName),
|
||||||
Utils.getAppVersion(context),
|
Utils.getAppVersion(context),
|
||||||
DEFAULT_APP_VERSION,
|
DEFAULT_APP_VERSION,
|
||||||
@@ -96,7 +93,7 @@ public class CacheHelper {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return diskLruCache;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
@@ -104,9 +101,7 @@ public class CacheHelper {
|
|||||||
// =======================================
|
// =======================================
|
||||||
|
|
||||||
public void put(String key, String value) {
|
public void put(String key, String value) {
|
||||||
// Log.e(TAG, "put: key = " + key + " value = " + value);
|
Log.e(TAG, "put: key = " + key + " value = " + value);
|
||||||
mMMKV.encode(key + "_time", System.currentTimeMillis());
|
|
||||||
mMMKV.encode(key + "_mmkv", value);
|
|
||||||
|
|
||||||
DiskLruCache.Editor edit = null;
|
DiskLruCache.Editor edit = null;
|
||||||
BufferedWriter bw = null;
|
BufferedWriter bw = null;
|
||||||
@@ -116,15 +111,16 @@ public class CacheHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
OutputStream os = edit.newOutputStream(0);
|
OutputStream os = edit.newOutputStream(0);
|
||||||
bw = new BufferedWriter(new OutputStreamWriter(os));
|
bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
|
||||||
bw.write(value);
|
bw.write(value);
|
||||||
edit.commit();//write CLEAN
|
edit.commit();//write CLEAN
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Log.e(TAG, "put: " + e.getMessage());
|
Log.e(TAG, "put: " + e.getMessage());
|
||||||
try {
|
try {
|
||||||
//s
|
if (edit != null) {
|
||||||
edit.abort();//write REMOVE
|
edit.abort();//write REMOVE
|
||||||
|
}
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
Log.e(TAG, "put: " + e1.getMessage());
|
Log.e(TAG, "put: " + e1.getMessage());
|
||||||
@@ -142,30 +138,30 @@ public class CacheHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getAsString(String key) {
|
public String getAsString(String key) {
|
||||||
// Log.e(TAG, "getAsString: " + key);
|
Log.e(TAG, "getAsString: " + key);
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
//write READ
|
|
||||||
inputStream = get(key);
|
inputStream = get(key);
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
return mMMKV.decodeString(key + "_mmkv", null);
|
return null;
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int len = 0;
|
InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||||
byte[] buf = new byte[128];
|
char[] buf = new char[1024];
|
||||||
while ((len = inputStream.read(buf)) != -1) {
|
int len;
|
||||||
sb.append(new String(buf, 0, len));
|
while ((len = isr.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, len);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Log.e(TAG, "getAsString: " + e.getMessage());
|
Log.e(TAG, "getAsString: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {
|
try {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
} catch (IOException e1) {
|
} catch (IOException e) {
|
||||||
e1.printStackTrace();
|
e.printStackTrace();
|
||||||
Log.e(TAG, "getAsString: " + e1.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,9 +195,9 @@ public class CacheHelper {
|
|||||||
|
|
||||||
public JSONArray getAsJSONArray(String key) {
|
public JSONArray getAsJSONArray(String key) {
|
||||||
String JSONString = getAsString(key);
|
String JSONString = getAsString(key);
|
||||||
|
if (JSONString == null) return null;
|
||||||
try {
|
try {
|
||||||
JSONArray obj = new JSONArray(JSONString);
|
return new JSONArray(JSONString);
|
||||||
return obj;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
@@ -233,7 +229,9 @@ public class CacheHelper {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
try {
|
try {
|
||||||
editor.abort();//write REMOVE
|
if (editor != null) {
|
||||||
|
editor.abort();//write REMOVE
|
||||||
|
}
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -256,8 +254,8 @@ public class CacheHelper {
|
|||||||
if (is == null) {
|
if (is == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
try {
|
try {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
byte[] buf = new byte[256];
|
byte[] buf = new byte[256];
|
||||||
int len = 0;
|
int len = 0;
|
||||||
while ((len = is.read(buf)) != -1) {
|
while ((len = is.read(buf)) != -1) {
|
||||||
@@ -266,6 +264,12 @@ public class CacheHelper {
|
|||||||
res = baos.toByteArray();
|
res = baos.toByteArray();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -289,7 +293,9 @@ public class CacheHelper {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
try {
|
try {
|
||||||
editor.abort();
|
if (editor != null) {
|
||||||
|
editor.abort();
|
||||||
|
}
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -365,8 +371,8 @@ public class CacheHelper {
|
|||||||
// =======================================
|
// =======================================
|
||||||
public boolean remove(String key) {
|
public boolean remove(String key) {
|
||||||
try {
|
try {
|
||||||
key = Utils.hashKeyForDisk(key);
|
String hashKey = Utils.hashKeyForDisk(key);
|
||||||
return mDiskLruCache.remove(key);
|
return mDiskLruCache.remove(hashKey);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -379,7 +385,6 @@ public class CacheHelper {
|
|||||||
|
|
||||||
public void delete() throws IOException {
|
public void delete() throws IOException {
|
||||||
mDiskLruCache.delete();
|
mDiskLruCache.delete();
|
||||||
mMMKV.clearAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
@@ -413,12 +418,12 @@ public class CacheHelper {
|
|||||||
//basic editor
|
//basic editor
|
||||||
public DiskLruCache.Editor editor(String key) {
|
public DiskLruCache.Editor editor(String key) {
|
||||||
try {
|
try {
|
||||||
key = Utils.hashKeyForDisk(key);
|
String hashKey = Utils.hashKeyForDisk(key);
|
||||||
//wirte DIRTY
|
//wirte DIRTY
|
||||||
DiskLruCache.Editor edit = mDiskLruCache.edit(key);
|
DiskLruCache.Editor edit = mDiskLruCache.edit(hashKey);
|
||||||
//edit maybe null :the entry is editing
|
//edit maybe null :the entry is editing
|
||||||
if (edit == null) {
|
if (edit == null) {
|
||||||
Log.w(TAG, "the entry spcified key:" + key + " is editing by other . ");
|
Log.w(TAG, "the entry spcified key:" + hashKey + " is editing by other . ");
|
||||||
}
|
}
|
||||||
return edit;
|
return edit;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -436,7 +441,7 @@ public class CacheHelper {
|
|||||||
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(Utils.hashKeyForDisk(key));
|
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(Utils.hashKeyForDisk(key));
|
||||||
if (snapshot == null) //not find entry , or entry.readable = false
|
if (snapshot == null) //not find entry , or entry.readable = false
|
||||||
{
|
{
|
||||||
// Log.e(TAG, "not find entry , or entry.readable = false");
|
Log.e(TAG, "not find entry , or entry.readable = false");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//write READ
|
//write READ
|
||||||
@@ -455,20 +460,17 @@ public class CacheHelper {
|
|||||||
// =======================================
|
// =======================================
|
||||||
|
|
||||||
private File getDiskCacheDir(Context context, String uniqueName) {
|
private File getDiskCacheDir(Context context, String uniqueName) {
|
||||||
String cachePath;
|
File cacheDir;
|
||||||
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|
||||||
|| !Environment.isExternalStorageRemovable()) {
|
|| !Environment.isExternalStorageRemovable()) {
|
||||||
if (context.getExternalCacheDir() != null) {
|
cacheDir = context.getExternalCacheDir();
|
||||||
cachePath = context.getExternalCacheDir().getPath();
|
if (cacheDir == null) {
|
||||||
} else if (context.getExternalFilesDir("cache") != null) {
|
cacheDir = context.getCacheDir();
|
||||||
cachePath = context.getExternalFilesDir("cache").getPath();
|
|
||||||
} else {
|
|
||||||
cachePath = context.getCacheDir().getPath();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cachePath = context.getCacheDir().getPath();
|
cacheDir = context.getCacheDir();
|
||||||
}
|
}
|
||||||
return new File(cachePath + File.separator + uniqueName);
|
return new File(cacheDir, uniqueName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ import com.fuying.sn.network.api.SnControlApi;
|
|||||||
import com.fuying.sn.network.api.SnInfoApi;
|
import com.fuying.sn.network.api.SnInfoApi;
|
||||||
import com.fuying.sn.network.api.SnLogApi;
|
import com.fuying.sn.network.api.SnLogApi;
|
||||||
import com.fuying.sn.network.api.newly.ControlApi;
|
import com.fuying.sn.network.api.newly.ControlApi;
|
||||||
|
import com.fuying.sn.network.api.newly.StatisticsApi;
|
||||||
|
import com.fuying.sn.network.cache.RxCacheManager;
|
||||||
import com.fuying.sn.network.interceptor.RepeatRequestInterceptor;
|
import com.fuying.sn.network.interceptor.RepeatRequestInterceptor;
|
||||||
import com.fuying.sn.service.LogcatService;
|
import com.fuying.sn.service.LogcatService;
|
||||||
import com.fuying.sn.utils.ApkUtils;
|
import com.fuying.sn.utils.ApkUtils;
|
||||||
@@ -125,19 +127,39 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
|||||||
public class NetInterfaceManager {
|
public class NetInterfaceManager {
|
||||||
private static final String TAG = "NetInterfaceManager";
|
private static final String TAG = "NetInterfaceManager";
|
||||||
|
|
||||||
|
private static final RxCacheManager CACHE_MANAGER = RxCacheManager.getInstance();
|
||||||
|
|
||||||
|
public static void clearAllCache() {
|
||||||
|
Log.e(TAG, "clearAllCache: ");
|
||||||
|
CACHE_MANAGER.clearAllCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeCache(String key) {
|
||||||
|
Log.e(TAG, "removeCache: key = " + key);
|
||||||
|
CACHE_MANAGER.removeCache(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
//缓存过期时间1秒
|
||||||
|
private static final long CACHE_EXPIRE_ONE_SECOND = 1000;
|
||||||
|
//默认 缓存过期时间10秒
|
||||||
|
private static final long DEFAULT_CACHE_EXPIRE = 10 * CACHE_EXPIRE_ONE_SECOND;
|
||||||
|
//缓存过期时间1分钟
|
||||||
|
private static final long CACHE_EXPIRE_ONE_MINUTE = 60 * CACHE_EXPIRE_ONE_SECOND;
|
||||||
|
//超时时间
|
||||||
|
private static final int TIME_OUT = 30;
|
||||||
|
// 缓存文件最大限制大小20M
|
||||||
|
private static final long CACHE_SIZE = 1024 * 1024 * 64;
|
||||||
|
|
||||||
|
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private static NetInterfaceManager INSTANCE;
|
private static NetInterfaceManager INSTANCE;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private ContentResolver crv;
|
private ContentResolver crv;
|
||||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
|
||||||
private Retrofit mRetrofit;
|
|
||||||
private OkHttpClient okHttpClient;
|
|
||||||
private CacheHelper mCacheHelper;
|
private CacheHelper mCacheHelper;
|
||||||
|
|
||||||
//超时时间
|
private Retrofit mRetrofit;
|
||||||
private static final int timeOut = 15;
|
private OkHttpClient mOkHttpClient;
|
||||||
// 缓存文件最大限制大小20M
|
|
||||||
private static final long cacheSize = 1024 * 1024 * 64;
|
|
||||||
|
|
||||||
private NetInterfaceManager(Context context) {
|
private NetInterfaceManager(Context context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
@@ -148,23 +170,23 @@ public class NetInterfaceManager {
|
|||||||
this.mCacheHelper = new CacheHelper(mContext);
|
this.mCacheHelper = new CacheHelper(mContext);
|
||||||
|
|
||||||
if (null == mRetrofit) {
|
if (null == mRetrofit) {
|
||||||
if (okHttpClient == null) {
|
if (mOkHttpClient == null) {
|
||||||
//如果无法生存缓存文件目录,检测权限使用已经加上,检测手机是否把文件读写权限禁止了
|
//如果无法生存缓存文件目录,检测权限使用已经加上,检测手机是否把文件读写权限禁止了
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
builder.connectTimeout(timeOut, TimeUnit.SECONDS); // 设置连接超时时间
|
builder.connectTimeout(TIME_OUT, TimeUnit.SECONDS); // 设置连接超时时间
|
||||||
builder.writeTimeout(timeOut, TimeUnit.SECONDS);// 设置写入超时时间
|
builder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);// 设置写入超时时间
|
||||||
builder.readTimeout(timeOut, TimeUnit.SECONDS);// 设置读取数据超时时间
|
builder.readTimeout(TIME_OUT, TimeUnit.SECONDS);// 设置读取数据超时时间
|
||||||
builder.retryOnConnectionFailure(true);// 设置进行连接失败重试
|
builder.retryOnConnectionFailure(true);// 设置进行连接失败重试
|
||||||
builder.addInterceptor(new RepeatRequestInterceptor());
|
builder.addInterceptor(new RepeatRequestInterceptor());
|
||||||
|
|
||||||
// 设置缓存文件路径
|
// 设置缓存文件路径
|
||||||
String cacheDirectory = getCacheDir() + "/OkHttpCache";
|
String cacheDirectory = getCacheDir() + "/OkHttpCache";
|
||||||
Cache cache = new Cache(new File(cacheDirectory), cacheSize);
|
Cache cache = new Cache(new File(cacheDirectory), CACHE_SIZE);
|
||||||
builder.cache(cache);// 设置缓存
|
builder.cache(cache);// 设置缓存
|
||||||
okHttpClient = builder.build();
|
mOkHttpClient = builder.build();
|
||||||
}
|
}
|
||||||
mRetrofit = new Retrofit.Builder()
|
mRetrofit = new Retrofit.Builder()
|
||||||
.client(okHttpClient)
|
.client(mOkHttpClient)
|
||||||
.baseUrl(BuildConfig.ROOT_URL)
|
.baseUrl(BuildConfig.ROOT_URL)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||||
@@ -206,10 +228,132 @@ public class NetInterfaceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public OkHttpClient getOkHttpClient() {
|
public OkHttpClient getOkHttpClient() {
|
||||||
return okHttpClient;
|
return mOkHttpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 极简调用:仅需 网络请求Observable + 缓存过期时间
|
||||||
|
*
|
||||||
|
* @param networkObservable 原始网络请求
|
||||||
|
* @param expireTimeMillis 缓存有效期(毫秒)
|
||||||
|
* @return 带自动缓存的Observable
|
||||||
|
*/
|
||||||
|
public static <T> Observable<T> withCache(Observable<T> networkObservable, String cacheKey, long expireTimeMillis) {
|
||||||
|
return Observable.defer(() -> {
|
||||||
|
// Log.e(TAG, "withCache: cacheKey = " + cacheKey);
|
||||||
|
// 1. 读取缓存
|
||||||
|
T cacheData = CACHE_MANAGER.getCache(cacheKey);
|
||||||
|
if (cacheData != null) {
|
||||||
|
Log.e(TAG, "withCache: getCache " + cacheKey);
|
||||||
|
return Observable.just(cacheData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "withCache: request " + cacheKey);
|
||||||
|
// 2. 无缓存则发起网络请求,成功后缓存
|
||||||
|
return networkObservable
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.doOnNext(data -> CACHE_MANAGER.saveCache(cacheKey, data, expireTimeMillis));
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io()) // 缓存IO切到子线程
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用缓存策略:优先从内存缓存获取,其次从磁盘缓存获取,最后请求网络
|
||||||
|
* 统一使用 cacheKey 同时作为内存和磁盘缓存的键,且支持 Stale-While-Revalidate 策略
|
||||||
|
*
|
||||||
|
* @param networkObservable 网络请求Observable
|
||||||
|
* @param cacheKey 缓存键(内存与磁盘通用)
|
||||||
|
* @param type 数据类型(通过 TypeToken 获取,支持泛型)
|
||||||
|
* @param expireTimeMillis 内存缓存过期时间(毫秒)
|
||||||
|
* @return 带三级缓存策略的Observable,可能会发射两次(缓存 + 最新网络数据)
|
||||||
|
*/
|
||||||
|
public static <T> Observable<BaseResponse<T>> withMultiLevelCache(
|
||||||
|
Observable<BaseResponse<T>> networkObservable,
|
||||||
|
String cacheKey,
|
||||||
|
Type type,
|
||||||
|
long expireTimeMillis) {
|
||||||
|
|
||||||
|
return Observable.<BaseResponse<T>>create(emitter -> {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: cacheKey = " + cacheKey);
|
||||||
|
|
||||||
|
// 1. 优先从内存缓存获取
|
||||||
|
BaseResponse<T> memoryCache = CACHE_MANAGER.getCache(cacheKey);
|
||||||
|
if (memoryCache != null) {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: hit memory cache");
|
||||||
|
emitter.onNext(memoryCache);
|
||||||
|
} else {
|
||||||
|
// 2. 内存未命中,从磁盘缓存获取
|
||||||
|
try {
|
||||||
|
String diskCacheJson = INSTANCE.mCacheHelper.getAsString(cacheKey);
|
||||||
|
if (!TextUtils.isEmpty(diskCacheJson)) {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: hit disk cache");
|
||||||
|
BaseResponse<T> diskCache = GsonUtils.toJavaObject(diskCacheJson, type);
|
||||||
|
if (diskCache != null) {
|
||||||
|
emitter.onNext(diskCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: read disk cache error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 始终请求网络数据以同步更新
|
||||||
|
Log.e(TAG, "withMultiLevelCache: request from network");
|
||||||
|
networkObservable
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<BaseResponse<T>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
emitter.setDisposable(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(BaseResponse<T> response) {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: network success code = " + (response != null ? response.code : "null"));
|
||||||
|
if (response != null && response.code == 200) {
|
||||||
|
// 保存到内存和磁盘缓存
|
||||||
|
CACHE_MANAGER.saveCache(cacheKey, response, expireTimeMillis);
|
||||||
|
try {
|
||||||
|
String json = GsonUtils.toJSONString(response);
|
||||||
|
INSTANCE.mCacheHelper.put(cacheKey, json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: save disk cache error", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 状态码不为 200,清除缓存
|
||||||
|
Log.e(TAG, "withMultiLevelCache: code != 200, clear cache");
|
||||||
|
CACHE_MANAGER.removeCache(cacheKey);
|
||||||
|
INSTANCE.mCacheHelper.remove(cacheKey);
|
||||||
|
}
|
||||||
|
emitter.onNext(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
Log.e(TAG, "withMultiLevelCache: network error", e);
|
||||||
|
// 网络错误,清除缓存
|
||||||
|
CACHE_MANAGER.removeCache(cacheKey);
|
||||||
|
INSTANCE.mCacheHelper.remove(cacheKey);
|
||||||
|
if (!emitter.isDisposed()) {
|
||||||
|
emitter.onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
if (!emitter.isDisposed()) {
|
||||||
|
emitter.onComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* API
|
* API
|
||||||
@@ -257,6 +401,10 @@ public class NetInterfaceManager {
|
|||||||
return mRetrofit.create(ControlApi.class);
|
return mRetrofit.create(ControlApi.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StatisticsApi getStatisticsApi() {
|
||||||
|
return mRetrofit.create(StatisticsApi.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ public class UrlAddress {
|
|||||||
public final static String DEVICE_INFO = "device/info";
|
public final static String DEVICE_INFO = "device/info";
|
||||||
/*检查设备守护更新*/
|
/*检查设备守护更新*/
|
||||||
public final static String CHECK_UPDATE = "device/check-update";
|
public final static String CHECK_UPDATE = "device/check-update";
|
||||||
|
|
||||||
/*使用统计*/
|
/*使用统计*/
|
||||||
/*获取整机控制配置*/
|
/*获取整机控制配置*/
|
||||||
public final static String CONTROL_STATUS = "statistics/control-status";
|
public final static String CONTROL_STATUS = "statistics/control-status";
|
||||||
|
|||||||
30
app/src/main/java/com/fuying/sn/network/cache/CacheEntry.java
vendored
Normal file
30
app/src/main/java/com/fuying/sn/network/cache/CacheEntry.java
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.fuying.sn.network.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存实体:存储请求结果、创建时间、有效期
|
||||||
|
*
|
||||||
|
* @param <T> 接口返回数据类型
|
||||||
|
*/
|
||||||
|
public class CacheEntry<T> {
|
||||||
|
// 缓存的成功数据
|
||||||
|
private final T data;
|
||||||
|
// 缓存创建时间(毫秒)
|
||||||
|
private final long createTime;
|
||||||
|
// 缓存有效期(毫秒)
|
||||||
|
private final long expireTimeMillis;
|
||||||
|
|
||||||
|
public CacheEntry(T data, long createTime, long expireTimeMillis) {
|
||||||
|
this.data = data;
|
||||||
|
this.createTime = createTime;
|
||||||
|
this.expireTimeMillis = expireTimeMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断缓存是否过期
|
||||||
|
public boolean isExpired() {
|
||||||
|
return System.currentTimeMillis() - createTime > expireTimeMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
app/src/main/java/com/fuying/sn/network/cache/RxCacheManager.java
vendored
Normal file
63
app/src/main/java/com/fuying/sn/network/cache/RxCacheManager.java
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package com.fuying.sn.network.cache;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局缓存管理器:线程安全,存储所有接口缓存
|
||||||
|
*/
|
||||||
|
public class RxCacheManager {
|
||||||
|
// 单例
|
||||||
|
private static volatile RxCacheManager INSTANCE;
|
||||||
|
// 缓存容器:key=请求唯一标识,value=缓存实体
|
||||||
|
private final ConcurrentHashMap<String, CacheEntry<?>> cacheMap;
|
||||||
|
|
||||||
|
private RxCacheManager() {
|
||||||
|
cacheMap = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RxCacheManager getInstance() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
synchronized (RxCacheManager.class) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new RxCacheManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存缓存
|
||||||
|
*/
|
||||||
|
public <T> void saveCache(String key, T data, long expireTimeMillis) {
|
||||||
|
if (key == null || data == null) return;
|
||||||
|
cacheMap.put(key, new CacheEntry<>(data, System.currentTimeMillis(), expireTimeMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存(自动判断过期)
|
||||||
|
*/
|
||||||
|
public <T> T getCache(String key) {
|
||||||
|
CacheEntry<?> entry = cacheMap.get(key);
|
||||||
|
if (entry == null || entry.isExpired()) {
|
||||||
|
// 缓存不存在/已过期,移除并返回null
|
||||||
|
cacheMap.remove(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (T) entry.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有缓存
|
||||||
|
*/
|
||||||
|
public void clearAllCache() {
|
||||||
|
cacheMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定缓存
|
||||||
|
*/
|
||||||
|
public void removeCache(String key) {
|
||||||
|
cacheMap.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/src/main/java/com/fuying/sn/network/cache/RxCacheRequest.java
vendored
Normal file
38
app/src/main/java/com/fuying/sn/network/cache/RxCacheRequest.java
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.fuying.sn.network.cache;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化后:无需手动传CacheKey,自动从请求获取
|
||||||
|
*/
|
||||||
|
public class RxCacheRequest {
|
||||||
|
private static final RxCacheManager CACHE_MANAGER = RxCacheManager.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 极简调用:仅需 网络请求Observable + 缓存过期时间
|
||||||
|
*
|
||||||
|
* @param networkObservable 原始网络请求
|
||||||
|
* @param expireTimeMillis 缓存有效期(毫秒)
|
||||||
|
* @return 带自动缓存的Observable
|
||||||
|
*/
|
||||||
|
public static <T> Observable<T> withCache(Observable<T> networkObservable, String cacheKey, long expireTimeMillis) {
|
||||||
|
return Observable.defer(() -> {
|
||||||
|
|
||||||
|
// 2. 读取缓存
|
||||||
|
T cacheData = CACHE_MANAGER.getCache(cacheKey);
|
||||||
|
if (cacheData != null) {
|
||||||
|
return Observable.just(cacheData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 无缓存则发起网络请求,成功后缓存
|
||||||
|
return networkObservable
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.doOnNext(data -> CACHE_MANAGER.saveCache(cacheKey, data, expireTimeMillis));
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io()) // 缓存IO切到子线程
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
66
app/src/main/java/com/fuying/sn/network/cache/RxTimeCacheManager.java
vendored
Normal file
66
app/src/main/java/com/fuying/sn/network/cache/RxTimeCacheManager.java
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package com.fuying.sn.network.cache;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
import io.reactivex.rxjava3.core.ObservableSource;
|
||||||
|
import io.reactivex.rxjava3.core.ObservableTransformer;
|
||||||
|
|
||||||
|
public class RxTimeCacheManager {
|
||||||
|
|
||||||
|
// Thread-safe memory cache to hold our data and its timestamp
|
||||||
|
private static final ConcurrentHashMap<String, CacheEntry<?>> cacheMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Industry Standard RxJava Cache Transformer.
|
||||||
|
* * @param cacheKey Unique key for this specific request (e.g., URL or Method Name)
|
||||||
|
*
|
||||||
|
* @param validTimeMillis How long the data is valid before a new network request is allowed
|
||||||
|
*/
|
||||||
|
public static <T> ObservableTransformer<T, T> applyCache(final String cacheKey, final long validTimeMillis) {
|
||||||
|
return new ObservableTransformer<T, T>() {
|
||||||
|
@Override
|
||||||
|
public ObservableSource<T> apply(Observable<T> upstream) {
|
||||||
|
// defer() ensures this logic runs EVERY time someone subscribes, not at initialization
|
||||||
|
return Observable.defer(() -> {
|
||||||
|
CacheEntry<?> entry = cacheMap.get(cacheKey);
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 1. Check if cache exists and is within the time limit
|
||||||
|
if (entry != null && (currentTime - entry.timestamp) < validTimeMillis) {
|
||||||
|
// Cache HIT -> Return cached data immediately, do not trigger network
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T cachedData = (T) entry.data;
|
||||||
|
return Observable.just(cachedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Cache MISS or EXPIRED -> Trigger upstream (network) and save result
|
||||||
|
return upstream.doOnNext(data -> {
|
||||||
|
// Save the fresh data and current timestamp back to the cache
|
||||||
|
cacheMap.put(cacheKey, new CacheEntry<>(data, System.currentTimeMillis()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears a specific cache key if you need to force a refresh manually.
|
||||||
|
*/
|
||||||
|
public static void clearCache(String cacheKey) {
|
||||||
|
cacheMap.remove(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal wrapper to hold the payload and the time it was fetched.
|
||||||
|
*/
|
||||||
|
private static class CacheEntry<T> {
|
||||||
|
final T data;
|
||||||
|
final long timestamp;
|
||||||
|
|
||||||
|
CacheEntry(T data, long timestamp) {
|
||||||
|
this.data = data;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user