From 5f50bd6ea5d5bdb7b8ea4d9e9a5851067b9aec1b Mon Sep 17 00:00:00 2001
From: Administrator <123>
Date: Tue, 09 Nov 2021 02:22:38 +0000
Subject: [PATCH] 网络请求框架

---
 app/src/main/java/com/duqing/missions/retrofit/api/LoginApiCenter.java                         |   21 
 app/src/main/res/anim/slide_in_left.xml                                                        |    7 
 app/src/main/res/navigation/mobile_navigation.xml                                              |   30 
 app/src/main/java/com/duqing/missions/retrofit/converter/GsonRequestBodyConverter.java         |   44 +
 app/src/main/res/values/strings.xml                                                            |    1 
 app/src/main/java/com/duqing/missions/retrofit/Interceptor/EncryptInterceptor.java             |  104 +++
 app/src/main/java/com/duqing/missions/base/model/ItemViewModel.java                            |   16 
 app/src/main/AndroidManifest.xml                                                               |   21 
 app/src/main/java/com/duqing/missions/retrofit/utils/HttpPrintUtils.java                       |  210 ++++++
 app/src/main/res/xml/network_security_config.xml                                               |    4 
 app/src/main/java/com/duqing/missions/util/NetWorkUtils.java                                   |  212 ++++++
 app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonResponseBodyConverter.java |   95 ++
 app/src/main/java/com/duqing/missions/retrofit/NetWorkCost.java                                |   14 
 app/src/main/java/com/duqing/missions/retrofit/AndroidScheduler.java                           |   39 +
 app/src/main/java/com/duqing/missions/retrofit/NetWorkListenear.java                           |  173 +++++
 app/src/main/java/com/duqing/missions/retrofit/api/CommonApiCenter.java                        |   65 +
 app/src/main/java/com/duqing/missions/retrofit/RetrofitUtils.java                              |  126 +++
 app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java                         |   50 +
 app/src/main/java/com/duqing/missions/retrofit/utils/RSAUtils.java                             |  178 +++++
 app/src/main/java/com/duqing/missions/util/GsonUtils.java                                      |  256 +++++++
 app/src/main/java/com/duqing/missions/retrofit/Interceptor/HttpLoggingInterceptor.java         |  277 ++++++++
 app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonConverterFactory.java      |   59 +
 app/build.gradle                                                                               |   16 
 23 files changed, 2,012 insertions(+), 6 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 0b61201..6fe0e3c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,8 +16,18 @@
     }
 
     buildTypes {
+        debug{
+            minifyEnabled false
+            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"'
+            buildConfigField 'String','ENVIRONMENT','"release"'
+            resValue "string", "app_name", "趣为帮扶测试"
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
         release {
             minifyEnabled false
+            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"'
+            buildConfigField 'String','ENVIRONMENT','"release"'
+            resValue "string", "app_name", "趣为帮扶"
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
         }
     }
@@ -58,10 +68,10 @@
     implementation 'com.github.d-max:spots-dialog:1.1@aar'//loading view
     implementation 'com.google.code.gson:gson:2.8.6'
     implementation 'com.ansen.http:okhttpencapsulation:1.0.1'//版本更新下载
-    implementation 'com.squareup.okhttp3:okhttp:3.12.1'
-    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
+    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
+    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
     //RXjava和retrofit结合
-    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
+    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
     implementation 'com.permissionx.guolindev:permissionx:1.2.2'    //权限依赖让你推广你就发群里?没有别的群了?
     implementation 'com.github.bumptech.glide:glide:4.12.0'
     annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 066f60b..c95a50d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,26 @@
     tools:ignore="ProtectedPermissions"
     package="com.duqing.missions" >
 
+
+    <uses-permission android:name="android.permission.CALL_PHONE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.GET_TASKS" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <!--写入SD卡的权限:如果你希望保存相机拍照后的照片-->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <!--读取SD卡的权限:打开相册选取图片所必须的权限-->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!--android Q 安装权限-->
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <application
         android:name=".MyApplication"
@@ -12,6 +32,7 @@
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
+        android:networkSecurityConfig="@xml/network_security_config"
         android:theme="@style/Theme.Missions" >
         <activity
             android:name=".ui.main.MainActivity"
diff --git a/app/src/main/java/com/duqing/missions/base/model/ItemViewModel.java b/app/src/main/java/com/duqing/missions/base/model/ItemViewModel.java
new file mode 100644
index 0000000..8107563
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/base/model/ItemViewModel.java
@@ -0,0 +1,16 @@
+package com.duqing.missions.base.model;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+/**
+ * Created by Administrator on 2021/11/5 0005.
+ */
+public class ItemViewModel<T> extends ViewModel {
+
+    MutableLiveData<T> liveData = new MutableLiveData<>();
+
+    public MutableLiveData<T> getLiveData() {
+        return liveData;
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/AndroidScheduler.java b/app/src/main/java/com/duqing/missions/retrofit/AndroidScheduler.java
new file mode 100644
index 0000000..3760cc8
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/AndroidScheduler.java
@@ -0,0 +1,39 @@
+package com.duqing.missions.retrofit;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+import io.reactivex.Scheduler;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Created by Administrator on 2021/11/8 0008.
+ */
+public class AndroidScheduler implements Executor {
+    private static AndroidScheduler instance;
+
+    private final Scheduler mMainScheduler;
+    private final Handler mHandler;
+
+    private AndroidScheduler() {
+        mHandler = new Handler(Looper.myLooper());
+        mMainScheduler = Schedulers.from(this);
+    }
+
+    public static synchronized Scheduler mainThread() {
+        if (instance == null) {
+            instance = new AndroidScheduler();
+        }
+        return instance.mMainScheduler;
+    }
+
+    @Override
+    public void execute(@NonNull Runnable command) {
+        mHandler.post(command);
+    }
+
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/Interceptor/EncryptInterceptor.java b/app/src/main/java/com/duqing/missions/retrofit/Interceptor/EncryptInterceptor.java
new file mode 100644
index 0000000..6aa7ba2
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/Interceptor/EncryptInterceptor.java
@@ -0,0 +1,104 @@
+package com.duqing.missions.retrofit.Interceptor;
+
+
+import com.duqing.missions.retrofit.utils.RSAUtils;
+
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+import okhttp3.FormBody;
+import okhttp3.Headers;
+import okhttp3.Interceptor;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okio.Buffer;
+
+/**
+ * My father is Object, ites purpose of     加密拦截器
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-10-8.
+ */
+
+public class EncryptInterceptor implements Interceptor {
+
+    protected static final Charset UTF8 = Charset.forName("UTF-8");
+    private final String ENCRYPT = "encrypt";
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        return chain.proceed(encryptRequest(chain.request()));
+    }
+
+
+    //加密
+    protected Request encryptRequest(Request request) throws IOException {
+        Headers headers = request.headers();
+        RequestBody requestBody = request.body();
+        Request.Builder builder = request.newBuilder();
+        for(int i = 0 ; i < headers.size() ; i ++){
+            builder.addHeader(headers.name(i),headers.value(i));
+        }
+        if(requestBody != null){
+            Charset charset = UTF8;
+            MediaType contentType = requestBody.contentType();
+            if (contentType != null) {
+                charset = contentType.charset(UTF8);
+            }
+            HashMap param = new HashMap();
+            if(requestBody instanceof MultipartBody){
+                MultipartBody body = (MultipartBody) requestBody;
+                for(MultipartBody.Part part:body.parts()){
+                    Buffer buffer1 = new Buffer();
+                    part.body().writeTo(buffer1);
+                    String str=buffer1.readString(charset).replaceAll("%(?![0-9a-fA-F]{2})","%25");
+                    param.put(part.headers().get(part.headers().name(0)), URLDecoder.decode(str, "UTF-8"));
+                }
+                MultipartBody.Builder mbuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
+                mbuilder.addFormDataPart(ENCRYPT,encryptParam(param));
+                builder.post(mbuilder.build());
+            }else if(requestBody instanceof FormBody){
+                FormBody body = (FormBody) requestBody;
+                for(int i = 0 ; i < body.size() ; i ++ ){
+                    param.put(body.name(i),body.value(i));
+                }
+                FormBody.Builder formBuild = new FormBody.Builder();
+                formBuild.add(ENCRYPT,encryptParam(param));
+                builder.post(formBuild.build());
+            }else{
+                Buffer buffer = new Buffer();
+                requestBody.writeTo(buffer);
+                String str = buffer.readString(charset);
+                String encrypt = encryptJson(str);
+                param.put(ENCRYPT,encrypt);
+                builder.post(RequestBody.create(MediaType.parse("application/json;charset=utf-8"), new JSONObject(param).toString()));
+            }
+        }
+        return builder.build();
+    }
+
+
+    /**
+     * 加密传递的参数
+     * @param params
+     * @return
+     */
+    public static String encryptParam(Map<String, Object> params){
+        return encryptJson(new JSONObject(params).toString());
+    }
+    public static String encryptJson(String json){
+        try {
+            return RSAUtils.encrypt(json,RSAUtils.getPublicKey(RSAUtils.PUBLIC_KEY));
+        }catch (Exception e){
+            e.printStackTrace();
+            return e.getMessage();
+        }
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/Interceptor/HttpLoggingInterceptor.java b/app/src/main/java/com/duqing/missions/retrofit/Interceptor/HttpLoggingInterceptor.java
new file mode 100644
index 0000000..9d0f496
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/Interceptor/HttpLoggingInterceptor.java
@@ -0,0 +1,277 @@
+package com.duqing.missions.retrofit.Interceptor;
+
+import android.util.Log;
+
+import com.duqing.missions.retrofit.NetWorkCost;
+import com.duqing.missions.retrofit.NetWorkListenear;
+import com.duqing.missions.retrofit.utils.HttpPrintUtils;
+import com.duqing.missions.util.GsonUtils;
+
+import org.json.JSONObject;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import okhttp3.FormBody;
+import okhttp3.Headers;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okhttp3.internal.http.HttpHeaders;
+import okio.Buffer;
+import okio.BufferedSource;
+
+/**
+ * My father is Object, ites purpose of     log打印
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-10-21.
+ */
+
+public class HttpLoggingInterceptor extends EncryptInterceptor {
+
+    final String TAG = "HttpLogging";
+
+    public HttpLoggingInterceptor() {
+    }
+
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+
+        Request request = chain.request();
+        int hashCode = request.hashCode();
+        ArrayList<String> logArrays = getRequestLog(request);
+        int position = logArrays.size() +2;
+        Response response;
+        try {
+            request = encryptRequest(request);//加密
+            response = chain.proceed(request);
+            logArrays.addAll(getResponseLog(response));
+            Log.d(TAG,"hashcode:"+hashCode);
+            NetWorkCost netWorkCost = NetWorkListenear.workCostMap.get(hashCode);
+            String cost = String.format("dns:%s,secure:%s,connect:%s,requestH:%s,requestB:%s,responseH:%s,responseB:%s",  convertTimes(netWorkCost.dns) ,convertTimes(netWorkCost.secure) , convertTimes(netWorkCost.connect),convertTimes(netWorkCost.requestHeader),convertTimes(netWorkCost.requestBody) ,convertTimes(netWorkCost.resposeHeader),convertTimes(netWorkCost.resposeBody)    );
+            logArrays.add(position,"<-- costtimes : "+convertTimes(netWorkCost.total)+" (" +cost + ')');
+            NetWorkListenear.workCostMap.remove(hashCode);
+            new Thread(){
+                @Override
+                public void run() {
+                    HttpPrintUtils.getInstance().printLog(logArrays, true);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应
+                }
+            }.start();
+        } catch (Exception e) {
+            logArrays.add("<-- response url:" + URLDecoder.decode(request.url().toString(), "UTF-8"));
+            NetWorkCost netWorkCost = NetWorkListenear.workCostMap.get(hashCode);
+            String cost = String.format("dns:%s,secure:%s,connect:%s,requestH:%s,requestB:%s,responseH:%s,responseB:%s",  convertTimes(netWorkCost.dns) ,convertTimes(netWorkCost.secure) , convertTimes(netWorkCost.connect),convertTimes(netWorkCost.requestHeader),convertTimes(netWorkCost.requestBody) ,convertTimes(netWorkCost.resposeHeader),convertTimes(netWorkCost.resposeBody)    );
+            logArrays.add("<-- costtimes : "+convertTimes(netWorkCost.total)+" (" +cost + ')');
+            logArrays.add("<-- response failed " + e.getLocalizedMessage());
+            logArrays.add("<--                 " + e.toString());
+            new Thread(){
+                @Override
+                public void run() {
+                    HttpPrintUtils.getInstance().printLog(logArrays, false);//线程安全方法,需在新线程执行,避免阻塞当前线程,导致程序无响应
+                }
+            }.start();
+            throw e;//抛出异常,用于请求接收信息
+        }
+        return response;
+    }
+
+    private ArrayList<String> getRequestLog(Request request) throws IOException {
+        RequestBody requestBody = request.body();
+        ArrayList<String> logArrays = new ArrayList<>();
+        String requestStartMessage = "--> " + request.method() + ' ' + URLDecoder.decode(request.url().toString() ,"UTF-8")+ ' ' ;
+        if ( requestBody != null) {
+            requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
+        }
+        logArrays.add(requestStartMessage);
+        Headers headers = request.headers();
+        logArrays.add("---------->REQUEST HEADER<----------");
+        for (int i = 0, count = headers.size(); i < count; i++) {
+            logArrays.add(headers.name(i) + ": " + headers.value(i));
+        }
+        if (requestBody == null) {
+            logArrays.add("--> END " + request.method());
+        } else if (bodyEncoded(request.headers())) {
+            logArrays.add("--> END " + request.method() + " (encoded body omitted)");
+        } else {
+
+            Charset charset = UTF8;
+            MediaType contentType = requestBody.contentType();
+            if (contentType != null) {
+                charset = contentType.charset(UTF8);
+            }
+            HashMap param = new HashMap();
+            if(requestBody instanceof MultipartBody){
+                logArrays.add("---------->REQUEST BODY[MultipartBody]<----------");
+                MultipartBody body = (MultipartBody) requestBody;
+                for(MultipartBody.Part part:body.parts()){
+                    Buffer buffer1 = new Buffer();
+                    part.body().writeTo(buffer1);
+                    String str=buffer1.readString(charset).replaceAll("%(?![0-9a-fA-F]{2})","%25");
+                    param.put(part.headers().get(part.headers().name(0)),URLDecoder.decode(str, "UTF-8"));
+                }
+                logArrays.add(GsonUtils.retractJson(new JSONObject(param).toString()));
+            }else if(requestBody instanceof FormBody){
+                logArrays.add("---------->REQUEST BODY[FormBody]<----------");
+                FormBody body = (FormBody) requestBody;
+                for(int i = 0 ; i < body.size() ; i ++ ){
+                    param.put(body.name(i),body.value(i));
+                }
+                logArrays.add(GsonUtils.retractJson(new JSONObject(param).toString()));
+            }else{
+                Buffer buffer = new Buffer();
+                requestBody.writeTo(buffer);
+                logArrays.add("---------->REQUEST BODY<----------");
+                String str = buffer.readString(charset);
+                try{
+                    logArrays.add(GsonUtils.retractJson(URLDecoder.decode(str, "UTF-8")));
+                }catch (Exception e){
+                    logArrays.add(str);
+                }
+            }
+            logArrays.add("--> END " + request.method() + " " + contentType + " ( "
+                    + requestBody.contentLength() + "-byte body )");
+        }
+        return logArrays;
+
+    }
+
+
+    private ArrayList<String> getResponseLog(Response response) throws IOException {
+        ArrayList<String> logArrays = new ArrayList<>();
+        ResponseBody responseBody = response.body();
+        long contentLength = responseBody.contentLength();
+        String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
+        logArrays.add("<-- response code:" + response.code() + " message:" + response.message()+" contentlength:"+bodySize  );
+        logArrays.add("<-- response url:"+URLDecoder.decode(response.request().url().toString(),"UTF-8")  );
+
+        if ( !HttpHeaders.hasBody(response)) {
+            logArrays.add("<-- END HTTP");
+        } else if (bodyEncoded(response.headers())) {
+            logArrays.add("<-- END HTTP (encoded body omitted)");
+        } else {
+            BufferedSource source = responseBody.source();
+            source.request(Long.MAX_VALUE); // Buffer the entire body.
+            Buffer buffer = source.buffer();
+
+            Charset charset = UTF8;
+            MediaType contentType = responseBody.contentType();
+            if (contentType != null) {
+                charset = contentType.charset(UTF8);
+            }
+
+            if (isPlaintext(buffer)) {
+                logArrays.add("---------->RESPONSE BODY<----------");
+                if (contentLength != 0) {
+                    logArrays.add(retractJson(buffer.clone().readString(charset)));
+                }
+
+                logArrays.add("<-- END HTTP (" + buffer.size() + "-byte body)");
+            }
+
+        }
+        return logArrays;
+    }
+
+    /**
+     * 字符串缩进
+     * @param json
+     * @return
+     */
+    private String retractJson(String json){
+        int level = 0 ;
+        StringBuffer jsonForMatStr = new StringBuffer();
+        for(int index=0;index<json.length();index++)//将字符串中的字符逐个按行输出
+        {
+            //获取s中的每个字符
+            char c = json.charAt(index);
+            //          System.out.println(s.charAt(index));
+
+            //level大于0并且jsonForMatStr中的最后一个字符为\n,jsonForMatStr加入\t
+            if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) {
+                jsonForMatStr.append(getLevelStr(level));
+                //                System.out.println("123"+jsonForMatStr);
+            }
+            //遇到"{"和"["要增加空格和换行,遇到"}"和"]"要减少空格,以对应,遇到","要换行
+            switch (c) {
+                case '{':
+                case '[':
+                    jsonForMatStr.append(c + "\n");
+                    level++;
+                    break;
+                case ',':
+                    jsonForMatStr.append(c + "\n");
+                    break;
+                case '}':
+                case ']':
+                    jsonForMatStr.append("\n");
+                    level--;
+                    jsonForMatStr.append(getLevelStr(level));
+                    jsonForMatStr.append(c);
+                    break;
+                default:
+                    jsonForMatStr.append(c);
+                    break;
+            }
+        }
+        return jsonForMatStr.toString();
+    }
+
+    private  String getLevelStr(int level) {
+        StringBuffer levelStr = new StringBuffer();
+        for (int levelI = 0; levelI < level; levelI++) {
+            levelStr.append("\t");//\t或空格
+        }
+        return levelStr.toString();
+    }
+
+    /**
+     * Returns true if the body in question probably contains human readable text. Uses a small sample
+     * of code points to detect unicode control characters commonly used in binary file signatures.
+     */
+    static boolean isPlaintext(Buffer buffer) {
+        try {
+            Buffer prefix = new Buffer();
+            long byteCount = buffer.size() < 64 ? buffer.size() : 64;
+            buffer.copyTo(prefix, 0, byteCount);
+            for (int i = 0; i < 16; i++) {
+                if (prefix.exhausted()) {
+                    break;
+                }
+                int codePoint = prefix.readUtf8CodePoint();
+                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
+                    return false;
+                }
+            }
+            return true;
+        } catch (EOFException e) {
+            return false; // Truncated UTF-8 sequence.
+        }
+    }
+
+    private boolean bodyEncoded(Headers headers) {
+        String contentEncoding = headers.get("Content-Encoding");
+        return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
+    }
+
+    private String convertTimes(long ms){
+        String m = null,s=null;
+        final int utilS = 1000;
+        final int utilM = utilS*60;
+        if(ms/utilM>0){
+            m = ms/utilM+"m";
+        }
+        if(ms%utilM/utilS>0){
+            s = ms%utilM/utilS+"s";
+        }
+        return (m!=null?m:"")+(s!=null?s:"")+ms%utilS+"ms";
+    }
+
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/NetWorkCost.java b/app/src/main/java/com/duqing/missions/retrofit/NetWorkCost.java
new file mode 100644
index 0000000..53bb716
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/NetWorkCost.java
@@ -0,0 +1,14 @@
+package com.duqing.missions.retrofit;
+
+
+/**
+ * My father is Object, ites purpose of 网络消耗
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-3-12.
+ */
+
+public class NetWorkCost {
+
+    //网络消耗时间
+    public long dns,connect,total,secure,requestHeader,requestBody,resposeHeader,resposeBody;
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/NetWorkListenear.java b/app/src/main/java/com/duqing/missions/retrofit/NetWorkListenear.java
new file mode 100644
index 0000000..106346a
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/NetWorkListenear.java
@@ -0,0 +1,173 @@
+package com.duqing.missions.retrofit;
+
+import androidx.annotation.Nullable;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.nio.charset.Charset;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import okhttp3.Call;
+import okhttp3.EventListener;
+import okhttp3.Handshake;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * My father is Object, ites purpose of     接口请求耗时监听
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-9.
+ */
+
+public class NetWorkListenear extends EventListener {
+
+    private static final String TAG = "NetworkEventListener";
+    final Charset UTF8 = Charset.forName("UTF-8");
+    public static Map<Integer, NetWorkCost> workCostMap = new HashMap<>();
+
+    public static Factory get(){
+        Factory factory = new Factory() {
+            @NotNull
+            @Override
+            public EventListener create(@NotNull Call call) {
+                return new NetWorkListenear();
+            }
+        };
+        return factory;
+    }
+
+    @Override
+    public void callStart(@NotNull Call call) {
+        super.callStart(call);
+        //mRequestId = mNextRequestId.getAndIncrement() + "";
+        //getAndAdd,在多线程下使用cas保证原子性
+        NetWorkCost netWorkCost = new NetWorkCost();
+        netWorkCost.total = new Date().getTime();
+        workCostMap.put(call.request().hashCode(),netWorkCost);
+    }
+
+    @Override
+    public void dnsStart(@NotNull Call call, @NotNull String domainName) {
+        super.dnsStart(call, domainName);
+        //Log.d(TAG, "dnsStart");
+        workCostMap.get(call.request().hashCode()).dns = new Date().getTime();
+    }
+
+    @Override
+    public void dnsEnd(@NotNull Call call, @NotNull String domainName, @NotNull List<InetAddress> inetAddressList) {
+        super.dnsEnd(call, domainName, inetAddressList);
+        //Log.d(TAG, "dnsEnd");
+        workCostMap.get(call.request().hashCode()).dns = new Date().getTime()  - workCostMap.get(call.request().hashCode()).dns;
+    }
+
+    @Override
+    public void connectStart(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy) {
+        super.connectStart(call, inetSocketAddress, proxy);
+        //Log.d(TAG, "connectStart");
+        workCostMap.get(call.request().hashCode()).connect = new Date().getTime();
+    }
+
+    @Override
+    public void secureConnectStart(@NotNull Call call) {
+        super.secureConnectStart(call);
+        //Log.d(TAG, "secureConnectStart");
+        workCostMap.get(call.request().hashCode()).secure = new Date().getTime();
+    }
+
+    @Override
+    public void secureConnectEnd(@NotNull Call call, @Nullable Handshake handshake) {
+        super.secureConnectEnd(call, handshake);
+        //Log.d(TAG, "secureConnectEnd");
+        workCostMap.get(call.request().hashCode()).secure = new Date().getTime() - workCostMap.get(call.request().hashCode()).secure;
+    }
+
+    @Override
+    public void connectEnd(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress,
+                           @NotNull Proxy proxy, @Nullable Protocol protocol) {
+        super.connectEnd(call, inetSocketAddress, proxy, protocol);
+        //Log.d(TAG, "connectEnd");
+        workCostMap.get(call.request().hashCode()).connect = new Date().getTime() - workCostMap.get(call.request().hashCode()).connect;
+    }
+
+    @Override
+    public void connectFailed(@NotNull Call call, @NotNull InetSocketAddress inetSocketAddress, @NotNull Proxy proxy, @Nullable Protocol protocol, @NotNull IOException ioe) {
+        super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
+        workCostMap.get(call.request().hashCode()).connect = new Date().getTime() - workCostMap.get(call.request().hashCode()).connect;
+        workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total;
+        //Log.d(TAG, "connectFailed");
+    }
+
+    @Override
+    public void requestHeadersStart(@NotNull Call call) {
+        super.requestHeadersStart(call);
+        //Log.d(TAG, "requestHeadersStart");
+        workCostMap.get(call.request().hashCode()).requestHeader = new Date().getTime();
+    }
+
+    @Override
+    public void requestHeadersEnd(@NotNull Call call, @NotNull Request request) {
+        super.requestHeadersEnd(call, request);
+        //Log.d(TAG, "requestHeadersEnd");
+        workCostMap.get(call.request().hashCode()).requestHeader = new Date().getTime() - workCostMap.get(call.request().hashCode()).requestHeader;
+    }
+
+    @Override
+    public void requestBodyStart(@NotNull Call call) {
+        super.requestBodyStart(call);
+        //Log.d(TAG, "requestBodyStart");
+        workCostMap.get(call.request().hashCode()).requestBody = new Date().getTime();
+    }
+
+    @Override
+    public void requestBodyEnd(@NotNull Call call, long byteCount) {
+        super.requestBodyEnd(call, byteCount);
+        //Log.d(TAG, "requestBodyEnd");
+        workCostMap.get(call.request().hashCode()).requestBody = new Date().getTime() - workCostMap.get(call.request().hashCode()).requestBody;
+    }
+
+    @Override
+    public void responseHeadersStart(@NotNull Call call) {
+        super.responseHeadersStart(call);
+        //Log.d(TAG, "responseHeadersStart");
+        workCostMap.get(call.request().hashCode()).resposeHeader = new Date().getTime();
+    }
+
+    @Override
+    public void responseHeadersEnd(@NotNull Call call, @NotNull Response response) {
+        super.responseHeadersEnd(call, response);
+        //Log.d(TAG, "responseHeadersEnd");
+        workCostMap.get(call.request().hashCode()).resposeHeader = new Date().getTime() - workCostMap.get(call.request().hashCode()).resposeHeader;
+    }
+
+    @Override
+    public void responseBodyStart(@NotNull Call call) {
+        super.responseBodyStart(call);
+        //Log.d(TAG, "responseBodyStart");
+        workCostMap.get(call.request().hashCode()).resposeBody = new Date().getTime();
+    }
+
+    @Override
+    public void responseBodyEnd(@NotNull Call call, long byteCount) {
+        super.responseBodyEnd(call, byteCount);
+        //Log.d(TAG, "responseBodyEnd");
+        workCostMap.get(call.request().hashCode()).resposeBody = new Date().getTime() - workCostMap.get(call.request().hashCode()).resposeBody;
+        workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total;
+    }
+
+
+    @Override
+    public void callFailed(@NotNull Call call, @NotNull IOException ioe) {
+        super.callFailed(call, ioe);
+        workCostMap.get(call.request().hashCode()).total = new Date().getTime() - workCostMap.get(call.request().hashCode()).total;
+        //Log.d(TAG, "callFailed");
+    }
+
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/RetrofitUtils.java b/app/src/main/java/com/duqing/missions/retrofit/RetrofitUtils.java
new file mode 100644
index 0000000..1260308
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/RetrofitUtils.java
@@ -0,0 +1,126 @@
+package com.duqing.missions.retrofit;
+
+
+import com.duqing.missions.BuildConfig;
+import com.duqing.missions.retrofit.Interceptor.EncryptInterceptor;
+import com.duqing.missions.retrofit.Interceptor.HttpLoggingInterceptor;
+import com.duqing.missions.retrofit.api.CommonApiCenter;
+import com.duqing.missions.retrofit.converter.DecryptGsonConverterFactory;
+
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
+
+/**
+ * My father is Object, ites purpose of
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-9.
+ */
+
+public class RetrofitUtils {
+    public static String HOST_IP_ADDR;
+    static RetrofitUtils instance;
+    Retrofit retrofit/*log输出,驼峰转换*/,unHumpRetrofit/*log输出,不强制驼峰转换*/,
+            unLogRetrofit/*log不输出,驼峰转换*/,unLogHumpRetorfit/*log不输出,不强制驼峰转换*/;
+    CommonApiCenter commonApi;//常用接口
+
+    OkHttpClient.Builder builder = new OkHttpClient.Builder()
+            .addInterceptor(new EncryptInterceptor());
+    OkHttpClient.Builder logBuilder = new OkHttpClient.Builder()
+            .addInterceptor(new HttpLoggingInterceptor());//log打印拦截器
+
+    public static RetrofitUtils getInstance() {
+        if(instance == null){
+            instance = new RetrofitUtils();
+        }
+        return instance;
+    }
+
+    /**
+     * log输出,gson驼峰转换
+     * @return
+     */
+    public <T> T getRetrofit(Class<T> clas) {
+        if(retrofit == null){
+            retrofit = getRetrofit(getOkHttpClient(logBuilder),
+                    new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create(true))) ;
+        }
+        if(!BuildConfig.DEBUG){//正式版 不打印log
+            return getUnLogRetrofit(clas);
+        }
+        return retrofit.create(clas);
+    }
+
+    /**
+     * log输出,gson不转换驼峰
+     * @return
+     */
+    public <T> T getUnHumpRetrofit(Class<T> clas) {
+        if(unHumpRetrofit == null){
+            unHumpRetrofit = getRetrofit(getOkHttpClient(logBuilder),
+                    new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create())) ;
+        }
+        if(!BuildConfig.DEBUG){//正式版 不打印log
+            return getUnLogHumpRetorfit(clas);
+        }
+        return unHumpRetrofit.create(clas);
+    }
+
+    /**
+     * log不输出,gson驼峰转换
+     * @return
+     */
+    public <T> T  getUnLogRetrofit(Class<T> clas) {
+        if(unLogRetrofit == null){
+            unLogRetrofit = getRetrofit(getOkHttpClient(builder),
+                    new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create(true))) ;
+        }
+        return unLogRetrofit.create(clas);
+    }
+
+    /**
+     * log不输出,gson不转换驼峰
+     * @return
+     */
+    public <T> T getUnLogHumpRetorfit(Class<T> clas) {
+        if(unLogHumpRetorfit == null){
+            unLogHumpRetorfit = getRetrofit(getOkHttpClient(builder),
+                    new Retrofit.Builder().addConverterFactory(DecryptGsonConverterFactory.create())) ;
+        }
+        return unLogHumpRetorfit.create(clas);
+    }
+
+    private OkHttpClient getOkHttpClient(OkHttpClient.Builder builder){
+        return builder.connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
+                .readTimeout(30, TimeUnit.SECONDS)//设置读取超时时间
+                .protocols(Collections.singletonList(Protocol.HTTP_1_1))
+                .eventListenerFactory(NetWorkListenear.get())
+                .build();
+    }
+
+    private Retrofit getRetrofit(OkHttpClient client,Retrofit.Builder builder){
+        return builder
+                //设置OKHttpClient
+                .client(client)
+                //设置baseUrl,注意,baseUrl必须后缀"/"
+                .baseUrl(BuildConfig.ENVIRONMENT.equals("develop")?HOST_IP_ADDR:BuildConfig.HOST_IP_ADDR)
+                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+                .build();
+    }
+
+
+    /**
+     * 常用接口
+     * @return
+     */
+    public CommonApiCenter getCommonApi(){
+        if(commonApi == null){
+            commonApi  = getRetrofit(CommonApiCenter.class);
+        }
+        return commonApi;
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/api/CommonApiCenter.java b/app/src/main/java/com/duqing/missions/retrofit/api/CommonApiCenter.java
new file mode 100644
index 0000000..93fc000
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/api/CommonApiCenter.java
@@ -0,0 +1,65 @@
+package com.duqing.missions.retrofit.api;
+
+
+import com.duqing.missions.data.ApkUpGradeResult;
+
+import java.util.Map;
+
+import io.reactivex.Observable;
+import retrofit2.http.Field;
+import retrofit2.http.FieldMap;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Query;
+import retrofit2.http.QueryMap;
+import retrofit2.http.Url;
+
+/**
+ * My father is Object, ites purpose of     常用接口
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-21.
+ */
+
+public interface CommonApiCenter {
+
+    @GET
+    Observable<Object> getData(@Url String url, @QueryMap Map<String,String> param);
+
+    @FormUrlEncoded
+    @POST
+    Observable<Object> postData(@Url String url, @FieldMap Map<String,String> param);
+
+
+    /**
+     * 分页数据
+     * @param url       请求地址
+     * @param pageNum   页数
+     * @param pageSize  每页数量
+     * @param param     其他参数
+     * @return
+     */
+    @GET
+    Observable<Object> getPageData(@Url String url, @Query("pageNum") int pageNum, @Query("pageSize") int pageSize, @QueryMap Map<String,String> param);
+
+    /**
+     * 分页数据
+     * @param url       请求地址
+     * @param pageNum   页数
+     * @param pageSize  每页数量
+     * @param param     其他参数
+     * @return
+     */
+    @FormUrlEncoded
+    @POST
+    Observable<Object> postPageData(@Url String url, @Field("pageNum") int pageNum, @Field("pageSize") int pageSize, @FieldMap Map<String,String> param);
+
+    /**
+     * app更新
+     * @return
+     */
+    @GET("system/appupgrade/tourist/get/2")
+    Observable<ApkUpGradeResult> getAppUpdate();
+
+
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/api/LoginApiCenter.java b/app/src/main/java/com/duqing/missions/retrofit/api/LoginApiCenter.java
new file mode 100644
index 0000000..f061e10
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/api/LoginApiCenter.java
@@ -0,0 +1,21 @@
+package com.duqing.missions.retrofit.api;
+
+import io.reactivex.Observable;
+import retrofit2.Call;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.POST;
+
+/**
+ * Created by Administrator on 2021/11/8 0008.
+ */
+public interface LoginApiCenter {
+
+    @FormUrlEncoded
+    @POST("api/v1/login")
+    Observable<Object> login(@Field("paramString") String str);
+
+    @FormUrlEncoded
+    @POST("api/v1/login")
+    Call<Object> loginCall(@Field("paramString") String str);
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonConverterFactory.java b/app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonConverterFactory.java
new file mode 100644
index 0000000..497543e
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonConverterFactory.java
@@ -0,0 +1,59 @@
+package com.duqing.missions.retrofit.converter;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+import retrofit2.Converter;
+import retrofit2.Retrofit;
+
+/**
+ * My father is Object, ites purpose of     解密gson转换
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-22.
+ */
+
+public class DecryptGsonConverterFactory extends Converter.Factory {
+
+    public static DecryptGsonConverterFactory create() {
+        return create(new Gson(),false);
+    }
+
+    public static DecryptGsonConverterFactory create(boolean transHump) {
+        return create(new Gson(),transHump);
+    }
+
+
+    public static DecryptGsonConverterFactory create(Gson gson,boolean transHump) {
+        return new DecryptGsonConverterFactory(gson,transHump);
+    }
+
+    private final Gson gson;
+
+    private final boolean transHump;
+
+    public DecryptGsonConverterFactory(Gson gson, boolean transHump) {
+        if (gson == null) throw new NullPointerException("gson == null");
+        this.gson = gson;
+        this.transHump = transHump;
+    }
+
+    @Override
+    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
+                                                            Retrofit retrofit) {
+        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
+        return new DecryptGsonResponseBodyConverter<>(gson, adapter,transHump);
+    }
+
+    @Override
+    public Converter<?, RequestBody> requestBodyConverter(Type type,
+                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
+        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
+        return new GsonRequestBodyConverter<>(gson, adapter);
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonResponseBodyConverter.java b/app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonResponseBodyConverter.java
new file mode 100644
index 0000000..e8b708a
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/converter/DecryptGsonResponseBodyConverter.java
@@ -0,0 +1,95 @@
+package com.duqing.missions.retrofit.converter;
+
+import android.util.Log;
+
+import com.duqing.missions.data.BaseApiResult;
+import com.duqing.missions.retrofit.utils.RSAUtils;
+import com.duqing.missions.util.GsonUtils;
+import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+import okhttp3.ResponseBody;
+import retrofit2.Converter;
+
+/**
+ * My father is Object, ites purpose of     解密gson转换器
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-22.
+ */
+
+public class DecryptGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
+    private final Gson gson;
+    private final TypeAdapter<T> adapter;
+    private final Charset UTF_8 = Charset.forName("UTF-8");
+    private final boolean transHump;//驼峰转换
+    private final String ENCRYPT = "encrypt";
+
+    public DecryptGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter, boolean transHump) {
+        this.gson = gson;
+        this.adapter = adapter;
+        this.transHump = transHump;
+    }
+
+    @Override
+    public T convert(ResponseBody value) throws IOException {
+        String response = null;
+        try {
+            String val = new String(value.bytes(),UTF_8);
+            response = decryptJsonStr(val);//解密
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+            e.printStackTrace();
+            BaseApiResult apiResult = new BaseApiResult<>();
+            apiResult.code = 412;
+            apiResult.msg = "解密数据出错"+e.getMessage();
+            response = new Gson().toJson(apiResult);
+        } catch (JSONException e) {
+            e.printStackTrace();
+            BaseApiResult apiResult = new BaseApiResult<>();
+            apiResult.code = 414;
+            apiResult.msg = "非标准json";
+            response = new Gson().toJson(apiResult);
+        }catch (Exception e){
+            JsonReader jsonReader = gson.newJsonReader(value.charStream());
+            return adapter.read(jsonReader);
+        } finally {
+            InputStream inputStream = new ByteArrayInputStream(response.getBytes());
+            JsonReader jsonReader = gson.newJsonReader(new InputStreamReader(inputStream, UTF_8));
+            T result = adapter.read(jsonReader);
+            if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
+                throw new JsonIOException("JSON document was not fully consumed.");
+            }
+            value.close();
+            return result;
+        }
+    }
+
+    /**
+     * 解密json
+     * @param body
+     * @return
+     * @throws Exception
+     */
+    protected String decryptJsonStr(String body) throws Exception {
+        Log.e("Converter","decryptJsonStr body:"+body);
+        if(body.indexOf("{") == 0) {
+            JSONObject json = new JSONObject(body);
+            body = RSAUtils.decrypt(json.getString(ENCRYPT), RSAUtils.getPublicKey(RSAUtils.PUBLIC_KEY));//
+        }
+        return transHump? GsonUtils.toHumpJson(body):body;
+    }
+
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/converter/GsonRequestBodyConverter.java b/app/src/main/java/com/duqing/missions/retrofit/converter/GsonRequestBodyConverter.java
new file mode 100644
index 0000000..cdd3ac7
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/converter/GsonRequestBodyConverter.java
@@ -0,0 +1,44 @@
+package com.duqing.missions.retrofit.converter;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okio.Buffer;
+import retrofit2.Converter;
+
+/**
+ * My father is Object, ites purpose of
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-7-15.
+ */
+
+final class GsonRequestBodyConverter<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;
+
+    GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
+        this.gson = gson;
+        this.adapter = adapter;
+    }
+
+    @Override
+    public RequestBody convert(T value) throws IOException {
+        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());
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/utils/HttpPrintUtils.java b/app/src/main/java/com/duqing/missions/retrofit/utils/HttpPrintUtils.java
new file mode 100644
index 0000000..ad46bc7
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/utils/HttpPrintUtils.java
@@ -0,0 +1,210 @@
+package com.duqing.missions.retrofit.utils;
+
+import com.duqing.missions.BuildConfig;
+import com.duqing.missions.util.GsonUtils;
+import com.duqing.missions.util.MyLog;
+
+import java.io.EOFException;
+import java.util.ArrayList;
+
+import okio.Buffer;
+
+/**
+ * My father is Object, ites purpose of 单例模式  保证synchronized方法的线程安全性
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2021-5-13.
+ */
+
+public class HttpPrintUtils {
+    String TAG = "HttpPrintUtils";
+    static HttpPrintUtils instance;
+    public static  HttpPrintUtils getInstance(){
+        if(instance == null){
+           instance = new HttpPrintUtils();
+        }
+        return instance;
+    }
+
+    /**
+     * 打印log
+     * @param list
+     */
+    public synchronized void printLog(ArrayList<String> list, boolean info){
+        int length = 0 ;//计算每行最长的长度
+        ArrayList<String> logArrays = new ArrayList<>();
+        for(String str : list){
+            if(str.indexOf("\n")>-1){//有换行的拆分处理
+                String[] split = str.split("\n");
+                for(String s : split){
+                    s = s.replace("\t","    ");//缩进替换空格
+                    if(length<s.length()){
+                        length = s.length();
+                    }
+                }
+            }else{
+                if(length<str.length()){
+                    length = str.length();
+                }
+            }
+        }
+        length+=14;//左右间距
+        if(length>300){
+            length = 300;
+        }
+        String head = "HTTP  REQUEST START";
+        logArrays.add(" \n\n\n"+"\n");
+        //打印头部
+        String logHead = "┏"+getEmptyStr((length-head.length())/2,"━")+head+getEmptyStr((length-head.length())/2,"━")+"┓";
+        logArrays.add(logHead+"\n");
+        //打印内容
+        for(String str : list){
+            String logStr = "";
+            if(str.indexOf("\n")>-1){//内部换行替换
+                splitStr(str,logHead.length(),logArrays);
+            }else{
+                if(str.length()> logHead.length()){
+                    outOflength(str,logHead.length(),logArrays);
+                }else {
+                    logStr = "┃      " + str + getEmptyStr((length - 14 - str.length()), " ");
+                    //处理中文间距,保证打印无偏差
+                    logArrays.add(logStr + getEmptyStr((logHead.length() - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ \n");
+                }
+            }
+        }
+        String end = "HTTP  REQUEST END";
+        //打印结尾
+        logArrays.add("┗"+getEmptyStr((length-end.length())/2,"━")+end+getEmptyStr((length-end.length())/2,"━")+"┛\n");
+        logArrays.add(" \n\n\n");
+        //Logger.DEFAULT.log(sb.toString());//打印log,避免多个log语句,导致log输出时其他线程的log输出切入此输出阵列内
+        if(BuildConfig.DEBUG) {
+            for(int i = 0 ; i < logArrays.size() ; i ++ ){
+                String str = logArrays.get(i);
+                if (info) {
+                    MyLog.i(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i);
+                } else {
+                    MyLog.e(TAG , str.replace("\n","")+" "+logArrays.size()+" "+i);
+                }
+            }
+        }
+    }
+
+    /**
+     * 拆分
+     * @param str
+     * @param totalLength
+     * @param list
+     */
+    private void splitStr(String str,int totalLength,ArrayList<String> list){
+        String logStr = "";
+        String[] split = str.split("\n");
+        for(String s : split){
+            if(s.length()/totalLength>3){
+                s = s.substring(0,totalLength*3)+"...";
+            }
+            s = s.replace("\t","    ");//缩进替换空格
+            if(s.indexOf("\":{\"")>-1 || s.indexOf("\":[{\"")>-1 || s.indexOf("\":[[")>-1){//内容非校正缩进,且为json字符规范
+                splitStr(s.substring(0,s.indexOf("\":")+2)+ GsonUtils.retractJson(s.substring(s.indexOf("\":")+2)),totalLength,list);
+            }else {
+                if(s.length()> totalLength){
+                    outOflength(s,totalLength,list);
+                }else {
+                    logStr = "┃      " + s + getEmptyStr((totalLength - 16 - s.length()), " ");
+                    //处理中文间距,保证打印无偏差
+                    list.add(logStr + getEmptyStr((totalLength - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ "/*+logStr.length()+" "+logStr.getBytes().length+" "+(" ").getBytes().length +" "+hasCNchar(s)*/ + "\n");
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 超长字符拆分
+     * @param str
+     * @param total
+     * @param list
+     */
+    private void outOflength(String str,int total,ArrayList<String> list){
+        String logStr = "";
+        //缩进空间
+        String space  = getEmptyStr(str.length() - str.trim().length()+4," ");
+        //要拆分的实际长度
+        int length = (str.length()-space.length());
+        //每行数量
+        int count = total-16-space.length();//总长度-间距-缩进空间是每行的数量
+        //最终拆分数量
+        int lines = (length/count) + (length%(count)>0?1:0);
+
+        for(int i = 0 ; i < lines ; i ++){
+            int start = space.length() + (count * (i+1));//起始位
+            int end = start+count;//结束位
+            String s = "";
+            if(start > str.length() && i > 0){
+                break;
+            } else if(end > str.length() && i > 0 || i == lines-1){
+                s = str.substring(start);
+            } else if(i == 0 ){
+                s = str.substring(0, start);
+            } else {
+                s = str.substring(start, end);
+            }
+            if(i>0) {
+                s = space + s;
+            }
+            logStr = "┃      " + s + getEmptyStr((total - 16 - s.length()), " ");
+            list.add(logStr + getEmptyStr((total - logStr.length() - 1 - hasCNchar(logStr)), " ") + "┃ \n");
+        }
+    }
+
+
+    //返回包含中文数量,
+    private int hasCNchar(String str){
+        str = str.replace("┃","");
+        int size = 0 ;
+        for(int i = 0 ; i < str.length() ; i ++){
+            char c = str.charAt(i);
+
+            if((c >= 0x0391 && c <= 0xFFE5)) { //中文字符
+                size++;
+            }
+        }
+        return size>0?(int)(size/3.0*2):0;//+1为修正在log中与英文字符短一位
+    }
+
+    /**
+     * 占位符填充
+     * @param length 占位数量
+     * @param space 占位符
+     * @return
+     */
+    private String getEmptyStr(int length,String space){
+        StringBuilder sb = new StringBuilder();
+        for(int i = 0 ; i < length  ; i ++){
+            sb.append(space);
+        }
+        return sb.toString();
+    }
+    /**
+     * Returns true if the body in question probably contains human readable text. Uses a small sample
+     * of code points to detect unicode control characters commonly used in binary file signatures.
+     */
+    static boolean isPlaintext(Buffer buffer) {
+        try {
+            Buffer prefix = new Buffer();
+            long byteCount = buffer.size() < 64 ? buffer.size() : 64;
+            buffer.copyTo(prefix, 0, byteCount);
+            for (int i = 0; i < 16; i++) {
+                if (prefix.exhausted()) {
+                    break;
+                }
+                int codePoint = prefix.readUtf8CodePoint();
+                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
+                    return false;
+                }
+            }
+            return true;
+        } catch (EOFException e) {
+            return false; // Truncated UTF-8 sequence.
+        }
+    }
+
+}
diff --git a/app/src/main/java/com/duqing/missions/retrofit/utils/RSAUtils.java b/app/src/main/java/com/duqing/missions/retrofit/utils/RSAUtils.java
new file mode 100644
index 0000000..b11526c
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/retrofit/utils/RSAUtils.java
@@ -0,0 +1,178 @@
+package com.duqing.missions.retrofit.utils;
+
+import android.util.Base64;
+
+import java.io.ByteArrayOutputStream;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+
+
+public class RSAUtils {
+
+    public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALBGei0scHoOjTLImPHvASaGqYNrdLie0ckWp74Nkqv7FVeXPOvWEG8_jRJCVjJ1grr8SGd9sVY2sxn5XIz7fUEBfx7Vm8m0DaCNBWJpFLGw9xiaVZ2AUKoNyTD4NgZobbwZbt6ZNB6_fggPrGF18pq6GPyCndX1JW8ZiZKj33VBAgMBAAECgYB0q-EX3y7_CnyYXT8l-mxHhJ_T9R6HR89QimcyGqe2nvRMSjSvX7r29xg3OqL0uORzQKHnpcDncELw8SQ5yAbpENeIsD0dvdFlkoyFYU4ljeUbJ46binwwg20TNARjTbpNos9zbhTPh8qixdblxppXA1WC18HtXhixgca5bNG9lQJBAPQfNdpNdDL9l8Tw4hYVuDMszcFuZYbHbm0S4xcwqj-dXNWBztNf5W_K92-N5GIoHbOypkGzjlBjSZi_oKA0HusCQQC42irhw682CG44mKdP6YRDxy6OaauVX4yE9WnsbO8JFSSc9ZCKMMD0F3NGtytDrVMAJxG1iPWXa4ptEdtgwCmDAkAUW1npR1YuPllekdu4jb0bf1v1ClirAYxiyhVnxKYdweiQ4U827yM5zEoP4lwuFzxK1NXqWqe-alkjxK8HTPFbAkAviQLf_adP2MknSrIzzZQSreTeAHR8PA7xnf54KucpScOZjVh3AOSNoH4nYDEC_U5LysA2E5s8Lg5xz9a_QYsrAkEAwV6gNED7_SYDsYyEWimQ6znUb_QSY-sSChnSCY-ILG1wpynBHw_t1Oi3ljl6gL_cYKG1O3uwOtvZtb-Vr1bNkQ";
+
+    public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwRnotLHB6Do0yyJjx7wEmhqmDa3S4ntHJFqe-DZKr-xVXlzzr1hBvP40SQlYydYK6_EhnfbFWNrMZ-VyM-31BAX8e1ZvJtA2gjQViaRSxsPcYmlWdgFCqDckw-DYGaG28GW7emTQev34ID6xhdfKauhj8gp3V9SVvGYmSo991QQIDAQAB";
+
+
+    /**
+     * RSA最大加密明文大小
+     */
+    private static final int MAX_ENCRYPT_BLOCK = 117;
+
+    /**
+     * RSA最大解密密文大小
+     */
+    private static final int MAX_DECRYPT_BLOCK = 128;
+
+    static final String KEY_RSA = "RSA"; //android标准 “RSA/ECB/PKCS1Padding” 服务端标准 “RSA”
+
+    /**
+     * 获取密钥对
+     *
+     * @return 密钥对
+     */
+    public static KeyPair getKeyPair() throws Exception {
+        KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_RSA);
+        generator.initialize(1024);
+        return generator.generateKeyPair();
+    }
+
+    /**
+     * 获取私钥
+     *
+     * @param privateKey 私钥字符串
+     * @return
+     */
+    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
+        byte[] decodedKey = Base64.decode(privateKey.getBytes(), Base64.URL_SAFE);
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
+        return keyFactory.generatePrivate(keySpec);
+    }
+
+    /**
+     * 获取公钥
+     *
+     * @param publicKey 公钥字符串
+     * @return
+     */
+    public static PublicKey getPublicKey(String publicKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
+        byte[] decodedKey = Base64.decode(publicKey.getBytes(), Base64.URL_SAFE);
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
+        return keyFactory.generatePublic(keySpec);
+    }
+
+    /**
+     * RSA加密
+     *
+     * @param data 待加密数据
+     * @param key 密钥
+     * @return
+     */
+    public static String encrypt(String data, Key key) throws Exception {
+        Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, key);
+        int inputLen = data.getBytes().length;
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int offset = 0;
+        byte[] cache;
+        int i = 0;
+        // 对数据分段加密
+        while (inputLen - offset > 0) {
+            if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
+                cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
+            } else {
+                cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
+            }
+            out.write(cache, 0, cache.length);
+            i++;
+            offset = i * MAX_ENCRYPT_BLOCK;
+        }
+        byte[] encryptedData = out.toByteArray();
+        out.close();
+        // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
+        // 加密后的字符串
+        return Base64.encodeToString(encryptedData,Base64.URL_SAFE | Base64.NO_WRAP);
+    }
+
+    /**
+     * RSA解密
+     *
+     * @param data 待解密数据
+     * @param key 密钥
+     * @return
+     */
+    public static String decrypt(String data, Key key) throws Exception {
+        Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
+        cipher.init(Cipher.DECRYPT_MODE, key);
+        byte[] dataBytes = Base64.decode(data.getBytes(), Base64.URL_SAFE);
+        int inputLen = dataBytes.length;
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int offset = 0;
+        byte[] cache;
+        int i = 0;
+        // 对数据分段解密
+        while (inputLen - offset > 0) {
+            if (inputLen - offset > MAX_DECRYPT_BLOCK) {
+                cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
+            } else {
+                cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
+            }
+            out.write(cache, 0, cache.length);
+            i++;
+            offset = i * MAX_DECRYPT_BLOCK;
+        }
+        byte[] decryptedData = out.toByteArray();
+        out.close();
+        // 解密后的内容
+        return new String(decryptedData, "UTF-8");
+    }
+
+    /**
+     * 签名
+     *
+     * @param data 待签名数据
+     * @param privateKey 私钥
+     * @return 签名
+     */
+    public static String sign(String data, PrivateKey privateKey) throws Exception {
+        byte[] keyBytes = privateKey.getEncoded();
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
+        PrivateKey key = keyFactory.generatePrivate(keySpec);
+        Signature signature = Signature.getInstance("MD5withRSA");
+        signature.initSign(key);
+        signature.update(data.getBytes());
+        return  Base64.encodeToString(signature.sign(),Base64.DEFAULT);
+    }
+
+    /**
+     * 验签
+     *
+     * @param srcData 原始字符串
+     * @param publicKey 公钥
+     * @param sign 签名
+     * @return 是否验签通过
+     */
+    public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
+        byte[] keyBytes = publicKey.getEncoded();
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
+        PublicKey key = keyFactory.generatePublic(keySpec);
+        Signature signature = Signature.getInstance("MD5withRSA");
+        signature.initVerify(key);
+        signature.update(srcData.getBytes());
+        return signature.verify(Base64.decode(sign.getBytes(),Base64.DEFAULT));
+    }
+
+}
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java
index 896d934..93e7ac4 100644
--- a/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java
@@ -21,6 +21,12 @@
 import com.duqing.missions.R;
 import com.duqing.missions.base.activities.BaseTitleBarActivity;
 import com.duqing.missions.databinding.ActivityLoginBinding;
+import com.duqing.missions.retrofit.RetrofitUtils;
+import com.duqing.missions.retrofit.api.LoginApiCenter;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
 
 
 public class LoginActivity extends BaseTitleBarActivity<ActivityLoginBinding,LoginViewModel> {
@@ -116,8 +122,48 @@
         loginButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                viewModel.login(phoneEdit.getText().toString(),
-                        passwordEditText.getText().toString());
+                /*RetrofitUtils.getInstance().getRetrofit(LoginApiCenter.class).login(";lajks;dkfjal;ksjdf")
+
+                        .subscribeOn(Schedulers.io())//指定网络请求在io后台线程中进行
+                        .observeOn(AndroidScheduler.mainThread())//指定observer回调在UI主线程中进行
+                        .subscribe(new io.reactivex.Observer<Object>() {
+                            @Override
+                            public void onSubscribe(Disposable d) {
+                                v.setEnabled(false);
+                                Log.d(TAG,"onSubscribe");
+                            }
+
+                            @Override
+                            public void onNext(Object value) {
+                                Log.d(TAG,"onNext value:"+value);
+
+                            }
+
+                            @Override
+                            public void onError(Throwable e) {
+                                v.setEnabled(true);
+                                Log.d(TAG,"onError "+e.getMessage());
+
+                            }
+
+                            @Override
+                            public void onComplete() {
+                                v.setEnabled(true);
+                                Log.d(TAG,"onComplete");
+
+                            }
+                        });//发起请求,请求的结果会回调到订阅者observer中*/
+                RetrofitUtils.getInstance().getRetrofit(LoginApiCenter.class).loginCall("asdfasdfasdfasdfasdf").enqueue(new Callback<Object>() {
+                    @Override
+                    public void onResponse(Call<Object> call, Response<Object> response) {
+
+                    }
+
+                    @Override
+                    public void onFailure(Call<Object> call, Throwable t) {
+
+                    }
+                });
             }
         });
 
diff --git a/app/src/main/java/com/duqing/missions/util/GsonUtils.java b/app/src/main/java/com/duqing/missions/util/GsonUtils.java
new file mode 100644
index 0000000..5d572da
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/util/GsonUtils.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.duqing.missions.util;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Json工具类.
+ */
+public class GsonUtils {
+    private static Gson gson = new GsonBuilder().create();
+
+    public static String toJson(Object value) {
+        return gson.toJson(value);
+    }
+
+    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonParseException {
+        return gson.fromJson(json, classOfT);
+    }
+
+    public static <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
+        return (T) gson.fromJson(json, typeOfT);
+    }
+
+    /**
+     * 将对象转换为驼峰命名的json
+     * @param value
+     * @return
+     */
+    public static String toHumpJson(Object value) {
+        try {
+            if(value instanceof Collection){
+                    return convertToHumpJsonArray(new JSONArray(gson.toJson(value)) ).toString();
+            }else {
+                return convertToHumpJsonObj(new JSONObject(gson.toJson(value)) ).toString();
+            }
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return gson.toJson(value);
+        }
+    }
+
+    /**
+     *
+     * 将json转换为驼峰命名的json
+     * @param json
+     * @return
+     */
+    public static String toHumpJson(String json) throws JSONException {
+        if(json.indexOf("[") == 0){
+            return convertToHumpJsonArray(new JSONArray(json) ).toString();
+        }else {
+            return convertToHumpJsonObj(new JSONObject(json) ).toString();
+        }
+    }
+
+    /**
+     * 驼峰命名转换
+     * @param json
+     * @param classOfT
+     * @param <T>
+     * @return
+     * @throws JsonParseException
+     */
+    public static <T> T fromJsonToHump(String json, Class<T> classOfT) throws JsonParseException, JSONException {
+        return  gson.fromJson(toHumpJson(json), classOfT);
+    }
+
+    /**
+     * 驼峰命名转换
+     * @param json
+     * @param typeOfT
+     * @param <T>
+     * @return
+     * @throws JsonParseException
+     */
+    public static <T> T fromJsonToHump(String json, Type typeOfT) throws JsonParseException, JSONException {
+        return (T) gson.fromJson(toHumpJson(json), typeOfT);
+    }
+
+    /**
+     * 转换驼峰命名
+     * @param jsonObject
+     * @return
+     */
+    public static JSONObject convertToHumpJsonObj(JSONObject jsonObject){
+        JSONObject temp = new JSONObject();
+        Iterator<String> it = jsonObject.keys();
+        while ( it.hasNext()){
+            String key = it.next();
+            String humpKey = humpName(key);
+            try {
+                if(jsonObject.get(key) instanceof JSONObject){
+                    temp.put(humpKey,convertToHumpJsonObj(jsonObject.getJSONObject(key)));
+                }else if(jsonObject.get(key) instanceof JSONArray){
+                    temp.put(humpKey,convertToHumpJsonArray(jsonObject.getJSONArray(key)));
+                }else {
+                    temp.put(humpKey,jsonObject.get(key));
+                }
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+        }
+        return temp;
+
+    }
+
+    public static JSONArray convertToHumpJsonArray(JSONArray array){
+        JSONArray jsons = new JSONArray();
+        for(int i = 0 ; i < array.length() ; i ++){
+            try {
+                if(array.get(i) instanceof JSONObject){
+                    jsons.put(convertToHumpJsonObj(array.getJSONObject(i)));
+                }else if(array.get(i) instanceof JSONArray){
+                    jsons.put(convertToHumpJsonArray(array.getJSONArray(i)));
+                }else {
+                    jsons.put(array.get(i));
+                }
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+        }
+        return jsons;
+
+    }
+    /**
+     * 将key转换为驼峰
+     * @param param
+     * @return
+     */
+    public static Map convertToHumpMap(Map<String, Object> param){
+        Map temp = new TreeMap();
+        for(String key: param.keySet()){
+            String humpKey = humpName(key);
+            if(param.get(key) instanceof Map){
+                temp.put(humpKey,convertToHumpMap((Map<String, Object>) param.get(key)));
+            }else if(param.get(key) instanceof List){
+                temp.put(humpKey,convertToHumpList((List)param.get(key)));
+            }else {
+                temp.put(humpKey,param.get(key));
+            }
+        }
+        return temp;
+    }
+
+
+    public static List convertToHumpList(List list){
+        List ars = new ArrayList();
+        for(Object object : list){
+            if(object instanceof Map){
+                ars.add(convertToHumpMap((Map)object));
+            }else if(object instanceof List){
+                ars.add(convertToHumpList((List)object));
+            }else {
+                ars.add(object);
+            }
+        }
+        return ars;
+    }
+    /**
+     * 驼峰命名
+     * @param name
+     * @return
+     */
+    public static String humpName(String name){
+        String[] strings = name.split("_");
+        StringBuilder sb = new StringBuilder();
+        sb.append(strings[0]);
+        for(int i = 1 ; i < strings.length ; i ++){
+            sb.append(toUperFirst(strings[i]));
+        }
+        if(sb.toString().equals("new")){//关键字 转成大写
+            return "NEW";
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 首字母大写
+     * @param name
+     * @return
+     */
+    public static String toUperFirst(String name){
+        return name.substring(0,1).toUpperCase()+name.substring(1);
+    }
+
+    /**
+     * json字符串缩进
+     * @param json
+     * @return
+     */
+    public static String retractJson(String json){
+        int level = 0 ;
+        StringBuffer jsonForMatStr = new StringBuffer();
+        for(int index=0;index<json.length();index++)//将字符串中的字符逐个按行输出
+        {
+            //获取s中的每个字符
+            char c = json.charAt(index);
+            //          System.out.println(s.charAt(index));
+
+            //level大于0并且jsonForMatStr中的最后一个字符为\n,jsonForMatStr加入\t
+            if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) {
+                jsonForMatStr.append(getLevelStr(level));
+                //                System.out.println("123"+jsonForMatStr);
+            }
+            //遇到"{"和"["要增加空格和换行,遇到"}"和"]"要减少空格,以对应,遇到","要换行
+            switch (c) {
+                case '{':
+                case '[':
+                    jsonForMatStr.append(c + "\n");
+                    level++;
+                    break;
+                case ',':
+                    if(index>0 && index < json.length()-2 && (json.charAt(index-1) != '\n') && json.charAt(index+1) == '"'){
+                        jsonForMatStr.append(c + "\n");
+                    }else{
+                        jsonForMatStr.append(c);
+                    }
+                    break;
+                case '}':
+                case ']':
+                    jsonForMatStr.append("\n");
+                    level--;
+                    jsonForMatStr.append(getLevelStr(level));
+                    jsonForMatStr.append(c);
+                    break;
+                default:
+                    jsonForMatStr.append(c);
+                    break;
+            }
+        }
+        return jsonForMatStr.toString();
+    }
+    private static String getLevelStr(int level) {
+        StringBuffer levelStr = new StringBuffer();
+        for (int levelI = 0; levelI < level; levelI++) {
+            levelStr.append("\t");//\t或空格
+        }
+        return levelStr.toString();
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/util/NetWorkUtils.java b/app/src/main/java/com/duqing/missions/util/NetWorkUtils.java
new file mode 100644
index 0000000..138e22d
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/util/NetWorkUtils.java
@@ -0,0 +1,212 @@
+package com.duqing.missions.util;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * My father is Object, ites purpose of
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-19.
+ */
+
+public class NetWorkUtils {
+
+
+    /**
+     * 获取外网IP地址
+     * @return
+     */
+    public static String  getNetIp() {
+        final Map<String,String> param = new HashMap<>();
+        Thread thread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                String line = "";
+                URL infoUrl = null;
+                InputStream inStream = null;
+                try {
+                    infoUrl = new URL("http://pv.sohu.com/cityjson?ie=utf-8");
+                    URLConnection connection = infoUrl.openConnection();
+                    HttpURLConnection httpConnection = (HttpURLConnection) connection;
+                    int responseCode = httpConnection.getResponseCode();
+                    if (responseCode == HttpURLConnection.HTTP_OK) {
+                        inStream = httpConnection.getInputStream();
+                        BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
+                        StringBuilder strber = new StringBuilder();
+                        while ((line = reader.readLine()) != null)
+                            strber.append(line + "\n");
+                        inStream.close();
+                        // 从反馈的结果中提取出IP地址
+                        int start = strber.indexOf("{");
+                        int end = strber.indexOf("}");
+                        String json = strber.substring(start, end + 1);
+                        if (json != null) {
+                            try {
+                                JSONObject jsonObject = new JSONObject(json);
+                                line = jsonObject.optString("cip");
+                            } catch (JSONException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        param.put("ip",line);
+                    }
+                } catch (MalformedURLException e) {
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+        try {
+            thread.start();
+            thread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return param.get("ip");
+    }
+
+    /***
+     * 获取局域网ip
+     * @param context
+     * @return
+     */
+    @SuppressLint("MissingPermission")
+    public static String getLocalIpAddress(Context context) {
+
+        ConnectivityManager connect = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        //检查网络连接
+        NetworkInfo info = connect.getActiveNetworkInfo();
+
+        if (info == null) {
+            return "";
+        }
+        int netType = info.getType();
+        int netSubtype = info.getSubtype();
+
+        if (netType == ConnectivityManager.TYPE_WIFI) {  //WIFI
+            return getWifiIpAddress(context);
+        } else /*if (netType == ConnectivityManager.TYPE_MOBILE) {   //MOBILE
+            return getGPRSIpAddress();
+        } else */if (netType == ConnectivityManager.TYPE_ETHERNET) {   //MOBILE
+            return getEthernetIp();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * 使用wifi
+     * @param context
+     * @return
+     */
+    @SuppressLint("MissingPermission")
+    public static String getWifiIpAddress(Context context) {
+
+        //获取wifi服务
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        //判断wifi是否开启
+        //             if (!wifiManager.isWifiEnabled()) {
+        //      <span style="white-space:pre">    </span>   wifiManager.setWifiEnabled(true);
+        //             }
+        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+        int ipAddress = wifiInfo.getIpAddress();
+        String ip = intToIp(ipAddress);
+        return ip;
+    }
+
+    private static String intToIp(int i) {
+        return (i & 0xFF) + "." +
+                ((i >> 8) & 0xFF) + "." +
+                ((i >> 16) & 0xFF) + "." +
+                (i >> 24 & 0xFF);
+    }
+    /**
+     * 使用GPRS
+     * @return
+     */
+    public static String getGPRSIpAddress() {
+        try {
+            for (Enumeration<NetworkInterface> en = NetworkInterface
+                    .getNetworkInterfaces(); en.hasMoreElements(); ) {
+                NetworkInterface intf = en.nextElement();
+                for (Enumeration<InetAddress> enumIpAddr = intf
+                        .getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                    InetAddress inetAddress = enumIpAddr.nextElement();
+                    if (!inetAddress.isLoopbackAddress()) {
+                        return inetAddress.getHostAddress().toString();
+                    }
+                }
+            }
+        } catch (SocketException ex) {
+            MyLog.e("Exception", ex.toString());
+        }
+        return "127.0.0.1";
+    }
+
+    public static String getEthernetIp() {
+        String hostIp = null;
+        try {
+            Enumeration nis = NetworkInterface.getNetworkInterfaces();
+            InetAddress ia = null;
+            while (nis.hasMoreElements()) {
+                NetworkInterface ni = (NetworkInterface) nis.nextElement();
+                Enumeration<InetAddress> ias = ni.getInetAddresses();
+                while (ias.hasMoreElements()) {
+                    ia = ias.nextElement();
+                    if (ia instanceof Inet6Address) {
+                        continue;// skip ipv6
+                    }
+                    String ip = ia.getHostAddress();
+                    if (!"127.0.0.1".equals(ip)) {
+                        hostIp = ia.getHostAddress();
+                        break;
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            Log.i("yao", "SocketException");
+            e.printStackTrace();
+        }
+        return hostIp;
+    }
+
+
+    /**
+     * 将得到的int类型的IP转换为String类型
+     *
+     * @param ip
+     * @return
+     */
+    public static String intIP2StringIP(int ip) {
+        return (ip & 0xFF) + "." +
+                ((ip >> 8) & 0xFF) + "." +
+                ((ip >> 16) & 0xFF) + "." +
+                (ip >> 24 & 0xFF);
+    }
+
+}
diff --git a/app/src/main/res/anim/slide_in_left.xml b/app/src/main/res/anim/slide_in_left.xml
new file mode 100644
index 0000000..59d1c77
--- /dev/null
+++ b/app/src/main/res/anim/slide_in_left.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set  xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="-50%p" android:toXDelta="0"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_mediumAnimTime" />
+</set>
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
new file mode 100644
index 0000000..08bc86b
--- /dev/null
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/mobile_navigation"
+    app:startDestination="@+id/navigation_home">
+
+    <fragment
+        android:id="@+id/navigation_home"
+        android:name="com.duqing.missions.ui.main.home.HomeFragment"
+        android:label="@string/title_home"
+        tools:layout="@layout/fragment_home" />
+
+    <fragment
+        android:id="@+id/navigation_hall"
+        android:name="com.duqing.missions.ui.main.hall.HallFragment"
+        android:label="@string/title_hall"
+        tools:layout="@layout/layout_tab_viewpager" />
+
+    <fragment
+        android:id="@+id/navigation_dynamic"
+        android:name="com.duqing.missions.ui.main.dynamic.DynamicFragment"
+        android:label="@string/title_dynamic"
+        tools:layout="@layout/layout_tab_viewpager" />
+    <fragment
+        android:id="@+id/navigation_mine"
+        android:name="com.duqing.missions.ui.main.mine.MineFragment"
+        android:label="@string/title_mine"
+        tools:layout="@layout/fragment_mine" />
+</navigation>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eaaea45..6b5f248 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,4 @@
 <resources>
-    <string name="app_name">Missions</string>
     <string name="title_home">Home</string>
     <string name="title_hall">Hall</string>
     <string name="title_dynamic">Dynamic</string>
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..dca93c0
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+    <base-config cleartextTrafficPermitted="true" />
+</network-security-config>
\ No newline at end of file

--
Gitblit v1.9.1