From c56260d3be75e059c4525df802e4f17bcfb0b630 Mon Sep 17 00:00:00 2001
From: Administrator <123>
Date: Mon, 15 Nov 2021 03:01:19 +0000
Subject: [PATCH] 工具类、网络请求框架、mvvm框架

---
 app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java                        |  326 +++
 app/src/main/java/com/runt/open/mvvm/MyApplication.java                                       |  136 +
 app/src/main/res/menu/bottom_nav_menu.xml                                                     |   19 
 app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java              |   44 
 app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java                                 |   21 
 app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java                        |  127 +
 app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java                |   60 
 app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java                         |   33 
 app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java                            |   31 
 app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java                                      |  256 ++
 app/src/main/res/drawable/ic_home_black_24dp.xml                                              |    9 
 app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java |   95 
 gradlew.bat                                                                                   |   89 
 app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java                               |  194 +
 app/src/main/res/layout/activity_main.xml                                                     |   35 
 app/src/main/res/mipmap-xxhdpi/icon_delete.png                                                |    0 
 app/build.gradle                                                                              |   66 
 app/src/main/res/values/dimens.xml                                                            |    9 
 app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java             |   19 
 gradlew                                                                                       |  185 +
 app/src/main/res/values/strings.xml                                                           |    5 
 app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java                                |   44 
 app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java                                     |  374 +++
 app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java                            |  166 +
 app/src/main/java/com/runt/open/mvvm/util/MyLog.java                                          |   42 
 app/src/main/java/com/runt/open/mvvm/util/SpUtils.java                                        |  476 ++++
 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml                                            |    5 
 app/src/main/res/mipmap-xhdpi/ic_launcher.webp                                                |    0 
 app/src/main/res/layout/fragment_notifications.xml                                            |   23 
 app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java                       |  173 +
 app/src/main/res/drawable/ic_notifications_black_24dp.xml                                     |    9 
 app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java                                   |  212 ++
 app/src/main/res/mipmap-hdpi/ic_launcher.webp                                                 |    0 
 app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java                            |   14 
 app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java                        |   40 
 app/src/main/res/layout/layout_null.xml                                                       |   18 
 app/src/main/res/mipmap-mdpi/ic_launcher.webp                                                 |    0 
 app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java                           |   39 
 app/proguard-rules.pro                                                                        |   21 
 app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java                       |  210 ++
 .gitignore                                                                                    |   41 
 app/src/main/res/values/styles.xml                                                            |   15 
 app/src/main/res/values/themes.xml                                                            |   16 
 gradle/wrapper/gradle-wrapper.jar                                                             |    0 
 app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java                 |  105 +
 app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java                                |  256 ++
 app/src/main/res/layout/refresh_recycler.xml                                                  |   14 
 app/src/main/res/drawable/ic_launcher_background.xml                                          |  170 +
 app/src/main/res/xml/network_security_config.xml                                              |    4 
 gradle/wrapper/gradle-wrapper.properties                                                      |    6 
 app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java      |   58 
 app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java                         |   66 
 app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java                                  |   23 
 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp                                           |    0 
 app/src/main/res/values/colors.xml                                                            |   19 
 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp                                          |    0 
 app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java                             |  178 +
 app/src/main/res/drawable/ic_dashboard_black_24dp.xml                                         |    9 
 app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java                                 |   51 
 app/src/main/res/layout/fragment_dashboard.xml                                                |   23 
 build.gradle                                                                                  |   17 
 app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java         |   44 
 app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java             |  104 +
 app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java         |  280 ++
 app/src/main/res/drawable-v24/ic_launcher_foreground.xml                                      |   30 
 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp                                               |    0 
 app/.gitignore                                                                                |    3 
 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp                                         |    0 
 settings.gradle                                                                               |   10 
 app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java                               |   29 
 app/src/main/res/navigation/mobile_navigation.xml                                             |   25 
 app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java                |   96 
 app/src/main/java/com/runt/open/mvvm/MainActivity.java                                        |   30 
 app/src/main/AndroidManifest.xml                                                              |   24 
 app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java                        |   51 
 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp                                        |    0 
 app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java                            |   15 
 app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java                    |   69 
 app/src/main/res/layout/fragment_home.xml                                                     |   23 
 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml                                      |    5 
 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp                                              |    0 
 gradle.properties                                                                             |   19 
 app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java                    |   13 
 app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java                     |   78 
 app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java                     |   19 
 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp                                           |    0 
 app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java                               |   19 
 app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java                      |   74 
 app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java                        |   66 
 app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java                      |   44 
 90 files changed, 5,841 insertions(+), 25 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3a36c6e..5885f03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,17 @@
-# Built application files
-*.apk
-*.ap_
-
-# Files for the Dalvik VM
-*.dex
-
-# Java class files
-*.class
-
-# Generated files
-bin/
-gen/
-
-# Gradle files
-.gradle/
-build/
-
-# Local configuration file (sdk path, etc)
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
 local.properties
