1、沉浸式布局
在AndroidMainfest清单文件中,默认设置的AppTheme就是3种颜色值,因此状态栏的颜色就是默认的颜色,如果想要改变其中的颜色,那么就需要自定义style;
在API 19
以上,需要设置android:windowTranslucentStatus
为false,否则系统还是会默认将状态栏设置为绿色;
从Android5.x开始,也就是API 21
以上,需要将android:statusBarColor
设置为透明,然后再设置一下属性,这样就能实现沉浸式布局。
<style name="StatusBarTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!--沉浸式状态栏-->
<!--API > 19 需要设置这个属性-->
<item name="android:windowTranslucentStatus">false</item>
<!--Android 5.x开始把颜色设置为透明,否则会默认赋值-->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/white</item>
<item name="android:fitsSystemWindows">true</item>
<!--允许Activity的转场动画-->
<item name="android:windowActivityTransitions">true</item>
</style>
2、网络请求组件开发
导入OkHttp的依赖,做二次封装,主要从Request
、OkHttpClient
、Response3
部分做Base封装。
implementation 'com.squareup.okio:okio:2.6.0'
implementation 'com.squareup.okhttp3:okhttp:4.7.0'
(1)Request
请求的种类主要分为如下几种:get、post、文件上传,因此封装一个通用 的请求类,供应用层使用。
对于get请求和post请求来说,在使用的时候,需要自己创建请求Request,对于get请求方式来说,请求体是跟url拼接在一起的,暴露在url中的,而post请求的请求头和请求体是分开的。
/**
* 对外提供get/post/文件上传等请求
*/
public class CommRequest {
/**
* 创建post请求
* @param url Http的url地址
* @param header 请求头
* @param body 请求体
* @return
*/
public static Request createPostRequest(String url, RequestParams header, RequestParams body){
//请求体(表单格式)
FormBody.Builder formBody = new FormBody.Builder();
if(body != null){
for(Map.Entry<String,String> entry : body.urlParams.entrySet()){
formBody.add(entry.getKey(),entry.getValue());
}
}
//请求头
Headers.Builder headers = new Headers.Builder();
if(header != null){
for(Map.Entry<String,String> entry : header.urlParams.entrySet()){
headers.add(entry.getKey(),entry.getValue());
}
}
//创建请求
Request request = new Request.Builder()
.url(url).headers(headers.build())
.post(formBody.build())
.build();
return request;
}
/**
* 重载的创建post请求(没有请求头)
* @param url post请求的url地址
* @param params 请求体
* @return
*/
public static Request createPostRequest(String url,RequestParams params){
return createPostRequest(url,null,params);
}
/**
* 创建get请求
* @param url get请求的url地址
* @param params 请求体
* @param header 请求头
* @return
*/
public static Request createGetRequest(String url,RequestParams params,RequestParams header){
StringBuilder getUrl = new StringBuilder(url).append("?");
if(params != null){
for(Map.Entry<String,String> entry : params.urlParams.entrySet()){
getUrl.append(entry.getKey()).append("=").append(entry.getValue());
}
}
//请求头
Headers.Builder headers = new Headers.Builder();
if(header != null){
for(Map.Entry<String,String> entry : header.urlParams.entrySet()){
headers.add(entry.getKey(),entry.getValue());
}
}
Request request = new Request.Builder()
.url(url)
.headers(headers.build())
//可不写,默认是get请求
.get()
.build();
return request;
}
/**
* 重载get请求,没有请求头
* @param url 请求的url地址
* @param params 请求的参数
* @return
*/
public static Request createGetRequest(String url,RequestParams params){
return createGetRequest(url,params);
}
}
对于文件上传,常常使用post请求方式,对于多媒体类型的文件,需要设置Content-type
或者叫做MediaType
,常见的Content-type有:
多媒体类型 | 名称 |
---|---|
text/html | HTML格式 |
text/pain | 纯文本格式 |
image/jpeg | JPEG图片格式 |
application/json | JSON格式数据 |
application/octet-stream | 二进制流数据(文件下载) |
application/x-www-form-urlencoded | 表单数据以kay/value形式发送到服务器 |
multipart/form-data | 表单上传文件格式 |
/**
* 上传多媒体数据(文件)
* @param url
* @param params 请求体
* @param header
* @return
*/
public static Request createMutiPostRequest(String url,RequestParams params,RequestParams header){
MultipartBody.Builder partBuilder = new MultipartBody.Builder();
//设置表单格式上传
partBuilder.setType(MultipartBody.FORM);
if(params != null){
for(Map.Entry<String,Object> entry : params.fileParams.entrySet()){
if(entry.getValue() instanceof File){
partBuilder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + entry.getKey() + "\""),
RequestBody.create((File) entry.getValue(),FILE_TYPE));
}
}
}
//请求头
Headers.Builder headers = new Headers.Builder();
if(header != null){
for(Map.Entry<String,String> entry : header.urlParams.entrySet()){
headers.add(entry.getKey(),entry.getValue());
}
}
Request request = new Request.Builder()
.url(url)
.headers(headers.build())
.post(partBuilder.build())
.build();
return request;
}
(2)Response
请求的获取的响应,往往是JSON字符串,因此需要对获取到的JSON字符串进行解析。
/**
* 通用的JSON数据Callback回调
*/
public class CommJsonCallback implements Callback {
//网络异常
private static final int NETWORK_ERROR = -1;
//Json数据异常
private static final int JSON_ERROR = -2;
//其他异常
private static final int OTHER_ERROR = -3;
//响应分发
private DisposeDataListener mListener;
//线程切换
private Handler mDeliveryHandler;
//Json数据转换为Class实体类
private Class<?> classBean;
public CommJsonCallback(DisposeDataHandle handle){
mListener = handle.mListener;
classBean = handle.mClass;
mDeliveryHandler = new Handler(Looper.getMainLooper());
}
@Override
public void onFailure(@NotNull Call call, @NotNull final IOException e) {
//处理响应失败的回调
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
mListener.onFailed(new OkHttpException(NETWORK_ERROR,e));
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//拿到响应的JSON数据
final String result = response.body().string();
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
//JSON数据解析
parseResponse(result);
}
});
}
private void parseResponse(String result) {
if(result == null || result.trim().equals("")){
mListener.onFailed(new OkHttpException(JSON_ERROR,"JSON数据错误"));
return;
}
if(classBean == null){
//业务层不需要解析JSON数据,直接返回JSON字符串
mListener.onSuccess(result);
}else{
//解析JSON数据
Gson gson = new Gson();
Object obj = gson.fromJson(result, classBean);
if(obj != null){
//解析成功,返回主线程
mListener.onSuccess(obj);
}else{
mListener.onFailed(new OkHttpException(JSON_ERROR,""));
}
}
}
}
也包括文件数据等多媒体响应,文件通常使用IO流解析。
public class CommFileCallback implements Callback {
//网络错误
private static final int NETWORK_ERROR = -1;
//IO错误
private static final int IO_ERROR = -2;
//错误信息
private String EMPTY_MSG = "";
private static final int FILE_PROGRESS = 0x01;
//真正处理回调的接口
private DisposeFileListener mListener;
//线程切换的Handler
private Handler mDeliveryHandler;
//下载文件的进度和路径
private String filePath;
private int mProgress;
public CommFileCallback(DisposeDataHandle handle){
this.mListener = (DisposeFileListener) handle.mListener;
this.filePath = handle.mSource;
this.mDeliveryHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case FILE_PROGRESS:
//回调当前的进度
mListener.onProgress((Integer) msg.obj);
break;
}
}
};
}
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
mListener.onFailed(new OkHttpException(NETWORK_ERROR,EMPTY_MSG));
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final File file = handleResponse(response);
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
if(file != null){
mListener.onSuccess(file);
}else{
mListener.onFailed(new OkHttpException(IO_ERROR,EMPTY_MSG));
}
}
});
}
/**
* 通过文件流来读写文件
* @param response
* @return
*/
private File handleResponse(Response response) {
InputStream is = null;
FileOutputStream fos = null;
int length;
int currentLength = 0;
int sumLength;
byte[] bytes = new byte[2048];
//读取输入流
try{
//创建文件
File file = new File(filePath);
if(!file.exists()){
file.mkdir();
}
is = response.body().byteStream();
fos = new FileOutputStream(file);
while((length = is.read(bytes) ) != -1){
fos.write(bytes,0,bytes.length);
//当前下载的进度
currentLength += length;
//总进度
sumLength = (int) (currentLength / response.body().contentLength() *100);
//将进度值发布
mDeliveryHandler.obtainMessage(FILE_PROGRESS,sumLength).sendToTarget();
}
fos.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
fos.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
对于两类的响应处理方式,有很多重复的代码,因此可以创建一个抽象基类,避免重复的代码。
public abstract class BaseCallback implements Callback {
//网络异常
private static final int NETWORK_ERROR = -1;
//Json数据异常
protected static final int JSON_ERROR = -2;
//其他异常
private static final int OTHER_ERROR = -3;
//IO错误
private static final int IO_ERROR = -4;
//错误信息
protected String EMPTY_MSG = "";
//真正处理逻辑的监听接口
protected DisposeDataListener mListener;
//handler线程切换
protected Handler mDeliveryHandler;
private static final int FILE_PROGRESS = 0x01;
//下载文件的进度和路径
private String filePath;
private int mProgress;
public BaseCallback(DisposeDataHandle handle){
this.mListener = getListener(handle);
mDeliveryHandler = new Handler(Looper.getMainLooper());
}
protected abstract DisposeDataListener getListener(DisposeDataHandle handle);
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
//发生网络错误
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
mListener.onFailed(new OkHttpException(NETWORK_ERROR,EMPTY_MSG));
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//数据解析,这个部分需要抽象解析
handleResponse(response);
}
protected abstract void handleResponse(Response response);
}
对于子类的实现类,就拿JSON响应来举例子
public class JsonCallback extends BaseCallback {
//Json数据转换为Class实体类
private Class<?> classBean;
public JsonCallback(DisposeDataHandle handle) {
super(handle);
this.classBean = handle.mClass;
}
@Override
protected DisposeDataListener getListener(DisposeDataHandle handle) {
return handle.mListener;
}
@Override
protected void handleResponse(Response response) {
//解析响应数据
try {
final String result = response.body().string();
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
//JSON数据解析
parseResponse(result);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
private void parseResponse(String result) {
if (result == null || result.trim().equals("")) {
mListener.onFailed(new OkHttpException(JSON_ERROR, "JSON数据错误"));
return;
}
if (classBean == null) {
//业务层不需要解析JSON数据,直接返回JSON字符串
mListener.onSuccess(result);
} else {
//解析JSON数据
Gson gson = new Gson();
Object obj = gson.fromJson(result, classBean);
if (obj != null) {
//解析成功,返回主线程
mListener.onSuccess(obj);
} else {
mListener.onFailed(new OkHttpException(JSON_ERROR, ""));
}
}
}
}
因为JSON需要解析实体类,因此需要在构造方法中,添加一个形参,这就是抽象的概念,对于子类来说,在继承父类的构造方法中,可以添加新的参数,作为子类的构造方法。
然后创建一个监听器,子类的监听器,视情况而定,最重要的就是解析数据,这个对于每一个子类,都需要自己实现。
(3)OkHttpClient
在使用OkHttpClient的时候,首先需要初始化,一般有以下几种处理:
//host域名验证,验证客户端请求的url是否和服务端一致,防止中间人攻击
clientBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
添加域名验证,此举是为了防止第三方攻击服务器。
当使用Https协议的时候,通常服务器会将证书信息(包含公钥)发送给客户端,客户端将生成数据传输的公钥,通过服务器端的公钥加密,发送到服务器使用私钥解密,这个过程其实是不安全的。
当服务器发送的公钥被第三方获取,将假公钥给到客户端,客户端加密后被第三方获取,将数据传输的公钥修改后,使用公钥加密给到服务器,这样双方对称加密的公钥就是被窃取,这样非常不安全。
所以客户端需要验证,证书是否来自服务器,也就是说CA证书所在服务器的host域名和请求的host域名是否一致,如果不一致那么就会回调hostnameVerifier
这个接口,验证主机名host。
/**
* 公共的OkHttpClient
*/
public class CommOkHttpClient {
//连接超时
private static final int TIME_OUT = 30;
//OkHttp对象
public static OkHttpClient okHttpClient;
//初始化OkHttpClient对象
static {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
//host域名验证,验证客户端请求的url是否和服务端一致,防止中间人攻击
clientBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
//添加请求头
clientBuilder.addInterceptor(new Interceptor() {
@NotNull
@Override
public Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request().newBuilder().
addHeader("User-Agent", "Imooc-Mobile").build();
return chain.proceed(request);
}
});
//设置超时
clientBuilder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
clientBuilder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
clientBuilder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);
//允许重定向
clientBuilder.followRedirects(true);
//创建OkHttpClient对象
okHttpClient = clientBuilder.build();
}
//创建get请求
public static Call get(Request request, DisposeDataHandle handle){
Call call = okHttpClient.newCall(request);
call.enqueue(new CommJsonCallback(handle));
return call;
}
//创建post请求
public static Call post(Request request, DisposeDataHandle handle){
Call call = okHttpClient.newCall(request);
call.enqueue(new CommJsonCallback(handle));
return call;
}
//创建get请求
public static Call downLoadFile(Request request, DisposeDataHandle handle){
Call call = okHttpClient.newCall(request);
call.enqueue(new CommFileCallback(handle));
return call;
}
}
在使用时,既可以简化api结构
public static void postRequest(String url, RequestParams params,
DisposeDataListener listener,Class<?> aClass){
CommOkHttpClient.post(CommRequest.createPostRequest(url,params),
new DisposeDataHandle(listener,aClass));
}
public static void LoginRequest(DisposeDataListener listener,Class<?> aClass){
RequestParams params = new RequestParams();
params.put("admin","123456");
postRequest(HttpConstants.LOGIN,params,listener,aClass);
}