- 发表于
Android 整理
- Authors
- 作者
- Masachi Zhang
- @MasachiZhang
Android
1. Gradle Example
此处提供一个自用得Gradle Dependencies,由于是多年前的Dependencies,版本一定不是最新的,请参考使用
Dependencies
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:support-v4:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'commons-io:commons-io:2.6'
implementation 'commons-collections:commons-collections:3.2.2'
//网络请求
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
//图片加载
implementation 'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
//弹出框
implementation 'com.orhanobut:dialogplus:1.11@aar'
//flexBox
implementation 'com.google.android:flexbox:1.0.0'
//tab
implementation 'devlight.io:navigationtabbar:1.2.5'
implementation 'com.ogaclejapan.smarttablayout:library:1.6.1@aar'
implementation 'com.ogaclejapan.smarttablayout:utils-v4:1.6.1@aar'
//时间选择
implementation 'com.wdullaer:materialdatetimepicker:3.6.2'
//状态栏
implementation 'com.jaeger.statusbarutil:library:1.5.1'
//通知
implementation 'com.github.GrenderG:Toasty:1.3.0'
//角标
implementation 'me.leolin:ShortcutBadger:1.1.21@aar'
//路由
implementation 'com.chenenyu.router:router:1.5.2'
// 每个使用了Router注解的module都要添加该注解处理器
annotationProcessor 'com.chenenyu.router:compiler:1.5.0'
//JSON
implementation 'com.squareup.moshi:moshi:1.6.0'
//Ptr & Load More
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-14'
implementation 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar'
//Localization
implementation 'com.akexorcist:localizationactivity:1.2.2'
//主题
implementation 'skin.support:skin-support:3.1.0-beta1'
//大图查看
implementation 'com.github.piasy:BigImageViewer:1.4.6'
implementation 'com.github.piasy:GlideImageLoader:1.4.6'
//权限
implementation('com.github.hotchemi:permissionsdispatcher:3.3.1') {
exclude module: "support-v13"
}
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:3.3.1"
//QR
implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
implementation 'com.github.prolificinteractive:material-calendarview:1.6.0'
//图库
implementation 'com.github.lovetuzitong:MultiImageSelector:1.2'
//下拉框
implementation 'com.github.arcadefire:nice-spinner:1.3.4'
//expand
implementation 'com.github.traex.expandablelayout:library:1.2.2'
//toolbar
implementation 'com.wuhenzhizao:titlebar:1.1.3'
implementation 'com.jaeger.statusbarutil:library:1.5.1'
//drawer
implementation 'com.mxn.soul:flowingdrawer-core:2.0.0'
implementation 'com.nineoldandroids:library:2.4.0'
//右滑返回
implementation 'me.imid.swipebacklayout.lib:library:1.1.0'
//右滑删除
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
//alphabet
implementation 'com.github.viethoa:fastscroller:1.2.0'
//保活
implementation project(':LibMarsdaemon')
implementation 'org.apache.commons:commons-lang3:3.8'
//RxJava
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'log4j:log4j:1.2.17'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
//pdf
implementation 'com.github.barteksc:android-pdf-viewer:3.1.0-beta.1'
}
同时推几个网址
2. Retrofit
据我所知,retrofit多用于Android上,Server之间通信一般用Eureka或者nacos等注册中心 或者是 Dubbo、Feign等RPC
Retrofit Api
public class ServerApi{
private static ServerApi api;
private Retrofit retrofit;
private ServerApi() {
initRetrofit(new TokenInterceptor());
}
private ServerApi(Interceptor interceptor) {
initRetrofit(interceptor);
}
private void initRetrofit(Interceptor interceptor) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(Constant.Http.HTTP_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(Constant.Http.HTTP_READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(Constant.Http.HTTP_WRITE_TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(Env.SERVER_URL)
.addConverterFactory(ConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
}
public static ServerApi defaultInstance(){
if(api == null) {
api = new ServerApi();
}
return api;
}
public static ServerApi getInstance(HttpHeaderInterceptor interceptor) {
api = null;
return new ServerApi(interceptor);
}
public <T> T create(Class<T> service) {
return retrofit.create(service);
}
}
public class TokenInterceptor implements Interceptor {
private Map<String,String> headerParams = new HashMap<>();
public TokenInterceptor() {}
@Override
public Response intercept(Chain chain) throws IOException {
Request oldRequest = chain.request();
Request.Builder newRequestBuilder = oldRequest.newBuilder()
.method(oldRequest.method(), oldRequest.body())
.addHeader("Authorization", "Bearer " + MainApplication.getInstance().getAccessToken())
.addHeader("Content-Type", "application/json");
return chain.proceed(newRequestBuilder.build());
}
}
Common Header Interceptor
public class HttpHeaderInterceptor implements Interceptor {
private Map<String,String> headerParams = new HashMap<>();
public HttpHeaderInterceptor() {}
@Override
public Response intercept(Chain chain) throws IOException{
Request oldRequest = chain.request();
Request.Builder newRequestBuilder = oldRequest.newBuilder()
.method(oldRequest.method(), oldRequest.body());
if(headerParams.size() > 0){
for(Map.Entry<String, String> params : headerParams.entrySet()){
newRequestBuilder.header(params.getKey(), params.getValue());
}
}
return chain.proceed(newRequestBuilder.build());
}
public static class Builder {
HttpHeaderInterceptor httpHeaderInterceptor;
public Builder () {
httpHeaderInterceptor = new HttpHeaderInterceptor();
}
public Builder addHeaderParams(String key, int value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, float value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, double value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, long value){
return addHeaderParams(key, String.valueOf(value));
}
public Builder addHeaderParams(String key, String value){
httpHeaderInterceptor.headerParams.put(key, value);
return this;
}
public HttpHeaderInterceptor build() {
return httpHeaderInterceptor;
}
}
}
ConvertFactory
public class ConverterFactory extends Converter.Factory {
/**
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static ConverterFactory create() {
return create(new Gson());
}
/**
* Create an instance using {@code gson} for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static ConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new ConverterFactory(gson);
}
private final Gson gson;
private ConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new ResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new RequestBodyConverter<>(gson, adapter);
}
}
Request/Response Body Converter
public class RequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
RequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
((Request)value).setBody(trimRequest(((Request) value).getBody()));
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
private Object trimRequest (Object object) {
if(object == null) return null;
Field[] fields = object.getClass().getDeclaredFields();
for(Field field : fields) {
Class<?> type = field.getType();
if("class java.lang.String".equals(type.toString())) {
String fieldName = field.getName();
Object value = getFieldValueByName(fieldName, object);
if(value != null) {
setTrimedValue(object, type, fieldName, value.toString().trim());
}
}
}
return object;
}
private Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter, new Class[]{});
Object value = method.invoke(o, new Object[]{});
return value;
} catch (Exception e) {
return null;
}
}
private static void setTrimedValue(Object obj,Class<?> classType,String fieldName,String value){
String firstLetter=fieldName.substring(0,1).toUpperCase();
String setter = "set"+firstLetter+fieldName.substring(1);
try{
Method method = obj.getClass().getMethod(setter, new Class[]{classType});
method.invoke(obj, new Object[] {value});
}catch (Exception e){
e.printStackTrace();
}
}
}
public class ResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
ResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String responseString = value.string();
try {
Response response = gson.fromJson(responseString, Response.class);
switch (response.getCode()) {
case 200:
return adapter.read(gson.newJsonReader(new StringReader(gson.toJson(response.getBody()))));
default:
throw new ServerException(response.getCode(), response.getMessage());
}
} finally {
value.close();
}
}
}
Request/Response Entity
public class Request<T> {
public Request(T body, HashMap<String, String> secure) {
this.body = body;
this.secure = secure;
}
public Request(T body) {
this.body = body;
}
public Request() {
}
private T body;
private HashMap<String, String> secure = null;
public void setSecure(HashMap<String, String> secure) {
this.secure = secure;
}
public void setBody(T body) {
this.body = body;
}
public T getBody() {
return body;
}
}
public class Response<T> {
private int code;
private String message;
private T body;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getBody() {
return body;
}
public void setBody(T body) {
this.body = body;
}
}
3. RxJava
RxJava原理:
被观察者 (Observable) 通过 订阅(Subscribe) 按顺序发送事件 给观察者 (Observer)
观察者(Observer) 按顺序接收事件 & 作出对应的响应动作。
RxJava大部分情况还是用在网络请求上,Android的主线程是不建议做耗时的任务。 RxJava运算符:
3.1 RxJava & Retrofit
3.1.1 传统请求
例子
描述网络请求接口
// 传统方式:Call<..>接口形式
public interface GetRequest_Interface {
@GET("url地址")
Call<Translation> getCall();
// 注解里传入 网络请求 的部分URL地址
// getCall()是接受网络请求数据的方法
}
// RxJava 方式:Observable<..>接口形式
@GET("url地址") // 这里只需要写path就行
public interface GetRequest_Interface {
Observable<Translation> getCall();
Retrofit发起请求
<-- 传统方式 ->>
// 1. 创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
// 2. 采用Call<..>接口 对 发送请求 进行封装
Call<Translation> call = request.getCall();
// 3. 发送网络请求(异步)
call.enqueue(new Callback<Translation>() {
// 请求成功时回调
@Override
public void onResponse(Call<Translation> call, Response<Translation> response) {
...
}
// 请求失败时回调
@Override
public void onFailure(Call<Translation> call, Throwable throwable) {
....
}
});
<-- RxJava 版方式 ->>
// 1. 创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
// 2. 采用Observable<...>形式 对 网络请求 进行封装
Observable<Translation> observable = request.getCall();
// 3. 发送网络请求(异步)
observable.subscribeOn(Schedulers.io()) // 在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 回到主线程 处理请求结果
.subscribe(new Observer<Translation>() {
// 发送请求后调用该复写方法(无论请求成功与否)
@Override
public void onSubscribe(Disposable d) {
...// 初始化工作
}
// 发送请求成功后调用该复写方法
@Override
public void onNext(Translation result) {
...// 对返回结果Translation类对象 进行处理
}
// 发送请求成功后,先调用onNext()再调用该复写方法
@Override
public void onComplete() {
Log.d(TAG, "请求成功");
}
// 发送请求失败后调用该复写方法
@Override
public void onError(Throwable e) {
Log.d(TAG, "请求失败");
}
});
}
实例:
ServerApi.defaultInstance() // ServerApi封装了domain 、Header、request response 解析 以及其他的一系列参数
.create(ApplyService.class) // 接口定义
.getLeaveType(new Request(new Object())) // request body
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new HylaaObserver<LeaveTypeEntity>() {
@Override
public void onNext(LeaveTypeEntity leaveTypeEntity) {
// 处理
}
});
3.1.2 嵌套请求
嵌套请求通过使用flatMap在处理完上一个请求之后返回下一个Observable
例子
public class FileUpload {
public static Observable<List<FileEntity>> uploadFiles(Context context, List<String> images) {
List<Observable<?>> uploadRequests = new ArrayList<>();
for (String image : images) {
try {
if(!image.equals("ADD")) {
uploadRequests.add(uploadFile(context, image));
}
} catch (Exception e) {
e.printStackTrace();
}
}
if(uploadRequests.size() > 0) {
return Observable.zip(
Observable.fromIterable(uploadRequests),
new Function<Object[], List<FileEntity>>() {
@Override
public List<FileEntity> apply(Object[] objects) throws Exception {
List<FileEntity> result = new ArrayList<>();
for(int i = 0;i< objects.length; i++) {
result.add((FileEntity) objects[i]);
}
return result;
}
}
);
}
return Observable.just(new ArrayList<>());
}
private static Observable<FileEntity> uploadFile(Context context, String image) {
return FileApi.defaultInstance()
.create(FileService.class)
.requestFileUpload(new FileRequestEntity(image.substring(image.lastIndexOf("/") + 1)))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.flatMap(fileUploadEntity -> {
Map<String, RequestBody> requestBodyMap = new HashMap<>();
for (String key : fileUploadEntity.getUploadParams().keySet()) {
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"),
fileUploadEntity.getUploadParams().get(key) == null ? "" : fileUploadEntity.getUploadParams().get(key));
requestBodyMap.put(key, requestBody);
}
// RequestBody fileBody = RequestBody.create(MediaType.parse("image/*"), new File(image));
requestBodyMap.put("success_action_status", RequestBody.create(MediaType.parse("multipart/form-data"), "200"));
RequestBody fileBody = RequestBody.create(MediaType.parse("image/*"), new File(FileRealPath.getFilePathFromURI(context, image)));
MultipartBody.Part file = MultipartBody.Part.createFormData("file", fileUploadEntity.getFileName(), fileBody);
return Observable.zip(
OSSFileApi.getInstance(fileUploadEntity.getUploadUrl())
.create(FileService.class)
.uploadFile(requestBodyMap, file),
Observable.just(fileUploadEntity),
new BiFunction<ResponseBody, FileUploadEntity, FileUploadEntity>() {
@Override
public FileUploadEntity apply(ResponseBody responseBody, FileUploadEntity fileUploadEntity) throws Exception {
if (responseBody != null) {
return fileUploadEntity;
} else {
throw new ServerException(10000, "Upload Failed");
}
}
}
);
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.flatMap(new Function<FileUploadEntity, ObservableSource<FileEntity>>() {
@Override
public ObservableSource<FileEntity> apply(FileUploadEntity fileUploadEntity) throws Exception {
return Observable.zip(
FileApi.defaultInstance()
.create(FileService.class)
.getFileInfo(new IdEntity(fileUploadEntity.getId()))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()),
Observable.just(fileUploadEntity),
new BiFunction<FileInfoEntity, FileUploadEntity, FileEntity>() {
@Override
public FileEntity apply(FileInfoEntity fileInfoEntity, FileUploadEntity fileUploadEntity) throws Exception {
return new FileEntity(
fileUploadEntity.getId(),
fileUploadEntity.getName(),
fileUploadEntity.getDownloadUrl(),
fileUploadEntity.getViewUrl(),
fileUploadEntity.getFileName().substring(fileUploadEntity.getFileName().lastIndexOf(".") + 1),
fileInfoEntity.getMime()
);
}
}
);
}
});
}
private byte[] getBytes(String image) {
ByteArrayOutputStream out = null;
try {
InputStream in = new FileInputStream(new File(image));
out = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = 0;
while ((i = in.read(b)) != -1) {
out.write(b, 0, b.length);
}
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return out.toByteArray();
}
}
3.1.3 重试
栗子
发送网络请求 & 通过retryWhen()进行重试
// 注:主要异常才会回调retryWhen()进行重试
observable.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
// 参数Observable<Throwable>中的泛型 = 上游操作符抛出的异常,可通过该条件来判断异常的类型
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
// 输出异常信息
Log.d(TAG, "发生异常 = "+ throwable.toString());
/**
* 需求1:根据异常类型选择是否重试
* 即,当发生的异常 = 网络异常 = IO异常 才选择重试
*/
if (throwable instanceof IOException){
Log.d(TAG, "属于IO异常,需重试" );
/**
* 需求2:限制重试次数
* 即,当已重试次数 < 设置的重试次数,才选择重试
*/
if (currentRetryCount < maxConnectCount){
// 记录重试次数
currentRetryCount++;
Log.d(TAG, "重试次数 = " + currentRetryCount);
/**
* 需求2:实现重试
* 通过返回的Observable发送的事件 = Next事件,从而使得retryWhen()重订阅,最终实现重试功能
*
* 需求3:延迟1段时间再重试
* 采用delay操作符 = 延迟一段时间发送,以实现重试间隔设置
*
* 需求4:遇到的异常越多,时间越长
* 在delay操作符的等待时间内设置 = 每重试1次,增多延迟重试时间1s
*/
// 设置等待时间
waitRetryTime = 1000 + currentRetryCount* 1000;
Log.d(TAG, "等待时间 =" + waitRetryTime);
return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);
}else{
// 若重试次数已 > 设置重试次数,则不重试
// 通过发送error来停止重试(可在观察者的onError()中获取信息)
return Observable.error(new Throwable("重试次数已超过设置次数 = " +currentRetryCount + ",即 不再重"))
}
}
// 若发生的异常不属于I/O异常,则不重试
// 通过返回的Observable发送的事件 = Error事件 实现(可在观察者的onError()中获取信息)
else{
return Observable.error(new Throwable("发生了非网络异常(非I/O异常)"));
}
}
});
}
}).subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
.subscribe(new Observer<Translation>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Translation result) {
// 接收服务器返回的数据
Log.d(TAG, "发送成功");
result.show();
}
@Override
public void onError(Throwable e) {
// 获取停止重试的信息
Log.d(TAG, e.toString());
}
@Override
public void onComplete() {
}
});
}