-
-# Proguard folder generated by Eclipse
-proguard/
-
-#Log Files
-*.log
+/.idea/
+/.idea/
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..423e3dc
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,3 @@
+/build
+/src/androidTest/
+/src/test/
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..1789858
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,66 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdk 31
+
+    defaultConfig {
+        applicationId "com.runt.open.mvvm"
+        minSdk 23
+        targetSdk 31
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        debug{
+            minifyEnabled false
+            buildConfigField 'String','HOST_IP_ADDR','"http://192.168.100.82:8080/"'
+            buildConfigField 'String','ENVIRONMENT','"release"'
+            resValue "string", "app_name", "MVVM开源项目测试"
+            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", "MVVM开源项目"
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    buildFeatures {
+        viewBinding true
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.3.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
+    implementation 'androidx.navigation:navigation-fragment:2.3.5'
+    implementation 'androidx.navigation:navigation-ui:2.3.5'
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    //// 注意:分包之后不会有默认的Header和Footer需要手动添加!还是原来的三种方法!
+    implementation  'com.scwang.smart:refresh-layout-kernel:2.0.1'      //核心必须依赖
+    implementation  'com.scwang.smart:refresh-header-classics:2.0.1'    //经典刷新头
+    implementation 'com.github.d-max:spots-dialog:1.1@aar'//loading view
+    implementation 'com.google.code.gson:gson:2.8.6'
+    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
+    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'    //RXjava和retrofit结合
+    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'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..58dc725
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.runt.open.mvvm" >
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.OpemMvvm" >
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/MainActivity.java b/app/src/main/java/com/runt/open/mvvm/MainActivity.java
new file mode 100644
index 0000000..b180224
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/MainActivity.java
@@ -0,0 +1,30 @@
+package com.runt.open.mvvm;
+
+import android.os.Bundle;
+
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.navigation.NavController;
+import androidx.navigation.Navigation;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+
+import com.runt.open.mvvm.databinding.ActivityMainBinding;
+
+public class MainActivity extends AppCompatActivity {
+
+    private ActivityMainBinding binding;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        binding = ActivityMainBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+
+        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
+        NavigationUI.setupWithNavController(binding.navView, navController);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/MyApplication.java b/app/src/main/java/com/runt/open/mvvm/MyApplication.java
new file mode 100644
index 0000000..8ed880c
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/MyApplication.java
@@ -0,0 +1,136 @@
+package com.runt.open.mvvm;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.runt.open.mvvm.util.MyLog;
+import com.scwang.smart.refresh.footer.ClassicsFooter;
+import com.scwang.smart.refresh.header.ClassicsHeader;
+import com.scwang.smart.refresh.layout.SmartRefreshLayout;
+import com.scwang.smart.refresh.layout.api.RefreshFooter;
+import com.scwang.smart.refresh.layout.api.RefreshHeader;
+import com.scwang.smart.refresh.layout.api.RefreshLayout;
+import com.scwang.smart.refresh.layout.listener.DefaultRefreshFooterCreator;
+import com.scwang.smart.refresh.layout.listener.DefaultRefreshHeaderCreator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Administrator on 2021/10/28 0028.
+ */
+public class MyApplication extends Application {
+
+    final  String TAG = "MyApplication";
+    List<Activity> activities = new ArrayList<>();
+    private Activity currentActivity;//当前activity
+    private boolean isInfront; //是否前台运行
+    private static MyApplication application;
+    public static MyApplication getApplication() {
+        return application;
+    }
+
+    public Activity getCurrentActivity() {
+        return currentActivity;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        application = this;
+        //CrashReport.initCrashReport(getApplicationContext(), "8d88679ae9", false);//注册bugly
+        //设置全局的Header构建器
+        SmartRefreshLayout.setDefaultRefreshHeaderCreator(new DefaultRefreshHeaderCreator() {
+            @Override
+            public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) {
+                //layout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white);//全局设置主题颜色
+                return new ClassicsHeader(context);//.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header
+            }
+        });
+        //设置全局的Footer构建器
+        SmartRefreshLayout.setDefaultRefreshFooterCreator(new DefaultRefreshFooterCreator() {
+            @Override
+            public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) {
+                //指定为经典Footer,默认是 BallPulseFooter
+                return new ClassicsFooter(context).setDrawableSize(20);
+            }
+        });
+        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
+            @Override
+            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
+                MyLog.d(TAG,"onActivityCreated "+activity.getClass().getSimpleName());
+                if(!activities.contains(activity)){
+                    activities.add(activity);
+                }
+            }
+
+            @Override
+            public void onActivityStarted(@NonNull Activity activity) {
+                MyLog.d(TAG,"onActivityStarted "+activity.getClass().getSimpleName());
+                currentActivity = activity;
+            }
+
+            @Override
+            public void onActivityResumed(@NonNull Activity activity) {
+                MyLog.d(TAG,"onActivityResumed "+activity.getClass().getSimpleName());
+                currentActivity = activity;
+                isInfront = true;
+            }
+
+            @Override
+            public void onActivityPaused(@NonNull Activity activity) {
+                MyLog.d(TAG,"onActivityPaused "+activity.getClass().getSimpleName());
+
+            }
+
+            @Override
+            public void onActivityStopped(@NonNull Activity activity) {
+                MyLog.d(TAG,"onActivityStopped "+activity.getClass().getSimpleName());
+
+            }
+
+            @Override
+            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {
+                MyLog.d(TAG,"onActivitySaveInstanceState "+activity.getClass().getSimpleName());
+                isInfront = false;
+
+            }
+
+            @Override
+            public void onActivityDestroyed(@NonNull Activity activity) {
+                MyLog.d(TAG,"onActivityDestroyed "+activity.getClass().getSimpleName());
+                if(activities.contains(activity)){
+                    activities.remove(activity);
+                }
+            }
+        });
+    }
+
+    /**
+     * 退出程序
+     */
+    public void quitApp(){
+        for(Activity activity:activities){
+            activity.finish();
+        }
+        System.exit(0);
+    }
+
+    public void clearActivities(){
+        for(Activity activity:activities){
+            /*if(activity instanceof LoginActivity){
+                continue;
+            }*/
+            activity.finish();
+        }
+    }
+
+    public boolean isInfront(){
+        return isInfront;
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java
new file mode 100644
index 0000000..355a46d
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseActivity.java
@@ -0,0 +1,326 @@
+package com.runt.open.mvvm.base.activities;
+
+import android.Manifest;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.FileProvider;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.viewbinding.ViewBinding;
+
+import com.runt.open.mvvm.MyApplication;
+import com.runt.open.mvvm.R;
+import com.runt.open.mvvm.base.model.BaseViewModel;
+import com.runt.open.mvvm.base.model.ViewModelFactory;
+import com.runt.open.mvvm.data.ApkUpGradeResult;
+import com.runt.open.mvvm.util.MyLog;
+import com.runt.open.mvvm.util.SpUtils;
+import com.permissionx.guolindev.PermissionX;
+import com.permissionx.guolindev.callback.ExplainReasonCallbackWithBeforeParam;
+import com.permissionx.guolindev.callback.ForwardToSettingsCallback;
+import com.permissionx.guolindev.callback.RequestCallback;
+import com.permissionx.guolindev.request.ExplainScope;
+import com.permissionx.guolindev.request.ForwardScope;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.Date;
+import java.util.List;
+
+import dmax.dialog.SpotsDialog;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+/**
+ * activity 封装
+ * Created by Administrator on 2021/10/27 0027.
+ */
+public abstract class BaseActivity<B extends ViewBinding,VM extends BaseViewModel> extends AppCompatActivity {
+
+    protected  B binding;
+    protected  VM viewModel;
+    protected String TAG ;
+    public final String[] FILE_PERMISSIONS = new String []{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};
+    public final String[] LOCATION_PERMISSIONS = new String []{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION};
+    public final String[] CAMERA_PERMISSIONS = new String[]{ FILE_PERMISSIONS[0],FILE_PERMISSIONS[1], Manifest.permission.CAMERA};
+    public final String[] CAMERA_RECORD_PERMISSIONS = new String[]{ FILE_PERMISSIONS[0],FILE_PERMISSIONS[1], Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO};
+
+    public static final int RESULT_LOGIN = 100,RESULT_LOGIN_RECREATE = 103,RESULT_BIND = 101,RESULT_SENDEDFILES = 105,RESULT_DISSCONNECT = 104,
+            RESULT_UPDATEUSER =  115,RESULT_LOGOUT = 113, REQUEST_CODE_ACTIVITY = 333;
+    public static final int REQUEST_CODE_LOGOUT = 20009,/*退出*/
+            REQUEST_CODE_ORDER = 10010,/*订单详情*/
+            REQUEST_CODE_LOGIN = 20010,/*登录*/
+            REQUEST_CODE_SCAN = 20011/*扫描请求*/,
+            REQUEST_CODE_PIC = 20012,/*选择图片*/
+            REQUEST_CODE_PAYPASS = 20112,/*支付验证*/
+            REQUEST_CODE_PAYPASS_FOR_ALIPAY = 20110,/*支付宝修改支付验证*/
+            REQUEST_CODE_PAYPASS_FOR_REALNAME = 20111,/*支付宝修改支付验证*/
+            REQUEST_CODE_PERMISSION = 20013,/*请求权限*/
+            REQUEST_VERSION_PERMISSION = 20013,/*检查更新*/
+            REQUEST_INSTALL_PERMISSION = 20014,/*请求安装权限*/
+            REQUEST_CODE_MANAGE_GROUP = 20015,/*选择分组*/
+            REQUEST_CODE_MANAGE_DEL = 20016,/*选择删除*/
+            REQUEST_CODE_MANAGE_EDIT = 20017,/*选择编辑*/
+            REQUEST_CODE_MANAGE_ADD = 20018,/*选择添加*/
+            REQUEST_CODE_MANAGE_DRAFT = 20019,/*选择草稿编辑*/
+            REQUEST_CODE_WEPAY = 20020,/*微信支付*/
+            REQUEST_CODE_ALIPAY = 20021,/*支付宝*/
+            REQUEST_CODE_PINKAGE = 20022,/*包邮设置*/
+            REQUEST_INSTALL_APK = 20200,/*请求安装权限*/
+            REQUEST_CODE_COMMENT_REFRESH = 20222,/*请求安装权限*/
+            RESULT_CODE_DRAFT = 4041/*草稿*/,
+            RESULT_CODE_UPDATED = 4042/*更新*/,
+            RESULT_CODE_FAILD = 4044/*失败*/,
+            RESULT_CODE_SUCESS = 4046/*成功*/,
+            RESULT_CODE_CANCEL = 4043/*取消*/;
+    protected Context mContext;
+
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // get genericity "B"
+        setStatusBarBgColor(R.color.white);
+        setStatusBarTextColor(true);
+        final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
+        try {
+            Class<B> entityClass = (Class<B>) type.getActualTypeArguments()[0];
+            Method method = entityClass.getMethod("inflate", LayoutInflater.class);//get method from name "inflate";
+            binding = (B) method.invoke(entityClass,getLayoutInflater());//execute method to create a objct of viewbind;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1];
+        viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass);
+        setContentView(binding.getRoot());
+        mContext = this;
+        try {
+            //设置坚屏 一定要放到try catch里面,否则会崩溃
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        } catch (Exception e) {
+        }
+        TAG = this.getClass().getSimpleName();
+        initViews();
+    }
+
+    public abstract void initViews();
+
+    AlertDialog loadingDialog;
+    /**
+     * 显示加载弹框
+     * @param msg
+     */
+    public void showLoadingDialog(String msg){
+        if(loadingDialog != null  && loadingDialog.isShowing()){
+            loadingDialog.dismiss();
+        }
+        loadingDialog = new SpotsDialog.Builder().setContext(mContext).build();
+        loadingDialog.setMessage(msg);
+        loadingDialog.setCancelable(false);
+        loadingDialog.show();
+    }
+
+    /**
+     * 清除弹框
+     */
+    public void dissmissLoadingDialog(){
+        if(loadingDialog != null && loadingDialog.isShowing()){
+            loadingDialog.dismiss();
+        }
+    }
+
+    public ViewModelProvider.Factory getViewModelFactory(){
+        return ViewModelFactory.getInstance();
+    }
+
+    public void setStatusBarTransparent(boolean isBlack){
+        //透明状态栏
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        //透明导航栏
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+    }
+
+    /**
+     * 修改状态栏颜色,支持4.4以上版本
+     * @param colorId
+     */
+    public void setStatusBarBgColor(@ColorRes int colorId) {
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        getWindow().setStatusBarColor(getResources().getColor(colorId));
+    }
+
+    /**
+     * 修改状态栏文本颜色
+     * @param isBlack
+     */
+    public void setStatusBarTextColor(boolean isBlack){
+        View decor = getWindow().getDecorView();
+        if (isBlack) {
+            decor.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+        } else {
+            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+        }
+    }
+
+    /**
+     * 隐藏虚拟按键
+     */
+    public void hideBottomUIMenu() {
+        //隐藏虚拟按键
+        if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api
+            View v = getWindow().getDecorView();
+            v.setSystemUiVisibility(View.GONE);
+        } else if (Build.VERSION.SDK_INT >= 19) {
+            //for new api versions.
+            View decorView = getWindow().getDecorView();
+            int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY  ;
+            decorView.setSystemUiVisibility(uiOptions);
+        }
+
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {  //把操作放在用户点击的时候
+            View v = getCurrentFocus();      //得到当前页面的焦点,ps:有输入框的页面焦点一般会被输入框占据
+            if (isShouldHideKeyboard(v, ev)) { //判断用户点击的是否是输入框以外的区域
+                hideSoftKeyboard ();   //收起键盘
+            }
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    /**
+     * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时则不能隐藏
+     *
+     * @param v
+     * @param event
+     * @return
+     */
+    private boolean isShouldHideKeyboard(View v, MotionEvent event) {
+        if (v != null && (v instanceof EditText)) {  //判断得到的焦点控件是否包含EditText
+            int[] l = {0, 0};
+            v.getLocationInWindow(l);
+            int left = l[0],    //得到输入框在屏幕中上下左右的位置
+                    top = l[1],
+                    bottom = top + v.getHeight(),
+                    right = left + v.getWidth();
+            if (event.getX() > left && event.getX() < right
+                    && event.getY() > top && event.getY() < bottom) {
+                // 点击位置如果是EditText的区域,忽略它,不收起键盘。
+                return false;
+            } else {
+                return true;
+            }
+        }
+        // 如果焦点不是EditText则忽略
+        return false;
+    }
+    /**
+     * 判断软键盘输入法是否弹出
+     */
+    public boolean isKeyboardVisbility(Context context, View v) {
+        InputMethodManager imm = (InputMethodManager) context.getSystemService(context.INPUT_METHOD_SERVICE);
+        if (imm.hideSoftInputFromWindow(v.getWindowToken(), 0)) {
+            imm.showSoftInput(v, 0);
+            return true;//键盘显示中
+        } else {
+            return false;//键盘未显示
+        }
+    }
+    protected void hideSoftKeyboard() {
+        if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {
+            if (getCurrentFocus() != null)
+                ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
+                        InputMethodManager.HIDE_NOT_ALWAYS);
+        }
+    }
+    /**
+     * 状态栏高度
+     * @return
+     */
+    public int getStatusBarHeight() {
+        int result = 0;
+        int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        if (resId > 0) {
+            result = getResources().getDimensionPixelOffset(resId);
+        }
+        return result;
+    }
+
+
+    long mExitTime= 0 ;
+    /**
+     * 返回键退出程序
+     */
+    public void backExit() {
+        if ((System.currentTimeMillis() - mExitTime) > 2000) {
+            Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show();
+            mExitTime = System.currentTimeMillis();
+        } else {
+            //此方法导致app关闭后重启
+            MyApplication.getApplication().quitApp();
+            System.exit(0);
+            //quitApp();
+        }
+    }
+    public void showToast(String message){
+        Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
+    }
+
+    public void showToast(@StringRes int msg){
+        showToast(getString(msg));
+    }
+
+    /**
+     * 获取文件保存路径 sdcard根目录/download/文件名称
+     * @param fileUrl
+     * @return
+     */
+    public static String getSaveFilePath(String fileUrl){
+        String fileName=fileUrl.substring(fileUrl.lastIndexOf("/")+1,fileUrl.length());//获取文件名称
+        String storePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Download";
+        File appDir = new File(storePath);
+        if (!appDir.exists()) {
+            appDir.mkdirs();
+        }
+        return storePath + File.separator +fileName;
+    }
+
+
+    protected boolean onBackKeyDown() {
+        onBackPressed();
+        return false;
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java
new file mode 100644
index 0000000..9e2695a
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseLoadPageActivity.java
@@ -0,0 +1,96 @@
+package com.runt.open.mvvm.base.activities;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewbinding.ViewBinding;
+
+import com.runt.open.mvvm.base.adapter.BaseAdapter;
+import com.runt.open.mvvm.base.model.BaseLoadPageViewModel;
+import com.runt.open.mvvm.data.BasePageResult;
+import com.runt.open.mvvm.databinding.RefreshRecyclerBinding;
+import com.scwang.smart.refresh.footer.ClassicsFooter;
+import com.scwang.smart.refresh.header.ClassicsHeader;
+import com.scwang.smart.refresh.layout.SmartRefreshLayout;
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.HashMap;
+
+/**
+ * Created by Administrator on 2021/11/4 0004.
+ */
+public abstract class BaseLoadPageActivity<B extends ViewBinding,VM extends BaseLoadPageViewModel,A extends BaseAdapter,D extends BasePageResult>
+        extends BaseTitleBarActivity<B,VM>  implements OnRefreshLoadMoreListener {
+
+    protected SmartRefreshLayout smartRefresh;
+    protected RecyclerView recycler;
+    final String TAG = "RecyclerFragment";
+    protected A adapter;//适配器
+    protected String url;//请求地址
+    protected HashMap param;//参数
+    protected int page=1;
+    protected final int SIZE = 10;
+    protected boolean isRefresh = false;//是否正在刷新
+    
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2];
+            this.adapter = entityClass.newInstance();//实例化泛型
+            String smartStr = "smartRefresh";
+            smartRefresh = (SmartRefreshLayout) binding.getClass().getField(smartStr).get(binding);
+            recycler =  (RecyclerView) binding.getClass().getDeclaredField("recycler").get(binding);
+        } catch (NoSuchFieldException e) {
+            try {
+                RefreshRecyclerBinding includeRefreshRecycler = (RefreshRecyclerBinding) binding.getClass().getDeclaredField ("includeRefreshRecycler").get(binding);
+                smartRefresh = includeRefreshRecycler.smartRefresh;
+                recycler =  includeRefreshRecycler.recycler;
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        }
+        smartRefresh.setRefreshHeader(new ClassicsHeader(mContext));
+        smartRefresh.setRefreshFooter(new ClassicsFooter(mContext));
+        smartRefresh.setOnRefreshLoadMoreListener(this);
+        recycler.setLayoutManager(new LinearLayoutManager(mContext));
+        recycler.setAdapter(adapter);
+    }
+
+    private void finishFreshLoadmore(D result){
+        if(result.code == 200){
+
+            smartRefresh.setEnableRefresh(true);
+            smartRefresh.finishRefresh();
+            if(page == 1){
+                adapter.getData().clear();
+                adapter.setData(result.rows);
+            }else{
+                adapter.getData().addAll(result.rows);
+                adapter.notifyDataSetChanged();
+            }
+            if(result.total <= adapter.getData().size()// 总数是否已经加载完
+                    || result.rows.size() < SIZE // 最后一页数据的数量一般不满size
+            ){//判断是否没有数据了
+                smartRefresh.finishLoadMoreWithNoMoreData();
+            }else {
+                smartRefresh.finishLoadMore();
+            }
+        }else{
+            smartRefresh.setEnableRefresh(true);
+            smartRefresh.finishRefresh();
+            smartRefresh.finishLoadMore();
+        }
+    }
+
+    public A getAdapter() {
+        return adapter;
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java
new file mode 100644
index 0000000..c14ee7b
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTabActivity.java
@@ -0,0 +1,78 @@
+package com.runt.open.mvvm.base.activities;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.viewbinding.ViewBinding;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.runt.open.mvvm.base.adapter.FragmentAdapter;
+import com.runt.open.mvvm.base.fragments.BaseFragment;
+import com.runt.open.mvvm.base.model.BaseViewModel;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by Administrator on 2021/11/4 0004.
+ */
+public abstract class BaseTabActivity<B extends ViewBinding,VM extends BaseViewModel>  extends BaseActivity<B,VM> {
+
+    TabLayout tabLayout;
+    FragmentAdapter fragmentAdapter;
+    List<String> tabTitles = new ArrayList<>();
+    ViewPager2 viewPager2;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        fragmentAdapter = new FragmentAdapter(this);
+        fragmentAdapter.setFragments(initFragments());
+        setTabTitles(initTabTitles());
+        //设置当前可见Item左右可见page数,次范围内不会被销毁
+        //禁用预加载
+        try {
+            viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding);
+            tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+        ;
+        viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT);
+        viewPager2.setAdapter(fragmentAdapter);
+        viewPager2.setCurrentItem(0);
+        viewPager2.setUserInputEnabled(false); //true:滑动,false:禁止滑动
+        new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> tab.setText(tabTitles.get(position))).attach();
+    }
+
+
+    protected abstract List<String> initTabTitles();
+
+    protected abstract List<BaseFragment> initFragments();
+
+    protected List<String> getTabTitles(){
+        return tabTitles;
+    }
+
+    public FragmentAdapter getFragmentAdapter() {
+        return fragmentAdapter;
+    }
+
+    public void setTabTitles(List<String> tabTitles) {
+        if(tabTitles == null){
+            this.tabTitles.clear();
+        }
+        this.tabTitles = tabTitles;
+    }
+    public void setTabTitles(String[] tabTitles) {
+        if(tabTitles == null){
+            this.tabTitles.clear();
+        }
+        setTabTitles(new ArrayList<>(Arrays.asList(tabTitles)));
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java
new file mode 100644
index 0000000..414103e
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/activities/BaseTitleBarActivity.java
@@ -0,0 +1,60 @@
+package com.runt.open.mvvm.base.activities;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.viewbinding.ViewBinding;
+
+import com.runt.open.mvvm.base.model.BaseViewModel;
+import com.runt.open.mvvm.widgets.TitleBarView;
+
+/**
+ * 带有标题栏的activity封装
+ * Created by Administrator on 2021/11/2 0002.
+ */
+public abstract class BaseTitleBarActivity<B extends ViewBinding,VM extends BaseViewModel> extends BaseActivity<B,VM> {
+    TitleBarView titleBarView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        try {
+            titleBarView = (TitleBarView) binding.getClass().getDeclaredField("titleBar").get(binding);
+            titleBarView.setLeftClick(v -> onTitleLeftClick());
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+    }
+
+    protected void setTitle(String text){
+        titleBarView.setTitleText(text);
+    }
+    
+    protected void onTitleLeftClick(){
+        onBackKeyDown();
+    }
+
+    protected void setTitleRight(String text){
+        titleBarView.setRightText(text);
+        titleBarView.setRightDra(null);
+    }
+
+    protected void setTitleRight(Drawable drawable){
+        titleBarView.setRightText(null);
+        titleBarView.setRightDra(drawable);
+    }
+
+    @Override
+    public void setStatusBarTransparent(boolean isBlack) {
+        super.setStatusBarTransparent(isBlack);
+        final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) titleBarView.getLayoutParams();
+        layoutParams.topMargin = layoutParams.topMargin+getStatusBarHeight();
+        titleBarView.setLayoutParams(layoutParams);
+
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java b/app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java
new file mode 100644
index 0000000..d022444
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/adapter/BaseAdapter.java
@@ -0,0 +1,166 @@
+package com.runt.open.mvvm.base.adapter;
+
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewbinding.ViewBinding;
+
+import com.runt.open.mvvm.base.activities.BaseActivity;
+import com.runt.open.mvvm.databinding.LayoutNullBinding;
+import com.runt.open.mvvm.util.DeviceUtil;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Administrator on 2021/10/27 0027.
+ *  T  数据类型
+ *  V 适配器视图
+ */
+public abstract class BaseAdapter<B extends ViewBinding,T> extends RecyclerView.Adapter {
+
+    protected List<T> mData = new ArrayList<>();
+
+    protected Drawable nullDrawable;
+    protected String nullTxt="暂无数据";
+    protected String TAG = "BaseAdapter";
+    protected BaseActivity activity;
+
+    public BaseAdapter(){
+    }
+
+    public BaseAdapter(@NonNull List<T> data){
+        mData = data;
+    }
+
+    public List<T> getData() {
+        return mData;
+    }
+
+    public void setData(@NonNull List<T> data){
+        mData = data;
+        notifyDataSetChanged();
+    }
+
+    @NonNull
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        if(viewType == 1 ){
+            // get genericity "B"
+            Class<B> entityClass = (Class<B>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+            try {
+               /* for(Method method: entityClass.getMethods()){
+                    StringBuilder sb = new StringBuilder();
+                    for(Class type : method.getParameterTypes()){
+                        sb.append(type.getSimpleName()+",");
+                    }
+                    Log.e(TAG,String.format("method:%s,return:%s,param:%s",method.getName(),method.getReturnType().getSimpleName(),sb.toString()));
+                }*/
+                Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate";
+                B vBind = (B) method.invoke(entityClass,LayoutInflater.from(parent.getContext()),parent,false);//execute method to create a objct of viewbind;
+                return new ViewBindHolder(vBind);
+            } catch (SecurityException e) {
+                e.printStackTrace();
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                Throwable t = e.getTargetException();// 获取目标异常
+                t.printStackTrace();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return new NullViewHolder( LayoutNullBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false));
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+        //MyLog.i(TAG,"onBindViewHolder position:"+position+" "+mData.size()+" "+getItemViewType(position));
+        if(activity == null){
+            activity = (BaseActivity) holder.itemView.getContext();
+        }
+        if(getItemViewType(position)==0){
+            bindView((NullViewHolder) holder);
+        }else {
+            bindView((ViewBindHolder) holder, mData.size() == 0 ? null : mData.get(position), position);
+        }
+    }
+
+    /**
+     * 设置最后一个底部间隔
+     * @param holder
+     * @param position
+     */
+    protected void setBottomMargin(ViewBindHolder holder, int position){
+        setBottomMargin(holder,position,23);
+    }
+
+    /**
+     * 设置最后一个底部间隔
+     * @param holder
+     * @param position  位置
+     * @param dp        间距
+     */
+    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position,int dp){
+        setBottomMargin(holder,position,dp,0);
+    }
+    protected void setBottomMargin(RecyclerView.ViewHolder holder, int position, int dp, int defaultDp){
+        ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
+        if(position == mData.size() -1){
+            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(dp,holder.itemView.getContext()));
+        }else{
+            params1.setMargins(params1.leftMargin, params1.topMargin, params1.rightMargin, DeviceUtil.convertDpToPixel(defaultDp,holder.itemView.getContext()));
+        }
+    }
+    protected abstract void bindView(ViewBindHolder holder,T data,int position);
+
+
+    protected void bindView(NullViewHolder holder){
+
+    }
+
+    @Override
+    public int getItemCount() {
+        //默认显示空视图,若不显示空视图则重写该方法,返回mData.size()
+        return mData == null || mData.size() == 0 ?1:mData.size();
+    }
+
+
+    @Override
+    public int getItemViewType(int position) {
+        //当下标为0,数据集合为0 返回0(意味当前应显示空数据视图))
+        //MyLog.i(TAG,"getItemViewType position:"+position+" mdata:"+mData.size()+" "+(position ==0 && mData.size()==0));
+        return position == 0 && mData.size()==0?0:1;
+    }
+
+    public class ViewBindHolder extends RecyclerView.ViewHolder{
+        public B binding;
+        public ViewBindHolder( B binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+        }
+    }
+    /**
+     * 空数据显示
+     * Created by Administrator on 2021/10/28 0028.
+     */
+    public class NullViewHolder extends RecyclerView.ViewHolder {
+        LayoutNullBinding binding;
+
+        public NullViewHolder(LayoutNullBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
+        }
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java b/app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java
new file mode 100644
index 0000000..589ec63
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/adapter/FragmentAdapter.java
@@ -0,0 +1,51 @@
+package com.runt.open.mvvm.base.adapter;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import com.runt.open.mvvm.base.fragments.BaseFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Administrator on 2021/11/3 0003.
+ */
+public class FragmentAdapter extends FragmentStateAdapter {
+
+    private List<BaseFragment> fragments = new ArrayList<>();
+
+    public FragmentAdapter(@NonNull FragmentActivity fragmentActivity) {
+        super(fragmentActivity);
+    }
+
+    public void setFragments(List<BaseFragment> fragments) {
+        this.fragments = fragments;
+    }
+
+    public void removeFragment(BaseFragment fragment){
+        this.fragments.remove(fragment);
+    }
+
+    public void removeFragment(int index){
+        this.fragments.remove(index);
+    }
+
+    public void addFragment(BaseFragment fragment){
+        fragments.add(fragment);
+    }
+
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        return fragments.get(position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return fragments.size();
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java
new file mode 100644
index 0000000..04fab76
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseFragment.java
@@ -0,0 +1,66 @@
+package com.runt.open.mvvm.base.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.viewbinding.ViewBinding;
+
+import com.runt.open.mvvm.base.activities.BaseActivity;
+import com.runt.open.mvvm.base.model.ViewModelFactory;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * fragment 封装
+ * Created by Administrator on 2021/10/28 0028.
+ */
+public abstract class BaseFragment<B extends ViewBinding,VM extends ViewModel> extends Fragment {
+
+    protected BaseActivity activity;
+    protected  B binding;
+    protected  VM viewModel;
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+        // get genericity "B"
+        try {
+            final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
+            Class<B> entityClass = (Class<B>) type.getActualTypeArguments()[0];
+            Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate";
+            binding = (B) method.invoke(entityClass,inflater,container,false);//execute method to create a objct of viewbind;
+            Class<VM> vmClass = (Class<VM>) type.getActualTypeArguments()[1];
+            viewModel = new ViewModelProvider(this,getViewModelFactory()).get(vmClass);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return binding.getRoot();
+    }
+
+    public ViewModelProvider.Factory getViewModelFactory(){
+        return ViewModelFactory.getInstance();
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        activity = (BaseActivity) getActivity();
+        initViews();
+    }
+
+    public abstract void initViews();
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        binding = null;
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java
new file mode 100644
index 0000000..4d691b0
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseLoadPageFragment.java
@@ -0,0 +1,105 @@
+package com.runt.open.mvvm.base.fragments;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewbinding.ViewBinding;
+
+import com.runt.open.mvvm.base.adapter.BaseAdapter;
+import com.runt.open.mvvm.base.model.BaseLoadPageViewModel;
+import com.runt.open.mvvm.data.BasePageResult;
+import com.runt.open.mvvm.databinding.RefreshRecyclerBinding;
+import com.scwang.smart.refresh.footer.ClassicsFooter;
+import com.scwang.smart.refresh.header.ClassicsHeader;
+import com.scwang.smart.refresh.layout.SmartRefreshLayout;
+import com.scwang.smart.refresh.layout.api.RefreshLayout;
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.HashMap;
+
+/**
+ * 分页fragment 封装
+ * Created by Administrator on 2021/11/3 0003.
+ */
+public abstract class BaseLoadPageFragment<B extends ViewBinding,VM extends BaseLoadPageViewModel,A extends BaseAdapter,D extends BasePageResult> extends BaseFragment<B,VM>
+        implements OnRefreshLoadMoreListener {
+    protected SmartRefreshLayout smartRefresh;
+    protected RecyclerView recycler;
+    final String TAG = "RecyclerFragment";
+    protected A adapter;//适配器
+    protected String url;//请求地址
+    protected HashMap param;//参数
+    protected int page=1;
+    protected final int SIZE = 10;
+    protected boolean isRefresh = false;//是否正在刷新
+
+    @Override
+    public void initViews() {
+        try {
+            Class<A> entityClass = (Class<A>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2];
+            this.adapter = entityClass.newInstance();//实例化泛型
+            String smartStr = "smartRefresh";
+            smartRefresh = (SmartRefreshLayout) binding.getClass().getField(smartStr).get(binding);
+            recycler =  (RecyclerView) binding.getClass().getDeclaredField("recycler").get(binding);
+        } catch (NoSuchFieldException e) {
+            try {
+                RefreshRecyclerBinding includeRefreshRecycler = (RefreshRecyclerBinding) binding.getClass().getDeclaredField ("includeRefreshRecycler").get(binding);
+                smartRefresh = includeRefreshRecycler.smartRefresh;
+                recycler =  includeRefreshRecycler.recycler;
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (java.lang.InstantiationException e) {
+            e.printStackTrace();
+        }
+        smartRefresh.setRefreshHeader(new ClassicsHeader(getContext()));
+        smartRefresh.setRefreshFooter(new ClassicsFooter(getContext()));
+        smartRefresh.setOnRefreshLoadMoreListener(this);
+        recycler.setLayoutManager(new LinearLayoutManager(getContext()));
+        recycler.setAdapter(adapter);
+    }
+
+    @Override
+    public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
+        page++;
+        viewModel.onLoadMore();
+    }
+
+    @Override
+    public void onRefresh(@NonNull RefreshLayout refreshLayout) {
+        page = 1;
+        viewModel.onRefresh();
+    }
+
+    private void finishFreshLoadmore(D result){
+        if(result.code == 200){
+
+            smartRefresh.setEnableRefresh(true);
+            smartRefresh.finishRefresh();
+            if(page == 1){
+                adapter.getData().clear();
+                adapter.setData(result.rows);
+            }else{
+                adapter.getData().addAll(result.rows);
+                adapter.notifyDataSetChanged();
+            }
+            if(result.total <= adapter.getData().size()// 总数是否已经加载完
+                    || result.rows.size() < SIZE // 最后一页数据的数量一般不满size
+            ){//判断是否没有数据了
+                smartRefresh.finishLoadMoreWithNoMoreData();
+            }else {
+                smartRefresh.finishLoadMore();
+            }
+        }else{
+            smartRefresh.setEnableRefresh(true);
+            smartRefresh.finishRefresh();
+            smartRefresh.finishLoadMore();
+        }
+    }
+    public A getAdapter() {
+        return adapter;
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java
new file mode 100644
index 0000000..f482d6b
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/fragments/BaseTabFragment.java
@@ -0,0 +1,74 @@
+package com.runt.open.mvvm.base.fragments;
+
+import androidx.lifecycle.ViewModel;
+import androidx.viewbinding.ViewBinding;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+import com.runt.open.mvvm.base.adapter.FragmentAdapter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 带有tablayout fragment封装
+ * Created by Administrator on 2021/11/3 0003.
+ */
+public abstract class BaseTabFragment<B extends ViewBinding,VM extends ViewModel> extends BaseFragment<B,VM> {
+
+    TabLayout tabLayout;
+    FragmentAdapter fragmentAdapter;
+    List<String> tabTitles = new ArrayList<>();
+    ViewPager2 viewPager2;
+
+    @Override
+    public void initViews() {
+
+        fragmentAdapter = new FragmentAdapter(activity);
+        fragmentAdapter.setFragments(initFragments());
+        setTabTitles(initTabTitles());
+        //设置当前可见Item左右可见page数,次范围内不会被销毁
+        //禁用预加载
+        try {
+            viewPager2 = (ViewPager2) binding.getClass().getDeclaredField("viewPager2").get(binding);
+            tabLayout = (TabLayout) binding.getClass().getDeclaredField("tabLayout").get(binding);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+        ;
+        viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT);
+        viewPager2.setAdapter(fragmentAdapter);
+        viewPager2.setCurrentItem(0);
+        viewPager2.setUserInputEnabled(false); //true:滑动,false:禁止滑动
+        new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> tab.setText(tabTitles.get(position))).attach();
+    }
+
+    protected abstract List<String> initTabTitles();
+
+    protected abstract List<BaseFragment> initFragments();
+
+    protected List<String> getTabTitles(){
+        return tabTitles;
+    }
+
+    public FragmentAdapter getFragmentAdapter() {
+        return fragmentAdapter;
+    }
+
+    public void setTabTitles(List<String> tabTitles) {
+        if(tabTitles == null){
+            this.tabTitles.clear();
+        }
+        this.tabTitles = tabTitles;
+    }
+    public void setTabTitles(String[] tabTitles) {
+        if(tabTitles == null){
+            this.tabTitles.clear();
+        }
+        setTabTitles(new ArrayList<>(Arrays.asList(tabTitles)));
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java b/app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java
new file mode 100644
index 0000000..150ef54
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/model/BaseLoadPageViewModel.java
@@ -0,0 +1,13 @@
+package com.runt.open.mvvm.base.model;
+
+/**
+ * 分页
+ * Created by Administrator on 2021/11/3 0003.
+ */
+public abstract class BaseLoadPageViewModel extends BaseViewModel {
+
+    public abstract void onRefresh();
+
+    public abstract void onLoadMore();
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java b/app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java
new file mode 100644
index 0000000..a50ff27
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/model/BaseViewModel.java
@@ -0,0 +1,31 @@
+package com.runt.open.mvvm.base.model;
+
+import androidx.lifecycle.ViewModel;
+
+import com.runt.open.mvvm.retrofit.observable.HttpObserver;
+import com.runt.open.mvvm.retrofit.AndroidScheduler;
+
+import io.reactivex.Observable;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Created by Administrator on 2021/11/11 0011.
+ */
+public class BaseViewModel extends ViewModel {
+
+
+    /**
+     * 网络请求观察
+     * @param observable
+     * @param <T>
+     * @return
+     */
+    public <T> void httpObserverOn(Observable<T> observable, HttpObserver observer){
+        observable.subscribeOn(Schedulers.io())//指定网络请求在io后台线程中进行
+                .observeOn(AndroidScheduler.mainThread())
+                .subscribe(observer);
+    }
+
+
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java b/app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java
new file mode 100644
index 0000000..ef2d908
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/model/ItemViewModel.java
@@ -0,0 +1,15 @@
+package com.runt.open.mvvm.base.model;
+
+import androidx.lifecycle.MutableLiveData;
+
+/**
+ * Created by Administrator on 2021/11/5 0005.
+ */
+public class ItemViewModel<T> extends BaseViewModel {
+
+    MutableLiveData<T> liveData = new MutableLiveData<>();
+
+    public MutableLiveData<T> getLiveData() {
+        return liveData;
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java b/app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java
new file mode 100644
index 0000000..7e861c5
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/base/model/ViewModelFactory.java
@@ -0,0 +1,33 @@
+package com.runt.open.mvvm.base.model;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * Created by Administrator on 2021/11/4 0004.
+ */
+public class ViewModelFactory implements ViewModelProvider.Factory {
+
+    static ViewModelFactory sInstance;
+
+    public static ViewModelFactory getInstance() {
+        if (sInstance == null) {
+            sInstance = new ViewModelFactory();
+        }
+        return sInstance;
+    }
+
+    @Override
+    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+        //noinspection TryWithIdenticalCatches
+        try {
+            return modelClass.newInstance();
+        } catch (InstantiationException e) {
+            throw new RuntimeException("(创建实例失败,没有对应的构造方法,请重写一个实现类)Cannot create an instance of " + modelClass, e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+        }
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java b/app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java
new file mode 100644
index 0000000..6b9d44a
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/data/ApkUpGradeResult.java
@@ -0,0 +1,29 @@
+package com.runt.open.mvvm.data;
+
+/**
+ * Created by Administrator on 2021/11/15 0015.
+ */
+public class ApkUpGradeResult extends BaseApiResult<ApkUpGradeResult.AppInfo>{
+
+    public class AppInfo {
+        //以下为声明的参数
+        public int type, isEnable, isImportant, id, code;
+        public String url,version, content, udate, remark;
+
+
+        @Override
+        public String toString() {
+            return "ApkUpGrade{" +
+                    "type=" + type + "," +
+                    "isEnable=" + isEnable + "," +
+                    "isImportant=" + isImportant + "," +
+                    "id=" + id + "," +
+                    "url=" + url + "," +
+                    "version=" + version + "," +
+                    "content=" + content + "," +
+                    "udate=" + udate + "," +
+                    '}';
+        }
+
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java b/app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java
new file mode 100644
index 0000000..9b758bf
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/data/BaseApiResult.java
@@ -0,0 +1,23 @@
+package com.runt.open.mvvm.data;
+
+import java.io.Serializable;
+
+/**
+ * Created by Administrator on 2021/10/28 0028.
+ */
+public class BaseApiResult<D extends Object> implements Serializable {
+    public String msg;
+    public int code = 200;
+    public D data;
+
+
+    @Override
+    public String toString() {
+        return "BaseApiData{" +
+                "msg='" + msg + '\'' +
+                ", code=" + code +
+                ", data=" + data +
+                '}';
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java b/app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java
new file mode 100644
index 0000000..6eba4d3
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/data/BasePageResult.java
@@ -0,0 +1,21 @@
+package com.runt.open.mvvm.data;
+
+import java.util.ArrayList;
+
+/**
+ * Created by Administrator on 2021/10/28 0028.
+ */
+public class BasePageResult<T> extends BaseApiResult<String>{
+    public int pages;
+    public int total;
+    public int pageNum;
+    public ArrayList<T> rows;
+
+    @Override
+    public String toString() {
+        return "PageBean{" +
+                ", total=" + total +
+                ", rows=" + rows +
+                '}';
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java b/app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java
new file mode 100644
index 0000000..3315c0f
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/AndroidScheduler.java
@@ -0,0 +1,39 @@
+package com.runt.open.mvvm.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/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java
new file mode 100644
index 0000000..6db29f3
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/EncryptInterceptor.java
@@ -0,0 +1,104 @@
+package com.runt.open.mvvm.retrofit.Interceptor;
+
+
+import com.runt.open.mvvm.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/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java
new file mode 100644
index 0000000..221f070
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/Interceptor/HttpLoggingInterceptor.java
@@ -0,0 +1,280 @@
+package com.runt.open.mvvm.retrofit.Interceptor;
+
+import android.util.Log;
+
+import com.runt.open.mvvm.retrofit.net.NetWorkCost;
+import com.runt.open.mvvm.retrofit.net.NetWorkListenear;
+import com.runt.open.mvvm.retrofit.utils.HttpPrintUtils;
+import com.runt.open.mvvm.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);
+            if(netWorkCost != null) {
+                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 + ')');
+            NetWorkListenear.workCostMap.remove(hashCode);
+            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/runt/open/mvvm/retrofit/api/CommonApiCenter.java b/app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java
new file mode 100644
index 0000000..fef1d1e
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/api/CommonApiCenter.java
@@ -0,0 +1,66 @@
+package com.runt.open.mvvm.retrofit.api;
+
+
+
+import com.runt.open.mvvm.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/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java
new file mode 100644
index 0000000..519ea76
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonConverterFactory.java
@@ -0,0 +1,58 @@
+package com.runt.open.mvvm.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/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java
new file mode 100644
index 0000000..4213ddc
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/DecryptGsonResponseBodyConverter.java
@@ -0,0 +1,95 @@
+package com.runt.open.mvvm.retrofit.converter;
+
+import android.util.Log;
+
+import com.runt.open.mvvm.data.BaseApiResult;
+import com.runt.open.mvvm.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 = json.toString();
+            //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/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java
new file mode 100644
index 0000000..7df807d
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/converter/GsonRequestBodyConverter.java
@@ -0,0 +1,44 @@
+package com.runt.open.mvvm.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/runt/open/mvvm/retrofit/net/NetWorkCost.java b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java
new file mode 100644
index 0000000..afc660a
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkCost.java
@@ -0,0 +1,14 @@
+package com.runt.open.mvvm.retrofit.net;
+
+
+/**
+ * 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/runt/open/mvvm/retrofit/net/NetWorkListenear.java b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java
new file mode 100644
index 0000000..6f49237
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/net/NetWorkListenear.java
@@ -0,0 +1,173 @@
+package com.runt.open.mvvm.retrofit.net;
+
+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/runt/open/mvvm/retrofit/observable/HttpObserver.java b/app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java
new file mode 100644
index 0000000..b02e526
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/observable/HttpObserver.java
@@ -0,0 +1,69 @@
+package com.runt.open.mvvm.retrofit.observable;
+
+import android.util.Log;
+
+import androidx.lifecycle.MutableLiveData;
+
+
+import com.runt.open.mvvm.data.BaseApiResult;
+
+import java.lang.reflect.ParameterizedType;
+import java.net.SocketTimeoutException;
+
+import io.reactivex.observers.DisposableObserver;
+
+/**
+ * 网络请求观察
+ * Created by Administrator on 2021/11/11 0011.
+ */
+public abstract class HttpObserver<T extends BaseApiResult> extends DisposableObserver<T>{
+
+    final String TAG = "HttpObserver";
+
+    MutableLiveData<T> resultLive;
+
+    public HttpObserver(MutableLiveData<T> resultLive) {
+        this.resultLive = resultLive;
+    }
+
+
+    @Override
+    public void onNext(T value) {
+        resultLive.setValue(value);
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        Log.i("subscribe","onError");
+
+        try {
+            Log.e(TAG,this.getClass().getSimpleName()+" "+throwable.getMessage());
+            Class<T> entityClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+            T t = entityClass.newInstance();//实例化一个泛型
+            t.code = 410;
+            if( throwable instanceof SocketTimeoutException){
+                t.msg = "服务请求超时,请稍候再试";//设置错误信息
+            }else{
+                t.msg = "网络连接不畅,请检查您的网络设置";//设置错误信息
+            }
+            resultLive.setValue(t);
+        } catch (ClassCastException e) {
+            e.printStackTrace();
+            T t = (T) new BaseApiResult<String>();
+            t.code = 409;
+            t.msg = "实例化对象未指定泛型实体类";
+            resultLive.setValue(t);
+        } catch (Exception e) {
+            e.printStackTrace();
+            T t = (T) new BaseApiResult<String>();
+            t.code = 409;
+            t.msg = e.getMessage();
+            resultLive.setValue(t);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        Log.i("subscribe","onComplete");
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java
new file mode 100644
index 0000000..53dd3b0
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/HttpPrintUtils.java
@@ -0,0 +1,210 @@
+package com.runt.open.mvvm.retrofit.utils;
+
+import com.runt.open.mvvm.BuildConfig;
+import com.runt.open.mvvm.util.GsonUtils;
+import com.runt.open.mvvm.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/runt/open/mvvm/retrofit/utils/RSAUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java
new file mode 100644
index 0000000..fd5a98c
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RSAUtils.java
@@ -0,0 +1,178 @@
+package com.runt.open.mvvm.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/runt/open/mvvm/retrofit/utils/RetrofitUtils.java b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java
new file mode 100644
index 0000000..df2ae38
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/retrofit/utils/RetrofitUtils.java
@@ -0,0 +1,127 @@
+package com.runt.open.mvvm.retrofit.utils;
+
+
+import com.runt.open.mvvm.BuildConfig;
+import com.runt.open.mvvm.retrofit.Interceptor.EncryptInterceptor;
+import com.runt.open.mvvm.retrofit.Interceptor.HttpLoggingInterceptor;
+import com.runt.open.mvvm.retrofit.api.CommonApiCenter;
+import com.runt.open.mvvm.retrofit.converter.DecryptGsonConverterFactory;
+import com.runt.open.mvvm.retrofit.net.NetWorkListenear;
+
+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/runt/open/mvvm/ui/dashboard/DashboardFragment.java b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java
new file mode 100644
index 0000000..7266305
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardFragment.java
@@ -0,0 +1,44 @@
+package com.runt.open.mvvm.ui.dashboard;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import com.runt.open.mvvm.R;
+import com.runt.open.mvvm.databinding.FragmentDashboardBinding;
+
+public class DashboardFragment extends Fragment {
+
+    private DashboardViewModel dashboardViewModel;
+private FragmentDashboardBinding binding;
+
+    public View onCreateView(@NonNull LayoutInflater inflater,
+            ViewGroup container, Bundle savedInstanceState) {
+        dashboardViewModel =
+                new ViewModelProvider(this).get(DashboardViewModel.class);
+
+    binding = FragmentDashboardBinding.inflate(inflater, container, false);
+    View root = binding.getRoot();
+
+        final TextView textView = binding.textDashboard;
+        dashboardViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                textView.setText(s);
+            }
+        });
+        return root;
+    }
+
+@Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        binding = null;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java
new file mode 100644
index 0000000..d7234f7
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/ui/dashboard/DashboardViewModel.java
@@ -0,0 +1,19 @@
+package com.runt.open.mvvm.ui.dashboard;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class DashboardViewModel extends ViewModel {
+
+    private MutableLiveData<String> mText;
+
+    public DashboardViewModel() {
+        mText = new MutableLiveData<>();
+        mText.setValue("This is dashboard fragment");
+    }
+
+    public LiveData<String> getText() {
+        return mText;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java
new file mode 100644
index 0000000..bade84b
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeFragment.java
@@ -0,0 +1,44 @@
+package com.runt.open.mvvm.ui.home;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import com.runt.open.mvvm.R;
+import com.runt.open.mvvm.databinding.FragmentHomeBinding;
+
+public class HomeFragment extends Fragment {
+
+    private HomeViewModel homeViewModel;
+private FragmentHomeBinding binding;
+
+    public View onCreateView(@NonNull LayoutInflater inflater,
+            ViewGroup container, Bundle savedInstanceState) {
+        homeViewModel =
+                new ViewModelProvider(this).get(HomeViewModel.class);
+
+    binding = FragmentHomeBinding.inflate(inflater, container, false);
+    View root = binding.getRoot();
+
+        final TextView textView = binding.textHome;
+        homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                textView.setText(s);
+            }
+        });
+        return root;
+    }
+
+@Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        binding = null;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java
new file mode 100644
index 0000000..a728c8a
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/ui/home/HomeViewModel.java
@@ -0,0 +1,19 @@
+package com.runt.open.mvvm.ui.home;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class HomeViewModel extends ViewModel {
+
+    private MutableLiveData<String> mText;
+
+    public HomeViewModel() {
+        mText = new MutableLiveData<>();
+        mText.setValue("This is home fragment");
+    }
+
+    public LiveData<String> getText() {
+        return mText;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java
new file mode 100644
index 0000000..fc02849
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsFragment.java
@@ -0,0 +1,44 @@
+package com.runt.open.mvvm.ui.notifications;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import com.runt.open.mvvm.R;
+import com.runt.open.mvvm.databinding.FragmentNotificationsBinding;
+
+public class NotificationsFragment extends Fragment {
+
+    private NotificationsViewModel notificationsViewModel;
+private FragmentNotificationsBinding binding;
+
+    public View onCreateView(@NonNull LayoutInflater inflater,
+            ViewGroup container, Bundle savedInstanceState) {
+        notificationsViewModel =
+                new ViewModelProvider(this).get(NotificationsViewModel.class);
+
+    binding = FragmentNotificationsBinding.inflate(inflater, container, false);
+    View root = binding.getRoot();
+
+        final TextView textView = binding.textNotifications;
+        notificationsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                textView.setText(s);
+            }
+        });
+        return root;
+    }
+
+@Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        binding = null;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java
new file mode 100644
index 0000000..3e3c5d7
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/ui/notifications/NotificationsViewModel.java
@@ -0,0 +1,19 @@
+package com.runt.open.mvvm.ui.notifications;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class NotificationsViewModel extends ViewModel {
+
+    private MutableLiveData<String> mText;
+
+    public NotificationsViewModel() {
+        mText = new MutableLiveData<>();
+        mText.setValue("This is notifications fragment");
+    }
+
+    public LiveData<String> getText() {
+        return mText;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java b/app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java
new file mode 100644
index 0000000..58761bb
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/util/DeviceUtil.java
@@ -0,0 +1,374 @@
+package com.runt.open.mvvm.util;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.os.Build;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.core.app.ActivityCompat;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+
+
+/**
+ * Created by EDZ on 2018/1/30.
+ */
+public class DeviceUtil {
+    public static final String TAG = "DeviceUtil";
+
+    /**
+     * 设备信息
+     *
+     * @param context
+     */
+    public static void getDisplay(Context context) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Version code is  \n");
+        sb.append("设备的Android版本号:");//设备的Android版本号
+        sb.append(getSDKInt() + " " + getSDKVersion() + "\t");//设备的Android版本号
+        sb.append("设备型号:");//设备型号
+        sb.append(getDeviceModel() + "\t");//设备型号
+        sb.append("设备厂商:");//设备型号
+        sb.append(getDeviceBrand() + "\t");//设备型号
+        sb.append("程序版本号:" + getAppVersionCode(context) + " " + getAppVersionName(context) + "\t");//程序版本号
+        sb.append("设备唯一标识符:" + getSerialNumber(context));
+        sb.append("\n设备imei:" + getIMEI(context));
+        String str = sb.toString() + " \n";
+        str += getDisplayInfomation(context) + " \n";
+        str += getDensity(context) + " \n";
+        str += "屏幕大小:" + getScreenInch(context) + "英寸 \n";
+        str += getAndroiodScreenProperty(context) + "\n";
+        Log.i(TAG, str);
+    }
+
+    /**
+     * Double类型保留指定位数的小数,返回double类型(四舍五入)
+     * newScale 为指定的位数
+     */
+    private static double formatDouble(double d, int newScale) {
+        BigDecimal bd = new BigDecimal(d);
+        return bd.setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    /**
+     * 设备型号
+     * @return
+     */
+    public static String getDeviceModel() {
+        return Build.MODEL;
+    }
+
+    /**
+     * 设备厂商
+     * @return
+     */
+    public static String getDeviceBrand() {
+        return Build.BRAND;
+    }
+
+    /**
+     * 设备的Android版本号
+     * @return
+     */
+    public static int getSDKInt() {
+        return Build.VERSION.SDK_INT;
+    }
+
+    /**
+     * 设备的Android版本号
+     * @return
+     */
+    public static String getSDKVersion() {
+        return Build.VERSION.RELEASE;
+    }
+
+    /**
+     * 程序版本号
+     * @param context
+     * @return
+     */
+    public static int getAppVersionCode(Context context) {
+        try {
+            return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return 1;
+    }
+
+    /**
+     * 程序版本号
+     *
+     * @param context
+     * @return
+     */
+    public static String getAppVersionName(Context context) {
+        try {
+            return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * 屏幕像素px
+     */
+    @SuppressLint("NewApi")
+    public static String getDisplayInfomation(Context context) {
+        Point point = new Point();
+        ((Activity) context).getWindowManager().getDefaultDisplay().getSize(point);
+        Log.d(TAG, "the screen size is " + point.toString());
+        ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(point);
+        Log.d(TAG, "the screen real size is " + point.toString());
+        return point.toString();
+    }
+
+    /**
+     * 屏幕信息
+     * @param context
+     * @return
+     */
+    public static String getAndroiodScreenProperty(Context context) {
+        int width = getScreenPixel(context).widthPixels;         // 屏幕宽度(像素)
+        int height = getScreenPixel(context).heightPixels;       // 屏幕高度(像素)
+        float density = getDensity(getScreenPixel(context));         // 屏幕密度(0.75 / 1.0 / 1.5)
+        int densityDpi = getDpi(context);     // 屏幕密度dpi(120 / 160 / 240)
+        // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度
+        int screenWidth = getDp(context)[0];  // 屏幕宽度(dp)
+        int screenHeight = getDp(context)[1];// 屏幕高度(dp)
+
+
+        Log.d("h_bl", "屏幕宽度(像素):" + width);
+        Log.d("h_bl", "屏幕高度(像素):" + height);
+        Log.d("h_bl", "屏幕密度(0.75 / 1.0 / 1.5):" + density);
+        Log.d("h_bl", "屏幕密度dpi(120 / 160 / 240):" + densityDpi);
+        Log.d("h_bl", "屏幕宽度(dp):" + screenWidth);  // 屏幕适配文件夹(例:layout-sw300dp),是以该属性为准则
+        Log.d("h_bl", "屏幕高度(dp):" + screenHeight);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("屏幕宽度(像素):" + width + "\n");
+        sb.append("屏幕高度(像素):" + height + "\n");
+        sb.append("屏幕密度(0.75 / 1.0 / 1.5):" + density + "\n");
+        sb.append("屏幕密度dpi(120 / 160 / 240):" + densityDpi + "\n");
+        sb.append("屏幕宽度(dp):" + screenWidth + "\n");
+        sb.append("屏幕高度(dp):" + screenHeight + "\n");
+        return sb.toString();
+
+    }
+
+    /**
+     * 通知栏高度
+     * @param context
+     * @return
+     */
+    public static int getStatusBarHeight(Context context) {
+        Class<?> c = null;
+        Object obj = null;
+        Field field = null;
+        int x = 0, statusBarHeight = 0;
+        try {
+            c = Class.forName("com.android.internal.R$dimen");
+            obj = c.newInstance();
+            field = c.getField("status_bar_height");
+            x = Integer.parseInt(field.get(obj).toString());
+            statusBarHeight = context.getResources().getDimensionPixelSize(x);
+        } catch (Exception e1) {
+            e1.printStackTrace();
+        }
+        return statusBarHeight;
+    }
+
+    /**
+     * 屏幕密度dpi
+     */
+    public static int getDpi(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(dm);
+        return dm.densityDpi;
+    }
+
+
+    /**
+     * 屏幕像素px
+     */
+    @SuppressLint("NewApi")
+    public static Point getDisplayPixel(Context context) {
+        Point point = new Point();
+        ((Activity) context).getWindowManager().getDefaultDisplay().getSize(point);
+        Log.d(TAG, "the screen size is " + point.toString());
+        ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(point);
+        Log.d(TAG, "the screen real size is " + point.toString());
+        return point;
+    }
+
+    /**
+     * 屏幕像素px
+     */
+    public static DisplayMetrics getScreenPixel(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(dm);
+        return dm;
+    }
+
+    /**
+     * 屏幕dp
+     */
+    public static int[] getDp(Context context) {
+        int width = getScreenPixel(context).widthPixels;         // 屏幕宽度(像素)
+        int height = getScreenPixel(context).heightPixels;       // 屏幕高度(像素)
+        float density = getDensity(getScreenPixel(context));         // 屏幕密度(0.75 / 1.0 / 1.5)
+        // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度
+        int screenWidth = (int) (width / density);  // 屏幕宽度(dp)
+        int screenHeight = (int) (height / density);// 屏幕高度(dp)
+        return new int[]{screenWidth, screenHeight};
+    }
+
+    /**
+     * 屏幕密度
+     */
+    public static float getDensity(DisplayMetrics dm) {
+        return dm.density;         // 屏幕密度(0.75 / 1.0 / 1.5);
+    }
+
+    /**
+     * 屏幕密度dpi
+     */
+    public static String getDensity(Context context) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        Log.d(TAG, "Density is " + displayMetrics.density + " densityDpi is " + displayMetrics.densityDpi + " height: " + displayMetrics.heightPixels +
+                " width: " + displayMetrics.widthPixels);
+        return "Density is " + displayMetrics.density + " densityDpi is " + displayMetrics.densityDpi + " height: " + displayMetrics.heightPixels +
+                " width: " + displayMetrics.widthPixels;
+    }
+
+
+    /**
+     * 屏幕尺寸inch
+     */
+    public static double getScreenInch(Context context) {
+
+        try {
+            int realWidth = 0, realHeight = 0;
+            Display display = ((Activity) context).getWindowManager().getDefaultDisplay();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            if (Build.VERSION.SDK_INT >= 17) {
+                Point size = new Point();
+                display.getRealSize(size);
+                realWidth = size.x;
+                realHeight = size.y;
+            } else if (Build.VERSION.SDK_INT < 17
+                    && Build.VERSION.SDK_INT >= 14) {
+                Method mGetRawH = Display.class.getMethod("getRawHeight");
+                Method mGetRawW = Display.class.getMethod("getRawWidth");
+                realWidth = (Integer) mGetRawW.invoke(display);
+                realHeight = (Integer) mGetRawH.invoke(display);
+            } else {
+                realWidth = metrics.widthPixels;
+                realHeight = metrics.heightPixels;
+            }
+
+            return formatDouble(Math.sqrt((realWidth / metrics.xdpi) * (realWidth / metrics.xdpi) + (realHeight / metrics.ydpi) * (realHeight / metrics.ydpi)), 1);
+
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return 0;
+    }
+
+    /**
+     * dp获取dip
+     * @param dp
+     * @return
+     */
+    public static int convertDpToPixel(float dp, Context context) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        return (int) (dp * displayMetrics.density);
+    }
+
+    /***
+     * px获取dip
+     * @param pixel
+     * @return
+     */
+    public static int convertPixelToDp(int pixel, Context context) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        return (int) (pixel / displayMetrics.density);
+    }
+
+    public static String getSerialNumber(Context context) {
+        String serial = "";
+        try {
+            if (Build.VERSION.SDK_INT >= 28) {//9.0+
+                if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
+                    Log.i(TAG, "getMEID meid: READ_PHONE_STATE" );
+                    ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.READ_PHONE_STATE}, 1567);
+                } else {
+                    serial = Build.getSerial();
+                }
+            } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
+                serial = Build.SERIAL;
+            } else {//8.0-
+                Class<?> c = Class.forName("android.os.SystemProperties");
+                Method get = c.getMethod("get", String.class);
+                serial = (String) get.invoke(c, "ro.serialno");
+            }
+        } catch (Exception e) {
+            Log.e("e", "读取设备序列号异常:" + e.toString());
+        }
+        return serial;
+    }
+
+    public static  String getIMEI(Context context) {
+        String deviceId = "";
+        TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (null != tm) {
+            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
+                Log.i(TAG, "getMEID meid: READ_PHONE_STATE" );
+                ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.READ_PHONE_STATE}, 1567);
+            } else {
+                if (tm.getDeviceId() != null) {
+                    deviceId = tm.getDeviceId();
+                } else {
+                    deviceId = Settings.Secure.getString(context.getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
+                }
+            }
+            Log.d("deviceId--->", deviceId);
+        }
+        return "";
+    }
+
+    public static  String getMEID() {
+        try {
+            Class clazz = Class.forName("android.os.SystemProperties");
+            Method method = clazz.getMethod("get", String.class, String.class);
+
+            String meid = (String) method.invoke(null, "ril.cdma.meid", "");
+            if (!TextUtils.isEmpty(meid)) {
+                Log.d(TAG, "getMEID meid: " + meid);
+                return meid;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.w(TAG, "getMEID error : " + e.getMessage());
+        }
+        return "";
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java b/app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java
new file mode 100644
index 0000000..73494a0
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/util/DimensionUtils.java
@@ -0,0 +1,51 @@
+package com.runt.open.mvvm.util;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+/**
+ * Created by Administrator on 2021/11/2 0002.
+ */
+public class DimensionUtils {
+    /**
+     * dp获取dip
+     * @param dp
+     * @return
+     */
+    public int convertDpToPixel(float dp,Context context) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        return (int) (dp * displayMetrics.density);
+    }
+
+    /***
+     * px获取dip
+     * @param pixel
+     * @return
+     */
+    public int convertPixelToDp(int pixel,Context context) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        return (int) (pixel / displayMetrics.density);
+    }
+    /**
+     * 把pix值转换为sp
+     *
+     * @return
+     */
+    public static float convertPixelToSp( float pixValue,Context context) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return  pixValue / fontScale + 0.5f;
+    }
+
+    /**
+     * 将sp值转换为px值,保证文字大小不变
+     *
+     * @param spValue
+     * @param context
+     *            (DisplayMetrics类中属性scaledDensity)
+     * @return
+     */
+    public static int convertSpToPixel(Context context, float spValue) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (spValue * fontScale + 0.5f);
+    }
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java b/app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java
new file mode 100644
index 0000000..080cdfe
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/util/GsonUtils.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.runt.open.mvvm.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/runt/open/mvvm/util/MyLog.java b/app/src/main/java/com/runt/open/mvvm/util/MyLog.java
new file mode 100644
index 0000000..8a3b994
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/util/MyLog.java
@@ -0,0 +1,42 @@
+package com.runt.open.mvvm.util;
+
+import android.util.Log;
+
+import com.runt.open.mvvm.BuildConfig;
+
+
+/**
+ * My father is Object, ites purpose of
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-4-5.
+ */
+public class MyLog {
+
+
+    public static void i(String tag,String msg){
+        if(BuildConfig.DEBUG) {
+            Log.i(tag, BuildConfig.BUILD_TYPE +" "+ msg);
+        }
+    }
+
+    public static void e(String tag,String msg){
+        if(BuildConfig.DEBUG) {
+            Log.e(tag, msg);
+        }
+    }
+
+
+    public static void d(String tag,String msg){
+        if(BuildConfig.DEBUG) {
+            Log.d(tag, msg);
+        }
+    }
+
+
+    public static void v(String tag,String msg){
+        if(BuildConfig.DEBUG) {
+            Log.v(tag, msg);
+        }
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java b/app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java
new file mode 100644
index 0000000..dc1d86a
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/util/NetWorkUtils.java
@@ -0,0 +1,212 @@
+package com.runt.open.mvvm.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/java/com/runt/open/mvvm/util/SpUtils.java b/app/src/main/java/com/runt/open/mvvm/util/SpUtils.java
new file mode 100644
index 0000000..5233129
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/util/SpUtils.java
@@ -0,0 +1,476 @@
+package com.runt.open.mvvm.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.collection.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Created by Administrator on 2021/10/28 0028.
+ */
+public class SpUtils {
+
+    static SpUtils instance;
+    
+    /**
+     * 获取SP实例
+     *
+     * @return {@link SpUtils}
+     */
+    public static SpUtils getInstance() {
+        if(instance == null){
+            instance = new SpUtils();
+        }
+        return instance;
+    }
+
+    
+    public final String PROJECT = "project";
+    public final String USER = "user";
+
+
+    public boolean clearData(Context context, String keyShared){
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        return settings.edit().clear().commit();
+    }
+
+
+    public boolean clearData(Context context, String key, String keyShared){
+        putString(context,key,null,keyShared);
+        return true;
+    }
+
+
+
+    /**
+     * remove key preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public void  removeKey(Context context, String key, String keyShared){
+
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.remove(key);
+    }
+
+    /**
+     * remove value preferences
+     *
+     * @param context
+     * @param value The name of the preference to modify
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public void  removeValue(Context context, String value, String keyShared){
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.remove(value);
+    }
+
+
+    /**
+     * put string preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @param value The new value for the preference
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public boolean putString(Context context, String key, String value, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putString(key, value);
+        return editor.commit();
+    }
+
+    /**
+     * get string preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @return The preference value if it exists, or null. Throws ClassCastException if there is a preference with this
+     *         name that is not a string
+     * @see #getString(Context, String, String)
+     */
+    public String getString(Context context, String key, String keyShared) {
+        return getString(context, key, null,keyShared);
+    }
+
+    /**
+     * get string preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @param defaultValue Value to return if this preference does not exist
+     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
+     *         this name that is not a string
+     */
+    public String getString(Context context, String key, String defaultValue, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        return settings.getString(key, defaultValue);
+    }
+
+    /**
+     * put int preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @param value The new value for the preference
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public boolean putInt(Context context, String key, int value, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putInt(key, value);
+        return editor.commit();
+    }
+
+    /**
+     * get int preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this
+     *         name that is not a int
+     */
+    public int getInt(Context context, String key, String keyShared) {
+        return getInt(context, key, -1,keyShared);
+    }
+
+    /**
+     * get int preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @param defaultValue Value to return if this preference does not exist
+     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
+     *         this name that is not a int
+     */
+    public int getInt(Context context, String key, int defaultValue, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        try {
+            return settings.getInt(key, defaultValue);
+        }catch (ClassCastException e){
+            try {
+                return Integer.parseInt(settings.getString(key,defaultValue+""));
+            }catch (NumberFormatException en){
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * put long preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @param value The new value for the preference
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public boolean putLong(Context context, String key, long value, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putLong(key, value);
+        return editor.commit();
+    }
+
+    /**
+     * get long preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this
+     *         name that is not a long
+     */
+    public long getLong(Context context, String key, String keyShared) {
+        return getLong(context, key, -1,keyShared);
+    }
+
+    /**
+     * get long preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @param defaultValue Value to return if this preference does not exist
+     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
+     *         this name that is not a long
+     */
+    public long getLong(Context context, String key, long defaultValue, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        try {
+            return settings.getLong(key, defaultValue);
+        }catch (ClassCastException e){
+            try {
+                return Long.parseLong(settings.getString(key,defaultValue+""));
+            }catch (NumberFormatException en){
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * put float preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @param value The new value for the preference
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public boolean putFloat(Context context, String key, float value, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putFloat(key, value);
+        return editor.commit();
+    }
+
+    /**
+     * get float preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this
+     *         name that is not a float
+     */
+    public float getFloat(Context context, String key, String keyShared) {
+        return getFloat(context, key, -1,keyShared);
+    }
+
+    /**
+     * get float preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @param defaultValue Value to return if this preference does not exist
+     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
+     *         this name that is not a float
+     */
+    public float getFloat(Context context, String key, float defaultValue, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        try {
+            return settings.getFloat(key, defaultValue);
+        }catch (ClassCastException e){
+            try {
+                return Float.parseFloat(settings.getString(key,defaultValue+""));
+            }catch (NumberFormatException en){
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * put boolean preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @param value The new value for the preference
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public boolean putBoolean(Context context, String key, boolean value, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putBoolean(key, value);
+        return editor.commit();
+    }
+
+    /**
+     * get boolean preferences, default is false
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this
+     *         name that is not a boolean
+     */
+    public boolean getBoolean(Context context, String key, String keyShared) {
+        return getBoolean(context, key, false,keyShared);
+    }
+
+    /**
+     * get boolean preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @param defaultValue Value to return if this preference does not exist
+     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
+     *         this name that is not a boolean
+     */
+    public boolean getBoolean(Context context, String key, boolean defaultValue, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        try {
+            return settings.getBoolean(key, defaultValue);
+        }catch (ClassCastException e){
+            try {
+                return Boolean.parseBoolean(settings.getString(key,defaultValue+""));
+            }catch (NumberFormatException en){
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * put boolean preferences
+     *
+     * @param context
+     * @param key The name of the preference to modify
+     * @param value The new value for the preference ,  the value of set ,canot be the other class out of java collection
+     * @return True if the new values were successfully written to persistent storage.
+     */
+    public boolean putStringSet(Context context, String key, Set value, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putStringSet(key, value);
+        return editor.commit();
+    }
+
+    /**
+     * get boolean preferences, default is false
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this
+     *         name that is not a boolean 获取出来的值最终被转换为hashset类型
+     */
+    public Set getStringSet(Context context, String key, String keyShared) {
+        return getStringSet(context, key,new ArraySet(),keyShared);
+    }
+
+    /**
+     * get boolean preferences
+     *
+     * @param context
+     * @param key The name of the preference to retrieve
+     * @param defaultValue Value to return if this preference does not exist
+     * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with
+     *         this name that is not a boolean 获取出来的值最终被转换为hashset类型
+     */
+    public Set getStringSet(Context context, String key, Set defaultValue, String keyShared) {
+        SharedPreferences settings = context.getSharedPreferences(keyShared, Context.MODE_PRIVATE);
+        return settings.getStringSet(key, defaultValue);
+    }
+
+    public boolean getBooleanValOfUser(Context context, String key){
+        return getBoolean(context,key,false,USER);
+    }
+
+    public boolean getBooleanValOfProject(Context context, String key){
+        return getBoolean(context,key,false,PROJECT);
+    }
+
+    public String getStringValOfUser(Context context, String key){
+        return getString(context,key,"",USER);
+    }
+
+    public String getStringValOfProject(Context context, String key){
+        return getString(context,key,"",PROJECT);
+    }
+
+    public int getIntValOfProject(Context context, String key){
+        return getInt(context,key,0,PROJECT);
+    }
+
+    public Long getLongValOfProject(Context context, String key){
+        return getLong(context,key,0,PROJECT);
+    }
+
+    public float getFloatValOfProject(Context context, String key){
+        return getFloat(context,key,0,PROJECT);
+    }
+
+    public Set getStringSetValOfProject(Context context, String key){
+        return getStringSet(context,key,PROJECT);
+    }
+
+    public int getIntValOfUser(Context context, String key){
+        return getInt(context,key,0,USER);
+    }
+
+    public Long getLongValOfUser(Context context, String key){
+        return getLong(context,key,0,USER);
+    }
+
+    public float getFloatValOfUser(Context context, String key){
+        return getFloat(context,key,0,USER);
+    }
+
+    public Set getStringSetValOfUser(Context context, String key){
+        return getStringSet(context,key,USER);
+    }
+
+
+    public void putBooleanValOfUser(Context context, String key ,Boolean value){
+        putBoolean(context,key,value,USER);
+    }
+
+    public void putBooleanValOfProject(Context context, String key,Boolean value){
+        putBoolean(context,key,value,PROJECT);
+    }
+
+    public void putStringValOfUser(Context context,String key,String value){
+        putString(context,key,value,USER);
+    }
+
+    public void putStringValOfProject(Context context,String key,String value){
+        putString(context,key,value,PROJECT);
+    }
+
+    public void putIntValOfProject(Context context,String key,int value){
+        putInt(context,key,value,PROJECT);
+    }
+
+    public void putLongValOfProject(Context context,String key,long value){
+        putLong(context,key,value,PROJECT);
+    }
+
+    public void putFloatValOfProject(Context context,String key,float value){
+        putFloat(context,key,value,PROJECT);
+    }
+
+    public void putStringSetValOfProject(Context context,String key,Set value){
+        putStringSet(context,key,value,PROJECT);
+    }
+
+    public void putIntValOfUser(Context context,String key,int value){
+        putInt(context,key,value,USER);
+    }
+
+    public void putLongValOfUser(Context context,String key,long value){
+        putLong(context,key,value,USER);
+    }
+
+    public void putFloatValOfUser(Context context,String key,float value){
+        putFloat(context,key,value,USER);
+    }
+
+    public void putStringSetValOfUser(Context context,String key, Set value){
+        putStringSet(context,key,value,USER);
+    }
+
+
+    public void removeUserKey(Context context, String key){
+        removeKey(context,key,USER);
+    }
+
+    public void removeProjectKey(Context context, String key){
+        removeKey(context,key,PROJECT);
+    }
+
+    public void removeUserValue(Context context, String Value){
+        removeValue(context,Value,USER);
+    }
+
+    public void removeProjectValue(Context context, String Value){
+        removeValue(context,Value,PROJECT);
+    }
+
+
+    public void clearProjectData(Context context){
+        clearData(context,PROJECT);
+    }
+    public void clearUserData(Context context){
+        clearData(context,USER);
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java b/app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java
new file mode 100644
index 0000000..f75dc39
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/widgets/ClearEditText.java
@@ -0,0 +1,194 @@
+package com.runt.open.mvvm.widgets;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.CycleInterpolator;
+import android.view.animation.TranslateAnimation;
+import android.widget.EditText;
+
+import com.runt.open.mvvm.R;
+
+
+/**
+ * My father is Object, ites purpose of
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-2-25.
+ */
+@SuppressLint("AppCompatCustomView")
+public class ClearEditText extends EditText implements View.OnFocusChangeListener,
+        TextWatcher {
+    /**
+     * 删除按钮的引用
+     */
+    private Drawable mClearDrawable;
+    /**
+     * 控件是否有焦点
+     */
+    private boolean hasFoucs;
+
+    /**
+     * 是否可清除内容
+     */
+    private boolean isClearable = true;
+
+    /**
+     * @param context
+     * @Description TODO
+     */
+    public ClearEditText(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * @param context
+     * @param attrs
+     * @Description TODO
+     */
+    public ClearEditText(Context context, AttributeSet attrs) {
+        // 这里构造方法也很重要,不加这个很多属性不能再XML里面定义
+        this(context, attrs, android.R.attr.editTextStyle);
+    }
+
+    /**
+     * @param context
+     * @param attrs
+     * @param defStyle
+     * @Description TODO
+     */
+    public ClearEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        // 获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
+        mClearDrawable = getCompoundDrawables()[2];
+        if (mClearDrawable == null) {
+            // throw new
+            // NullPointerException("You can add drawableRight attribute in XML");
+            mClearDrawable = getResources().getDrawable(R.mipmap.icon_delete);
+        }
+
+        mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(),
+                mClearDrawable.getIntrinsicHeight());
+
+        // 默认设置隐藏图标
+        setClearIconVisible(false);
+        // 设置焦点改变的监听
+        setOnFocusChangeListener(this);
+        // 设置输入框里面内容发生改变的监听
+        addTextChangedListener(this);
+    }
+
+    /**
+     * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件 当我们按下的位置 在 EditText的宽度 -
+     * 图标到控件右边的间距 - 图标的宽度 和 EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (getCompoundDrawables()[2] != null) {
+
+                boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight())
+                        && (event.getX() < ((getWidth() - getPaddingRight())));
+
+                if (touchable && isClearable) {
+                    this.setText("");
+                }
+            }
+        }
+
+        return super.onTouchEvent(event);
+    }
+
+    /**
+     * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏
+     */
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        this.hasFoucs = hasFocus;
+        if (hasFocus) {
+            setClearIconVisible(getText().toString().length() > 0);
+        } else {
+            setClearIconVisible(false);
+        }
+    }
+
+    /**
+     * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
+     *
+     * @param visible
+     */
+    public void setClearIconVisible(boolean visible) {
+        Drawable right = visible ? mClearDrawable : null;
+        setCompoundDrawables(getCompoundDrawables()[0],
+                getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
+    }
+
+    /**
+     * 当输入框里面内容发生变化的时候回调的方法
+     */
+    @Override
+    public void onTextChanged(CharSequence s, int start, int count, int after) {
+        if (hasFoucs) {
+            setClearIconVisible(s.toString().length() > 0);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count,
+                                  int after) {
+
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+
+    }
+
+    /**
+     * 设置晃动动画
+     */
+    public void startShakeAnimation() {
+        this.startAnimation(shakeAnimation(5));
+    }
+
+    /**
+     * 晃动动画
+     *
+     * @param counts 1秒钟晃动多少下
+     * @return
+     */
+    public static Animation shakeAnimation(int counts) {
+        Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
+        translateAnimation.setInterpolator(new CycleInterpolator(counts));
+        translateAnimation.setDuration(1000);
+        return translateAnimation;
+    }
+
+    /**
+     * 设置是否可清除
+     *
+     * @param clearable
+     */
+    public void setClearable(boolean clearable) {
+        isClearable = clearable;
+    }
+
+    /**
+     * 获取是否可清除
+     *
+     * @return
+     */
+    public boolean getClearable() {
+        return isClearable;
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java b/app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java
new file mode 100644
index 0000000..90feadf
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/widgets/QuadrateLinearLayout.java
@@ -0,0 +1,40 @@
+package com.runt.open.mvvm.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+/**
+ * My father is Object, ites purpose of   正方形控件
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2019-3-24.
+ */
+
+public class QuadrateLinearLayout extends LinearLayout {
+
+    public QuadrateLinearLayout(Context context, AttributeSet attrs,
+                                int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public QuadrateLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public QuadrateLinearLayout(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
+                getDefaultSize(0, heightMeasureSpec));
+
+        int childWidthSize = getMeasuredWidth();
+        // 高度和宽度一样
+        heightMeasureSpec = widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                childWidthSize, MeasureSpec.EXACTLY);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+}
diff --git a/app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java b/app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java
new file mode 100644
index 0000000..746b72a
--- /dev/null
+++ b/app/src/main/java/com/runt/open/mvvm/widgets/TitleBarView.java
@@ -0,0 +1,256 @@
+package com.runt.open.mvvm.widgets;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+
+import com.runt.open.mvvm.R;
+import com.runt.open.mvvm.util.DimensionUtils;
+
+
+/**
+ * Created by Administrator on 2021/11/1 0001.
+ */
+public class TitleBarView extends View {
+    Drawable leftDra,rightDra;
+    private Rect mRect;       // 绘制区域
+    private RectF leftClickRect,rightClickRect;//点击事件响应区域
+    String titleText,rightText;
+    @ColorInt int titleColor,rightTextColor;
+    float titleSize,rightTextSize,rightPadding;
+    Paint textPaint,rightTextPaint,drawPaint;
+    int viewWidth,viewHeight;
+    OnClickListener leftClick,rightClick;
+    int touchStartX,touchStartY;
+
+
+    public TitleBarView(Context context) {
+        this(context,null);
+    }
+
+    public TitleBarView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs,0);
+    }
+
+    public TitleBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initWidget(context.obtainStyledAttributes(attrs, R.styleable.TitleBarView));
+    }
+
+    public TitleBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initWidget(context.obtainStyledAttributes(attrs,R.styleable.TitleBarView));
+    }
+
+    public void initWidget(TypedArray array){
+        leftDra = array.getDrawable(R.styleable.TitleBarView_leftDrawable);
+        rightDra = array.getDrawable(R.styleable.TitleBarView_rightDrawable);
+        titleText = array.getString(R.styleable.TitleBarView_titleText);
+        titleColor = array.getColor(R.styleable.TitleBarView_titleTextColor, Color.BLACK);
+        titleSize = array.getDimension(R.styleable.TitleBarView_titleTextSize, DimensionUtils.convertSpToPixel(getContext(),16));
+        rightText = array.getString(R.styleable.TitleBarView_rightText);
+        rightTextColor = array.getColor(R.styleable.TitleBarView_rightTextColor,Color.BLACK);
+        rightTextSize = array.getDimension(R.styleable.TitleBarView_rightTextSize, DimensionUtils.convertSpToPixel(getContext(),14));
+        rightPadding = array.getDimension(R.styleable.TitleBarView_rightDrawablePadding,10);
+
+        textPaint = new Paint();
+        textPaint.setAntiAlias(true); // 是否抗锯齿
+        //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255
+        textPaint.setColor(titleColor);
+        textPaint.setTextSize(titleSize);
+        //        设置画笔属性
+        textPaint.setStyle(Paint.Style.FILL);//画笔属性是实心圆
+        //  paint.setStyle(Paint.Style.STROKE);//画笔属性是空心圆
+        textPaint.setStrokeWidth(4);//设置画笔粗细
+
+
+        rightTextPaint = new Paint();
+        rightTextPaint.setAntiAlias(true); // 是否抗锯齿
+        //mTextPaint.setAlpha(50); // 设置alpha不透明度,范围为0~255
+        rightTextPaint.setColor(rightTextColor);
+        rightTextPaint.setTextSize(rightTextSize);
+        //        设置画笔属性
+        rightTextPaint.setStyle(Paint.Style.FILL);//画笔属性是实心圆
+        //  paint.setStyle(Paint.Style.STROKE);//画笔属性是空心圆
+        rightTextPaint.setStrokeWidth(4);//设置画笔粗细
+
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        viewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+        viewHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+        mRect = new Rect(getPaddingLeft(),getPaddingTop(),getMeasuredWidth() - getPaddingRight(),getMeasuredHeight() - getPaddingBottom());
+        Log.e("TitleBarView","onMeasure mRect:"+mRect);
+
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        Log.e("TitleBarView","onDraw mRect:"+mRect);
+        if(leftDra != null){
+            final Bitmap bitmap = ((BitmapDrawable) leftDra).getBitmap();
+            float top = mRect.top+(viewHeight-bitmap.getHeight()*2f)/2;
+            float left = mRect.left-bitmap.getWidth()*0.5f;
+            leftClickRect = new RectF(left,top,left + (bitmap.getWidth()*2f),top + bitmap.getHeight()*2f);
+            canvas.drawBitmap(bitmap,mRect.left,mRect.top+(viewHeight-bitmap.getHeight())/2,null);
+
+        }
+        if(rightDra != null){
+            final Bitmap bitmap = ((BitmapDrawable) rightDra).getBitmap();
+            float chaTop = (viewHeight-bitmap.getHeight())/2;
+            float top = mRect.top+chaTop;
+            float left = mRect.right-bitmap.getWidth();
+            rightClickRect = new RectF(left-bitmap.getWidth()/2,top-bitmap.getHeight()/2,left+bitmap.getWidth()*2f,top+bitmap.getHeight()*1.5f);
+            canvas.drawBitmap(bitmap,left,top,null);
+        }
+        if(titleText != null){
+            final int textWidth = getTextWidth(textPaint, titleText);
+            final int textHeight = getTextHeight(textPaint);
+            final Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
+            canvas.drawText(titleText,mRect.left+((viewWidth-textWidth)/2),mRect.top+((viewHeight-textHeight)/2)+(0-fontMetrics.ascent),textPaint);
+        }
+        if(rightText != null){
+            final int textWidth = getTextWidth(rightTextPaint, rightText);
+            final int textHeight = getTextHeight(rightTextPaint);
+            final Paint.FontMetrics fontMetrics = rightTextPaint.getFontMetrics();
+            float left = mRect.right-textWidth;
+            float top = mRect.top+((viewHeight-textHeight)/2)+(0-fontMetrics.ascent);
+            rightClickRect = new RectF(left-textWidth/2,top-textHeight,left+textWidth*2f,top+textHeight/2);
+            canvas.drawText(rightText,left,top,rightTextPaint);
+        }
+    }
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                touchStartX = x;
+                touchStartY = y;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                break;
+            case MotionEvent.ACTION_UP:
+                if(isTouched(x,y,leftClickRect) && leftClick != null){
+                    leftClick.onClick(this);
+                }else if(isTouched(x,y,rightClickRect) && rightClick != null){
+                    rightClick.onClick(this);
+                }
+
+                break;
+        }
+        return true;
+    }
+
+    /**
+     * 是否为点击事件
+     * @param x
+     * @param y
+     * @param rectF
+     * @return
+     */
+    private boolean isTouched(int x, int y, RectF rectF){
+        if(rectF == null){
+            return false;
+        }
+        if(x < rectF.right && x > rectF.left &&
+                touchStartX < rectF.right && touchStartX > rectF.left &&
+                y < rectF.bottom && y > rectF.top &&
+                touchStartY < rectF.bottom && touchStartY > rectF.top  ){
+            return true;
+        }
+        return false;
+    }
+    public void setLeftDra(Drawable leftDra) {
+        this.leftDra = leftDra;
+        invalidate();
+    }
+
+    public void setRightDra(Drawable rightDra) {
+        this.rightDra = rightDra;
+        invalidate();
+    }
+
+    public void setTitleText(String titleText) {
+        this.titleText = titleText;
+        invalidate();
+    }
+
+    public void setRightText(String rightText) {
+        this.rightText = rightText;
+        invalidate();
+    }
+
+    public void setTitleColor(@ColorInt int titleColor) {
+        textPaint.setColor(titleColor);
+        invalidate();
+    }
+
+    public void setRightTextColor(@ColorInt int rightTextColor) {
+        rightTextPaint.setColor(rightTextColor);
+        invalidate();
+    }
+
+    public void setTitleSize(int titleSize) {
+        textPaint.setTextSize(DimensionUtils.convertSpToPixel(getContext(),titleSize));
+        invalidate();
+    }
+
+    public void setRightTextSize(float rightTextSize) {
+        rightTextPaint.setTextSize(DimensionUtils.convertSpToPixel(getContext(),rightTextSize));
+        invalidate();
+    }
+
+    public void setRightPadding(float rightPadding) {
+        this.rightPadding = rightPadding;
+        invalidate();
+    }
+
+    public void setLeftClick(OnClickListener leftClick) {
+        this.leftClick = leftClick;
+    }
+
+    public void setRightClick(OnClickListener rightClick) {
+        this.rightClick = rightClick;
+    }
+
+    //第二个参数是一个数组.传进去个长度跟字符串长度相同的float数组,方法调用后,里边塞的是每个字符的长度.
+    public  int  getTextWidth(Paint paint, String str) {
+        int iRet = 0;
+        if (str != null && str.length() > 0) {
+            int len = str.length();
+            float[] widths = new float[len];
+            paint.getTextWidths(str, widths);
+            for (int j = 0; j < len; j++) {
+                iRet += (int) Math.ceil(widths[j]);
+            }
+        }
+        return iRet;
+    }
+
+    //第二个参数是一个数组.传进去个长度跟字符串长度相同的float数组,方法调用后,里边塞的是每个字符的长度.
+    public  int  getTextHeight(Paint paint) {
+        Paint.FontMetrics fm = paint.getFontMetrics();
+        float height1 = fm.descent - fm.ascent;//文字的高度
+        float height2 = fm.bottom - fm.top + fm.leading;//行高
+        return (int) height2;
+    }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
new file mode 100644
index 0000000..663d572
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml
new file mode 100644
index 0000000..70fb291
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml
new file mode 100644
index 0000000..7009a67
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
+</vector>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..a57477b
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="?attr/actionBarSize" >
+
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/nav_view"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="0dp"
+        android:layout_marginEnd="0dp"
+        android:background="?android:attr/windowBackground"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_nav_menu"/>
+
+    <fragment
+        android:id="@+id/nav_host_fragment_activity_main"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:defaultNavHost="true"
+        app:layout_constraintBottom_toTopOf="@id/nav_view"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:navGraph="@navigation/mobile_navigation"
+    />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644
index 0000000..0ef53b9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.dashboard.DashboardFragment" >
+
+    <TextView
+        android:id="@+id/text_dashboard"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAlignment="center"
+        android:textSize="20sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
new file mode 100644
index 0000000..7ecfe18
--- /dev/null
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.home.HomeFragment" >
+
+    <TextView
+        android:id="@+id/text_home"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAlignment="center"
+        android:textSize="20sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml
new file mode 100644
index 0000000..01a3222
--- /dev/null
+++ b/app/src/main/res/layout/fragment_notifications.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.notifications.NotificationsFragment" >
+
+    <TextView
+        android:id="@+id/text_notifications"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAlignment="center"
+        android:textSize="20sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_null.xml b/app/src/main/res/layout/layout_null.xml
new file mode 100644
index 0000000..9d96af7
--- /dev/null
+++ b/app/src/main/res/layout/layout_null.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    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:layout_width="match_parent"
+    android:minHeight="400dp"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="暂无数据"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="center_horizontal" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/refresh_recycler.xml b/app/src/main/res/layout/refresh_recycler.xml
new file mode 100644
index 0000000..fceabe9
--- /dev/null
+++ b/app/src/main/res/layout/refresh_recycler.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.scwang.smart.refresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/smart_refresh"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        tools:itemCount="1"
+        tools:listitem="@layout/layout_null"/>
+</com.scwang.smart.refresh.layout.SmartRefreshLayout>
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
new file mode 100644
index 0000000..52321c6
--- /dev/null
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/navigation_home"
+        android:icon="@drawable/ic_home_black_24dp"
+        android:title="@string/title_home"/>
+
+    <item
+        android:id="@+id/navigation_dashboard"
+        android:icon="@drawable/ic_dashboard_black_24dp"
+        android:title="@string/title_dashboard"/>
+
+    <item
+        android:id="@+id/navigation_notifications"
+        android:icon="@drawable/ic_notifications_black_24dp"
+        android:title="@string/title_notifications"/>
+
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_delete.png b/app/src/main/res/mipmap-xxhdpi/icon_delete.png
new file mode 100644
index 0000000..a2fdf07
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/icon_delete.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
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..1887f2e
--- /dev/null
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -0,0 +1,25 @@
+<?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.runt.open.mvvm.ui.home.HomeFragment"
+        android:label="@string/title_home"
+        tools:layout="@layout/fragment_home" />
+
+    <fragment
+        android:id="@+id/navigation_dashboard"
+        android:name="com.runt.open.mvvm.ui.dashboard.DashboardFragment"
+        android:label="@string/title_dashboard"
+        tools:layout="@layout/fragment_dashboard" />
+
+    <fragment
+        android:id="@+id/navigation_notifications"
+        android:name="com.runt.open.mvvm.ui.notifications.NotificationsFragment"
+        android:label="@string/title_notifications"
+        tools:layout="@layout/fragment_notifications" />
+</navigation>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b15bd48
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="black_4">#373737</color>
+    <color name="white">#FFFFFFFF</color>
+    <color name="white_2">#F3F3F3</color>
+    <color name="red">#FF1414</color>
+    <color name="trans_red">#4DFF1414</color>
+    <color name="light_gray">#E3E3E3</color>
+    <color name="gray">#CDCDCD</color>
+    <color name="deep_gray">#808080</color>
+    <color name="trans_gray">#4DFFFFFF</color>
+    <color name="enable">#ECECEC</color>
+    <color name="enable_sky">#889DB6</color>
+    <color name="deep_sky">#4184D6</color>
+    <color name="sky">#509CFA</color>
+    <color name="gold">#FAD550</color>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..60f9c7e
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,9 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="small_radios">3dp</dimen>
+    <dimen name="circle">1000dp</dimen>
+    <dimen name="radios">10dp</dimen>
+    <dimen name="frame_margin_lr">10dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1c9793b
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<resources>
+    <string name="title_home">Home</string>
+    <string name="title_dashboard">Dashboard</string>
+    <string name="title_notifications">Notifications</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..f26a5c1
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <declare-styleable name="TitleBarView">
+        <attr name="leftDrawable" format="reference" />
+        <attr name="rightDrawable" format="reference" />
+        <attr name="rightDrawablePadding" format="dimension" />
+        <attr name="titleText" format="string" />
+        <attr name="titleTextSize" format="dimension" />
+        <attr name="titleTextColor" format="color" />
+        <attr name="rightText" format="string" />
+        <attr name="rightTextSize" format="dimension" />
+        <attr name="rightTextColor" format="color" />
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..21b5366
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.OpemMvvm" parent="Theme.MaterialComponents.Light.NoActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/sky</item>
+        <item name="colorPrimaryVariant">@color/red</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/black_4</item>
+        <item name="colorSecondaryVariant">@color/black_4</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
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
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7cbb664
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,17 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+    dependencies {
+        classpath "com.android.tools.build:gradle:7.0.3"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..52f5917
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1b585ee
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Nov 15 09:23:49 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..9563052
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,10 @@
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+        jcenter() // Warning: this repository is going to shut down soon
+    }
+}
+rootProject.name = "OpemMvvm"
+include ':app'

--
Gitblit v1.9.1