网络库的封装,泛型参数边界
- 网络请求采用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类中