当前位置:网站首页>第4章 搭建网络库&Room缓存框架
第4章 搭建网络库&Room缓存框架
2022-08-03 11:51:00 【gujunhe】
网络库的封装,泛型参数边界
- 网络请求采用okhttp
- 日志输出采用logging-interceptor
- 数据缓存持久化采用jetpack的Room组件
网络请求主入口:
package com.mooc.libnetwork;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
public class ApiService {
protected static final OkHttpClient okHttpClient;
protected static String sBaseUrl;
protected static Convert sConvert;
static {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpClient = new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build();
//http 证书问题
TrustManager[] trustManagers = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}};
try {
SSLContext ssl = SSLContext.getInstance("SSL");
ssl.init(null, trustManagers, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(ssl.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
public static void init(String baseUrl, Convert convert) {
sBaseUrl = baseUrl;
if (convert == null) {
convert = new JsonConvert();
}
sConvert = convert;
}
public static <T> GetRequest<T> get(String url) {
return new GetRequest<>(sBaseUrl + url);
}
public static <T> PostRequest<T> post(String url) {
return new PostRequest<>(sBaseUrl + url);
}
}
- Request类
public abstract class Request<T, R extends Request> implements Cloneable {
protected String mUrl;
protected HashMap<String, String> headers = new HashMap<>();
protected HashMap<String, Object> params = new HashMap<>();
//仅仅只访问本地缓存,即便本地缓存不存在,也不会发起网络请求
public static final int CACHE_ONLY = 1;
//先访问缓存,同时发起网络的请求,成功后缓存到本地
public static final int CACHE_FIRST = 2;
//仅仅只访问服务器,不存任何存储
public static final int NET_ONLY = 3;
//先访问网络,成功后缓存到本地
public static final int NET_CACHE = 4;
private String cacheKey;
private Type mType;
//private Class mClaz;
private int mCacheStrategy = NET_ONLY;
@IntDef({
CACHE_ONLY, CACHE_FIRST, NET_CACHE, NET_ONLY})
@Retention(RetentionPolicy.SOURCE)
public @interface CacheStrategy {
}
public Request(String url) {
//user/list
mUrl = url;
}
public R addHeader(String key, String value) {
headers.put(key, value);
return (R) this;
}
public R addParam(String key, Object value) {
if (value == null) {
return (R) this;
}
//int byte char short long double float boolean 和他们的包装类型,但是除了 String.class 所以要额外判断
try {
if (value.getClass() == String.class) {
params.put(key, value);
} else {
Field field = value.getClass().getField("TYPE");
Class claz = (Class) field.get(null);
if (claz.isPrimitive()) {
params.put(key, value);
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return (R) this;
}
public R cacheStrategy(@CacheStrategy int cacheStrategy) {
mCacheStrategy = cacheStrategy;
return (R) this;
}
public R cacheKey(String key) {
this.cacheKey = key;
return (R) this;
}
public R responseType(Type type) {
mType = type;
return (R) this;
}
public R responseType(Class claz) {
mType = claz;
return (R) this;
}
private Call getCall() {
okhttp3.Request.Builder builder = new okhttp3.Request.Builder();
addHeaders(builder);
okhttp3.Request request = generateRequest(builder);
Call call = ApiService.okHttpClient.newCall(request);
return call;
}
protected abstract okhttp3.Request generateRequest(okhttp3.Request.Builder builder);
private void addHeaders(okhttp3.Request.Builder builder) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
}
public ApiResponse<T> execute() {
if (mType == null) {
throw new RuntimeException("同步方法,response 返回值 类型必须设置");
}
if (mCacheStrategy == CACHE_ONLY) {
return readCache();
}
if (mCacheStrategy != CACHE_ONLY) {
ApiResponse<T> result = null;
try {
Response response = getCall().execute();
result = parseResponse(response, null);
} catch (IOException e) {
e.printStackTrace();
if (result == null) {
result = new ApiResponse<>();
result.message = e.getMessage();
}
}
return result;
}
return null;
}
@SuppressLint("RestrictedApi")
public void execute(final JsonCallback callback) {
if (mCacheStrategy != NET_ONLY) {
ArchTaskExecutor.getIOThreadExecutor().execute(new Runnable() {
@Override
public void run() {
ApiResponse<T> response = readCache();
if (callback != null && response.body != null) {
callback.onCacheSuccess(response);
}
}
});
}
if (mCacheStrategy != CACHE_ONLY) {
getCall().enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
ApiResponse<T> result = new ApiResponse<>();
result.message = e.getMessage();
callback.onError(result);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
ApiResponse<T> result = parseResponse(response, callback);
if (!result.success) {
callback.onError(result);
} else {
callback.onSuccess(result);
}
}
});
}
}
private ApiResponse<T> readCache() {
String key = TextUtils.isEmpty(cacheKey) ? generateCacheKey() : cacheKey;
Object cache = CacheManager.getCache(key);
ApiResponse<T> result = new ApiResponse<>();
result.status = 304;
result.message = "缓存获取成功";
result.body = (T) cache;
result.success = true;
return result;
}
private ApiResponse<T> parseResponse(Response response, JsonCallback<T> callback) {
String message = null;
int status = response.code();
boolean success = response.isSuccessful();
ApiResponse<T> result = new ApiResponse<>();
Convert convert = ApiService.sConvert;
try {
String content = response.body().string();
if (success) {
if (callback != null) {
ParameterizedType type = (ParameterizedType) callback.getClass().getGenericSuperclass();
Type argument = type.getActualTypeArguments()[0];
result.body = (T) convert.convert(content, argument);
} else if (mType != null) {
result.body = (T) convert.convert(content, mType);
}
// } else if (mClaz != null) {
// result.body = (T) convert.convert(content, mClaz);
// }
else {
Log.e("request", "parseResponse: 无法解析 ");
}
} else {
message = content;
}
} catch (Exception e) {
message = e.getMessage();
success = false;
status = 0;
}
result.success = success;
result.status = status;
result.message = message;
if (mCacheStrategy != NET_ONLY && result.success && result.body != null && result.body instanceof Serializable) {
saveCache(result.body);
}
return result;
}
private void saveCache(T body) {
String key = TextUtils.isEmpty(cacheKey) ? generateCacheKey() : cacheKey;
CacheManager.save(key, body);
}
private String generateCacheKey() {
cacheKey = UrlCreator.createUrlFromParams(mUrl, params);
return cacheKey;
}
@NonNull
@Override
public Request clone() throws CloneNotSupportedException {
return (Request<T, R>) super.clone();
}
}
- JsonCallback
public abstract class JsonCallback<T> {
public void onSuccess(ApiResponse<T> response) {
}
public void onError(ApiResponse<T> response) {
}
public void onCacheSuccess(ApiResponse<T> response) {
}
}
- ApiResponse
public class ApiResponse<T> {
public boolean success;
public int status;
public String message;
public T body;
}
- GetRequest
public class GetRequest<T> extends Request<T, GetRequest> {
public GetRequest(String url) {
super(url);
}
@Override
protected okhttp3.Request generateRequest(okhttp3.Request.Builder builder) {
//get 请求把参数拼接在 url后面
String url = UrlCreator.createUrlFromParams(mUrl, params);
okhttp3.Request request = builder.get().url(url).build();
return request;
}
}
- PostRequest
public class GetRequest<T> extends Request<T, GetRequest> {
public GetRequest(String url) {
super(url);
}
@Override
protected okhttp3.Request generateRequest(okhttp3.Request.Builder builder) {
//get 请求把参数拼接在 url后面
String url = UrlCreator.createUrlFromParams(mUrl, params);
okhttp3.Request request = builder.get().url(url).build();
return request;
}
}
- UrlCreator
class UrlCreator {
public static String createUrlFromParams(String url, Map<String, Object> params) {
StringBuilder builder = new StringBuilder();
builder.append(url);
if (url.indexOf("?") > 0 || url.indexOf("&") > 0) {
builder.append("&");
} else {
builder.append("?");
}
for (Map.Entry<String, Object> entry : params.entrySet()) {
try {
String value = URLEncoder.encode(String.valueOf(entry.getValue()), "UTF-8");
builder.append(entry.getKey()).append("=").append(value).append("&");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
builder.deleteCharAt(builder.length() - 1);
return builder.toString();
}
}
- Convert接口
public interface Convert<T> {
T convert(String response, Type type);
}
- 默认Json Convert
public class JsonConvert implements Convert {
//默认的Json转 Java Bean的转换器
@Override
public Object convert(String response, Type type) {
JSONObject jsonObject = JSON.parseObject(response);
JSONObject data = jsonObject.getJSONObject("data");
if (data != null) {
Object data1 = data.get("data");
return JSON.parseObject(data1.toString(), type);
}
return null;
}
}
- 由使用方手动传入Type类型
public ApiResponse<T> execute() {
if (mType == null) {
throw new RuntimeException("同步方法,response 返回值 类型必须设置");
}
if (mCacheStrategy == CACHE_ONLY) {
return readCache();
}
if (mCacheStrategy != CACHE_ONLY) {
ApiResponse<T> result = null;
try {
Response response = getCall().execute();
result = parseResponse(response, null);
} catch (IOException e) {
e.printStackTrace();
if (result == null) {
result = new ApiResponse<>();
result.message = e.getMessage();
}
}
return result;
}
return null;
}
room数据库的创建
- 创建数据库
@Database(entities = {
Cache.class}, version = 1)
//数据读取、存储时数据转换器,比如将写入时将Date转换成Long存储,读取时把Long转换Date返回
//@TypeConverters(DateConverter.class)
public abstract class CacheDatabase extends RoomDatabase {
private static final CacheDatabase database;
static {
//创建一个内存数据库
//但是这种数据库的数据只存在于内存中,也就是进程被杀之后,数据随之丢失
//Room.inMemoryDatabaseBuilder()
database = Room.databaseBuilder(AppGlobals.getApplication(), CacheDatabase.class, "ppjoke_cache")
//是否允许在主线程进行查询
.allowMainThreadQueries()
//数据库创建和打开后的回调
//.addCallback()
//设置查询的线程池
//.setQueryExecutor()
//.openHelperFactory()
//room的日志模式
//.setJournalMode()
//数据库升级异常之后的回滚
//.fallbackToDestructiveMigration()
//数据库升级异常后根据指定版本进行回滚
//.fallbackToDestructiveMigrationFrom()
// .addMigrations(CacheDatabase.sMigration)
.build();
}
public abstract CacheDao getCache();
public static CacheDatabase get() {
return database;
}
// static Migration sMigration = new Migration(1, 3) {
// @Override
// public void migrate(@NonNull SupportSQLiteDatabase database) {
// database.execSQL("alter table teacher rename to student");
// database.execSQL("alter table teacher add column teacher_age INTEGER NOT NULL default 0");
// }
// };
}
- Entity
@Entity(tableName = "cache" //表名
// , indices = {@Index(value = "key", unique = false)}//本表索引,用于大量数据的查询优化,unique有时候需要保证数据表的某个或者某些字段只有唯一的记录,可以通过设置@Index注解的unique属性实现。以下实例代码实现了避免有两条记录包含一样的key值。
// , inheritSuperIndices = false//如果 该值为true,那么父类中标记的indices{}索引也会算作该表的索引
// , primaryKeys = {"key"}//主键,一些策略逻辑会用到,比如插入一条数据时如果已存在,则更新否则算新的插入,那么怎么判断 ,数据库中是否已存在该条数据呢?就判断提供的主键,在表中是否已存在
// , foreignKeys = {
//外键,一般用于多表数据查询.可以配置多个外键
//ForeignKey用来设置关联表数据更新时所进行的操作,比如可以在@ForeignKey注解中设置onDelete=CASCADE,这样当Cache表中某个对应记录被删除时,ForeignTable表的所有相关记录也会被删除掉。
//对于@Insert(OnConflict=REPLACE)注解,SQLite是进行REMOVE和REPLACE操作,而不是UPDATE操作,这个可能影响到foreign key的约束。
//value:关联查询的表的Java.class,这里给定ForeignTable.class
//parentColumns:与之关联表ForeignTable表中的列名
//childColumns:本表的列的名称,必须要和parentColumns个数一致。这两个可以理解为根据cache表中的那个字段去比对ForeignTable表中的那个字段,认为是有关联关系的数据。
//onDelete:关联表中某条记录被delete或update时,本表应该怎么做:
// NO_ACTION:什么也不做,
// RESTRICT:本表跟parentColumns有关系的数据会立刻删除或更新,但不允许一对多的关系,
// SET_NULL:本表所跟parentColumns有关系的数据被设置为null值,
// SET_DEFAULT:本表所有跟parentColumns有关系的数据被设置为默认值,也是null值
// CASCADE:本表所有跟parentColumns有关系的数据一同被删除或更新
//onUpdate:本表中某条记录被更新时,与之关联的表应该怎么做
//deferred:本表某条记录变更时,与之关联表的数据变更是否要立即执行,还是等待本表事务处理完再来处理关联表。默认是同时处理。
// @ForeignKey(value = ForeignTable.class,
// parentColumns = "foreign_key",
// childColumns = "key",
// onDelete = 1,
// onUpdate = 1,
// deferred = false)}
//本表中 那些字段 不需要 映射到表中
// , ignoredColumns = {"data"}
)
public class Cache implements Serializable {
//PrimaryKey 必须要有,且不为空,autoGenerate 主键的值是否由Room自动生成,默认false
@PrimaryKey(autoGenerate = false)
@NonNull
public String key;
//@ColumnInfo(name = "_data"),指定该字段在表中的列的名字
public byte[] data;
//@Embedded 对象嵌套,ForeignTable对象中所有字段 也都会被映射到cache表中,
//同时也支持ForeignTable 内部还有嵌套对象
//public ForeignTable foreignTable;
}
//public class ForeignTable implements Serializable {
// @PrimaryKey
// @NonNull
// public String foreign_key;
//
// //@ColumnInfo(name = "_data")
// public byte[] foreign_data;
//}
- CacheDao
@Dao
public interface CacheDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long save(Cache cache);
/** * 注意,冒号后面必须紧跟参数名,中间不能有空格。大于小于号和冒号中间是有空格的。 * select *from cache where【表中列名】 =:【参数名】------>等于 * where 【表中列名】 < :【参数名】 小于 * where 【表中列名】 between :【参数名1】 and :【参数2】------->这个区间 * where 【表中列名】like :参数名----->模糊查询 * where 【表中列名】 in (:【参数名集合】)---->查询符合集合内指定字段值的记录 * * @param key * @return */
//如果是一对多,这里可以写List<Cache>
@Query("select *from cache where `key`=:key")
Cache getCache(String key);
//只能传递对象昂,删除时根据Cache中的主键 来比对的
@Delete
int delete(Cache cache);
//只能传递对象昂,删除时根据Cache中的主键 来比对的
@Update(onConflict = OnConflictStrategy.REPLACE)
int update(Cache cache);
}
- DateConverter(Date和Long相互转化)
public class DateConverter {
@TypeConverter
public static Long date2Long(Date date) {
return date.getTime();
}
@TypeConverter
public static Date long2Date(Long data) {
return new Date(data);
}
}
room数据库实现缓存功能
- Request.java中
private void saveCache(T body) {
String key = TextUtils.isEmpty(cacheKey) ? generateCacheKey() : cacheKey;
CacheManager.save(key, body);
}
private String generateCacheKey() {
cacheKey = UrlCreator.createUrlFromParams(mUrl, params);
return cacheKey;
}
- CacheManager
public class CacheManager {
//反序列,把二进制数据转换成java object对象
private static Object toObject(byte[] data) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
bais = new ByteArrayInputStream(data);
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bais != null) {
bais.close();
}
if (ois != null) {
ois.close();
}
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
return null;
}
//序列化存储数据需要转换成二进制
private static <T> byte[] toByteArray(T body) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(body);
oos.flush();
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.close();
}
if (oos != null) {
oos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}
public static <T> void delete(String key, T body) {
Cache cache = new Cache();
cache.key = key;
cache.data = toByteArray(body);
CacheDatabase.get().getCache().delete(cache);
}
public static <T> void save(String key, T body) {
Cache cache = new Cache();
cache.key = key;
cache.data = toByteArray(body);
CacheDatabase.get().getCache().save(cache);
}
public static Object getCache(String key) {
Cache cache = CacheDatabase.get().getCache().getCache(key);
if (cache != null && cache.data != null) {
return toObject(cache.data);
}
return null;
}
}
- 应用缓存在Request类中
边栏推荐
- 什么是Weex
- mysql进阶(二十四)防御SQL注入的方法总结
- 微信小程序获取用户手机号码
- 【MySQL功法】第4话 · 和kiko一起探索MySQL中的运算符
- bash while循环和until循环
- 深度学习跟踪DLT (deep learning tracker)
- Realize 2d characters move left and right while jumping
- 深度学习中数据到底要不要归一化?实测数据来说明!
- OFDM 十六讲 4 -What is a Cyclic Prefix in OFDM
- PC client automation testing practice based on Sikuli GUI image recognition framework
猜你喜欢
最牛逼的集群监控系统,它始终位列第一!
Explain the virtual machine in detail!JD.com produced HotSpot VM source code analysis notes (with complete source code)
【MySQL】数据库进阶之索引内容详解(上篇 索引分类与操作)
4500字归纳总结,一名软件测试工程师需要掌握的技能大全
87.(cesium之家)cesium热力图(贴地形)
微信小程序获取用户手机号码
Matlab学习10-图像处理之傅里叶变换
【一起学Rust 基础篇】Rust基础——变量和数据类型
Five super handy phone open-source automation tools, which is suitable for you?
hystrix 服务熔断和服务降级
随机推荐
fastposter v2.9.0 programmer must-have poster generator
LP流动性挖矿DAPP系统开发丨流动性挖矿功能原理及说明
零信任架构分析【扬帆】
FE主导打造一个运营活动平台
TiKV & TiFlash 加速复杂业务查询丨TiFlash 应用实践
bash for loop
记住用户名案例(js)
C - 为什么指针常常初始化为 NULL?
LeetCode-48. 旋转图像
直播弱网优化
【一起学Rust】Rust学习前准备——注释和格式化输出
dataset数据集有哪些_数据集类型
JUC(三):锁核心类AQS ing
Take you understand the principle of CDN technology
深度学习:文本CNN-textcnn
Matlab学习10-图像处理之傅里叶变换
LeetCode刷题笔记:622.设计循环队列
[错题]电路维修
bash if条件判断
ssh 免密登录了解下