当前位置:网站首页>第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类中
边栏推荐
- 性能优化|从ping延时看CPU电源管理
- 卷起来!阿里高工携18位高级架构师耗时57天整合的1658页面试总结
- [论文阅读] (23)恶意代码作者溯源(去匿名化)经典论文阅读:二进制和源代码对比
- 【JS 逆向百例】某网站加速乐 Cookie 混淆逆向详解
- 缓存--伪共享问题
- Knowledge Graph Question Answering System Based on League of Legends
- flink流批一体有啥条件,数据源是从mysql批量分片读取,为啥设置成批量模式就不行
- mysql进阶(二十四)防御SQL注入的方法总结
- 分享一款实用的太阳能充电电路(室内光照可用)
- Android 技术面试准备(含面试题及答案)
猜你喜欢
FR9811S6 SOT-23-6 23V,2A同步降压DC/DC转换器
《数字经济全景白皮书》金融数字用户篇 重磅发布!
Knowledge Graph Question Answering System Based on League of Legends
OFDM 十六讲 4 -What is a Cyclic Prefix in OFDM
mysql advanced (twenty-four) method summary of defense against SQL injection
c语言进阶篇:内存函数
面试突击71:GET 和 POST 有什么区别?
云原生 Dev0ps 实践
LeetCode 899 有序队列[字典序] HERODING的LeetCode之路
Explain the virtual machine in detail!JD.com produced HotSpot VM source code analysis notes (with complete source code)
随机推荐
fast planner中拓扑路径搜索
opencv学习—VideoCapture 类基础知识「建议收藏」
PC client automation testing practice based on Sikuli GUI image recognition framework
零信任的基本概念【新航海】
JUC(三):锁核心类AQS ing
Generate interface documentation online
bash while循环和until循环
LeetCode刷题笔记:622.设计循环队列
4500字归纳总结,一名软件测试工程师需要掌握的技能大全
面试突击71:GET 和 POST 有什么区别?
bash if条件判断
面试官:SOA 和微服务的区别?这回终于搞清楚了!
学习软件测试需要掌握哪些知识点呢?
SmobilerService 推送实现
C language advanced article: memory function
国内数字藏品与国外NFT主要有以下六大方面的区别
LyScript 实现对内存堆栈扫描
性能优化|从ping延时看CPU电源管理
asdn涨薪技术之apifox+Jenkins如何玩转接口自动化测试
SmobilerService 推送实现