liupeng 5 lat temu
commit
58718907fd
100 zmienionych plików z 8600 dodań i 0 usunięć
  1. 14 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. BIN
      app/box.keystore
  4. 69 0
      app/build.gradle
  5. 21 0
      app/proguard-rules.pro
  6. 26 0
      app/src/androidTest/java/com/siwei/recyclebox/ExampleInstrumentedTest.java
  7. 96 0
      app/src/main/AndroidManifest.xml
  8. BIN
      app/src/main/assets/afterPushRod.mp3
  9. BIN
      app/src/main/assets/event.mp3
  10. BIN
      app/src/main/assets/netPullRod.mp3
  11. BIN
      app/src/main/assets/nonetPullRod.mp3
  12. BIN
      app/src/main/assets/onPushRod.mp3
  13. BIN
      app/src/main/assets/openLock.mp3
  14. 258 0
      app/src/main/java/com/siwei/recyclebox/application/AppApplication.java
  15. 109 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/BaseDeviceEntity.java
  16. 113 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/ByteUtil.java
  17. 282 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/ManyDevices.java
  18. 616 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/OtherDevice.java
  19. 103 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/PushRodDevice.java
  20. 216 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/RelayControllerDevice.java
  21. 177 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/SerialPortUtil.java
  22. 228 0
      app/src/main/java/com/siwei/recyclebox/deviceUtils/WeightDevice.java
  23. 24 0
      app/src/main/java/com/siwei/recyclebox/entity/DataEntity.java
  24. 23 0
      app/src/main/java/com/siwei/recyclebox/http/HttpDataSource.java
  25. 53 0
      app/src/main/java/com/siwei/recyclebox/http/HttpDataSourceImpl.java
  26. 22 0
      app/src/main/java/com/siwei/recyclebox/http/service/ApiService.java
  27. 30 0
      app/src/main/java/com/siwei/recyclebox/model/MainModel.java
  28. 26 0
      app/src/main/java/com/siwei/recyclebox/receiver/BCRUpgradeApk.java
  29. 20 0
      app/src/main/java/com/siwei/recyclebox/receiver/BootReceiver.java
  30. 45 0
      app/src/main/java/com/siwei/recyclebox/service/MyService.java
  31. 118 0
      app/src/main/java/com/siwei/recyclebox/ui/main/MainActivity.java
  32. 1935 0
      app/src/main/java/com/siwei/recyclebox/ui/main/MainViewModel.java
  33. 184 0
      app/src/main/java/com/siwei/recyclebox/utils/DemoOne.java
  34. 74 0
      app/src/main/java/com/siwei/recyclebox/utils/SilentInstall.java
  35. 111 0
      app/src/main/java/com/siwei/recyclebox/utils/unZipFileDemo.java
  36. 34 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  37. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  38. 338 0
      app/src/main/res/layout/activity_main.xml
  39. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  40. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  41. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  42. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  43. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  44. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  45. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  46. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  47. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  48. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  49. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  50. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  51. 6 0
      app/src/main/res/values/colors.xml
  52. 3 0
      app/src/main/res/values/strings.xml
  53. 11 0
      app/src/main/res/values/styles.xml
  54. 23 0
      app/src/main/res/xml/device_filter.xml
  55. 6 0
      app/src/main/res/xml/filepaths.xml
  56. 17 0
      app/src/test/java/com/siwei/recyclebox/ExampleUnitTest.java
  57. 30 0
      build.gradle
  58. 65 0
      config.gradle
  59. 15 0
      gradle.properties
  60. BIN
      gradle/wrapper/gradle-wrapper.jar
  61. 6 0
      gradle/wrapper/gradle-wrapper.properties
  62. 172 0
      gradlew
  63. 84 0
      gradlew.bat
  64. 1 0
      mvvmhabit/.gitignore
  65. 74 0
      mvvmhabit/build.gradle
  66. 26 0
      mvvmhabit/proguard-rules.pro
  67. 26 0
      mvvmhabit/src/androidTest/java/me/goldze/mvvmhabit/ExampleInstrumentedTest.java
  68. 21 0
      mvvmhabit/src/main/AndroidManifest.xml
  69. 199 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/AppManager.java
  70. 268 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseActivity.java
  71. 76 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseApplication.java
  72. 285 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseFragment.java
  73. 15 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseModel.java
  74. 247 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseViewModel.java
  75. 97 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/ContainerActivity.java
  76. 21 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/IBaseView.java
  77. 44 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/IBaseViewModel.java
  78. 11 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/IModel.java
  79. 16 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/ItemViewModel.java
  80. 25 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/MultiItemViewModel.java
  81. 69 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/ViewModelFactory.java
  82. 9 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingAction.java
  83. 75 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingCommand.java
  84. 10 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingConsumer.java
  85. 12 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingFunction.java
  86. 65 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/ResponseCommand.java
  87. 27 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/checkbox/ViewAdapter.java
  88. 55 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/edittext/ViewAdapter.java
  89. 28 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/image/ViewAdapter.java
  90. 114 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/listview/ViewAdapter.java
  91. 45 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/mswitch/ViewAdapter.java
  92. 26 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/radiogroup/ViewAdapter.java
  93. 163 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/recyclerview/DividerLine.java
  94. 43 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/recyclerview/LineManagers.java
  95. 113 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/recyclerview/ViewAdapter.java
  96. 64 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/scrollview/ViewAdapter.java
  97. 11 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/spinner/IKeyAndValue.java
  98. 70 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/spinner/ViewAdapter.java
  99. 32 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/swiperefresh/ViewAdapter.java
  100. 133 0
      mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/view/ViewAdapter.java

+ 14 - 0
.gitignore

xqd
@@ -0,0 +1,14 @@
+*.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
+.idea/

+ 1 - 0
app/.gitignore

xqd
@@ -0,0 +1 @@
+/build

BIN
app/box.keystore


+ 69 - 0
app/build.gradle

xqd
@@ -0,0 +1,69 @@
+apply plugin: 'com.android.application'
+
+android {
+
+    signingConfigs {
+        release {
+            keyAlias 'box.keystore'
+            keyPassword '159753'
+            storePassword '159753'
+//            storeFile file('C:\\AndroidStudiowork\\ai-garbage-box2\\app\\box.keystore')
+            storeFile file('\\box.keystore')
+        }
+        debug{
+            keyAlias 'box.keystore'
+            keyPassword '159753'
+            storePassword '159753'
+            storeFile file('\\box.keystore')
+        }
+    }
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "com.siwei.recyclebox"
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+        versionCode rootProject.ext.android.versionCode
+        versionName rootProject.ext.android.versionName
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    dataBinding {
+        enabled true
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            //
+            debuggable true
+            signingConfig signingConfigs.release
+        }
+        //以下配置记得添加!
+        debug {
+            signingConfig signingConfigs.release
+        }
+
+    }
+    compileOptions {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+}
+
+dependencies {
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+    implementation project(':mvvmhabit')
+    implementation 'com.aliyun.alink.linksdk:iot-linkkit:1.6.6'
+    implementation 'com.github.mik3y:usb-serial-for-android:2.1.0'
+    //内存泄漏测试
+    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
+    debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
+    implementation 'com.github.maybesix:Android-XHLibrary:v1.0.0'
+
+    implementation  'com.tencent.bugly:crashreport:3.1.0'
+}

+ 21 - 0
app/proguard-rules.pro

xqd
@@ -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

+ 26 - 0
app/src/androidTest/java/com/siwei/recyclebox/ExampleInstrumentedTest.java

xqd
@@ -0,0 +1,26 @@
+package com.siwei.recyclebox;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.siwei.recyclebox", appContext.getPackageName());
+    }
+}

+ 96 - 0
app/src/main/AndroidManifest.xml

xqd
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.siwei.recyclebox"
+
+    >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permisssion.READ_LOGS"/>
+<!--    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>-->
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-feature
+        android:name="android.hardware.bluetooth_le"
+        android:required="true" />
+    <uses-feature android:name="android.hardware.location.gps" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+
+    <!--Android 6.0及后续版本扫描蓝牙,需要定位权限(进入GPS设置,可以看到蓝牙定位)-->
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="com.android.example.USB_PERMISSION" />
+
+    <uses-feature
+        android:name="android.hardware.usb.host"
+        android:required="true" />
+    <uses-permission android:name="android.hardware.usb.host" />
+
+
+
+    <application
+        android:hardwareAccelerated="false"
+        android:largeHeap="true"
+        android:persistent="true"
+        android:name=".application.AppApplication"
+        android:allowBackup="true"
+        tools:replace="android:allowBackup"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="com.siwei.recyclebox.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/filepaths"
+                />
+        </provider>
+        <activity android:name=".ui.main.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+            </intent-filter>
+            <meta-data
+                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+                android:resource="@xml/device_filter" />
+        </activity>
+        <service android:name=".service.MyService" />
+        <receiver android:name=".receiver.BootReceiver"  android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".receiver.BCRUpgradeApk"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_REPLACED"/>
+                <data android:scheme="package"/>
+            </intent-filter>
+
+        </receiver>
+
+    </application>
+
+</manifest>

BIN
app/src/main/assets/afterPushRod.mp3


BIN
app/src/main/assets/event.mp3


BIN
app/src/main/assets/netPullRod.mp3


BIN
app/src/main/assets/nonetPullRod.mp3


BIN
app/src/main/assets/onPushRod.mp3


BIN
app/src/main/assets/openLock.mp3


+ 258 - 0
app/src/main/java/com/siwei/recyclebox/application/AppApplication.java

xqd
@@ -0,0 +1,258 @@
+package com.siwei.recyclebox.application;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.speech.tts.TextToSpeech;
+import android.support.v4.app.ActivityCompat;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.aliyun.alink.dm.api.DeviceInfo;
+import com.aliyun.alink.dm.model.ResponseModel;
+import com.aliyun.alink.linkkit.api.ILinkKitConnectListener;
+import com.aliyun.alink.linkkit.api.IoTMqttClientConfig;
+import com.aliyun.alink.linkkit.api.LinkKit;
+import com.aliyun.alink.linkkit.api.LinkKitInitParams;
+import com.aliyun.alink.linksdk.channel.core.persistent.mqtt.MqttConfigure;
+import com.aliyun.alink.linksdk.channel.core.persistent.mqtt.request.MqttPublishRequest;
+import com.aliyun.alink.linksdk.cmp.connect.hubapi.HubApiRequest;
+import com.aliyun.alink.linksdk.cmp.core.base.AMessage;
+import com.aliyun.alink.linksdk.cmp.core.base.ARequest;
+import com.aliyun.alink.linksdk.cmp.core.base.AResponse;
+import com.aliyun.alink.linksdk.cmp.core.base.ConnectState;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectNotifyListener;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSendListener;
+import com.aliyun.alink.linksdk.tmp.api.OutputParams;
+import com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper;
+import com.aliyun.alink.linksdk.tmp.listener.IPublishResourceListener;
+import com.aliyun.alink.linksdk.tools.AError;
+import com.aliyun.alink.linksdk.tools.ALog;
+import com.siwei.recyclebox.BuildConfig;
+import com.siwei.recyclebox.deviceUtils.SerialPortUtil;
+import com.siwei.recyclebox.ui.main.MainActivity;
+import com.siwei.recyclebox.R;
+import com.siwei.recyclebox.ui.main.MainViewModel;
+import com.squareup.leakcanary.LeakCanary;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.LogRecord;
+
+import me.goldze.mvvmhabit.base.BaseApplication;
+import me.goldze.mvvmhabit.crash.CaocConfig;
+import me.goldze.mvvmhabit.crash.CustomActivityOnCrash;
+import me.goldze.mvvmhabit.utils.KLog;
+import me.goldze.mvvmhabit.utils.SPUtils;
+import me.goldze.mvvmhabit.utils.StringUtils;
+import com.siwei.recyclebox.ui.main.MainViewModel;
+import com.tencent.bugly.crashreport.CrashReport;
+
+
+/**
+ * Created by goldze on 2017/7/16.
+ */
+
+public class AppApplication extends BaseApplication implements CustomActivityOnCrash.EventListener{
+
+    private TextToSpeech mTextToSpeech;
+    private String TAG = "Application";
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        SerialPortUtil.getInstance().init(this);        //初+始化串口
+        CrashReport.initCrashReport(getApplicationContext(), "40c3954b93", true);//测试阶段为true  发布时改成false
+
+        Log.i(TAG,"AppApplication------------------------");
+        //是否开启打印日志
+        KLog.init(BuildConfig.DEBUG);
+        //初始化全局异常崩溃
+        initCrash();
+        //内存泄漏检测
+        if (!LeakCanary.isInAnalyzerProcess(this)) {
+            LeakCanary.install(this);
+        }
+//        initAliIoT();
+        initIoTDynamic();
+
+    }
+
+    private Handler mHandler=new Handler(msg ->{
+        return true;
+    });
+    private void initAliIoT(DeviceInfo deviceInfo){
+        /**
+         * 设置设备三元组信息
+         */
+//        DeviceInfo deviceInfo = new DeviceInfo();
+//        deviceInfo.productKey = "a13H8L6bDyf";// 产品类型
+//        deviceInfo.deviceName = "9pD3trz6OaDV8GF7yRsb";// 设备名称
+////        deviceInfo.deviceSecret = "A08BEZ1GvOXZodAASIogQBi97f3Le1Cu";// 设备密钥
+//        deviceInfo.deviceSecret = SPUtils.getInstance().getString("deviceSecret");
+        /**
+         * 设置设备当前的初始状态值,属性需要和云端创建的物模型属性一致
+         * 如果这里什么属性都不填,物模型就没有当前设备相关属性的初始值。
+         * 用户调用物模型上报接口之后,物模型会有相关数据缓存。
+         */
+        Map<String, ValueWrapper> propertyValues = new HashMap<>();
+        //心跳包 interval 单位秒
+        MqttConfigure.setKeepAliveInterval(300);
+        // 示例
+        // propertyValues.put("LightSwitch", new ValueWrapper.BooleanValueWrapper(0));
+        IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(deviceInfo.productKey, deviceInfo.deviceName, deviceInfo.deviceSecret);
+        // 对应 receiveOfflineMsg = !cleanSession, 默认不接受离线消息
+        clientConfig.receiveOfflineMsg = true;
+        // 慎用 设置 mqtt 请求域名,默认 productKey+".iot-as-mqtt.cn-shanghai.aliyuncs.com:1883" ,如果无具体的业务需求,请不要设置。
+        //clientConfig.channelHost = "xxx";
+        LinkKitInitParams params = new LinkKitInitParams();
+        params.deviceInfo = deviceInfo;
+        params.propertyValues = propertyValues;
+        params.mqttClientConfig = clientConfig;
+        /**
+         * 设备初始化建联
+         * onError 初始化建联失败,需要用户重试初始化。如因网络问题导致初始化失败。
+         * onInitDone 初始化成功
+         */
+        Log.i(TAG,params.toString()+"设备三元组信息");
+        LinkKit.getInstance().init(this, params, new ILinkKitConnectListener() {
+            @Override
+            public void onError(AError error) {
+                // 初始化失败 error包含初始化错误信息
+//                initIoTDynamic();
+                KLog.e("初始化失败."+error.getMsg());
+
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        initIoTDynamic();
+                    }
+                },30000);
+            }
+            @Override
+            public void onInitDone(Object data) {
+                // 初始化成功 data 作为预留参数
+                KLog.d("初始化成功,data:"+data);
+                MqttPublishRequest request = new MqttPublishRequest();
+
+
+            }
+        });
+//        ALog.setLevel(ALog.LEVEL_DEBUG);//TODO 开启内部日志输出
+        /*-----------连接状态监听---------------------------*/
+    }
+
+    /**
+     * 一型一密动态注册。
+     */
+    private void initIoTDynamic(){
+        /**
+         * 注意:动态注册成功,设备上线之后,不能再次执行动态注册,云端会返回已注册错误信息。
+         *   因此用户在编程时首先需要判断设备是否已获取过deviceSecret,没有获取过的情况下再
+         *   调用动态注册接口去获取deviceSecret
+         */
+        DeviceInfo myDeviceInfo = new DeviceInfo();
+        myDeviceInfo.productKey = "a13H8L6bDyf";
+        myDeviceInfo.productSecret = "Nc4Y4KsjofejCy27";
+        System.out.println(MainViewModel.getDeviceId(this)+"----------------------------+");
+        myDeviceInfo.deviceName = MainViewModel.getDeviceId(this);
+//        myDeviceInfo.deviceName="123456";
+//        SPUtils.getInstance().put("deviceSecret","lzR12lxbbuV3c4tZMxrIPl1Vh9K9Ssv4");//存
+        String secret = SPUtils.getInstance().getString("deviceSecret");//读
+        Log.i(TAG,"secret=="+secret);
+
+        if(secret != null &&  !secret.equals("")){
+            myDeviceInfo.deviceSecret = secret;
+            initAliIoT(myDeviceInfo);
+            return;
+        }
+        LinkKitInitParams params = new LinkKitInitParams();
+        params.deviceInfo = myDeviceInfo;
+        System.out.println("-------"+myDeviceInfo.deviceName+"  "+myDeviceInfo.productKey);
+// 设置动态注册请求 path 和 域名,域名使用默认即可
+        HubApiRequest hubApiRequest = new HubApiRequest();
+        hubApiRequest.path = "/auth/register/device";
+//        hubApiRequest.domain
+        LinkKit.getInstance().deviceRegister(this, params, hubApiRequest, new IConnectSendListener() {
+            @Override
+            public void onResponse(ARequest aRequest, AResponse aResponse) {
+                // aRequest 用户的请求数据
+                System.out.println("arequest:"+aRequest.toString()+"  aResponse:"+aResponse);
+                if (aResponse != null && aResponse.data != null) {
+                    ResponseModel<Map<String, String>> response = JSONObject.parseObject(aResponse.data.toString(),
+                            new TypeReference<ResponseModel<Map<String, String>>>() {
+                            }.getType());
+                    Log.i(TAG,"response=="+response+"/"+response.code);
+                    if ("200".equals(response.code) && response.data != null && response.data.containsKey("deviceSecret") &&
+                            !TextUtils.isEmpty(response.data.get("deviceSecret"))) {
+                        System.out.println("deviceSecret:"+response.data.get("deviceSecret"));
+                        myDeviceInfo.deviceSecret = response.data.get("deviceSecret");
+                        SPUtils.getInstance().put("deviceSecret",myDeviceInfo.deviceSecret);//存
+                        initAliIoT(myDeviceInfo);
+                    }
+                }
+            }
+            @Override
+            public void onFailure(ARequest aRequest, AError aError) {
+                System.out.println("code:"+aError.getCode()+"  subCode:"+aError.getSubCode());
+                System.out.println("msg:"+aError.getMsg());
+                Log.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError.getSubMsg() + "]");
+            }
+        });
+//  ####### 一型一密动态注册接口结束  ######
+    }
+    @Override
+    public void onTerminate() {
+        super.onTerminate();
+
+    }
+    /*
+    * 全局异常捕捉
+    * */
+    private void initCrash() {
+        CaocConfig.Builder.create()
+                .backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT) //背景模式,开启沉浸式
+                .enabled(true) //是否启动全局异常捕获
+                .showErrorDetails(true) //是否显示错误详细信息
+                .showRestartButton(true) //是否显示重启按钮
+                .trackActivities(true) //是否跟踪Activity
+                .minTimeBetweenCrashesMs(2000) //崩溃的间隔时间(毫秒)
+                .errorDrawable(R.mipmap.ic_launcher) //错误图标
+                .restartActivity(MainActivity.class) //重新启动后的activity
+//                .errorActivity(YourCustomErrorActivity.class) //崩溃后的错误activity
+                .eventListener(this) //崩溃后的错误监听
+                .apply();
+
+    }
+    @Override
+    public void onLaunchErrorActivity() {
+        Log.i(TAG,"1111+++");
+            Intent mStartActivity = new Intent(this,MainActivity.class);
+            int mPendingIntentId = 123456;
+            PendingIntent mPendingIntent = PendingIntent.getActivity(this, mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
+            AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
+            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
+            System.exit(0);
+
+    }
+
+    @Override
+    public void onRestartAppFromErrorActivity() {
+        Log.i(TAG,"222+++0");
+    }
+
+    @Override
+    public void onCloseAppFromErrorActivity() {
+        Log.i(TAG,"333++++");
+    }
+}

+ 109 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/BaseDeviceEntity.java

xqd
@@ -0,0 +1,109 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+import android.util.Log;
+
+import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.hoho.android.usbserial.util.SerialInputOutputManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import okio.ByteString;
+
+/**
+ * 所有串口设备的基类,每有一个新的串口设备添加就要实现本接口
+ * 基础属性有
+ */
+public abstract class BaseDeviceEntity {
+
+//    Class<? extends UsbSerialDriver> driverClass;
+    private static final String TAG = "BaseDeviceEntity";
+    public UsbSerialDriver driver;  //设备的驱动。
+    public UsbSerialPort port;
+//    public int vendorId;    //设备id
+    public UsbManager mUsbManager;
+
+    public BaseDeviceEntity(UsbManager usbManager,UsbSerialDriver driver){
+        this.mUsbManager = usbManager;
+        this.driver = driver;
+        this.port = driver.getPorts().get(0);
+        try {
+            connect(mUsbManager);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 串口的连接参数:
+     * baudRate: 波特率
+     * dataBits: 数据位 一般为8
+     * stopBits: 停止位 一般为1
+     * parity: 奇偶校验 UsbSerialPort.PARITY_ODD、UsbSerialPort.PARITY_EVEN、UsbSerialPort.PARITY_NONE
+     */
+    abstract int getBaudRate();
+    abstract int getDataBits();
+    abstract int getStopBits();
+    abstract int getParity();
+    abstract int getVendorId();
+    ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+    public void connect(UsbManager usbManager) throws IOException {
+        System.out.println("connectDevice SerialPort");
+        if(driver.getDevice().getVendorId() != getVendorId()){
+            Log.e(TAG,"该设备有误");
+            throw new IllegalArgumentException("vendorId 不正确 deviceVendorId:"+driver.getDevice().getVendorId()+"  vendorId:"+getVendorId());
+        }
+        UsbDeviceConnection connection = usbManager.openDevice(port.getDriver().getDevice());
+        port.open(connection);
+        port.setParameters(getBaudRate(), getDataBits(), getStopBits(), getParity());//连接参数
+        /*SerialInputOutputManager inputOutputManager = new SerialInputOutputManager(port, new SerialInputOutputManager.Listener() {
+            @Override
+            public void onNewData(byte[] data) {
+                System.out.println("inputOutputManager onNewData:" + ByteString.of(data)  );
+                onDataReceived(data);
+//                requestFilePermissions.setValue(data);
+//                requestFilePermissions.call();
+//                byte[] weightBytes = new byte[2];
+//                weightBytes[0] = data[3];
+//                weightBytes[1] = data[4];
+//                latestWeight = ByteUtil.byte2short(weightBytes);
+            }
+
+            @Override
+            public void onRunError(Exception e) {
+                e.printStackTrace();
+                executorService.shutdown();
+
+            }
+        });
+        executorService.execute(inputOutputManager);*/
+
+
+
+    }
+
+    abstract void onDataReceived(byte[] response);
+//    abstract Observer getObserver();
+    public void close(){
+        try {
+            port.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+}

+ 113 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/ByteUtil.java

xqd
@@ -0,0 +1,113 @@
+package com.siwei.recyclebox.deviceUtils;
+
+public class ByteUtil {
+
+    /**
+     * 字节数组依次异或生成校验码
+     * @param datas
+     * @return
+     */
+    public static byte getXor(byte[] datas){
+        byte temp=datas[1];
+        for (int i = 2; i <datas.length-2; i++) {
+            temp ^=datas[i];
+        }
+        return temp;
+    }
+
+    public static String bytesToString(byte[] bytes){
+        if (bytes == null) {
+            return null;
+        }
+        StringBuilder builder = new StringBuilder();
+        char[] hexArray = "0123456789ABCDEF".toCharArray();
+        for (byte aByte : bytes) {
+            int v = aByte & 0xFF;
+            builder.append(hexArray[v >>> 4]).append(hexArray[v & 0x0F]).append(" ");
+        }
+        return builder.toString();
+    }
+
+    public static byte[] generateBytes( int dataLen , byte[] data , byte[]sendBytes  ){
+        for( int i = 0;i<dataLen;i++){
+            sendBytes[5+i]=data[i];
+        }
+        return sendBytes;
+    }
+
+    /**
+     * 输入的16进制字符串转换成bytes
+     * @param hexString
+     * @return
+     */
+    public static byte[] hexStringToBytes(String hexString) throws IllegalArgumentException {
+        if (hexString == null || hexString.equals("")) {
+            return null;
+        }
+        hexString = hexString.replaceAll(" ","");
+        hexString = hexString.toUpperCase();
+        int length = hexString.length() / 2;
+        char[] hexChars = hexString.toCharArray();
+        byte[] d = new byte[length];
+        for (int i = 0; i < length; i++) {
+            int pos = i * 2;d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
+        }
+        return d;
+    }
+    private static byte charToByte(char c) throws IllegalArgumentException {
+        byte index = (byte) "0123456789ABCDEF".indexOf(c);
+        if(index == -1){
+            throw new IllegalArgumentException(c+" 不是16进制数");
+        }
+        return index;
+    }
+
+    public static boolean isEmpty(String string){
+        if(string == null || string.isEmpty())
+            return true;
+        return false;
+    }
+
+    public static byte[] short2byte(short s){
+        byte[] b = new byte[2];
+        for(int i = 0; i < 2; i++){
+            int offset = 16 - (i+1)*8; //因为byte占4个字节,所以要计算偏移量
+            b[i] = (byte)((s >> offset)&0xff); //把16位分为2个8位进行分别存储
+        }
+        return b;
+    }
+    public static byte[] int2shotybyte(int s){
+        byte[] b = new byte[2];
+        for(int i = 0; i < 2; i++){
+            int offset = 16 - (i+1)*8; //因为byte占4个字节,所以要计算偏移量
+            b[i] = (byte)((s >> offset)&0xff); //把16位分为2个8位进行分别存储
+        }
+        return b;
+    }
+    public static short byte2short(byte[] b){
+        short l = 0;
+        for (int i = 0; i < 2; i++) {
+            l<<=8; //<<=和我们的 +=是一样的,意思就是 l = l << 8
+            l |= (b[i] & 0xff); //和上面也是一样的  l = l | (b[i]&0xff)
+        }
+        return l;
+    }
+    public static byte[] int2byte(int s){
+        byte[] b = new byte[4];
+        for(int i = 0; i < 4; i++){
+            int offset = 32 - (i+1)*8; //因为byte占4个字节,所以要计算偏移量
+            b[i] = (byte)((s >> offset)&0xff); //把32位分为4个8位进行分别存储
+        }
+        return b;
+    }
+    public static int bytes2Int(byte[] bytes) {
+        int value = 0;
+        // 由高位到低位
+        for (int i = 0; i < 4; i++) {
+            int shift = (4 - 1 - i) * 8;
+            value += (bytes[i] & 0x000000FF) << shift;// 往高位游
+        }
+        return value;
+    }
+
+}

+ 282 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/ManyDevices.java

xqd
@@ -0,0 +1,282 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.hardware.usb.UsbManager;
+import android.util.Log;
+
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+
+import java.io.IOException;
+
+public class ManyDevices extends BaseDeviceEntity{
+
+    private static final String TAG = "ManyDevices";
+
+    private Object lock = new Object();
+    public ManyDevices(UsbManager usbManager, UsbSerialDriver driver) {
+        super(usbManager , driver);
+    }
+
+    /**
+     * 控制推杆
+     */
+    public synchronized void controlPushOpen(){
+        try {
+            Log.i(TAG,"openpush");
+            //伸杆 66 后面 01代表杆 01伸 05代表时间1-60s  02 1C表示电流报警阈值 默认540
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01, 0x01, 0x05, 0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);
+//            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01, 0x01, 0x05, 0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);//
+//            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01, 0x02, 0x05, 0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);//
+//            Thread.sleep(100);
+
+            for (int i=1;i>0;i++)
+            {
+//                Thread.sleep(50);
+                //发送红外线和门磁
+                port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0A,0x01,0x55,0x03, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+                byte[] response = new byte[12];
+                int len = port.read(response,10);
+                Log.i(TAG,ByteUtil.bytesToString(response)+"len hongwaix:"+len);
+                if (ByteUtil.bytesToString(response)=="AA BB 0A 01 55 03 01 FF FF FF CC DD "){//红外线有障碍 03红外线  01 有障碍
+                    //    AA,BB,0A,01,55,03,01,FF,FF,FF,CC,DD 有障碍
+//                    controlPushOff();
+                    //杆停止
+                    Log.i(TAG,"伸杆停止:1秒");
+                    port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01, 0x03, 0x05, 0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);
+                    Thread.sleep(1000);
+            }
+//                else if(ByteUtil.bytesToString(response)=="AA BB 0A 01 55 03 02 FF FF FF CC DD"){//红外线  02无障碍
+//                    //缩杆
+//                    port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01, 0x01, 0x05, 0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);
+//                }
+            }
+//            byte[] response = new byte[12];
+//            port.read(response,200);
+//            Log.i(TAG,ByteUtil.bytesToString(response)+"  //response");
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+    public synchronized void controlPushOff(){
+        try {
+            Log.i(TAG,"offpush");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01,0x02,0x05,0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);//
+            for (int i=1;i>0;i++)
+            {
+                //发送红外线和门磁
+                port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0A,0x01,0x55,0x03, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+                byte[] response = new byte[12];
+                int len = port.read(response,10);
+                Log.i(TAG,ByteUtil.bytesToString(response)+"len hongwaix:"+len);
+                if (ByteUtil.bytesToString(response)=="AA BB 0A 01 55 03 01 FF FF FF CC DD "){//红外线有障碍 03红外线  01 有障碍
+                    //杆停止
+                    Log.i(TAG,"伸杆停止:1秒");
+                    port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A,0x01,0x66,0x01, 0x03, 0x05, 0x02,0x1C, (byte) 0xCC, (byte) 0xDD},150);
+                    Thread.sleep(1000);
+                }
+            }
+
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+    public synchronized void controlLockOpen(){//开锁
+        try {
+            //电控锁   开锁
+            Log.i(TAG,"openlock");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0A,0x01,0x66,0x02,0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);//
+            Thread.sleep(100);
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+    public synchronized void controlLockOff(){
+        try {
+            //电控锁   关锁
+            Log.i(TAG,"offlock");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0A,0x01,0x66,0x02,0x02, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);//
+            Thread.sleep(100);
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+    //红外线和磁传感
+//    port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0C,0x01,0x55,0x03, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+//    byte[] response = new byte[12];
+    //门磁关
+//    AA BB 0C 01 55 04 01 FF FF FF CC DD
+    //门磁开
+//    A,BB,0C,01,55,04,02,FF,FF,FF,CC,DD
+//
+
+    //去皮清零
+//    port.write(new byte[]{0xAA,0xBB,0x0B,0x01,0x66,0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+    //称重
+//    port.write(new byte[]{0xAA,0xBB,0x0B,0x01,0x55,0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+//    AA,BB,0B,01,55,00,00,C9,FF,FF,CC,DD;//00 00 C9 是称重数据 0x0000C9=201  10进制
+
+    //距离查询
+//     port.write(new byte[]{0xAA,0xBB,0x0C,0x01,0x55,0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+//     AA,BB,0B,01,55,01,00,DB,FF,FF,CC,DD;//距离返回参数 01 是距离 00DB 是十进制219 mm单位
+
+    //温度查询
+//    port.write(new byte[]{0xAA,0xBB,0x0C,0x01,0x55,0x02, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+//    AA,BB,0B,01,55,02,0A,FF,FF,FF,CC,DD;//02是温度  0A是十进制 10
+//
+
+    //温度查询
+//    port.write(new byte[]{0xAA,0xBB,0x0D,0x01,0x55,0xFF,0xFF,0xFF,0xFF,0xFF,0xCC,0xDD},150);
+//      AA BB 0D 01 55 01 05 FF FF FF CC DD    //01 05是数据
+//返回的温度数据长度为两字节,高位在前低位在后,将这两字节转换成10进制数再除以10即为当前温度值;当最高位为1时表示负值,此时需将此值取补加1,也可将此值直接减去65536,即为当前温度值,下面举例说明:
+//返回: AA BB 0E 01 55 00 DB FF FF FF CC DD
+//00DB即为温度值,最高位为0,所以温度为正,将其转换为10进制=219,在将其除以10:21.9 即为当前温度值;
+//返回:AA BB 0E 01 55 FF 90 FF FF FF CC DD
+//FF 90为温度值,最高位为1,所以温度为负,将其转换为10进制=65424,再减去65536=  -11.2 即为当前温度值。
+
+    public synchronized void controlDoorMag(){//门磁
+        try {
+            Log.i(TAG,"doormag");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0C,0x01,0x66,0x02, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);//
+            Thread.sleep(100);
+            byte[] response = new byte[12];
+            port.read(response,10);
+            Log.i(TAG,ByteUtil.bytesToString(response)+"doormag:");
+            //门磁关
+//    AA,BB,0C,01,55,04,01,FF,FF,FF,CC,DD
+            //门磁开
+//    A,BB,0C,01,55,04,02,FF,FF,FF,CC,DD
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+
+
+
+    public synchronized void controlWeightClear(){
+        try {
+            //去皮清零
+            Log.i(TAG,"clearweight");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B,0x01,0x66,0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+            Thread.sleep(100);
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+    public synchronized void controlWeightOpen(){
+        try {
+            //称重
+            Log.i(TAG,"openweight");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B,0x01,0x55, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+            Thread.sleep(100);
+            //    AA BB 0B 01 55 00 00 C9 FF FF CC DD;//00 00 C9 是称重数据 0x0000C9=201  10进制
+            byte[] response = new byte[12];
+            int len = port.read(response,10);
+            Log.i(TAG,ByteUtil.bytesToString(response)+"称重:"+response[5]+response[6]+response[7]);
+
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+    public synchronized void distanceQuuery(){
+        try {
+            //距离查询
+            Log.i(TAG,"querydistance");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0C,0x01,0x55,0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+            Thread.sleep(100);
+            byte[] response = new byte[12];
+            port.read(response,10);
+            Log.i(TAG,ByteUtil.bytesToString(response)+"querydistance:");
+//     AA,BB,0B,01,55,01,00,DB,FF,FF,CC,DD;//距离返回参数 01 是距离 00DB 是十进制219 mm单位
+            
+            //AA BB 0C 01 66 02 18 FF FF FF CC DD   返回
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+
+    public synchronized void temperatureQuery(){
+        try {
+            //温度查询
+            Log.i(TAG,"querytemperature");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0C,0x01,0x55,0x02, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);
+            Thread.sleep(100);
+            byte[] response = new byte[12];
+            port.read(response,10);
+            Log.i(TAG,ByteUtil.bytesToString(response)+"querytemperature:");
+            //    AA,BB,0B,01,55,02,0A,FF,FF,FF,CC,DD;//02是温度  0A是十进制 10
+        }catch ( Exception e ){
+            e.printStackTrace();
+            Log.e(TAG,"",e);
+        }
+    }
+
+
+
+
+    @Override
+    public void connect(UsbManager usbManager) throws IOException {
+        super.connect(usbManager);
+        System.out.println("connect  readWeight:");
+
+    }
+
+    @Override
+    void onDataReceived(byte[] response) {
+        byte[] weightBytes = new byte[2];
+//        weightBytes[0] = response[3];
+//        weightBytes[1] = response[4];
+//        latestWeight = ByteUtil.byte2short(weightBytes);
+    }
+
+    java.util.Observer observer = new java.util.Observer() {
+
+        @Override
+        public void update(java.util.Observable observable, Object arg) {
+
+        }
+    };
+
+
+    /**
+     * 串口的连接参数:
+     * baudRate: 波特率
+     * dataBits: 数据位 一般为8
+     * stopBits: 停止位 一般为1
+     * parity: 奇偶校验 UsbSerialPort.PARITY_ODD、UsbSerialPort.PARITY_EVEN、UsbSerialPort.PARITY_NONE
+     */
+    @Override
+    int getBaudRate() {
+//        return 19200;
+        return 115200;
+    }
+
+    @Override
+    int getDataBits() {
+        return UsbSerialPort.DATABITS_8;
+    }
+
+    @Override
+    int getStopBits() {
+        return UsbSerialPort.STOPBITS_1;
+    }
+
+    @Override
+    int getParity() {
+//        return UsbSerialPort.PARITY_EVEN;
+        return UsbSerialPort.PARITY_NONE;
+    }
+
+    @Override
+    int getVendorId() {
+        return 6790;
+    }
+
+}

+ 616 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/OtherDevice.java

xqd
@@ -0,0 +1,616 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.hardware.usb.UsbManager;
+import android.speech.tts.TextToSpeech;
+import android.util.Log;
+import com.siwei.recyclebox.ui.main.MainViewModel;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+
+import android.os.Handler;
+
+import java.io.IOException;
+import java.util.logging.LogRecord;
+
+import me.goldze.mvvmhabit.utils.SPUtils;
+import me.goldze.mvvmhabit.utils.Utils;
+
+/**
+ * 温度传感器、红外传感器、距离、锁。
+ */
+public class OtherDevice extends BaseDeviceEntity {
+
+    private static final String TAG = "OtherDevice";
+
+    public OtherDevice(UsbManager usbManager, UsbSerialDriver driver) {
+        super(usbManager, driver);
+    }
+
+    /**
+     * 串口的连接参数:
+     * baudRate: 波特率
+     * dataBits: 数据位 一般为8
+     * stopBits: 停止位 一般为1
+     * parity: 奇偶校验 UsbSerialPort.PARITY_ODD、UsbSerialPort.PARITY_EVEN、UsbSerialPort.PARITY_NONE
+     */
+    @Override
+    int getBaudRate() {
+        return 115200;
+    }
+
+    @Override
+    int getDataBits() {
+        return UsbSerialPort.DATABITS_8;
+    }
+
+    @Override
+    int getStopBits() {
+        return UsbSerialPort.STOPBITS_1;
+    }
+
+    @Override
+    int getParity() {
+        return UsbSerialPort.PARITY_NONE;
+    }
+
+    @Override
+    int getVendorId() {
+        return 6790;
+    }
+
+    @Override
+    void onDataReceived(byte[] response) {
+        //设备回复: AA BB 0C 01 55 01 00 DB FF FF CC DD  //距离感应器
+        if(response.length != 12){
+            //回报长度不正确
+            return;
+        }
+        if(response[0] == 0xAA && response[1] == 0xBB && response[10] == 0xCC && response[11] == 0xDD){
+            //正确的包头包尾
+            switch (response[2]){
+                case 0x0A://电控锁
+
+                    break;
+                case 0x0C://距离/温度
+            }
+        }
+    }
+
+    public void getRodOpenNumber(){//计算开门次数
+        String rodNumberStr= SPUtils.getInstance().getString("RodNumber");
+        if(rodNumberStr.equals("")){
+            rodNumberStr="0";
+        }
+        int rodNumberInt=Integer.parseInt(rodNumberStr)+1;
+        rodNumberStr=String.valueOf(rodNumberInt);
+        SPUtils.getInstance().put("RodNumber",rodNumberStr);
+        Log.i(TAG,"开门次数"+rodNumberStr);
+    }
+    public void getLockOpenNumber(){//计算开门次数
+        String lockNumberStr=SPUtils.getInstance().getString("LockNumber");
+        if(lockNumberStr.equals("")){
+            lockNumberStr="0";
+        }
+        int lockNumberInt=Integer.parseInt(lockNumberStr)+1;
+        lockNumberStr=String.valueOf(lockNumberInt);
+        SPUtils.getInstance().put("LockNumber",lockNumberStr);
+        Log.i(TAG,"开锁次数"+lockNumberStr);
+    }
+
+    private Handler mHandler=new Handler(msg -> {
+
+       return true;
+    });
+
+    /**
+     * 查询门磁数值
+     */
+    public  Integer queryMagDoor(){
+        try {
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] pushRodOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x55, 0x04, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(pushRodOrderBytes, 150);//
+            byte[] response = new byte[12];
+            int readLen = port.read(response,200);
+
+            System.out.println("读取门磁命令:"+ByteUtil.bytesToString(pushRodOrderBytes));
+            System.out.println("读取门磁返回:"+ByteUtil.bytesToString(response));
+            if(readLen == 12 && response[5]==4&&response[6]==1/*&& response[0] == 0xAA&&response[1]==0xBB&& response[2]==0x0A&&response[3]==0x01&&response[4]==0x55 && response[5] == 0x03*/){
+                Log.i(TAG,"response[6] 门磁读取到"+response[6]);
+                return 0;
+            }
+            if(readLen == 12 && response[5]==4&&response[6]==2/*&& response[0] == 0xAA&&response[1]==0xBB&& response[2]==0x0A&&response[3]==0x01&&response[4]==0x55 && response[5] == 0x03*/){
+                Log.i(TAG,"response[6] 门磁读取到"+response[6]);
+                return 1;
+            }
+            MainViewModel.reportDeviceEvent("queryMagDoor");
+            return 0;
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("queryMagDoor");
+            Log.e(TAG,"读取门磁错误",e);
+            return 3;
+        }
+    }
+
+    /*
+    * 查询电流过大标志 00 没有 01 有
+    * */
+    public Integer queryCurrent(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+//            AA BB 0A 01 55 05 FF FF FF FF CC DD  查询电流过大标志  回复00 没有 01 有
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x55, 0x05, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD}, 150);//
+            byte[] response = new byte[12];
+            int readLen=port.read(response,200);
+            if(readLen == 12&&response[5]==5){
+                Log.i(TAG,"电流过大返回值response[6]========"+response[6]);
+                return (int) response[6];
+            }
+            MainViewModel.reportDeviceEvent("queryCurrent");
+            return 0;
+        }catch (Exception e){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("queryCurrent");
+            Log.e(TAG,"查询电流过大失败");
+            return 3;
+        }
+    }
+
+    /*
+     * 电流过大标志清零
+     * */
+    public void clearCurrent(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x55, 0x06, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD}, 150);//
+        }catch (Exception e){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("clearCurrent");
+            Log.i(TAG,"电流过大标志清零失败");
+        }
+
+    }
+
+    /**
+     * 查询红外感应器数值
+     */
+    public  Integer queryInfrared(){
+        try {
+            SPUtils.getInstance().put("RunningState","0");
+            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x55, 0x03, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD}, 150);//
+            byte[] response = new byte[12];
+            int readLen = port.read(response,200);
+            System.out.println("读取红外返回:"+ByteUtil.bytesToString(response));
+            if(readLen == 12 &&response[5]==3&&response[6]==1/*&& response[0] == 0xAA&&response[1]==0xBB&& response[2]==0x0A&&response[3]==0x01&&response[4]==0x55 && response[5] == 0x03*/){
+                Log.i(TAG,"response[6]"+response[6]);
+                return (int) 1;
+            }
+            if(readLen == 12 &&response[5]==3&&response[6]==2/*&& response[0] == 0xAA&&response[1]==0xBB&& response[2]==0x0A&&response[3]==0x01&&response[4]==0x55 && response[5] == 0x03*/){
+                Log.i(TAG,"response[6]"+response[6]);
+                return (int) 2;
+            }
+            MainViewModel.reportDeviceEvent("queryInfrared");
+            return 0;
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("queryInfrared");
+            Log.e(TAG,"读取红外线读数错误",e);
+            return 3;
+        }
+    }
+
+    /**
+     * 伸杆关门
+     */
+    public void pushRod(){//关门
+        Thread thread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    SPUtils.getInstance().put("RunningState","0");
+                    SPUtils.getInstance().put("RodSwitch","0");
+                    //门  66后面 01 表示杆 01表示伸杆  1c表示多少秒  02 1c 表示电流
+                    byte[] pushRodOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x01, 0x01,0x0B, (byte) 0x02,0x1C, (byte) 0xCC, (byte) 0xDD};
+                    Log.i(TAG,"关门"+ByteUtil.bytesToString(pushRodOrderBytes));
+//                    byte[] pushRodOrderBytes2 = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x01, 0x01,0x1c, (byte) 0xFF,0x1c, (byte) 0xCC, (byte) 0xDD};
+//                    port.write(pushRodOrderBytes2, 100);
+                    port.write(pushRodOrderBytes, 100);
+                    getRodOpenNumber();
+                    byte[] response = new byte[ 12];
+                    port.read(response,200);
+                    System.out.println("push response:"+ByteUtil.bytesToString(response));
+                    long curr=System.currentTimeMillis();
+                    while (System.currentTimeMillis() - curr < 8000) {
+                        Thread.sleep(300);
+                        Integer infrared = queryInfrared();
+                        if (infrared == 1) {//有障碍物
+                            //停止关门
+                            Log.i(TAG, "停止关门");
+                            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x01, 0x03, (byte) 0x02, 0x05, (byte) 0x1C, (byte) 0xCC, (byte) 0xDD}, 200);
+                            mHandler.postDelayed(new Runnable() {
+                                @Override
+                                public void run() {
+                                    try {
+                                        port.write(pushRodOrderBytes, 100);
+                                    } catch (IOException e) {
+                                        e.printStackTrace();
+                                    }
+                                }
+                            },10000);
+                            break;
+                        }
+                    }
+
+                }catch (Exception e ){
+                    MainViewModel.reportDeviceEvent("pushRod");
+                    e.printStackTrace();
+                    Log.i(TAG,"关门失败");
+                }
+            }
+        });
+        thread.start();
+    }
+    /**
+     * 伸杆开门
+     */
+    public  void pullRod(){//开门
+        Thread thread1=new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    SPUtils.getInstance().put("RunningState","0");
+                    SPUtils.getInstance().put("RodSwitch","1");
+                    //伸杆  66后面 01 表示杆 02表示缩杆  1c表示多少秒  02 1c 表示电流
+                    byte[] pullRodOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x01, 0x02,0x0B, (byte) 0xff, (byte) 0x1C, (byte) 0xCC, (byte) 0xDD};
+                    int writeLen = port.write(pullRodOrderBytes, 100);
+                    System.out.println("pullRod "+writeLen);
+                    byte[] response = new byte[12];
+                    port.read(response,200);
+                    System.out.println("response:"+ByteUtil.bytesToString(response));
+                }catch ( Exception e ){
+                    MainViewModel.reportDeviceEvent("pullRod");
+                    e.printStackTrace();
+                    Log.i(TAG,"开门失败");
+                }
+            }
+        });
+       thread1.start();
+    }
+
+    /**
+     * 运收门开锁
+     */
+    public  Integer openLock(){
+        try {
+            SPUtils.getInstance().put("RunningState","0");
+//            byte[] openLockOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x02, 0x01,0x03, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+          byte[] openLockOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x02, 0x01,0x07, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(openLockOrderBytes,200);
+            byte[] response=new byte[12];
+            port.read(response,500);
+            getLockOpenNumber();
+            Log.i(TAG,ByteUtil.bytesToString(response)+"开收运门");
+            if(response[4]==102&&response[5]==2&&response[6]==1){
+                return 0;
+            }else {
+                MainViewModel.reportDeviceEvent("openLock");
+                return 0;
+            }
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("openLock");
+            Log.i(TAG,"开收运门失败");
+            return 0;
+        }
+    }
+
+
+    /**
+     * 运收门关锁
+     */
+    public Integer closeLock(){
+        try {
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] closeLockOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0A, 0x01, 0x66, 0x02, 0x02, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(closeLockOrderBytes, 200);
+            byte[] response=new byte[12];
+            port.read(response,500);
+            Log.i(TAG,ByteUtil.bytesToString(response)+"关收运门");
+            if(response[4]==102&&response[5]==2&&response[6]==2){
+                return 0;
+            }else {
+                MainViewModel.reportDeviceEvent("closeLock");
+                return 0;
+            }
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("closeLock");
+            Log.i(TAG,"关收运门失败");
+            return 0;
+        }
+    }
+    /**
+     * 超声波
+     * 距离
+     * @return
+     */
+    public  Integer getDistance(){
+        try {
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes = new byte[]{(byte)0xAA ,(byte)0xBB ,(byte)0x0C ,(byte)0x01 ,(byte)0x55 ,(byte)0x01 ,(byte)0xFF ,(byte)0xFF,(byte)0xFF ,(byte)0xFF ,(byte)0xCC ,(byte)0xDD};
+            port.write(bytes,200);
+            byte[] response = new byte[12];
+            int len = port.read(response,200);
+            Log.i(TAG,ByteUtil.bytesToString(response)+len+"距离");
+            byte[] disBytes = new byte[4];
+            disBytes[2] = response[6];
+            disBytes[3] = response[7];
+            Log.i(TAG,ByteUtil.bytesToString(disBytes)+"juli");
+            int disInt = ByteUtil.bytes2Int(disBytes);
+            if(response[4]==85&&response[5]==1){
+                return disInt;
+            }else {
+                MainViewModel.reportDeviceEvent("getDistance");
+                return 1000;
+            }
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("getDistance");
+            Log.i(TAG,"测距离失败");
+            return 0;
+        }
+    }
+    /**
+     * 温度
+     * @return
+     */
+    public Integer geTemperature(){//获取温度
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+//            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB,0x0D,0x01,0x55, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            byte[] bytes = new byte[]{(byte)0xAA ,(byte)0xBB ,(byte)0x0C ,(byte)0x01 ,(byte)0x55 ,(byte)0x02 ,(byte)0xFF ,(byte)0xFF,(byte)0xFF ,(byte)0xFF ,(byte)0xCC ,(byte)0xDD};
+            port.write(bytes,200);
+            byte[] response=new byte[12];
+            int len=port.read(response,200);
+            Log.i(TAG,ByteUtil.bytesToString(response)+len+"温度"+response[4]+response[5]);
+            byte[] temBytes=new byte[4];
+//            temBytes[2]=response[5];
+            temBytes[3]=response[6];
+            Integer temInt=ByteUtil.bytes2Int(temBytes);
+//            double temDouble=0;
+//            if(temInt<65536){
+//                temDouble=temInt/10;
+//                return temDouble;
+//            }
+//            temDouble=-(temInt-65536)/10;
+//            return temDouble;
+            if(response[4]==85&&response[5]==2){
+                return temInt;
+            }else {
+                MainViewModel.reportDeviceEvent("geTemperature");
+                return 123456789;
+            }
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("geTemperature");
+            Log.i(TAG,"测温度失败");
+            return 0;
+        }
+    }
+    /*
+     * 开灯关灯
+     * */
+    public void openLight1(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x01, 0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("openLight1");
+            e.printStackTrace();
+            Log.i(TAG,"开灯1失败");
+        }
+    }
+    public void closeLight1(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x01, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("closeLight1");
+            e.printStackTrace();
+            Log.i(TAG,"关灯1失败");
+        }
+    }
+    public void openLight2(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x02, 0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("openLight2");
+            e.printStackTrace();
+            Log.i(TAG,"开灯2失败");
+        }
+    }
+    public void closeLight2(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x02, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("closeLight2");
+            e.printStackTrace();
+            Log.i(TAG,"关灯2失败");
+        }
+    }
+    public void openLight3(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x03, 0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("openLight3");
+            e.printStackTrace();
+            Log.i(TAG,"开灯3失败");
+        }
+    }
+    public void closeLight3(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x03, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("closeLight3");
+            e.printStackTrace();
+            Log.i(TAG,"关灯3失败");
+        }
+    }
+    public void openLight4(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x04, 0x01, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("openLight4");
+            e.printStackTrace();
+            Log.i(TAG,"开灯4失败");
+        }
+    }
+    public void closeLight4(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB, 0x0D, 0x01, 0x66, 0x04, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+        }catch (Exception e){
+            MainViewModel.reportDeviceEvent("closeLight4");
+            e.printStackTrace();
+            Log.i(TAG,"关灯4失败");
+        }
+    }
+
+    /*
+    * 称重
+    * */
+    public Integer readWeight(){
+        try{
+            SPUtils.getInstance().put("RunningState","0");
+//            主机发送 : AA BB 0B 01 55 FF FF FF FF FF CC DD
+//            设备回复: AA BB 0B 01 55 00 00 C9 FF FF CC DD
+//            00 00 C9 为数据
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B,0x01,0x55,(byte) 0xFF,(byte) 0xFF,(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+            byte[] response=new byte[12];
+            int len=port.read(response,200);
+            Log.i(TAG,"返回称重"+ByteUtil.bytesToString(response));
+            if(response[4]==85) {
+                byte[] weightBytes = new byte[4];
+                weightBytes[1] = response[5];
+                weightBytes[2] = response[6];
+                weightBytes[3] = response[7];
+                int weightInt = ByteUtil.bytes2Int(weightBytes);
+                Log.i(TAG,"称重数为:"+weightInt);
+                if(weightInt>=16711680){
+                    weightInt=0;
+                }
+                return weightInt;
+            }else {
+                return 0;
+            }
+
+        }catch(Exception e){
+            MainViewModel.reportDeviceEvent("readWeight");
+            e.printStackTrace();
+            Log.i(TAG,"称重失败");
+            return 0;
+        }
+    }
+    /*校准流程:1.设置分度值 2.去皮 3.零度校准 4.放砝码5.砝码校准*/
+    /*
+    * 设置分度值(几克起跳 精度)
+    * */
+    public void setScale(){
+//        主机发送 : AA BB 0B 01 66 01 61 XX FF FF CC DD   XX是分度值 只能是10 20 50 A0 分别是1g 2g 5g 和10g  (常规:100kg 误差10g ;200kg误差20g )
+//        设备回复:  AA BB 0B 01 66 01 61 00 60 FF CC DD
+        try{
+            byte[] bytes=new byte[]{(byte) 0xAA,0x0B ,0x01 ,0x66 ,0x01 ,0x61 ,0x10 , (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+            byte[] response=new byte[12];
+            port.read(response,200);
+            Log.i(TAG,"返回设置刻度"+ByteUtil.bytesToString(response));
+            if(response[8]==96){
+                Log.i(TAG,"设置分度成功");
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+            Log.i(TAG,"设置刻度失败");
+        }
+
+    }
+
+    /*
+    * 称重清零去皮
+    * */
+    public void clearWeight(){
+//        主机发送 : AA BB 0B 01 66 01 62 63 FF FF CC DD
+//        设备回复:  AA BB 0B 01 66 01 62 00 63 FF CC DD
+        try{
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B,0x01,0x66,0x01,0x62,0x63, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+            byte[] response=new byte[12];
+            port.read(response,200);
+            Log.i(TAG,"返回清零"+ByteUtil.bytesToString(response));
+            if(response[8]==99){
+                Log.i(TAG,"称重清零成功");
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+            Log.i(TAG,"称重清零去皮失败");
+        }
+    }
+
+    /*
+    * 零度校准
+    * */
+    public void zeroCalibration(){
+//        主机发送 : AA BB 0B 01 66 01 64 65 FF FF CC DD
+//        设备回复: AA BB 0B 01 66 01 64 00 65 FF CC DD
+        try{
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B,01,66,01,64,65, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            port.write(bytes,200);
+            byte[] response=new byte[12];
+            port.read(response,200);
+            Log.i(TAG,"返回零度校准"+ByteUtil.bytesToString(response));
+            if (response[8]==101){
+                Log.i(TAG,"零度校准成功");
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+            Log.i(TAG,"零度校准失败");
+        }
+    }
+
+    /*
+    * 砝码校准
+    * */
+    public void weightCalibration(){
+        try{
+//            主机发送 : AA BB 0B 01 66 01 65 XX XX FF CC DD  XX是校准的砝码 只能是单位g 防8kg 就是8000g 十六进制就是1F40
+//            设备回复:  AA BB 0B 01 66 01 65 00 64 FF CC DD
+            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B ,0x01 ,0x66 ,0x01 ,0x65 ,0x00 ,0x64 , (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};//64  100g
+//            byte[] bytes=new byte[]{(byte) 0xAA, (byte) 0xBB,0x0B ,0x01 ,0x66 ,0x01 ,0x65 ,0x27 ,0x10 , (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};//2710 10000g
+            port.write(bytes,200);
+            byte[] response=new byte[12];
+            port.read(response,200);
+            Log.i(TAG,"返回砝码校准"+ByteUtil.bytesToString(response));
+            if(response[8]==100){
+                Log.i(TAG,"砝码校准成功");
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+            Log.i(TAG,"砝码校准失败");
+        }
+    }
+
+}

+ 103 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/PushRodDevice.java

xqd
@@ -0,0 +1,103 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.hardware.usb.UsbManager;
+
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import io.netty.util.concurrent.SingleThreadEventExecutor;
+
+public class PushRodDevice extends BaseDeviceEntity {
+
+    ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+    public PushRodDevice(UsbManager usbManager, UsbSerialDriver driver) {
+        super(usbManager, driver);
+    }
+
+    /**
+     * 串口的连接参数:
+     * baudRate: 波特率
+     * dataBits: 数据位 一般为8
+     * stopBits: 停止位 一般为1
+     * parity: 奇偶校验 UsbSerialPort.PARITY_ODD、UsbSerialPort.PARITY_EVEN、UsbSerialPort.PARITY_NONE
+     */
+    @Override
+    int getBaudRate() {
+        return 115200;
+    }
+
+    @Override
+    int getDataBits() {
+        return UsbSerialPort.DATABITS_8;
+    }
+
+    @Override
+    int getStopBits() {
+        return UsbSerialPort.STOPBITS_1;
+    }
+
+    @Override
+    int getParity() {
+       return UsbSerialPort.PARITY_NONE;
+
+    }
+
+    @Override
+    int getVendorId() {
+        return 6790;
+    }
+
+    @Override
+    void onDataReceived(byte[] response) {
+
+    }
+
+    /**
+     * 推杆命令
+     */
+    public void pushRod(){
+        executorService.submit(() -> {
+
+            byte[] cleanOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB,0x0A ,0x01 ,0x66 ,0x01 ,0x02 , (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            System.out.println("推杆!!!!!");
+            try {
+                for(int i = 0;i<3;i++) {
+                    port.write(cleanOrderBytes, 150);
+                    Thread.sleep(10000);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        });
+
+    }
+
+    /**
+     * 收杆命令
+     */
+    public void pullRod(){
+        executorService.submit(()->{
+            byte[] cleanOrderBytes = new byte[]{(byte) 0xAA, (byte) 0xBB,0x0A ,0x01 ,0x66 ,0x01 ,0x01 , (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD};
+            System.out.println("收杆!!!!!");
+            try {
+                for(int i = 0;i<3;i++) {
+                    port.write(cleanOrderBytes, 150);
+                    Thread.sleep(10000);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        });
+
+    }
+
+
+
+
+}

+ 216 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/RelayControllerDevice.java

xqd
@@ -0,0 +1,216 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.hardware.usb.UsbManager;
+import android.util.Log;
+
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 继电器
+ */
+public class RelayControllerDevice extends BaseDeviceEntity {
+    private static final String TAG = "relayControllerDevice";
+
+    public RelayControllerDevice(UsbManager usbManager, UsbSerialDriver driver) {
+        super(usbManager, driver);
+    }
+
+    /**
+     * 串口的连接参数:
+     * baudRate: 波特率
+     * dataBits: 数据位 一般为8
+     * stopBits: 停止位 一般为1
+     * parity: 奇偶校验 UsbSerialPort.PARITY_ODD、UsbSerialPort.PARITY_EVEN、UsbSerialPort.PARITY_NONE
+     */
+    @Override
+    int getBaudRate() {
+        return 9600;
+    }
+
+    @Override
+    int getDataBits() {
+        return UsbSerialPort.DATABITS_8;
+    }
+
+    @Override
+    int getStopBits() {
+        return UsbSerialPort.STOPBITS_1;
+    }
+
+    @Override
+    int getParity() {
+        return UsbSerialPort.PARITY_NONE;
+    }
+
+    @Override
+    int getVendorId() {
+        return 1659;
+    }
+
+    @Override
+    void onDataReceived(byte[] response) {
+
+    }
+
+
+    /**
+     * 获取继电器所有口的状态
+     * @return
+     */
+    public List readRelayControllerStatus(){
+        try {
+             port.write(new byte[]{0x33, 0x01, 0x17, 0x00,0x00, 0x00, (byte) 0x00, 0x4B},2000);//继电器读取所有开关
+
+            List<byte[]> resList = new ArrayList<>();
+
+            long startTimeMillis =System.currentTimeMillis();
+            int readLengthCache = 0;
+            byte[] responseCache = new byte[8];
+            while (System.currentTimeMillis()-startTimeMillis  < 100 ){
+                byte[] response = new byte[8];
+                int len = port.read(response,20);
+                System.out.println("读取到:"+ByteUtil.bytesToString(response)+"  len:"+len);
+                if(len>0){
+                    if(readLengthCache == 0 && response[0] != 0x22){//一个包的起始为0x22
+                        continue;
+                    }
+                    for(int i = 0 ;i<len;i++){
+                        if(readLengthCache>=8)
+                            break;
+                        responseCache[readLengthCache] = response[i];
+                        readLengthCache++;
+                    }
+                    if(readLengthCache>=8){//读够了一帧。做校验。
+                        byte rex = 0x00;
+                        for(int i = 0 ;i< 7;i++){
+                            rex += responseCache[i];
+                        }
+                        if(rex == responseCache[7]){
+                            Log.i(TAG, "继电器回包校验成功");
+                            resList.add(responseCache);
+                        }else{
+                            Log.i(TAG, "继电器回包校验失败!");
+                        }
+                        System.out.println(ByteUtil.bytesToString(responseCache));
+                        responseCache = new byte[8];
+                        readLengthCache = 0;        //复位,从头又开始读。
+                    }
+                }
+            }
+            return resList;
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 断开继电器一个端口
+     * @param index 需要断电的端口的index
+     * @throws IOException
+     */
+    public void closeOne( short index) throws IOException {
+        if(index>3|| index<0){
+            Log.e(TAG,"index的值须在0-3之间。");
+            return;
+        }
+        byte[] closeBytes = new byte[8];
+        closeBytes[0] = 0x33;//帧头
+        closeBytes[1] = 0x01;//地址
+        closeBytes[2] = 0x11;//功能码:11-断开  12-吸合  13-全断开  14-全吸合  15-互锁  16-组合功能 17-查询所有继电器状态。
+        byte[] indexBytes = ByteUtil.int2byte(index);
+        System.arraycopy(indexBytes,0,closeBytes,3,4);
+        byte xor = 0x00;
+        for (int i = 0 ; i <7 ; i++){
+            xor+= closeBytes[i];
+        }
+        closeBytes[7] = xor;//最后一位是校验码,前7个数据相加的和,
+        port.write(closeBytes,200);
+        List<byte[]> responseBytes = readBytes();
+        if(responseBytes.size()!= 0){
+            byte[] response = responseBytes.get(0);
+            Arrays.equals(response,closeBytes);
+        }
+    }
+
+    /**
+     * 给继电器的一个端口通电。
+     * @param index 需要通电的端口的index
+     */
+    public void openOne( short index ) throws IOException {
+        if(index>3|| index<0){
+            Log.e(TAG,"index的值须在0-3之间。");
+            return;
+        }
+        byte[] openBytes = new byte[8];
+        openBytes[0] = 0x33;//帧头
+        openBytes[1] = 0x01;//地址
+        openBytes[2] = 0x12;//功能码:11-断开  12-吸合  13-全断开  14-全吸合  15-互锁  16-组合功能 17-查询所有继电器状态。
+        byte[] indexBytes = ByteUtil.int2byte(index);
+        System.arraycopy(indexBytes,0,openBytes,3,4);
+        byte xor = 0x00;
+        for (int i = 0 ; i <7 ; i++){
+            xor+= openBytes[i];
+        }
+        openBytes[7] = xor;//最后一位是校验码,前7个数据相加的和,
+        System.out.println("打开端口的完整命令:"+ByteUtil.bytesToString(openBytes));
+        port.write(openBytes,200);
+        List<byte[]> responseBytes = readBytes();
+        if(responseBytes.size()!= 0){
+            byte[] response = responseBytes.get(0);
+            Arrays.equals(response,openBytes);
+        }
+
+    }
+
+    private List<byte[]> readBytes( ){
+        List<byte[]> resList = new ArrayList<>();
+        try {
+            long startTimeMillis =System.currentTimeMillis();
+            int readLengthCache = 0;
+            byte[] responseCache = new byte[8];
+            while (System.currentTimeMillis()-startTimeMillis  < 70 ){
+                byte[] response = new byte[8];
+                int len = port.read(response,10);
+                System.out.println("读取到:"+ByteUtil.bytesToString(response)+"  len:"+len);
+                if(len>0){
+                    if(readLengthCache == 0 && response[0] != 0x22){//一个包的起始为0x22
+                        continue;
+                    }
+                    for(int i = 0 ;i<len;i++){
+                        if(readLengthCache>=8)
+                            break;
+                        responseCache[readLengthCache] = response[i];
+                        readLengthCache++;
+                    }
+                    if(readLengthCache>=8){//读够了一帧。做校验。
+                        byte rex = 0x00;
+                        for(int i = 0 ;i< 7;i++){
+                            rex += responseCache[i];
+                        }
+                        if(rex == responseCache[7]){
+                            Log.i(TAG, "继电器回包校验成功");
+                            resList.add(responseCache);
+                        }else{
+                            Log.i(TAG, "继电器回包校验失败!");
+                        }
+                        System.out.println(ByteUtil.bytesToString(responseCache));
+                        responseCache = new byte[8];
+                        readLengthCache = 0;        //复位,从头又开始读。
+                    }
+                }
+            }
+        }catch (Exception e ){
+            Log.e(TAG,e.getMessage(),e);
+        }
+        return resList;
+    }
+
+
+}

+ 177 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/SerialPortUtil.java

xqd
@@ -0,0 +1,177 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
+import com.hoho.android.usbserial.driver.ProlificSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.hoho.android.usbserial.driver.UsbSerialProber;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 串口读写工具。
+ */
+public class SerialPortUtil {
+    private Context mContext ;
+    private SerialPortUtil(){
+    }
+
+    private static final String TAG = "SerialPortUtil";
+    private static final String INTENT_ACTION_GRANT_USB = "com.siwei.recyclebox.GRANT_USB";
+    private static SerialPortUtil instance;
+    public static SerialPortUtil getInstance(){
+        if(instance == null){
+            instance = new SerialPortUtil();
+        }
+        return instance;
+    }
+
+    private UsbManager mUsbManager;
+    private List<UsbSerialPort> availablePorts = new ArrayList<>();
+
+    private List<BaseDeviceEntity> deviceList = new ArrayList<>();
+    public void init(Context context){
+        mContext = context;
+        mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
+
+        final List<UsbSerialDriver> drivers =
+                UsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager);
+
+        for (final UsbSerialDriver driver : drivers) {
+            System.out.println("SerialPortUtil:"+driver.toString());
+            System.out.println("device:"+driver.getDevice().toString());
+            UsbSerialPort port = driver.getPorts().get(0);
+           if (driver instanceof Ch34xSerialDriver){
+               BaseDeviceEntity weightDevice= isWeightDevice(driver);
+               if( weightDevice!= null){
+                   System.out.println("是称重设备");
+                   deviceList.add(weightDevice);
+                   continue;
+               }
+               BaseDeviceEntity otherDevicde = isOtherDevice(driver);
+               if(otherDevicde != null ){
+                   System.out.println("是otherDevice");
+                   deviceList.add(otherDevicde);
+               }
+
+
+//               deviceList.add(new PushRodDevice(mUsbManager,driver));
+//               deviceList.add(new WeightDevice(mUsbManager,driver));
+           }else if(driver instanceof ProlificSerialDriver){
+               deviceList.add(new RelayControllerDevice(mUsbManager,driver));
+           }
+/*
+            final List<UsbSerialPort> ports = driver.getPorts();
+            UsbDevice device= driver.getDevice();
+            System.err.println("deviceDescribeContent:"+device.describeContents()+"===deviceName:"+device.getDeviceName()+"  device.toString"+device.toString());
+            Log.d(TAG, String.format("+ %s: %s port%s",
+                    driver, Integer.valueOf(ports.size()), ports.size() == 1 ? "" : "s"));
+            for(UsbSerialPort usbSerialPort : ports){
+                Log.d(TAG,"usbSerialPort:"+usbSerialPort.toString());
+            }
+            if(driver instanceof ProlificSerialDriver){//继电器的232接口的驱动
+                availablePorts.add(ports.get(0));
+            }
+            if(driver instanceof Ch34xSerialDriver){        //称重的485接口的驱动。
+                availablePorts.add(ports.get(0));
+            }*/
+        }
+        BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if(intent.getAction().equals(INTENT_ACTION_GRANT_USB)) {
+                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+                        System.out.println("granted extra permission");
+//                        writeAndReadPort(port);
+
+                    } else {
+                        System.out.println("usbPermission denied");
+                        Toast.makeText(context, "USB permission denied", Toast.LENGTH_SHORT).show();
+                    }
+                }
+            }
+        };
+        mContext.registerReceiver(mUsbReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB));
+
+        System.out.println("ports size:" + availablePorts.size());
+        if(availablePorts.size() == 0) {
+            Log.e(TAG,"没有RS232 或 RS485 设备");
+            return;
+        }
+        PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 12, new Intent(INTENT_ACTION_GRANT_USB), 0);
+        for(UsbSerialPort port: availablePorts){
+            if(!mUsbManager.hasPermission(port.getDriver().getDevice())){
+                mUsbManager.requestPermission(port.getDriver().getDevice(),usbPermissionIntent);
+            }
+        }
+    }
+
+
+    public WeightDevice getWeightDevice(){
+        for(BaseDeviceEntity deviceEntity  : deviceList){
+            if(deviceEntity instanceof WeightDevice)
+                return (WeightDevice) deviceEntity;
+        }
+        return null;
+    }
+
+    public PushRodDevice getPushRodDevice(){
+        for(BaseDeviceEntity deviceEntity : deviceList){
+            if(deviceEntity instanceof PushRodDevice){
+                return (PushRodDevice) deviceEntity;
+            }
+
+        }
+        return null;
+    }
+
+    public RelayControllerDevice getRelayControllerDevive(){
+        for(BaseDeviceEntity deviceEntity : deviceList){
+            if(deviceEntity instanceof RelayControllerDevice){
+                return (RelayControllerDevice) deviceEntity;
+            }
+        }
+        return null;
+    }
+
+    public OtherDevice getOtherDevice(){
+        for(BaseDeviceEntity deviceEntity : deviceList ){
+            if(deviceEntity instanceof OtherDevice){
+                return (OtherDevice) deviceEntity;
+            }
+        }
+        return null;
+    }
+
+
+    private BaseDeviceEntity isOtherDevice( UsbSerialDriver driver ){
+        OtherDevice otherDevice = new OtherDevice(mUsbManager,driver);
+        Integer infrared = otherDevice.queryInfrared();
+        if(infrared == null) {
+            otherDevice.close();
+            return null;
+        }
+        return otherDevice;
+    }
+
+    private BaseDeviceEntity isWeightDevice(UsbSerialDriver driver){
+        WeightDevice weightDevice = new WeightDevice(mUsbManager,driver);
+        Integer weight = weightDevice.readWeight();
+        if(weight== null){
+            weightDevice.close();
+            return null;
+        }
+        return weightDevice;
+    }
+
+}

+ 228 - 0
app/src/main/java/com/siwei/recyclebox/deviceUtils/WeightDevice.java

xqd
@@ -0,0 +1,228 @@
+package com.siwei.recyclebox.deviceUtils;
+
+import android.hardware.usb.UsbManager;
+import android.util.Log;
+
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.siwei.recyclebox.ui.main.MainViewModel;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import me.goldze.mvvmhabit.utils.SPUtils;
+
+public class WeightDevice extends BaseDeviceEntity {
+
+    private static final String TAG = "WeightDevice";
+
+    public WeightDevice(UsbManager usbManager, UsbSerialDriver driver) {
+        super(usbManager , driver);
+    }
+
+    /**
+     * 读取称重器的读数
+     * return weight 单位:g
+     */
+    public synchronized Integer readWeight(){
+        try {
+            SPUtils.getInstance().put("RunningState","0");
+            port.write(new byte[]{0x1F, 0x03, 0x00, 0x2A,0x00, 0x01, (byte) 0xA6, 0x7C},150);//称重器读取数值的请求
+//            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0C ,0x01 ,0x55 , (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);//称重器读取数值的请求
+//            port.write(new byte[]{(byte) 0xAA, (byte) 0xBB,0x0C ,0x02 ,0x55 , (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xCC, (byte) 0xDD},150);//超声波读取数值的请求
+//            Thread.sleep(100);
+            byte[] response = new byte[7];
+            byte[] responseCache = new byte[7];
+            long curTimeMiles = System.currentTimeMillis();
+            int readLengthCache = 0;
+            while (System.currentTimeMillis()-curTimeMiles<200){
+                int len = port.read(response,10);
+                System.out.println("读取到:"+ByteUtil.bytesToString(response)+"  len:"+len);
+                if(len>0){
+                    if(readLengthCache == 0 && response[0] != 0x1f){//一个包的起始为0x22
+                        continue;
+                    }
+                    for(int i = 0 ;i<len;i++){
+                        if(readLengthCache>=7)
+                            break;
+                        responseCache[readLengthCache] = response[i];
+                        readLengthCache++;
+                    }
+                }
+            }
+            if(readLengthCache == 0)
+                return null;
+            if(readLengthCache >= 7 && responseCache[0] == 0x1f && responseCache[1] == 0x03){
+                byte[] weightBytes = new byte[4];
+                weightBytes[2] = responseCache[3];
+                weightBytes[3] = responseCache[4];
+                int weightInt = ByteUtil.bytes2Int(weightBytes);
+                weightInt=weightInt*5*2;
+                System.out.println("称重读数:"+weightBytes);
+                if(weightInt>=327670){
+                    weightInt=0;
+                }
+                return (int) weightInt;
+            }
+            /*int readLen = port.read(response,200);
+            System.out.println("readweight len:"+readLen+"  bytes;"+ByteUtil.bytesToString(response));
+            */
+            return 0;
+//            return latestWeight;
+            /*byte[] response = new byte[7];
+            int len = port.read(response,200);
+            System.out.println("readLength:"+len);
+            System.out.println("response:"+ ByteString.of(response));
+            if(len<7){
+                Log.e(TAG,"读取数据错误,读取字节数:"+len+" 应读取字节数:7");
+            }
+            byte[] weightBytes = new byte[2];
+            weightBytes[0] = response[3];
+            weightBytes[1] = response[4];
+            short weightShort = ByteUtil.byte2short(weightBytes);
+            return weightShort;*/
+        }catch ( Exception e ){
+            e.printStackTrace();
+            MainViewModel.reportDeviceEvent("readWeight");
+            Log.e(TAG,"读取称重器读数错误",e);
+            return null;
+        }
+//        try {
+//            port.close();
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+    }
+    /**
+     * 读数清零
+     */
+    public synchronized void weightClear(){
+        byte[] cleanOrderBytes = new byte[]{0x1F,0x06 ,0x00 ,0x24,0x00 ,0x00, (byte) 0xCA,0x7F};
+        System.out.println("称重器读数清零!!!!!");
+        try {
+            port.write(cleanOrderBytes,150);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    /*
+     * 设置分度值
+     * */
+    public synchronized void setScale(){
+        byte[] cleanOrderBytes = new byte[]{0x1F, 0x06 ,0x00 ,0x04 ,0x00 ,0x01 ,0x0A ,0x75};
+        try {
+            port.write(cleanOrderBytes,150);
+            Log.i(TAG,"设置分度值");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    /*
+     * 空载
+     * */
+    public synchronized void zeroCalibration(){
+        byte[] cleanOrderBytes = new byte[]{0x1F ,0x10 ,0x00 ,0x08 ,0x00 ,0x02 ,0x04 ,0x00 ,0x00 ,0x00 ,0x00 , (byte) 0x92,0x21 };
+        try {
+            port.write(cleanOrderBytes,150);
+            Log.i(TAG,"空载");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    /*
+     * 量程设定
+     * */
+    public synchronized void weightSet(){
+        byte[] cleanOrderBytes = new byte[]{0x1F ,0x10 ,0x00 ,0x00 ,0x00 ,0x02 ,0x04 , (byte) 0xff, (byte) 0xff,0x00 ,0x00 , (byte) 0x98, (byte) 0xF6};
+        try {
+            port.write(cleanOrderBytes,150);
+
+            Log.i(TAG,"l量程设定");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    /*
+     * 砝码校准
+     * */
+    public synchronized void weightCalibration(){
+        byte[] cleanOrderBytes = new byte[]{0x1F ,0x10 ,0x00 ,0x10 ,0x00 ,0x02 ,0x04 ,0x27 ,0x10 ,0x00 ,0x00, (byte) 0x96, (byte) 0xE4};
+        try {
+            port.write(cleanOrderBytes,150);
+            Log.i(TAG,"砝码校准kg");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+//    private SingleLiveEvent<byte[]> requestFilePermissions = new SingleLiveEvent<>();
+    private short latestWeight = 0;
+
+    @Override
+    public void connect(UsbManager usbManager) throws IOException {
+        super.connect(usbManager);
+        System.out.println("connect  readWeight:");
+//        weightClear();
+    }
+
+    @Override
+    void onDataReceived(byte[] response) {
+        byte[] weightBytes = new byte[2];
+        weightBytes[0] = response[3];
+        weightBytes[1] = response[4];
+        latestWeight = ByteUtil.byte2short(weightBytes);
+    }
+
+    java.util.Observer observer = new java.util.Observer() {
+        /**
+         * This method is called whenever the observed object is changed. An
+         * application calls an <tt>Observable</tt> object's
+         * <code>notifyObservers</code> method to have all the object's
+         * observers notified of the change.
+         *
+         * @param observable   the observable object.
+         * @param arg an argument passed to the <code>notifyObservers</code>
+         */
+        @Override
+        public void update(java.util.Observable observable, Object arg) {
+
+        }
+    };
+
+
+    /**
+     * 串口的连接参数:
+     * baudRate: 波特率
+     * dataBits: 数据位 一般为8
+     * stopBits: 停止位 一般为1
+     * parity: 奇偶校验 UsbSerialPort.PARITY_ODD、UsbSerialPort.PARITY_EVEN、UsbSerialPort.PARITY_NONE
+     */
+    @Override
+    int getBaudRate() {
+        return 19200;
+//        return 115200;
+    }
+
+    @Override
+    int getDataBits() {
+        return UsbSerialPort.DATABITS_8;
+    }
+
+    @Override
+    int getStopBits() {
+        return UsbSerialPort.STOPBITS_1;
+    }
+
+    @Override
+    int getParity() {
+        return UsbSerialPort.PARITY_EVEN;
+//        return UsbSerialPort.PARITY_NONE;
+    }
+
+    @Override
+    int getVendorId() {
+        return 6790;
+    }
+
+}

+ 24 - 0
app/src/main/java/com/siwei/recyclebox/entity/DataEntity.java

xqd
@@ -0,0 +1,24 @@
+package com.siwei.recyclebox.entity;
+
+public class DataEntity<T> {
+    private String order_id;//orderid
+
+    private String dictate;//pushrod
+
+
+    public String getOrder_id() {
+        return order_id;
+    }
+
+    public void setOrder_id(String order_id) {
+        this.order_id = order_id;
+    }
+
+    public String getDictate() {
+        return dictate;
+    }
+
+    public void setDictate(String dictate) {
+        this.dictate = dictate;
+    }
+}

+ 23 - 0
app/src/main/java/com/siwei/recyclebox/http/HttpDataSource.java

xqd
@@ -0,0 +1,23 @@
+package com.siwei.recyclebox.http;
+
+import com.siwei.recyclebox.entity.DataEntity;
+
+import io.reactivex.Observable;
+import me.goldze.mvvmhabit.http.BaseResponse;
+
+/**
+ * Created by goldze on 2019/3/26.
+ */
+public interface HttpDataSource {
+    //模拟登录
+    Observable<Object> login();
+
+    //模拟上拉加载
+//    Observable<DemoEntity> loadMore();
+//
+    Observable<BaseResponse<DataEntity>> demoGet();
+
+    Observable<BaseResponse<DataEntity>> demoPost(String catalog);
+
+
+}

+ 53 - 0
app/src/main/java/com/siwei/recyclebox/http/HttpDataSourceImpl.java

xqd
@@ -0,0 +1,53 @@
+package com.siwei.recyclebox.http;
+
+import com.siwei.recyclebox.entity.DataEntity;
+import com.siwei.recyclebox.http.service.ApiService;
+
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.Observable;
+import io.reactivex.ObservableEmitter;
+import io.reactivex.ObservableOnSubscribe;
+import me.goldze.mvvmhabit.http.BaseResponse;
+
+/**
+ * Created by goldze on 2019/3/26.
+ */
+public class HttpDataSourceImpl implements HttpDataSource {
+    private ApiService apiService;
+    private volatile static HttpDataSourceImpl INSTANCE = null;
+
+    public static HttpDataSourceImpl getInstance(ApiService apiService) {
+        if (INSTANCE == null) {
+            synchronized (HttpDataSourceImpl.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new HttpDataSourceImpl(apiService);
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    public static void destroyInstance() {
+        INSTANCE = null;
+    }
+
+    private HttpDataSourceImpl(ApiService apiService) {
+        this.apiService = apiService;
+    }
+
+    @Override
+    public Observable<Object> login() {
+        return Observable.just(new Object()).delay(3, TimeUnit.SECONDS); //延迟3秒
+    }
+
+    @Override
+    public Observable<BaseResponse<DataEntity>> demoGet() {
+        return apiService.get();
+    }
+
+    @Override
+    public Observable<BaseResponse<DataEntity>> demoPost(String catalog) {
+        return apiService.demoPost(catalog);
+    }
+}

+ 22 - 0
app/src/main/java/com/siwei/recyclebox/http/service/ApiService.java

xqd
@@ -0,0 +1,22 @@
+package com.siwei.recyclebox.http.service;
+
+import com.siwei.recyclebox.entity.DataEntity;
+
+import io.reactivex.Observable;
+import me.goldze.mvvmhabit.http.BaseResponse;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+
+public interface ApiService {
+
+    @GET("getData")
+    Observable<BaseResponse<DataEntity>> get();
+
+
+    @FormUrlEncoded
+    @POST("action/apiv2/banner")
+    Observable<BaseResponse<DataEntity>> demoPost(@Field("catalog") String catalog);
+
+}

+ 30 - 0
app/src/main/java/com/siwei/recyclebox/model/MainModel.java

xqd
@@ -0,0 +1,30 @@
+package com.siwei.recyclebox.model;
+
+import com.siwei.recyclebox.entity.DataEntity;
+import com.siwei.recyclebox.http.HttpDataSource;
+
+import io.reactivex.Observable;
+import me.goldze.mvvmhabit.base.BaseModel;
+import me.goldze.mvvmhabit.http.BaseResponse;
+
+/**
+ * MVVM的Model层,统一模块的数据仓库,包含网络数据和本地数据(一个应用可以有多个Repositor)
+ */
+public class MainModel extends BaseModel implements HttpDataSource {
+
+
+    @Override
+    public Observable<Object> login() {
+        return null;
+    }
+
+    @Override
+    public Observable<BaseResponse<DataEntity>> demoGet() {
+        return null;
+    }
+
+    @Override
+    public Observable<BaseResponse<DataEntity>> demoPost(String catalog) {
+        return null;
+    }
+}

+ 26 - 0
app/src/main/java/com/siwei/recyclebox/receiver/BCRUpgradeApk.java

xqd
@@ -0,0 +1,26 @@
+package com.siwei.recyclebox.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.siwei.recyclebox.ui.main.MainActivity;
+
+public class BCRUpgradeApk extends BroadcastReceiver {
+    private static final String TAG = "BroadcastReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals("android.intent.action.PACKAGE_REPLACED")){
+//            Toast.makeText(context,"升级了一个安装包",Toast.LENGTH_SHORT).show();
+
+            Log.e(TAG,"BroadcastReceiver//////////////////:"+intent);
+            Intent i = new Intent(context,MainActivity.class);
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            context.startActivity(i);
+
+        }
+
+    }
+}

+ 20 - 0
app/src/main/java/com/siwei/recyclebox/receiver/BootReceiver.java

xqd
@@ -0,0 +1,20 @@
+package com.siwei.recyclebox.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.siwei.recyclebox.ui.main.MainActivity;
+
+public class BootReceiver extends BroadcastReceiver {
+    private static final String TAG = "BootReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.e(TAG,"onReceive//////////////////:"+intent);
+        Intent i = new Intent(context,MainActivity.class);
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        context.startActivity(i);
+    }
+}

+ 45 - 0
app/src/main/java/com/siwei/recyclebox/service/MyService.java

xqd
@@ -0,0 +1,45 @@
+package com.siwei.recyclebox.service;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.siwei.recyclebox.ui.main.MainActivity;
+
+public class MyService extends Service {
+
+    @Override
+    public IBinder onBind(Intent arg0) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        // Let it continue running until it is stopped.
+//        Toast.makeText(this, "服务已经启动", Toast.LENGTH_LONG).show();
+        Log.i("","服务已经启动");
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+
+//            Intent intent=new Intent(this,MyService.class);
+//            this.startService(intent);
+
+        Intent mStartActivity = new Intent(this, MainActivity.class);
+        int mPendingIntentId = 123456;
+        PendingIntent mPendingIntent = PendingIntent.getActivity(this, mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
+        AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
+        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
+        System.exit(0);
+        super.onDestroy();
+//        Toast.makeText(this, "服务已经停止", Toast.LENGTH_LONG).show();
+//        Log.i("","服务已经停止");
+    }
+}

+ 118 - 0
app/src/main/java/com/siwei/recyclebox/ui/main/MainActivity.java

xqd
@@ -0,0 +1,118 @@
+package com.siwei.recyclebox.ui.main;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.arch.lifecycle.Observer;
+import android.content.Context;
+import android.content.Intent;
+import android.databinding.ViewDataBinding;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.view.View;
+
+import com.siwei.recyclebox.BR;
+import com.siwei.recyclebox.R;
+import com.siwei.recyclebox.databinding.ActivityMainBinding;
+import com.siwei.recyclebox.deviceUtils.SerialPortUtil;
+import com.siwei.recyclebox.service.MyService;
+import com.tbruyelle.rxpermissions2.RxPermissions;
+
+import java.io.IOException;
+
+import io.reactivex.disposables.Disposable;
+import me.goldze.mvvmhabit.base.BaseActivity;
+import me.goldze.mvvmhabit.utils.SPUtils;
+import me.goldze.mvvmhabit.utils.ToastUtils;
+
+public class MainActivity extends BaseActivity<ViewDataBinding,MainViewModel> {
+
+    private static final String TAG = "MainActivity";
+
+    //页面接收的参数方法
+    @Override
+    public void initParam() {
+        super.initParam();
+        Intent service = new Intent(getApplication(), MyService.class);
+        getApplication().startService(service);
+    }
+
+    //页面数据初始化方法。
+    @Override
+    public void initData() {
+        super.initData();
+//        viewModel.btnClick.onClick();
+//        viewModel.btnText.set("");
+        viewModel.registerAliIoTListener();
+        viewModel.startBLEServer();
+//        viewModel.netWorkInfo();
+//        viewModel.openWeight();
+//        System.out.println("serialPortUtil.readControllerStatus:"+ SerialPortUtil.getInstance().readRelayControllerStatus());
+
+    }
+
+    @Override
+    public void initViewObservable() {
+        super.initViewObservable();
+        viewModel.requestFilePermissions.observe(this, aBoolean -> {
+            //请求读写文件的权限。
+            Log.d(TAG,"请求文件读写的权限");
+            /*RxPermissions permissions = new RxPermissions(this);
+            Disposable disposable =permissions.request(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE})
+                .subscribe(granted->{
+                    if(granted){
+                        Log.d(TAG,"文件读写权限以获取!");
+                        viewModel.otaListener();
+                    }else{
+                        ToastUtils.showShortSafe("请同意文件读写权限。");
+
+                    }
+            });*/
+
+        });
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onDestroy() {
+
+        viewModel.closePort();
+        viewModel.stopBleServer();
+        viewModel.unRegisterIoTListener();
+        Log.e("MainActivity.","unregisterIoTListener!!!");
+        /*Intent mStartActivity = new Intent(getApplication(),MainActivity.class);
+        int mPendingIntentId = 123456;
+        PendingIntent mPendingIntent = PendingIntent.getActivity(getApplication(), mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
+        AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
+        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
+        System.exit(0);*/
+        super.onDestroy();
+
+    }
+
+    @Override
+    public int initContentView(Bundle savedInstanceState) {
+        return R.layout.activity_main;
+    }
+
+    @Override
+    public int initVariableId() {
+        return BR.viewModal;
+    }
+
+    @Override
+    public void onPointerCaptureChanged(boolean hasCapture) {
+        System.out.println("onPointerCaptureChanged:"+hasCapture);
+    }
+
+    @Override
+    public MainViewModel initViewModel() {
+        //View持有ViewModel的引用,如果没有特殊业务处理,这个方法可以不重写
+        return super.initViewModel();
+    }
+}

+ 1935 - 0
app/src/main/java/com/siwei/recyclebox/ui/main/MainViewModel.java

xqd
@@ -0,0 +1,1935 @@
+package com.siwei.recyclebox.ui.main;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.Application;
+
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.databinding.ObservableField;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+import android.media.MediaPlayer;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.SystemClock;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeechService;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.FileProvider;
+import android.support.v7.app.AppCompatActivity;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.alink.dm.api.IOta;
+import com.aliyun.alink.dm.api.ResultCallback;
+import com.aliyun.alink.linkkit.api.LinkKit;
+import com.aliyun.alink.linksdk.cmp.connect.channel.MqttPublishRequest;
+import com.aliyun.alink.linksdk.cmp.connect.channel.MqttRrpcRegisterRequest;
+import com.aliyun.alink.linksdk.cmp.connect.channel.MqttSubscribeRequest;
+import com.aliyun.alink.linksdk.cmp.core.base.AMessage;
+import com.aliyun.alink.linksdk.cmp.core.base.ARequest;
+import com.aliyun.alink.linksdk.cmp.core.base.AResponse;
+import com.aliyun.alink.linksdk.cmp.core.base.ConnectState;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectNotifyListener;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectRrpcHandle;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectRrpcListener;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSendListener;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSubscribeListener;
+import com.aliyun.alink.linksdk.tmp.api.OutputParams;
+import com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper;
+import com.aliyun.alink.linksdk.tmp.listener.IPublishResourceListener;
+import com.aliyun.alink.linksdk.tmp.listener.ITResRequestHandler;
+import com.aliyun.alink.linksdk.tmp.listener.ITResResponseCallback;
+import com.aliyun.alink.linksdk.tmp.utils.ErrorInfo;
+import com.aliyun.alink.linksdk.tools.AError;
+import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
+import com.hoho.android.usbserial.driver.ProlificSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.hoho.android.usbserial.driver.UsbSerialProber;
+import com.hoho.android.usbserial.util.SerialInputOutputManager;
+import com.siwei.recyclebox.BuildConfig;
+import com.siwei.recyclebox.deviceUtils.SerialPortUtil;
+import com.siwei.recyclebox.deviceUtils.WeightDevice;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Spliterator;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import me.goldze.mvvmhabit.base.BaseModel;
+import me.goldze.mvvmhabit.base.BaseViewModel;
+import me.goldze.mvvmhabit.binding.command.BindingAction;
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+import me.goldze.mvvmhabit.bus.event.SingleLiveEvent;
+import me.goldze.mvvmhabit.http.ApiDisposableObserver;
+import me.goldze.mvvmhabit.utils.SPUtils;
+import me.goldze.mvvmhabit.utils.StringUtils;
+import okio.ByteString;
+import top.maybesix.xhlibrary.serialport.ComPortData;
+import top.maybesix.xhlibrary.serialport.SerialPortHelper;
+import top.maybesix.xhlibrary.util.HexStringUtils;
+
+import com.siwei.recyclebox.entity.DataEntity;
+import com.siwei.recyclebox.service.MyService;
+import com.siwei.recyclebox.utils.DemoOne;
+import com.siwei.recyclebox.utils.SilentInstall;
+import com.siwei.recyclebox.utils.unZipFileDemo;
+import com.tencent.bugly.crashreport.CrashReport;
+
+import static android.content.Context.BATTERY_SERVICE;
+import static android.content.Context.EUICC_SERVICE;
+import static java.lang.Thread.sleep;
+
+public class MainViewModel extends BaseViewModel implements SerialPortHelper.OnSerialPortReceivedListener{
+
+    Integer lockSwitch=0;//锁的状态
+    private static Context context;//上下文
+    @RequiresApi(api = Build.VERSION_CODES.O)
+    public void onCreate(){
+        super.onCreate();
+        Intent service = new Intent(getApplication(), MyService.class);
+        getApplication().startService(service);//打开服务,把程序写在服务里不容易被系统kill
+
+        MainViewModel.context = getApplication();
+        SPUtils.getInstance().put("RunningState","0");//设备运行状态
+        SPUtils.getInstance().put("magDoorSwitch","0");//门磁状态
+        SPUtils.getInstance().put("type","0");//小程序传送数据类型
+
+        BatteryManager batteryManager = (BatteryManager)getSystemService(BATTERY_SERVICE);//电量管理
+        int battery = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);//查询电量值
+        Log.i(TAG,"电量值="+battery);
+
+    }
+    private MediaPlayer mMediaPlay=null;
+    public ObservableField<String> textReadWeightData = new ObservableField<>();//称重数据显示
+    public ObservableField<String> textReportData = new ObservableField<>();//上报数据显示
+    public ObservableField<String> textIMEI = new ObservableField<>();//imei显示
+    public ObservableField<String> textqueryCurrentData = new ObservableField<>();//电流过大查询显示
+
+    /*
+    * 按钮模拟指令
+    * */
+    public View.OnClickListener btnClickqueryCurrent = new View.OnClickListener() {
+        //电流过大查询值
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickqueryCurrent");
+            Integer queryCurrentData=SerialPortUtil.getInstance().getOtherDevice().queryCurrent();
+        }
+    };
+    public void onSerialPortDataReceived(ComPortData comPortData) {
+        //处理接收的串口消息
+        String s = HexStringUtils.byteArray2HexString(comPortData.getRecData());
+        Log.i(TAG, "onReceived: " + s);
+    }
+
+    public View.OnClickListener btnClickclearCurrent = new View.OnClickListener() {
+        //清除电流过大查询值
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickclearCurrent");
+            SerialPortUtil.getInstance().getOtherDevice().clearCurrent();
+        }
+    };
+    public View.OnClickListener btnClickSpeech=new View.OnClickListener() {
+        //播放音频
+        @Override
+        public void onClick(View v) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    Log.i(TAG,"-------");
+                    try {
+                        AssetFileDescriptor fd = getApplication().getAssets().openFd("onPushRod.mp3");
+                        mMediaPlay =new MediaPlayer();
+                        mMediaPlay.reset();
+                        mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                        mMediaPlay.prepare();
+                        Log.i(TAG,"播放音频");
+                        mMediaPlay.start();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }).start();
+        }
+    };
+
+    int a=1;
+    public View.OnClickListener btnClickOpenLight1234 = new View.OnClickListener() {
+        //打开关闭灯4按钮
+        @Override
+        public void onClick(View view) {
+            if(a%2==0){
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLight1();
+                    SerialPortUtil.getInstance().getOtherDevice().openLight2();
+                    SerialPortUtil.getInstance().getOtherDevice().openLight3();
+                    SerialPortUtil.getInstance().getOtherDevice().openLight4();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            else {
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight1();
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight2();
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight3();
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight4();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            a++;
+        }
+    };
+    public View.OnClickListener btnClickOpenLight4 = new View.OnClickListener() {
+        //打开关闭灯4按钮
+        @Override
+        public void onClick(View view) {
+            if(a%2==0){
+                SerialPortUtil.getInstance().getOtherDevice().openLight4();
+            }
+            else {
+                SerialPortUtil.getInstance().getOtherDevice().closeLight4();
+            }
+            a++;
+        }
+    };
+    public View.OnClickListener btnClickOpenLight3 = new View.OnClickListener() {
+        //打开关闭灯3按钮
+        @Override
+        public void onClick(View view) {
+            if(a%2==0){
+                SerialPortUtil.getInstance().getOtherDevice().openLight3();
+            }
+            else {
+                SerialPortUtil.getInstance().getOtherDevice().closeLight3();
+            }
+            a++;
+        }
+    };
+    public View.OnClickListener btnClickOpenLight2 = new View.OnClickListener() {
+        //打开关闭灯2按钮
+        @Override
+        public void onClick(View view) {
+            if(a%2==0){
+                SerialPortUtil.getInstance().getOtherDevice().openLight2();
+            }
+            else {
+                SerialPortUtil.getInstance().getOtherDevice().closeLight2();
+            }
+            a++;
+        }
+    };
+    public View.OnClickListener btnClickOpenLight1 = new View.OnClickListener() {
+        //打开关闭灯1按钮
+        @Override
+        public void onClick(View view) {
+           /* if(a%2==0){
+                SerialPortUtil.getInstance().getOtherDevice().openLight1();
+            }
+            else {
+                SerialPortUtil.getInstance().getOtherDevice().closeLight1();
+            }
+            a++;*/
+            reportDeviceEvent("haoge");
+        }
+    };
+    public View.OnClickListener btnClickreadWeight = new View.OnClickListener() {
+        //称重查询
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickreadWeight");
+//            Integer weightData=SerialPortUtil.getInstance().getOtherDevice().readWeight();
+            Integer weightData=SerialPortUtil.getInstance().getWeightDevice().readWeight();
+            textReadWeightData.set(weightData+"g");
+        }
+    };
+    public View.OnClickListener btnClicksetScale = new View.OnClickListener() {
+        //设置称重刻度值
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClicksetScale");
+//            SerialPortUtil.getInstance().getOtherDevice().setScale();
+            SerialPortUtil.getInstance().getWeightDevice().setScale();
+            SerialPortUtil.getInstance().getWeightDevice().weightSet();
+        }
+    };
+    public View.OnClickListener btnClickclearWeight = new View.OnClickListener() {
+        //称重去皮清零
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickclearWeight");
+//            SerialPortUtil.getInstance().getOtherDevice().clearWeight();
+            SerialPortUtil.getInstance().getWeightDevice().weightClear();
+        }
+    };
+    public View.OnClickListener btnClickweightCalibration = new View.OnClickListener() {
+        //称重砝码校准
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickweightCalibration");
+//            SerialPortUtil.getInstance().getOtherDevice().weightCalibration();
+            SerialPortUtil.getInstance().getWeightDevice().weightCalibration();
+        }
+    };
+    public View.OnClickListener btnClickzeroCalibration = new View.OnClickListener() {
+        //称重空载
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickzeroCalibration");
+//           SerialPortUtil.getInstance().getOtherDevice().zeroCalibration();
+            SerialPortUtil.getInstance().getWeightDevice().zeroCalibration();
+        }
+    };
+    public View.OnClickListener btnClickrestartApp = new View.OnClickListener() {
+        //重启app
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickrestartApp");
+            restartApp();//重启方法
+        }
+        private Object getSystemService(String name) {
+            return getApplication().getSystemService(name);
+        }
+    };
+    public View.OnClickListener btnClickReport = new View.OnClickListener() {
+        //数据上报
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickReport");
+            reportProperty(1);
+        }
+    };
+    public View.OnClickListener btnQeryIMEI = new View.OnClickListener() {
+        //查询imei
+        @Override
+        public void onClick(View view) {
+            textIMEI.set("IMEI=="+IMEI);
+        }
+
+    };
+    public View.OnClickListener btnClickPushRod = new View.OnClickListener() {
+        //关门推杆
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickPushRod");
+//            mHandler.sendEmptyMessage(2);
+//            SerialPortUtil.getInstance().getOtherDevice().pushRod();//关门
+            pushRodMethodNonet();
+        }
+    };
+    public View.OnClickListener btnClickPullRod = new View.OnClickListener() {
+        //开门拉杆
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickPullRod");
+            pullRodMethod();
+            Thread thread=new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    int numInt=0;
+                    long curr=System.currentTimeMillis();
+                    while(System.currentTimeMillis()-curr<89000){//规定时间内没关门就自动关门
+                        try {
+                            sleep(1000);
+                            numInt++;
+                            if(SPUtils.getInstance().getString("RodSwitch").equals("0")){//判断是否已经关门
+                                break;
+                            }
+                            if(numInt==82){//82秒后关门
+                                pushRodMethodNonet();
+                                break;
+                            }
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+
+                }
+            });
+            thread.start();
+//            mHandler.postDelayed(new Runnable() {
+//                @Override
+//                public void run() {
+//                    if(SPUtils.getInstance().getString("RodSwitch").equals("1")) {
+//                        pushRodMethodNonet();
+//                    }
+//                }
+//            },82000);//82s没关门就自动关门
+        }
+    };
+
+    int stopValue=0;
+    public void repeatRod(){
+        //循环开门推拉杆
+        stopValue=1;
+        SerialPortUtil.getInstance().getOtherDevice().pullRod();
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                SerialPortUtil.getInstance().getOtherDevice().pushRod();
+            }
+        },60000);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                repeatRod();
+            }
+        },120000);
+    };
+    int stopValue2=0;
+    public void repeatLock(){
+        //循环开关锁
+        stopValue2=1;
+        SerialPortUtil.getInstance().getOtherDevice().openLock();
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                SerialPortUtil.getInstance().getOtherDevice().closeLock();
+            }
+        },7000);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                repeatLock();
+            }
+        },40000);
+    };
+    public View.OnClickListener btnClickRodRepeat = new View.OnClickListener() {
+        //循环开门按钮
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickRodRepeat");
+            if(stopValue==0){
+                repeatRod();
+            }
+
+        }
+    };
+    public View.OnClickListener btnClickLockRepeat = new View.OnClickListener() {
+        //循环开锁按钮
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickRodRepeat");
+            if(stopValue2==0){
+                repeatLock();
+            }
+        }
+    };
+    public View.OnClickListener btnClickOpenLock = new View.OnClickListener() {
+        //开锁按钮
+        @Override
+        public void onClick(View view) {
+
+            System.out.println("btnClickOpenLock");
+            try {
+                SerialPortUtil.getInstance().getOtherDevice().openLock();
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            AssetFileDescriptor fd = getApplication().getAssets().openFd("openLock.mp3");
+                            mMediaPlay =new MediaPlayer();
+                            mMediaPlay.reset();
+                            mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                            mMediaPlay.prepare();
+                            Log.i(TAG,"播放音频");
+                            mMediaPlay.start();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                },200);
+                lockSwitch =1;//解锁
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        SerialPortUtil.getInstance().getOtherDevice().closeLock();
+                        lockSwitch=0;//关锁
+                    }
+                },7000);
+//                    SerialPortUtil.getInstance().getRelayControllerDevive().openOne(i);
+//                    SerialPortUtil.getInstance().getRelayControllerDevive().closeOne(i);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    };
+    private void pullRodMethod(){
+        SerialPortUtil.getInstance().getOtherDevice().pullRod();
+        try {
+            AssetFileDescriptor fd = getApplication().getAssets().openFd("nonetPullRod.mp3");
+            mMediaPlay =new MediaPlayer();
+            mMediaPlay.reset();
+            mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+            mMediaPlay.prepare();
+            Log.i(TAG,"播放音频");
+            mMediaPlay.start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    private void pullonNetRodMethod(){
+        SerialPortUtil.getInstance().getOtherDevice().pullRod();
+        try {
+            AssetFileDescriptor fd = getApplication().getAssets().openFd("netPullRod.mp3");
+            mMediaPlay =new MediaPlayer();
+            mMediaPlay.reset();
+            mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+            mMediaPlay.prepare();
+            Log.i(TAG,"播放音频");
+            mMediaPlay.start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    private void pushRodMethod(){
+        //有网下关门
+        SerialPortUtil.getInstance().getOtherDevice().pushRod();//关门
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                int a=avgWeight();
+                SPUtils.getInstance().put("avgWeight",a);
+            }
+        },3000);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    AssetFileDescriptor fd = getApplication().getAssets().openFd("onPushRod.mp3");
+                    mMediaPlay =new MediaPlayer();
+                    mMediaPlay.reset();
+                    mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                    mMediaPlay.prepare();
+                    Log.i(TAG,"播放音频");
+                    mMediaPlay.start();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        },200);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    AssetFileDescriptor fd = getApplication().getAssets().openFd("onPushRod.mp3");
+                    mMediaPlay =new MediaPlayer();
+                    mMediaPlay.reset();
+                    mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                    mMediaPlay.prepare();
+                    Log.i(TAG,"播放音频");
+                    mMediaPlay.start();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        },4000);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    AssetFileDescriptor fd = getApplication().getAssets().openFd("afterPushRod.mp3");
+                    mMediaPlay =new MediaPlayer();
+                    mMediaPlay.reset();
+                    mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                    mMediaPlay.prepare();
+                    Log.i(TAG,"播放音频");
+                    mMediaPlay.start();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        },13000);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                reportProperty(2);
+            }
+        },6500);
+    }
+
+    private int avgWeight(){
+        int weight = 0;
+        int [] arr = new int[]{};
+        int n=0;
+        long curr=System.currentTimeMillis();
+        while(System.currentTimeMillis()-curr<2000){
+            try {
+                sleep(200);
+                arr[n]=SerialPortUtil.getInstance().getWeightDevice().readWeight();
+//                arr[n]=SerialPortUtil.getInstance().getOtherDevice().readWeight();
+                Log.i("weight=", String.valueOf(arr[n]));
+                weight=arr[n]+weight;
+                n++;
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        int dvalue1 = Math.abs(arr[n-1]-arr[n-2]);
+        int dvalue2 = Math.abs(arr[n-1]-arr[n-3]);
+        if(dvalue1<30&&dvalue2<30){
+            Log.i("weight n-1=", String.valueOf(arr[n-1]));
+            return arr[n-1];
+        }
+        double v = weight / arr.length;
+        int avg = new Double(v).intValue();
+        Log.i("weight avg=", String.valueOf(avg));
+        return avg;
+    }
+
+
+    private void pushRodMethodNonet(){
+        //无网下关门
+        SerialPortUtil.getInstance().getOtherDevice().pushRod();//关门
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                Log.i(TAG,"关门中,谨防夹手");
+                try {
+                    AssetFileDescriptor fd = getApplication().getAssets().openFd("onPushRod.mp3");
+                    mMediaPlay =new MediaPlayer();
+                    mMediaPlay.reset();
+                    mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                    mMediaPlay.prepare();
+                    Log.i(TAG,"播放音频");
+                    mMediaPlay.start();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        },200);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                int a=avgWeight();
+                SPUtils.getInstance().put("avgWeight",a);
+            }
+        },3000);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                Log.i(TAG,"关门中,谨防夹手");
+                try {
+                    AssetFileDescriptor fd = getApplication().getAssets().openFd("onPushRod.mp3");
+                    mMediaPlay =new MediaPlayer();
+                    mMediaPlay.reset();
+                    mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                    mMediaPlay.prepare();
+                    Log.i(TAG,"播放音频");
+                    mMediaPlay.start();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        },4000);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                Log.i(TAG,"垃圾分类,人人有责");
+                try {
+                    AssetFileDescriptor fd = getApplication().getAssets().openFd("afterPushRod.mp3");
+                    mMediaPlay =new MediaPlayer();
+                    mMediaPlay.reset();
+                    mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                    mMediaPlay.prepare();
+                    Log.i(TAG,"播放音频");
+                    mMediaPlay.start();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        },13000);
+    }
+
+
+
+    public Handler mHandler = new Handler(msg -> {
+        return true;
+    });
+    public String IMEI = getDeviceId(getApplication());
+
+    private String TAG = "MainViewModel";
+    /*
+    * 接收物联网平台指令并执行
+    * */
+    private IConnectNotifyListener notifyListener = new IConnectNotifyListener() {
+        //物联网平台发送指令,接收并且响应
+        @Override
+        public void onNotify(String connectId, String topic, AMessage aMessage) {
+
+            System.out.println("收到云端下行的数据:connectId:"+connectId+"  topic :"+topic+"  aMessage data:"+ aMessage.getData().getClass().getSimpleName());
+            byte[] dataBytes = (byte[])aMessage.getData();
+
+            System.out.println(new String(dataBytes));
+            if (topic.endsWith("thing/event/property/post_reply")){ //推送设备属性的回包。
+            }
+            if(topic.endsWith("thing/service/restartApp")){
+                restartApp();
+            }
+            if(topic.endsWith("thing/service/RemoteOpen")){//   云端开门的请求。
+//                SerialPortUtil.getInstance().getPushRodDevice().pushRod();
+//                SerialPortUtil.getInstance().getWeightDevice().weightClear();//去皮清零
+//                SerialPortUtil.getInstance().getOtherDevice().clearWeight();//去皮清零
+                pullonNetRodMethod();
+                reportProperty(1);
+                Log.i(TAG,"欢迎使用智能回收箱");
+                Log.i(TAG,"收到开门=============");
+                Thread thread=new Thread(new Runnable() {
+                    @Override
+                    public void run() {
+                        int numInt=0;
+                        long curr=System.currentTimeMillis();
+                        while(System.currentTimeMillis()-curr<89000){
+                            try {
+                                sleep(1000);
+                                numInt++;
+                                if(SPUtils.getInstance().getString("RodSwitch").equals("0")){
+                                    break;
+                                }
+                                if(numInt==82){
+                                    pushRodMethod();
+                                    break;
+                                }
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+
+                    }
+                });
+                thread.start();
+//                mHandler.postDelayed(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        if(SPUtils.getInstance().getString("RodSwitch").equals("1")) {
+//                            pushRodMethod();
+//                        }
+//                    }
+//                },82000);//82s没关门就自动关门
+            }
+            if(topic.endsWith("thing/service/RemoteClose")){
+                Log.i(TAG,"收到关门========================");
+                pushRodMethod();
+            }
+            if(topic.endsWith("thing/service/RemoteAgentOpen")){//开收运门
+                Log.i(TAG,"开运收门");
+                SPUtils.getInstance().put("magDoorSwitch","1");
+                short i = 1;
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLock();
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                AssetFileDescriptor fd = getApplication().getAssets().openFd("openLock.mp3");
+                                mMediaPlay =new MediaPlayer();
+                                mMediaPlay.reset();
+                                mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                                mMediaPlay.prepare();
+                                Log.i(TAG,"播放音频");
+                                mMediaPlay.start();
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    },200);
+                    lockSwitch=1;
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            SerialPortUtil.getInstance().getOtherDevice().closeLock();
+                            lockSwitch=0;//关锁
+                            Thread thread=new Thread(new Runnable() {
+                                @Override
+                                public void run() {
+                                    try{
+                                        long curr=System.currentTimeMillis();
+                                        while(System.currentTimeMillis()-curr<300000) {
+                                            Thread.sleep(1000);
+                                            Integer magDoorSwitch=SerialPortUtil.getInstance().getOtherDevice().queryMagDoor();//通过门磁感应判断收运门是否关闭
+                                            if(magDoorSwitch==0){
+                                                SerialPortUtil.getInstance().getWeightDevice().weightClear();//若关闭,称重清零
+//                                                SerialPortUtil.getInstance().getOtherDevice().clearWeight();
+                                                mHandler.postDelayed(new Runnable() {
+                                                    @Override
+                                                    public void run() {
+                                                        reportProperty(1);
+                                                    }
+                                                },1000);
+                                                SPUtils.getInstance().put("magDoorSwitch","0");
+                                                break;
+                                            }
+                                        }
+                                    }catch (Exception e){
+                                        e.printStackTrace();
+                                    }
+                                }
+                            });
+                            thread.start();
+                        }
+                    },7000);
+//                    SerialPortUtil.getInstance().getRelayControllerDevive().openOne(i);
+//                    SerialPortUtil.getInstance().getRelayControllerDevive().closeOne(i);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/RemoteOpenPort2")){//开继电器2号端口
+                short i = 2;
+                try {
+                    SerialPortUtil.getInstance().getRelayControllerDevive().openOne(i);
+                    SerialPortUtil.getInstance().getRelayControllerDevive().closeOne(i);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/RemoteOpenPort2")){//开继电器3号端口
+                short i = 3;
+                try {
+                    SerialPortUtil.getInstance().getRelayControllerDevive().openOne(i);
+                    SerialPortUtil.getInstance().getRelayControllerDevive().closeOne(i);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/RemoteOpenPort2")){//开继电器4号端口
+                short i = 4;
+                try {
+                    SerialPortUtil.getInstance().getRelayControllerDevive().openOne(i);
+                    SerialPortUtil.getInstance().getRelayControllerDevive().closeOne(i);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if(topic.endsWith("thing/service/OpenLight1")){//开灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLight1();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/CloseLight1")){//关灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight1();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/OpenLight2")){//开灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLight2();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/CloseLight2")){//关灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight2();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/OpenLight3")){//开灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLight3();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/CloseLight3")){//关灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight3();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/OpenLight4")){//开灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLight4();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/CloseLight4")){//关灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight4();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/OpenLight1234")){//开灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().openLight1();
+                    SerialPortUtil.getInstance().getOtherDevice().openLight2();
+                    SerialPortUtil.getInstance().getOtherDevice().openLight3();
+                    SerialPortUtil.getInstance().getOtherDevice().openLight4();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/CloseLight1234")){//关灯
+                try {
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight1();
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight2();
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight3();
+                    SerialPortUtil.getInstance().getOtherDevice().closeLight4();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/RepeatRod")){//开灯
+                try {
+                    repeatRod();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/RepeatLock")){//关灯
+                try {
+                    repeatLock();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if(topic.endsWith("thing/service/currentWeight")){
+                Log.d(TAG , "读取当前读数");
+                int weight = SerialPortUtil.getInstance().getWeightDevice().readWeight();
+//                int weight = SerialPortUtil.getInstance().getOtherDevice().readWeight();
+                String pushDSata = weight+"";
+                MqttPublishRequest publishRequest = new MqttPublishRequest();
+                publishRequest.isRPC = false;
+                publishRequest.topic = topic.replace("request","response");
+                String[] array = topic.split("/");
+                String resId = array[3];
+                publishRequest.msgId = resId;
+                // TODO 用户根据实际情况填写 仅做参考
+                publishRequest.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{,\"weightNum\":"+weight+"} }";
+//                publishRequest.payloadObj = "{\"weightNum\":"+weight+"}";
+                LinkKit.getInstance().publish(publishRequest, new IConnectSendListener() {//Linkkit将设备接入阿里云平台 并管理控制
+                    @Override
+                    public void onResponse(ARequest aRequest, AResponse aResponse) {
+                        System.out.println("onresponse.");
+                    }
+                    @Override
+                    public void onFailure(ARequest aRequest, AError aError) {
+
+                    }
+                });
+            }
+            // 云端下行数据回调
+            // connectId 连接类型 topic 下行 topic; aMessage 下行数据
+            //String pushData = new String((byte[]) aMessage.data);
+            // pushData 示例  {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
+            // method 服务类型; params 下推数据内容
+        }
+        @Override
+        public boolean shouldHandle(String connectId, String topic) {
+            Log.e("MainViewModel","shouldHandler  connectId:"+connectId+"  topic:"+topic);
+            if(topic.contains("property/set"))
+                return false;
+            // 选择是否不处理某个 topic 的下行数据
+            // 如果不处理某个topic,则onNotify不会收到对应topic的下行数据
+            return true; //TODO 根基实际情况设置
+        }
+        @Override
+        public void onConnectStateChange(String connectId, ConnectState connectState) {
+            System.out.println("onConnectStateChange  connectId:"+connectId+"  connectState:"+connectState);
+            if(connectState == ConnectState.CONNECTED){
+                reportProperty(1);
+                int writePermission = ActivityCompat.checkSelfPermission(getApplication(),Manifest.permission.WRITE_EXTERNAL_STORAGE);
+                if(writePermission != PackageManager.PERMISSION_GRANTED){
+                    //未授权写权限。
+                    requestFilePermissions.call();
+                }
+                reportVersion();
+                otaListener();
+            }
+            // 对应连接类型的连接状态变化回调,具体连接状态参考 SDK ConnectState
+        }
+    };
+    public SingleLiveEvent<Boolean> requestFilePermissions = new SingleLiveEvent<>();
+
+    public MainViewModel(@NonNull Application application) {
+        super(application);
+    }
+    public MainViewModel(@NonNull Application application, BaseModel model) {
+        super(application, model);
+    }
+
+    /**
+     * 数据上行
+     * 上报状态
+     */
+    public void reportProperty(int num) {
+        Log.i(TAG,"上报数据");
+        try{
+
+//            SerialPortUtil.getInstance().getWeightDevice().weightClear();//去皮清零
+    //                SerialPortUtil.getInstance().getOtherDevice().clearWeight();//去皮清零
+            Integer weight = 0;
+            if(num==1){
+                weight = SerialPortUtil.getInstance().getWeightDevice().readWeight();//读称重
+            }else if(num==2){
+                weight=SPUtils.getInstance().getInt("avgWeight");
+            }
+
+//            weight=SerialPortUtil.getInstance().getOtherDevice().readWeight();//称重
+            Integer capacity=0;
+            capacity=SerialPortUtil.getInstance().getOtherDevice().getDistance();//超声波距离
+            Integer magDoorSwitch=0;
+            magDoorSwitch=SerialPortUtil.getInstance().getOtherDevice().queryMagDoor();//门磁
+            Integer currentTemperature=0;
+            currentTemperature=SerialPortUtil.getInstance().getOtherDevice().geTemperature();//温度
+
+
+            Log.i(TAG,"----------------------------------------");
+
+    //                Integer capacity=0;
+    //                Integer magDoorSwitch=0;
+    //                Integer currentTemperature=0;
+
+
+            Map<String, ValueWrapper> locationMap = new HashMap<>();
+            Map<String, ValueWrapper> reportData = new HashMap<>();
+//            SPUtils.getInstance().put("RodNumber","4721");
+            String rodNumberStr= SPUtils.getInstance().getString("RodNumber");
+            if(rodNumberStr.equals("")){
+                rodNumberStr="0";
+            }
+            int rodNumberInt=Integer.parseInt(rodNumberStr);
+            String rodSwitchStr=SPUtils.getInstance().getString("RodSwitch");
+            if(rodSwitchStr.equals("")){
+                rodSwitchStr="0";
+            }
+            int rodSwitchInt=Integer.parseInt(rodSwitchStr);
+//            SPUtils.getInstance().put("LockNumber","7562");
+            String lockNumberStr=SPUtils.getInstance().getString("LockNumber");
+            if(lockNumberStr.equals("")){
+                lockNumberStr="0";
+            }
+            int lockNumberInt=Integer.parseInt(lockNumberStr);
+
+            String runningStateStr=SPUtils.getInstance().getString("RunningState");
+            if(runningStateStr.equals("")){
+                runningStateStr="0";
+            }
+            int runningStateInt=Integer.parseInt(runningStateStr);
+            reportData.put("RodOpenNum",new ValueWrapper.IntValueWrapper(rodNumberInt));
+            reportData.put("LockOpenNum",new ValueWrapper.IntValueWrapper(lockNumberInt));
+
+            reportData.put("Weight", new ValueWrapper.IntValueWrapper(weight));    //重量  单位 --- 克
+            reportData.put("CurrentTemperature", new ValueWrapper.IntValueWrapper(currentTemperature));//温度,范围正负 ,精确到小数点后2位
+            reportData.put("RestCapacity", new ValueWrapper.IntValueWrapper(capacity));  //距离值,单位mm
+            reportData.put("MagDoorSwitch", new ValueWrapper.BooleanValueWrapper(magDoorSwitch));//门磁 0-关闭状态  1-开门状态
+
+            reportData.put("LockSwitch", new ValueWrapper.BooleanValueWrapper(lockSwitch));   //锁开关状态:0-关锁  1-解锁  解锁后3s就关锁
+            reportData.put("RodSwitch", new ValueWrapper.BooleanValueWrapper(rodSwitchInt));   //推杆状态:0-关闭状态  1-打开  开门
+
+            reportData.put("RunningState", new ValueWrapper.BooleanValueWrapper(runningStateInt)); // 0 正常 1 故障
+
+            System.out.println(IMEI+"DeviceId");
+            reportData.put("IMEI", new ValueWrapper.StringValueWrapper(IMEI));
+            locationMap.put("longitude", new ValueWrapper.DoubleValueWrapper(107.31));
+            locationMap.put("latitude", new ValueWrapper.DoubleValueWrapper(23.16));
+            locationMap.put("altitude", new ValueWrapper.DoubleValueWrapper(523.1));
+            reportData.put("GeoLocation", new ValueWrapper.StructValueWrapper(locationMap)); //
+
+            Log.i(TAG, "称重" + weight.toString() + "距离" + capacity.toString() + "温度" + currentTemperature);
+            textReportData.set("称重 " + weight.toString() + ";距离 " + capacity.toString() + "mm;温度 " +
+                    currentTemperature+"度;门磁 "+magDoorSwitch+";杆状态 "+rodSwitchInt+";锁状态 "+lockSwitch+";杆开门次数 "+rodNumberInt+";开锁次数 "+lockNumberInt
+            );
+
+            String  text = "称重 " + weight.toString() + ";距离 " + capacity.toString() + "mm;温度 " + currentTemperature+"度;门磁 "+magDoorSwitch+";杆状态 "+
+                    rodSwitchInt+";锁状态 "+lockSwitch+";杆开门次数 "+rodNumberInt+";开锁次数 "+lockNumberInt+"------------------------------";
+            Log.i(TAG, text + "=textReportData---------------------");
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
+                                @Override
+                                public void onSuccess(String s, Object o) {
+                                    Log.d("", "onSuccess() called with: s = [" + s + "], o = [" + o + "]");
+                                }
+                                @Override
+                                public void onError(String s, AError aError) {
+                                    Log.e("Main", s);
+                                }
+                            });
+                        }
+                    },200);
+
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+    }
+   /* public View.OnClickListener btnClickclearCurrent = new View.OnClickListener() {//清除
+        @Override
+        public void onClick(View view) {
+            System.out.println("btnClickclearCurrent");
+            String reportDataToString=reportDataMethod();
+            try {
+                String base64Str = DemoOne.java_openssl_encrypt("SSSS", "96e79218965eb72c92a549dd", "1234567891234567");//加密
+                Log.i(TAG,base64Str+"+++++base64Str");
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    };*/
+   /*
+   * 上报给蓝牙的数据格式
+   * */
+    public JSONObject reportDataMethod(){
+        Integer capacity=0;
+        capacity=SerialPortUtil.getInstance().getOtherDevice().getDistance();//超声波距离/
+        Integer magDoorSwitch=0;
+        magDoorSwitch=SerialPortUtil.getInstance().getOtherDevice().queryMagDoor();//门磁
+        Integer currentTemperature=0;
+        currentTemperature=SerialPortUtil.getInstance().getOtherDevice().geTemperature();//温度
+        Integer weight = 0;
+//        weight=SerialPortUtil.getInstance().getWeightDevice().readWeight();
+        weight=avgWeight();
+        //        weight=SerialPortUtil.getInstance().getOtherDevice().readWeight();//称重
+
+        String  text = "称重 " + weight.toString() + ";距离 " + capacity.toString() + "mm;温度 " + currentTemperature+"度;门磁 "+magDoorSwitch+";杆状态 "+";锁状态 "+lockSwitch;
+        Log.i(TAG, text + "=textReportData---------------------");
+
+        JSONObject locationMap =new JSONObject();
+        String rodNumberStr= SPUtils.getInstance().getString("RodNumber");
+        if(rodNumberStr.equals("")){
+            rodNumberStr="0";
+        }
+        int rodNumberInt=Integer.parseInt(rodNumberStr);
+        String rodSwitchStr=SPUtils.getInstance().getString("RodSwitch");
+        if(rodSwitchStr.equals("")){
+            rodSwitchStr="0";
+        }
+        int rodSwitchInt=Integer.parseInt(rodSwitchStr);
+
+        String lockNumberStr=SPUtils.getInstance().getString("LockNumber");
+        if(lockNumberStr.equals("")){
+            lockNumberStr="0";
+        }
+        int lockNumberInt=Integer.parseInt(lockNumberStr);
+
+        String runningStateStr=SPUtils.getInstance().getString("RunningState");
+        if(runningStateStr.equals("")){
+            runningStateStr="0";
+        }
+        int runningStateInt=Integer.parseInt(runningStateStr);
+        JSONObject tem1 =new JSONObject();
+        tem1.put("V",IMEI);
+        tem1.put("I","IMEI");
+
+        JSONObject tem2 =new JSONObject();
+        tem2.put("V",rodNumberInt);
+        tem2.put("I","RodOpenNum");
+
+        JSONObject tem3 =new JSONObject();
+        tem3.put("V",lockNumberInt);
+        tem3.put("I","LockOpenNum");
+
+        JSONObject tem4 =new JSONObject();
+        tem4.put("V",weight);
+        tem4.put("I","Weight");
+
+        JSONObject tem5 =new JSONObject();
+        tem5.put("V",currentTemperature);
+        tem5.put("I","CurrentTemperature");
+
+        JSONObject tem6 =new JSONObject();
+        tem6.put("V",capacity);
+        tem6.put("I","RestCapacity");
+
+        JSONObject tem7 =new JSONObject();
+        tem7.put("V",magDoorSwitch);
+        tem7.put("I","MagDoorSwitch");
+
+        JSONObject tem8 =new JSONObject();
+        tem8.put("V",lockSwitch);
+        tem8.put("I","LockSwitch");
+
+        JSONObject tem9 =new JSONObject();
+        tem9.put("V",rodSwitchInt);
+        tem9.put("I","RodSwitch");
+
+        JSONObject tem10 =new JSONObject();
+        tem10.put("V",runningStateInt);
+        tem10.put("I","RunningState");
+
+        locationMap.put("longitude", 107.31);
+        locationMap.put("latitude", 23.16);
+        locationMap.put("altitude", 523.1);
+        JSONObject tem11 =new JSONObject();
+        tem11.put("V",locationMap);
+        tem11.put("I","GeoLocation");
+
+        JSONArray array =new JSONArray();
+        array.add(tem1);
+        array.add(tem2);
+        array.add(tem3);
+        array.add(tem4);
+        array.add(tem5);
+        array.add(tem6);
+        array.add(tem7);
+        array.add(tem8);
+        array.add(tem9);
+        array.add(tem10);
+        array.add(tem11);
+
+        JSONObject reportData =new JSONObject();
+        reportData.put("P",array);
+
+
+        Log.i(TAG,"reportData=+++++"+reportData.toString());
+        return reportData;
+//        return (String) reportData;
+    }
+    /*
+    * 加密传给蓝牙的数据格式
+    * */
+    @RequiresApi(api = Build.VERSION_CODES.O)
+    public String reportDataMethodEncrypt() {
+        JSONObject reportData =new JSONObject();
+        reportData.put("T",SPUtils.getInstance().getString("type"));
+        //加密数据
+        String base64Str="";
+        try {
+            base64Str= DemoOne.encrypt(reportDataMethod().toString()+"@", "1234567890123456", "0987654321098765");//加密数据
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        reportData.put("D",base64Str);
+
+        return reportData.toString();
+    }
+    //设备事件上报
+    public static void reportDeviceEvent(String identifier){
+        SPUtils.getInstance().put("RunningState","1");
+        HashMap<String, ValueWrapper> hashMap = new HashMap<>();
+        // TODO 用户根据实际情况设置
+        // hashMap.put("ErrorCode", new ValueWrapper.IntValueWrapper(0));
+        hashMap.put("ErrorCode",new ValueWrapper.IntValueWrapper(3));
+//        hashMap.put("event",identifier);
+        OutputParams params = new OutputParams(hashMap);
+        Log.i("设备故障",identifier+"====="+params);
+        LinkKit.getInstance().getDeviceThing().thingEventPost(identifier, params, new IPublishResourceListener() {
+            @Override
+            public void onSuccess(String resId, Object o) { // 事件上报成功
+                Log.i("事件上报","上报成功");
+            }
+            @Override
+            public void onError(String resId, AError aError) { // 事件上报失败
+                Log.i("事件上报","上报失败");
+            }
+        });
+    }
+
+    private void getDeviceProperty(String identifier){
+        // 根据 identifier 获取当前物模型中该属性的值
+        LinkKit.getInstance().getDeviceThing().getPropertyValue(identifier);
+        // 获取所有属性
+        LinkKit.getInstance().getDeviceThing().getProperties();
+    }
+
+    public void registerAliIoTListener(){
+        // 注册下行监听,包括长连接的状态和云端下行的数据
+        LinkKit.getInstance().registerOnPushListener(notifyListener);
+        MqttRrpcRegisterRequest rrpcRegisterRequest = new MqttRrpcRegisterRequest();
+        rrpcRegisterRequest.topic = "/a13H8L6bDyf/9pD3trz6OaDV8GF7yRsb/thing/service/currentWeight";
+        MqttSubscribeRequest subscribeRequest = new MqttSubscribeRequest();
+        subscribeRequest.topic = "/a13H8L6bDyf/9pD3trz6OaDV8GF7yRsb/user/checkOnlineStatus";
+        subscribeRequest.isSubscribe = true;
+        subscribeRequest.qos = 0;
+        LinkKit.getInstance().subscribe(subscribeRequest, new IConnectSubscribeListener() {
+            @Override
+            public void onSuccess() {
+                System.out.println("checkOnlineStatus subscribe success");
+            }
+            @Override
+            public void onFailure(AError aError) {
+                System.out.println("checkOnlineStatus subscribe Fail");
+            }
+        });
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                publish();
+//                publishRepeat();
+
+            }
+        },600000);
+    }
+    private void reportVersion(){
+        //上报版本
+        IOta mOta = LinkKit.getInstance().getOta();
+        mOta.reportVersion(BuildConfig.VERSION_CODE + "", (i, s) -> Log.i(TAG,"上报版本  i:"+i+"  s:"+s));
+    }
+    /*
+    * 安装下载的app
+    * */
+    protected void installAPK() {
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                File apkFile = new File("/sdcard/Android/data/otaFile/app/app.apk");
+                if(apkFile.isDirectory()){
+                    unZipFileDemo.deleteDir(apkFile);
+                }
+                File apkFile2 = new File("/sdcard/Android/data/otaFile/file.zip");
+                unZipFileDemo.unZipFile(apkFile2,"/sdcard/Android/data/otaFile/app.apk");
+                File apkFile3 = new File("/sdcard/Android/data/otaFile/app.apk");
+
+                Log.i("安装","start");
+                if(SilentInstall.install(apkFile3.toString())){
+                    Log.i("安装","yes");
+                }else{
+                    Log.i("安装","no");
+                }
+
+//                Intent intent = new Intent(Intent.ACTION_VIEW);
+////      安装完成后,启动app
+//                Log.i(TAG,"安装");
+//                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+////                Uri uri = Uri.parse("file://" + apkFile.toString());
+//                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+//                    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+//                    Uri uri = FileProvider.getUriForFile(getApplication(), getApplication().getPackageName() + ".fileprovider", apkFile3);
+//                    intent.setDataAndType(uri, "application/vnd.android.package-archive");
+//                } else {
+//                    intent.setDataAndType(Uri.fromFile(apkFile3), "application/vnd.android.package-archive");
+//                }
+////                intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
+//                getApplication().startActivity(intent);
+            }
+        },1000);
+    }
+    /*
+    *监听是否有新版本app,有就下载
+    * */
+    public void otaListener(){
+        IOta mOta = LinkKit.getInstance().getOta();
+        IOta.OtaConfig mConfig = new IOta.OtaConfig();
+        mConfig.deviceVersion = "1";
+//        mConfig.otaFile = new File(getApplication().getCacheDir()+"/otaFile/file.apk");
+        System.out.println("dataDirPath:"+getApplication().getFilesDir().getAbsolutePath());
+        mConfig.otaFile = new File("/sdcard/Android/data/otaFile/file.zip");
+        if(!mConfig.otaFile.exists()){
+            boolean isCreate = mConfig.otaFile.mkdirs();
+            System.out.println("创建文件:"+isCreate);
+        }
+        Log.i(TAG,"路径为="+mConfig.otaFile.toString());
+        Log.i(TAG,"路径为-"+mConfig.otaFile.getAbsolutePath());
+        mOta.tryStartOta(mConfig, (step, otaResult) -> {
+            int code = otaResult.getErrorCode();
+            Object data = otaResult.getData();
+            System.out.println("stryStartOta:  code:"+code+"  data:"+data.toString());
+            if(data.toString().equals("100")){
+                installAPK();
+            }
+            switch (step) {
+                case IOta.STEP_REPORT_VERSION:
+                    // 上报版本
+                    reportVersion();
+                    break;
+                case IOta.STEP_SUBSCRIBE:
+                    // 订阅回调
+                    System.out.println("订阅回调。");
+                    break;
+                case IOta.STEP_RCVD_OTA:
+                    // 有新的OTA固件,返回true 表示继续升级
+                    System.out.println("有新的OTA固件!!!");
+                    File file=new File("/sdcard/Android/data/otaFile/file.zip");
+                    if(file.isDirectory()){
+                        unZipFileDemo.deleteDir(file);
+                    }
+                    break;
+                case IOta.STEP_DOWNLOAD:
+                    // 下载固件中
+                    System.out.println("下载固件中!!!!");
+                    break;
+            }
+            return true;
+        });
+    }
+    private int disconnectTimes = 0;
+
+    private void publish (){
+        //一个循环方法,用来更新数据
+        System.out.println("checkOnlineStatus");
+        MqttPublishRequest publishRequest = new MqttPublishRequest();
+        publishRequest.qos = 0;
+        publishRequest.topic = "/a13H8L6bDyf/device2/user/checkOnlineStatus";
+        publishRequest.payloadObj = "{\"id\":"+ publishRequest.msgId+", \"version\":\"1.0\",\"time\":" + System.currentTimeMillis() + "}";
+        Integer networkInt=netWorkInfo();
+        if(networkInt!=0){
+            reportProperty(1);
+            Log.i(TAG,"循环上报数据");
+        }
+        Log.i(TAG,"network网络"+networkInt);
+            LinkKit.getInstance().publish(publishRequest, new IConnectSendListener() {
+                @Override
+                public void onResponse(ARequest aRequest, AResponse aResponse) {
+                    disconnectTimes = 0;
+                    System.out.println("publish on Response :" + aResponse.getData());
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            publish();
+                        }
+                    }, 1800000);
+                }
+                @Override
+                public void onFailure(ARequest aRequest, AError aError) {
+                    System.out.println("publish onFailure  error:" + aError.getMsg());
+                    disconnectTimes++;
+                    if (disconnectTimes > 5) {
+//                    registerAliIoTListener(); //重新连接。
+                        disconnectTimes = 0;
+                        return;
+                    }
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            publish();
+                        }
+                    }, 1800000);
+                }
+            });
+    }
+
+    private void publishRepeat(){
+        //每隔一段时间检测磁控和温度 如果异常就上报(前提是有网条件下)
+        Integer currentTemperature=SerialPortUtil.getInstance().getOtherDevice().geTemperature();//温度
+        Integer magDoorSwitch=SerialPortUtil.getInstance().getOtherDevice().queryMagDoor();
+        if(currentTemperature>100){
+            SPUtils.getInstance().put("RunningState","2");//温度过高
+            Log.i(TAG,"温度过高");
+            Integer networkInt=netWorkInfo();
+            if(networkInt!=0){
+                reportProperty(1);
+            }
+        }
+        if(magDoorSwitch==1){
+            if(SPUtils.getInstance().getString("magDoorSwitch").equals("0")){
+                SPUtils.getInstance().put("RunningState","3");
+                Log.i(TAG,"收运门意外打开");
+                Integer networkInt=netWorkInfo();
+                if(networkInt!=0){
+                    reportProperty(1);
+                }
+            }
+        }
+        if(currentTemperature<100&magDoorSwitch==0) {
+        SPUtils.getInstance().put("RunningState","0");}
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                publishRepeat();
+            }
+        }, 420000);
+    }
+    public void unRegisterIoTListener(){
+        // 取消注册 notifyListener,notifyListener对象需和注册的时候是同一个对象
+        LinkKit.getInstance().unRegisterOnPushListener(notifyListener);
+        LinkKit.getInstance().deinit();
+    }
+
+    public static final UUID UUID_SERVICE =             UUID.fromString("7377647A-0000-1000-8000-00805F9B34FB");    //全部自定义.
+    public static final UUID UUID_DESC_NOTITY =         UUID.fromString("7377647A-0000-1000-adc5-89df72c789ec");
+    public static final UUID UUID_CHAR_WRITE =          UUID.fromString("7377647A-0000-1000-7264-00805F9B34FB");
+    public static final UUID UUID_CHAR_READ_NOTIFY =    UUID.fromString("7377647A-0000-1000-6e66-00805F9B34FB");
+    private BluetoothLeAdvertiser mBluetoothLeAdvertiser; // BLE广播
+    private BluetoothGattServer mBluetoothGattServer; // BLE服务端
+    private BluetoothGatt bluetoothGatt;
+    // BLE广播Callback
+    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
+        @Override
+        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+            Log.d(TAG,"BLE广播开启成功");
+        }
+
+        @Override
+        public void onStartFailure(int errorCode) {
+            Log.e(TAG,"BLE广播开启失败,错误码:" + errorCode);
+        }
+    };
+
+    /*
+    * 蓝牙连接,通信
+    * */
+    // BLE服务端Callback
+    private BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() {
+        @Override
+        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+            Log.i(TAG, String.format("onConnectionStateChange:%s,%s,%s,%s", device.getName(), device.getAddress(), status, newState));
+            Log.d(TAG,String.format(status == 0 ? (newState == 2 ? "与[%s]连接成功" : "与[%s]连接断开") : ("与[%s]连接出错,错误码:" + status), device));
+        }
+        @Override
+        public void onServiceAdded(int status, BluetoothGattService service) {
+            Log.i(TAG, String.format("onServiceAdded:%s,%s", status, service.getUuid()));
+            Log.d(TAG,String.format(status == 0 ? "添加服务[%s]成功" : "添加服务[%s]失败,错误码:" + status, service.getUuid()));
+        }
+        @Override
+        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+            Log.i(TAG, String.format("onCharacteristicReadRequest:%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, offset, characteristic.getUuid()));
+            new Thread(new Runnable() {
+                @RequiresApi(api = Build.VERSION_CODES.O)
+                @Override
+                public void run() {
+                    String response=SPUtils.getInstance().getString("response");
+//                    response="{\"encryptData\":\"81ypl9FQ9YyoyLyjPpd6BpvC0kT+QDGvKt9eQU8YjFhMuMpVsAMwSw/+kOFDhKKp8JEbFWKM3vTDZp5HhxIl0GloODH5RAutIFDNcLP1c13OrIfeFMmaOivV63omw5CbAMBQp0nuMXPbzHICiZ2HFJhcA0Ahm0g+R+4ze74ku4Ojbor+3s3Q4G3pF+6tnJelR/P4ovA81Ls0dvc1aBSC15hPjkD94j4mAsariqcj328zDQE9oXFb7I5k7z5jkAAVB4cm0qxnk/Np+KoEA1DU0TtQEjvN18Ak/kEND7hk5fU2xGcCmS6OMzltUk4Z3acaydWDaAfvZnChP/wUwXBcVmRdK50tgq8/70xhUWZT5WycOYmov7cO/F7gK1aP/u8hCNztYkG+sXyReXfHfFWk/0AtbT19+wUoyVgaihUfKI/knNSjqunBnVjksNKqRIKqWAUdQIarlxo+V7zdwsBcMnHQBCVeSGdG3OPfaEVMEOFopjOB7q7YLPd93AhYr/x9eQc6tRaZOR1dVgiq3hpxOD3uDCcumDjK88XcyvaD/EYV7rEukd8bTOF6lVEy3iCx+AT9GTqm123";
+                    byte[] responseBytes = response.getBytes();
+                    Log.i(TAG,"responseBytes.length=="+responseBytes.length);
+                    Log.i(TAG,"offset="+offset);
+                    if(responseBytes.length>offset){
+                        int restLen = responseBytes.length-offset;
+                        byte[] bytesCopy = new byte[restLen>22?22:restLen];
+                        System.arraycopy(responseBytes,offset,bytesCopy,0,restLen>22?22:restLen);
+                        mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,offset,bytesCopy );// 响应客户端
+                        Log.d(TAG,"客户端读取Characteristic[" + characteristic.getUuid() + "]:\n" + bytesCopy.toString());
+                        Log.d(TAG,"客户端读取Characteristic[" + characteristic.getUuid() + "]:\n" + response);
+                    }else{
+                        mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,offset,responseBytes );// 响应客户端
+                         }
+                    }
+            }).start();
+        }
+        @Override
+        public void onCharacteristicWriteRequest(final BluetoothDevice device, int requestId,
+         final BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
+            // 获取客户端发过来的数据
+            new Thread(new Runnable() {
+                @RequiresApi(api = Build.VERSION_CODES.O)
+                @Override
+                public void run() {
+                    Log.i(TAG,"device="+device.toString());
+                    Log.i(TAG,"requestId="+requestId);
+                    Log.i(TAG,"characteristic="+characteristic.toString());
+                    Log.i(TAG,"preparedWrite="+preparedWrite);
+                    Log.i(TAG,"responseNeeded="+responseNeeded);
+                    Log.i(TAG,"offset="+offset);
+                    Log.i(TAG,"requestBytes="+requestBytes.toString());
+
+                    String requestStr = new String(requestBytes);
+                    System.out.println(requestStr);
+                    Log.i(TAG,"changdu=="+requestStr.length());
+                    String[] data=requestStr.split("[|]");
+                        if(data[0].endsWith("fir3")) {
+                            mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);// 响应客户端
+                            if (data[1].equals("11")) {
+                                pullRodMethod();
+                                Log.i(TAG, "----------------------*************--"+data[1]+"+++"+data[2]);
+                                mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);// 响应客户端
+                                String timeStr=data[2];
+                                int timeInt=Integer.parseInt(timeStr);
+                                if(timeInt==0){
+                                    timeInt=82;
+                                }
+                                int finalTimeInt = timeInt;
+                                Thread thread=new Thread(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        int numInt=0;
+                                        long curr=System.currentTimeMillis();
+                                        while(System.currentTimeMillis()-curr<89000){
+                                            try {
+                                                sleep(1000);
+                                                numInt++;
+                                                if(SPUtils.getInstance().getString("RodSwitch").equals("0")){
+                                                    break;
+                                                }
+                                                if(numInt== finalTimeInt){
+                                                    pushRodMethod();
+                                                    break;
+                                                }
+                                            } catch (InterruptedException e) {
+                                                e.printStackTrace();
+                                            }
+                                        }
+
+                                    }
+                                });
+                                thread.start();
+                            }
+                            if(data[1].equals("22")){
+                                try {
+                                    SPUtils.getInstance().put("magDoorSwitch","1");
+                                    SerialPortUtil.getInstance().getOtherDevice().openLock();
+                                    mHandler.postDelayed(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            try {
+                                                AssetFileDescriptor fd = getApplication().getAssets().openFd("openLock.mp3");
+                                                mMediaPlay =new MediaPlayer();
+                                                mMediaPlay.reset();
+                                                mMediaPlay.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+                                                mMediaPlay.prepare();
+                                                Log.i(TAG,"播放音频");
+                                                mMediaPlay.start();
+                                            } catch (IOException e) {
+                                                e.printStackTrace();
+                                            }
+                                        }
+                                    },200);
+                                    lockSwitch =1;//解锁
+                                    mHandler.postDelayed(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            SerialPortUtil.getInstance().getOtherDevice().closeLock();
+                                            lockSwitch=0;//关锁
+                                            Thread thread=new Thread(new Runnable() {
+                                                @Override
+                                                public void run() {
+                                                    try{
+                                                    long curr=System.currentTimeMillis();
+                                                    while(System.currentTimeMillis()-curr<300000) {
+                                                        Thread.sleep(1000);
+                                                        Integer magDoorSwitch=SerialPortUtil.getInstance().getOtherDevice().queryMagDoor();//通过门磁感应判断收运门是否关闭
+                                                        if(magDoorSwitch==0){
+                                                            SerialPortUtil.getInstance().getWeightDevice().weightClear();//若关闭,称重清零
+                                                            SPUtils.getInstance().put("magDoorSwitch","0");
+//                                                            SerialPortUtil.getInstance().getOtherDevice().clearWeight();
+                                                            break;
+                                                        }
+                                                    }
+                                                    }catch (Exception e){
+                                                        e.printStackTrace();
+                                                    }
+                                                }
+                                            });
+                                            thread.start();
+                                        }
+                                    },7000);
+                                } catch (Exception e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                        }
+                    if(requestStr.equals("close")){
+                        pushRodMethodNonet();
+                            Log.i(TAG,"++++++收到321");
+                            Log.i(TAG,"+++++关门");
+                        SPUtils.getInstance().put("type","close");
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                String response=reportDataMethodEncrypt();
+                                SPUtils.getInstance().put("response",response);
+                                onCharacteristicReadRequest(device,  requestId, offset, characteristic);
+                            }
+                        },6500);
+
+                    }
+                    if(requestStr.equals("close2")){
+                        Log.i(TAG,"+++++收运门关");
+                        SPUtils.getInstance().put("type","close");
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                String response=reportDataMethodEncrypt();
+                                SPUtils.getInstance().put("response",response);
+                                onCharacteristicReadRequest(device,  requestId, offset, characteristic);
+                            }
+                        },500);
+                    }
+                    if(requestStr.equals("link")){
+                        Log.i(TAG,"++++++收到123");
+                        Log.i(TAG,"+++++开门请求");
+                        Log.i(TAG,"+++++requestStrlength"+requestStr.length());
+                        SPUtils.getInstance().put("type","open");
+                        String response=reportDataMethodEncrypt();
+                        SPUtils.getInstance().put("response",response);
+                        onCharacteristicReadRequest(device,  requestId, offset, characteristic);
+                    }
+                    Log.i(TAG, String.format("onCharacteristicWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, characteristic.getUuid(),
+                            preparedWrite, responseNeeded, offset, requestStr));
+                    Log.d(TAG,"客户端写入Characteristic[" + characteristic.getUuid() + "]write:\n" + requestStr);
+//                    mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);// 响应客户端
+                }
+            }).start();
+        }
+
+        @Override
+        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+            Log.i(TAG, String.format("onExecuteWrite:%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, execute));
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        @Override
+        public void onNotificationSent(BluetoothDevice device, int status) {
+            Log.i(TAG, String.format("onNotificationSent:%s,%s,%s", device.getName(), device.getAddress(), status));
+        }
+        @Override
+        public void onMtuChanged(BluetoothDevice device, int mtu) {
+            Log.i(TAG, String.format("onMtuChanged:%s,%s,%s", device.getName(), device.getAddress(), mtu));
+        }
+
+
+    };
+    BluetoothGattCharacteristic characteristicRead;
+    public void startBLEServer(){
+        BluetoothManager bluetoothManager = (BluetoothManager) getApplication().getSystemService(Context.BLUETOOTH_SERVICE);
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        bluetoothAdapter.setName("62F");
+        // ============启动BLE蓝牙广播(广告) =================================================================================
+        Log.i(TAG,"启动BLE蓝牙广播(广告)");
+        //广播设置(必须)
+        AdvertiseSettings settings = new AdvertiseSettings.Builder()
+                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) //广播模式: 低功耗,平衡,低延迟
+                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //发射功率级别: 极低,低,中,高
+                .setConnectable(true) //能否连接,广播分为可连接广播和不可连接广播
+                .setTimeout(0)
+                .build();
+        //广播数据(必须,广播启动就会发送)
+        AdvertiseData advertiseData = new AdvertiseData.Builder()
+                .setIncludeDeviceName(true) //包含蓝牙名称
+                .setIncludeTxPowerLevel(true) //包含发射功率级别
+                .addManufacturerData(0x09, "sw".getBytes()) //设备厂商数据,自定义
+                .build();
+        //扫描响应数据(可选,当客户端扫描时才发送)
+        AdvertiseData scanResponse = new AdvertiseData.Builder()
+                .addManufacturerData(2, new byte[]{66, 66,31,45, (byte) 0xac}) //设备厂商数据,自定义
+                .addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服务UUID
+//                .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服务数据,自定义
+                .build();
+        mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
+        mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);
+
+        // 注意:必须要开启可连接的BLE广播,其它设备才能发现并连接BLE服务端!
+        // =============启动BLE蓝牙服务端=====================================================================================
+        BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+        //添加可读+通知characteristic
+        characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY,
+                BluetoothGattCharacteristic.PROPERTY_NOTIFY|BluetoothGattCharacteristic.PROPERTY_READ|BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_READ|BluetoothGattCharacteristic.PERMISSION_WRITE);
+        characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ));
+        service.addCharacteristic(characteristicRead);
+        //添加可写characteristic
+        BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE,
+                BluetoothGattCharacteristic.PROPERTY_NOTIFY|BluetoothGattCharacteristic.PROPERTY_READ|BluetoothGattCharacteristic.PROPERTY_WRITE
+                , BluetoothGattCharacteristic.PERMISSION_WRITE|BluetoothGattCharacteristic.PERMISSION_READ);
+        service.addCharacteristic(characteristicWrite);
+        if (bluetoothManager != null)
+            mBluetoothGattServer = bluetoothManager.openGattServer(getApplication(), mBluetoothGattServerCallback);
+        mBluetoothGattServer.addService(service);
+    }
+
+    public void stopBleServer(){
+        if (mBluetoothLeAdvertiser != null)
+            mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
+        if (mBluetoothGattServer != null)
+            mBluetoothGattServer.close();
+    }
+
+
+    public static final String INTENT_ACTION_GRANT_USB = "com.siwei.recyclebox.GRANT_USB";
+    private List<UsbSerialPort> mEntries = new ArrayList<UsbSerialPort>();
+    private BroadcastReceiver mUsbReceiver;
+    private UsbManager mUsbManager;
+    UsbSerialPort port = null;
+
+    public void closePort(){
+        try {
+            port.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static String getDeviceId(Context context) {
+        StringBuilder deviceId = new StringBuilder();
+        // 渠道标志
+        try {
+            //IMEI(imei)
+            TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
+                // TODO : 没有权限,读不到deviceid,要向用户申请读取手机信息的权限
+                //    ActivityCompat#requestPermissions
+                // here to request the missing permissions, and then overriding
+                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                //                                          int[] grantResults)
+                // to handle the case where the user grants the permission. See the documentation
+                // for ActivityCompat#requestPermissions for more details.
+                return "";
+            }
+            String imei = tm.getDeviceId();
+            if(!StringUtils.isEmpty(imei)){
+//                deviceId.append("imei");
+                deviceId.append(imei);
+//                PALog.e("getDeviceId : ", deviceId.toString());
+                return deviceId.toString();
+            }
+            //wifi mac地址
+            WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+            WifiInfo info = wifi.getConnectionInfo();
+            String wifiMac = info.getMacAddress();
+            if(!StringUtils.isEmpty(wifiMac)){
+//                deviceId.append("wifi");
+                deviceId.append(wifiMac);
+//                PALog.e("getDeviceId : ", deviceId.toString());
+                return deviceId.toString();
+            }
+            //序列号(sn)
+            String sn = tm.getSimSerialNumber();
+            if(!StringUtils.isEmpty(sn)){
+                deviceId.append("sn");
+                deviceId.append(sn);
+//                PALog.e("getDeviceId : ", deviceId.toString());
+                return deviceId.toString();
+            }
+            //如果上面都没有, 则生成一个id:随机码
+//            String uuid = getUUID(context);
+//            if(!isEmpty(uuid)){
+//                deviceId.append("id");
+//                deviceId.append(uuid);
+//                PALog.e("getDeviceId : ", deviceId.toString());
+//                return deviceId.toString();
+//            }
+        } catch (Exception e) {
+            e.printStackTrace();
+//            deviceId.append("id").append(getUUID(context));
+        }
+//        PALog.e("getDeviceId : ", deviceId.toString());
+        return deviceId.toString();
+    }
+
+    /*@RequiresApi(api = Build.VERSION_CODES.N)
+    private JSONObject getAllDeviceProperty(){
+        ExecutorService executorService = Executors.newFixedThreadPool(10);
+        AtomicInteger weightResult = new AtomicInteger();
+        JSONObject jsonObject = new JSONObject();
+        CompletableFuture<Integer> weightFuture = CompletableFuture.supplyAsync(()->{
+            WeightDevice weightDevice = (WeightDevice) SerialPortUtil.getInstance().getWeightDevice();
+            int weight = weightDevice.readWeight();
+            return weight;
+        },executorService);
+        CompletableFuture<Double> temperatureFuture = CompletableFuture.supplyAsync(()->{
+            return 41.1;
+        },executorService);
+
+        weightFuture.thenAccept((result)->{
+            jsonObject.put("weight",result);
+        });
+        temperatureFuture.thenAccept((result)->{
+            jsonObject.put("currentTemperature",result);
+        });
+        jsonObject.put("IMEI",IMEI);
+        jsonObject.put("RunningState",0);
+        jsonObject.put("LockSwitch",1);
+
+        return jsonObject;
+    }*/
+    /*
+    * 判断是否有网
+    * */
+    public Integer netWorkInfo(){
+        ConnectivityManager connectivity = (ConnectivityManager) getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = connectivity.getActiveNetworkInfo();
+        if(info != null && info.isAvailable()){
+            if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+                Log.i(TAG,"WiFi网络...");//WiFi网络
+                return 2;
+            } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
+//移动网络
+                Log.i(TAG,"移动网络....");
+                return 1;
+            } else {
+//网络错误
+                Log.i(TAG,"网络错误....");
+                return 0;
+            }
+        }else{
+//网络错误
+            Log.i(TAG,"网络错误....");
+            return 0;
+        }
+    }
+    /*
+    * 重启app方法
+    * */
+    public void restartApp(){
+            Intent mStartActivity = new Intent(getApplication(),MainActivity.class);
+            int mPendingIntentId = 123456;
+            PendingIntent mPendingIntent = PendingIntent.getActivity(getApplication(), mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
+            AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
+            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
+            System.exit(0);
+    }
+
+    private Object getSystemService(String name) {
+        return getApplication().getSystemService(name);
+    }
+
+
+
+}

+ 184 - 0
app/src/main/java/com/siwei/recyclebox/utils/DemoOne.java

xqd
@@ -0,0 +1,184 @@
+package com.siwei.recyclebox.utils;
+
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import com.siwei.recyclebox.deviceUtils.ByteUtil;
+import com.siwei.recyclebox.ui.main.MainViewModel;
+//import org.apache.commons.codec.binary.Base64;
+
+//import libs.codec.org.apache.commons.codec.binary.Base64;
+
+
+import java.security.MessageDigest;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import android.util.Log;
+import android.util.Base64;
+//import org.apache.commons.codec.binary.Base64;
+//import org.apache2.commons2.codecdown.binary.Base64;
+
+
+public class DemoOne {
+    @RequiresApi(api = Build.VERSION_CODES.O)
+
+//    private String sKey = "1234567890123456";//key,可自行修改
+//    private String ivParameter = "0987654321098765";//偏移量,可自行修改
+    public static String encrypt(String encData ,String secretKey,String vector) throws Exception {
+        try {
+            if (secretKey == null) {
+                return null;
+            }
+            if (secretKey.length() != 16) {
+                return null;
+            }
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            byte[] raw = secretKey.getBytes();
+            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+            IvParameterSpec iv = new IvParameterSpec(vector.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
+            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
+            byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));
+            //return Base64.encodeBase64String(encrypted);
+            return Base64.encodeToString(encrypted, Base64.NO_WRAP);// 此处使用BASE64做转码。
+        }catch (Exception e){
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    public static String decrypt(String sSrc,String key,String ivs) throws Exception {
+        try {
+            byte[] raw = key.getBytes("ASCII");
+            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());
+            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
+            byte[] encrypted1 = Base64.decode(sSrc.getBytes(), Base64.NO_WRAP);// 先用base64解密
+            byte[] original = cipher.doFinal(encrypted1);
+            String originalString = new String(original, "utf-8");
+            return originalString;
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+
+//        String base64Str = java_openssl_encrypt("{MagDoorSwitch=0, RunningState=1, CurrentTemperature=0, LockSwitch=0, RodSwitch=0, IMEI=, GeoLocation={altitude=523.1, latitude=23.16, longitude=107.31}, Weight=0, RestCapacity=0}", "96e79218965eb72c92a549dd", "1234567891234567");
+//        System.out.print("++"+base64Str+"++");
+//        Map<String,String> arr = new HashMap<String,String>();
+//        arr.put("sss", "1111");
+//    public static void main(String[] args) {
+//        Map<String,String> arr = new HashMap<String,String>();
+//        arr.put("ssss", "11112");
+//        StringBuilder data = new StringBuilder("");
+//        int k = 0;
+//        for (Map.Entry<String, String> entry : arr.entrySet()) {
+//            if (k > 0) {
+//                data.append("&");
+//            }
+//            data.append(entry.getKey() + "=" + entry.getValue());
+//            k++;
+//        }
+//        data.append("@");
+//        int size = 16;
+////        StringBuilder str = new StringBuilder("\0");
+//        char[] ascll0 = new char[]{0};
+//        char[] ascll7 = new char[]{7};
+//        String iv = repeat(new String(ascll0), size);
+//        String key = "1111111111111111";
+//        int padding = size - data.length() % size;
+//        char a = '\u0007';
+//        String str2 = String.valueOf(a);
+//        String plaintext = data +  repeat(str2, 7);
+//        System.out.println(plaintext);
+        /*int size = 16;
+        char[] ascll0 = new char[]{0};
+        String iv = repeat(new String(ascll0), size);
+        try {
+            String base64Str = DemoOne.encrypt("1213211", "1234567891234567", iv);
+//            String base64Str2 = java_openssl_encrypt("{MagDoorSwitch=0, RunningState=1, CurrentTemperature=0, LockSwitch=0, RodSwitch=0, IMEI=, GeoLocation={altitude=523.1, latitude=23.16, longitude=107.31}, Weight=0, RestCapacity=0}", "96e79218965eb72c92a549dd", iv);
+            System.out.print("++"+base64Str+"++");
+//            String base64Str = java_openssl_encrypt(data.toString(), key);
+//            System.out.print(base64Str);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }*/
+    }
+
+    public static String repeat(String data, int times) {
+        StringBuilder str = new StringBuilder(data);
+        for (int i = 0; i < times - 1; i++) {
+            str.append(data);
+        }
+        String res = str.toString();
+        System.out.println(str.length() + " // " + res.length());
+        return res;
+    }
+
+   /* public static String java_openssl_encrypt(String data, String password, String iv) throws Exception {
+//        byte[] key = new byte[32];
+
+//         * for (int i = 0; i < 32; i++) { if (i < password.getBytes().length) { key[i] =
+//         * password.getBytes()[i]; } else { key[i] = 0; } }
+
+//        data = "sss=1111";
+//        iv = "1111111111111111";
+//        System.out.println("data bytes:"+ByteUtil.bytesToString(data.getBytes()));
+//        System.out.println("iv:"+ByteUtil.bytesToString(iv.getBytes()));
+//        MessageDigest md5 = MessageDigest.getInstance("MD5");
+//        byte[] key = md5.digest(password.getBytes());
+//        System.out.println("md5password:"+ ByteUtil.bytesToString(key));
+//        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+        //加密  pasword  iv 可以定死
+//        public static String java_openssl_encrypt (String data, String password) throws Exception {
+//            char[] ascll0 = new char[]{0};
+//            String iv = repeat(new String(ascll0), 16);
+//        MessageDigest md5 = MessageDigest.getInstance("MD5");
+//        byte[] key = md5.digest(password.getBytes());
+
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(password.getBytes(), "AES"),
+                    new IvParameterSpec(iv.getBytes("UTF-8")));
+//            String base64Str = Base64.encodeBase64String(cipher.doFinal(data.getBytes()));
+        String base64Str = Base64.encodeToString(cipher.doFinal(data.getBytes()), Base64.DEFAULT);
+            System.out.println(base64Str);
+            //解密
+            Cipher decryptCipher = Cipher.getInstance("AES/CBC/NoPadding");
+            decryptCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(password.getBytes(), "AES"), new IvParameterSpec(iv.getBytes()));
+//        byte[] deBytes = decryptCipher.doFinal(Base64.decodeBase64(base64Str));
+//        System.out.println(new String(deBytes));
+            return base64Str;
+
+        }*/
+
+
+//        public static String java_openssl_decrypt (String base64Content, String password ) throws
+//        Exception {
+//            char[] ascll0 = new char[]{0};
+//            String iv = repeat(new String(ascll0), 16);
+//            Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+//            decryptCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(password.getBytes(), "AES"), new IvParameterSpec(iv.getBytes()));
+//
+//            byte[] deBytes = decryptCipher.doFinal(Base64.decodeBase64(base64Content));
+//            String decryptContent = new String(deBytes);
+//            return decryptContent;
+//
+//        }
+ /*   public static String java_openssl_dencrypt(String data, String password, String iv) throws Exception {
+        //解密
+        Cipher decryptCipher = Cipher.getInstance("AES/CBC/NoPadding");
+        decryptCipher.init(Cipher.DECRYPT_MODE,new SecretKeySpec(password.getBytes(),"AES"),new IvParameterSpec(iv.getBytes()));
+        byte[] deBytes = decryptCipher.doFinal(Base64.decodeBase64(data));
+        String deByteStr=new String(deBytes);
+        return deByteStr;
+    }*/
+
+    }

+ 74 - 0
app/src/main/java/com/siwei/recyclebox/utils/SilentInstall.java

xqd
@@ -0,0 +1,74 @@
+package com.siwei.recyclebox.utils;
+
+//静默安装
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+
+/**
+ * 静默安装的实现类,调用install()方法执行具体的静默安装逻辑。
+ * 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
+ * @author guolin
+ * @since 2015/12/7
+ */
+public class SilentInstall {
+    /**
+     * 执行具体的静默安装逻辑,需要手机ROOT。
+     * @param apkPath
+     *          要安装的apk文件的路径
+     * @return 安装成功返回true,安装失败返回false。
+     */
+    public static boolean install(String apkPath) {
+        boolean result = false;
+        DataOutputStream dataOutputStream = null;
+        BufferedReader errorStream = null;
+        try {
+            // 申请su权限
+            Process process = Runtime.getRuntime().exec("su");
+            dataOutputStream = new DataOutputStream(process.getOutputStream());
+            // 执行pm install命令
+            String command = "pm install -r " + apkPath + "\n";
+            dataOutputStream.write(command.getBytes(Charset.forName("utf-8")));
+            dataOutputStream.flush();
+            dataOutputStream.writeBytes("exit\n");
+            dataOutputStream.flush();
+            process.waitFor();
+            errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+            String msg = "";
+            String line;
+            // 读取命令的执行结果
+            while ((line = errorStream.readLine()) != null) {
+                msg += line;
+            }
+            Log.d("TAG", "install msg is " + msg);
+            // 如果执行结果中包含Failure字样就认为是安装失败,否则就认为安装成功
+            if (!msg.contains("Failure")) {
+                result = true;
+                //adb shell am start com.siwei.recyclebox/.ui.main.MainActivity 这个是启动应用
+                dataOutputStream = new DataOutputStream(process.getOutputStream());
+                String command1 = "am start com.siwei.recyclebox/.ui.main.MainActivity" + "\n";
+                dataOutputStream.write(command1.getBytes(Charset.forName("utf-8")));
+                dataOutputStream.flush();
+            }
+        } catch (Exception e) {
+            Log.e("TAG", e.getMessage(), e);
+        } finally {
+            try {
+                if (dataOutputStream != null) {
+                    dataOutputStream.close();
+                }
+                if (errorStream != null) {
+                    errorStream.close();
+                }
+            } catch (IOException e) {
+                Log.e("TAG", e.getMessage(), e);
+            }
+        }
+        return result;
+    }
+}

+ 111 - 0
app/src/main/java/com/siwei/recyclebox/utils/unZipFileDemo.java

xqd
@@ -0,0 +1,111 @@
+package com.siwei.recyclebox.utils;
+
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import static android.content.ContentValues.TAG;
+
+public class unZipFileDemo {
+    /**
+     * zipFile 压缩文件
+     * folderPath 解压后的文件路径
+     * */
+    public static void unZipFile(File zipFile, String folderPath) {
+        try {
+            Log.i(TAG,"解压");
+            ZipFile zfile = new ZipFile(zipFile);
+            Enumeration zList = zfile.entries();
+            ZipEntry ze = null;
+            byte[] buf = new byte[1024];
+            while (zList.hasMoreElements()) {
+                ze = (ZipEntry) zList.nextElement();
+
+                if (ze.isDirectory()) {
+                    String dirstr = folderPath +  File.separator + ze.getName();
+                    dirstr = new String(dirstr.getBytes("8859_1"), "GB2312");
+                    File f = new File(dirstr);
+                    boolean mdir = f.mkdir();
+                    continue;
+                }
+
+                OutputStream os = new BufferedOutputStream(new FileOutputStream(getRealFileName(folderPath,ze.getName())));
+                Log.e(TAG,"---->getRealFileName(folderPath,ze.getName()): " + getRealFileName(folderPath,ze.getName()).getPath() + "  name:" + getRealFileName(folderPath,ze.getName()).getName());
+                InputStream is = new BufferedInputStream(zfile.getInputStream(ze));
+                int readLen = 0;
+                while ((readLen = is.read(buf, 0, 1024)) != -1) {
+                    os.write(buf, 0, readLen);
+                }
+                is.close();
+                os.close();
+            }
+            zfile.close();
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    /**
+     * 根据保存zip的文件路径和zip文件相对路径名,返回一个实际的文件
+     * 因为zip文件解压后,里边可能是多重文件结构
+     * */
+    public static File getRealFileName(String baseDir, String absFileName) {
+        String[] dirs = absFileName.split("/");
+        File ret = new File(baseDir);
+        String substr = null;
+        if (dirs.length > 1) {
+            for (int i = 0; i < dirs.length - 1; i++) {
+                substr = dirs[i];
+                try {
+                    substr = new String(substr.getBytes("8859_1"), "GB2312");
+                } catch (UnsupportedEncodingException e) {
+                    e.printStackTrace();
+                }
+                ret = new File(ret, substr);
+
+            }
+            if (!ret.exists()) {
+                ret.mkdirs();
+            }
+            substr = dirs[dirs.length - 1];
+            try {
+                substr = new String(substr.getBytes("8859_1"), "GB2312");
+            } catch (UnsupportedEncodingException e) {
+                e.printStackTrace();
+            }
+            ret = new File(ret, substr);
+            return ret;
+        }
+        return ret;
+    }
+    /**
+     * 删除整个文件夹 或者 文件
+     */
+    public static void deleteDir(File file) {
+        if (!file.exists()) {
+            return;
+        }
+        if (file.isDirectory()) {
+            File[] childFiles = file.listFiles();
+            if (childFiles == null || childFiles.length == 0) {
+                file.delete();
+            }
+
+            for (int index = 0; index < childFiles.length; index++) {
+                deleteDir(childFiles[index]);
+            }
+        }
+        file.delete();
+    }
+}

+ 34 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

xqd
@@ -0,0 +1,34 @@
+<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:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                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="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

xqd
@@ -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="#008577"
+        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>

+ 338 - 0
app/src/main/res/layout/activity_main.xml

xqd
@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:tools="http://schemas.android.com/tools"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:binding="http://schemas.android.com/apk/res-auto">
+
+    <data>
+        <variable
+            name="viewModal"
+            type="com.siwei.recyclebox.ui.main.MainViewModel"/>
+    </data>
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <!--<TextView
+                android:id="@+id/textView"
+                android:layout_width="0dp"
+                android:layout_height="11dp"
+                android:text=""
+                android:textSize="18sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintHorizontal_bias="0.283"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintRight_toRightOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_bias="0.022" />-->
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+
+
+                <LinearLayout
+                    android:layout_width="120dp"
+                    android:layout_height="match_parent">
+                    <Button
+                        android:layout_width="120dp"
+                        android:layout_height="96dp"
+                        android:onClick="@{viewModal.btnQeryIMEI}"
+                        android:text="查询IMEI:**"
+                        android:textSize="24sp" />
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="77dp"
+                        android:layout_marginStart="1dp"
+                        android:layout_marginTop="1dp"
+                        android:layout_marginEnd="2dp"
+                        android:text="@={viewModal.textIMEI}"
+                        android:textSize="30dp" />
+                </LinearLayout>
+
+            </LinearLayout>
+            <LinearLayout
+                android:id="@+id/linearLayout"
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+
+
+                <LinearLayout
+                    android:layout_width="120dp"
+                    android:layout_height="match_parent">
+                <Button
+                    android:layout_width="120dp"
+                    android:layout_height="96dp"
+                    android:onClick="@{viewModal.btnClickReport}"
+                    android:text="数据上报"
+                    android:textSize="24sp" />
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="77dp"
+                    android:layout_marginStart="1dp"
+                    android:layout_marginTop="1dp"
+                    android:layout_marginEnd="2dp"
+                    android:text="@={viewModal.textReportData}"
+                    android:textSize="30dp" />
+                </LinearLayout>
+
+            </LinearLayout>
+
+
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="120dp"
+                android:padding="16dp">
+                <Button
+                    android:layout_width="161dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickPullRod}"
+                    android:text="开门"
+                    android:textSize="24sp"
+                    />
+                <Button
+                    android:layout_width="154dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickPushRod}"
+                    android:text="关门"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="开锁 3秒后 关锁"
+                            android:textSize="24sp"
+                            />
+                        <Button
+                            android:layout_width="150dp"
+                            android:layout_height="100dp"
+                            android:onClick="@{viewModal.btnClickOpenLock}"
+                            android:text="开锁"
+                            android:textSize="24sp"
+                             />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+                <LinearLayout
+                    android:layout_width="130dp"
+                    android:layout_height="100dp">
+                <Button
+                    android:layout_width="130dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickreadWeight}"
+                    android:text="称重"
+                    android:textSize="24sp"
+                />
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="100dp"
+                    android:text="@={viewModal.textReadWeightData}"
+                    android:textSize="42sp"
+                    />
+                </LinearLayout>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClicksetScale}"
+                    android:text="设置分度值"
+                    android:textSize="24sp"
+                    />
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickclearWeight}"
+                    android:text="称重清零去皮"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp"
+                >
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickzeroCalibration}"
+                    android:text="零度校准"
+                    android:textSize="24sp"
+                    />
+
+                <Button
+                    android:id="@+id/button"
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickweightCalibration}"
+                    android:text="砝码校准"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+            <Button
+                android:layout_width="150dp"
+                android:layout_height="100dp"
+                android:onClick="@{viewModal.btnClickRodRepeat}"
+                android:text="连续伸缩杆"
+                android:textSize="24sp"
+                 />
+                <Button
+                    android:id="@+id/button2"
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickLockRepeat}"
+                    android:text="连续开锁"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="130dp">
+            <Button
+                android:layout_width="150dp"
+                android:layout_height="100dp"
+                android:onClick="@{viewModal.btnClickqueryCurrent}"
+                android:text="电流过大查询"
+                android:textSize="24sp"
+                />
+            </LinearLayout>
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="130dp"
+                    >
+                <TextView
+                    android:layout_width="50dp"
+                    android:layout_height="100dp"
+                    android:text="@={viewModal.textqueryCurrentData}"
+                    android:textSize="24sp"
+                    />
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="130dp">
+            <Button
+                android:layout_width="150dp"
+                android:layout_height="100dp"
+                android:onClick="@{viewModal.btnClickclearCurrent}"
+                android:text="清除"
+                android:textSize="24sp"
+                 />
+                </LinearLayout>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp"
+                >
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickSpeech}"
+                    android:text="播放语音"
+                    android:textSize="24sp"
+                    />
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickOpenLight1234}"
+                    android:text="开灯1234"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp"
+                >
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickOpenLight1}"
+                    android:text="开灯1"
+                    android:textSize="24sp"
+                    />
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickOpenLight2}"
+                    android:text="开灯2"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp"
+                >
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickOpenLight3}"
+                    android:text="开灯3"
+                    android:textSize="24sp"
+                    />
+                <Button
+                    android:layout_width="150dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickOpenLight4}"
+                    android:text="开灯4"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="130dp"
+                android:padding="16dp">
+
+                <Button
+                    android:layout_width="131dp"
+                    android:layout_height="100dp"
+                    android:onClick="@{viewModal.btnClickrestartApp}"
+                    android:text="重启"
+                    android:textSize="24sp"
+                    />
+            </LinearLayout>
+
+        </LinearLayout>
+    </ScrollView>
+
+</layout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

xqd
@@ -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>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

xqd
@@ -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>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 6 - 0
app/src/main/res/values/colors.xml

xqd
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

xqd
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">RecycleBox</string>
+</resources>

+ 11 - 0
app/src/main/res/values/styles.xml

xqd
@@ -0,0 +1,11 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>

+ 23 - 0
app/src/main/res/xml/device_filter.xml

xqd
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- 0x0403 / 0x6001: FTDI FT232R UART -->
+    <usb-device vendor-id="1027" product-id="24577" />
+
+    <!-- 0x0403 / 0x6015: FTDI FT231X -->
+    <usb-device vendor-id="1027" product-id="24597" />
+
+    <!-- 0x2341 / Arduino -->
+    <usb-device vendor-id="9025" />
+
+    <!-- 0x16C0 / 0x0483: Teensyduino  -->
+    <usb-device vendor-id="5824" product-id="1155" />
+
+    <!-- 0x10C4 / 0xEA60: CP210x UART Bridge -->
+    <usb-device vendor-id="4292" product-id="60000" />
+
+    <!-- 0x067B / 0x2303: Prolific PL2303 -->
+    <usb-device vendor-id="1659" product-id="8963" />
+
+    <!-- 0x1a86 / 0x7523: Qinheng CH340 -->
+    <usb-device vendor-id="6790" product-id="29987" />
+</resources>

+ 6 - 0
app/src/main/res/xml/filepaths.xml

xqd
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <paths xmlns:android="http://schemas.android.com/apk/res/android">
+        <external-path name="external" path=""/>
+    </paths>
+</PreferenceScreen>

+ 17 - 0
app/src/test/java/com/siwei/recyclebox/ExampleUnitTest.java

xqd
@@ -0,0 +1,17 @@
+package com.siwei.recyclebox;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 30 - 0
build.gradle

xqd
@@ -0,0 +1,30 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+apply from: "config.gradle"
+buildscript {
+    repositories {
+        maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
+        maven { url 'https://jitpack.io' }
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.3.2'
+        
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
+        maven { url 'https://jitpack.io' }
+        google()
+        jcenter()
+        
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 65 - 0
config.gradle

xqd
@@ -0,0 +1,65 @@
+ext {
+    //android开发版本配置
+    android = [
+            compileSdkVersion: 28,
+            buildToolsVersion: "28.0.0",
+            applicationId    : "com.goldze.mvvmhabit",
+            minSdkVersion    : 21,
+            targetSdkVersion : 25,
+            versionCode      : 1,
+            versionName      : "1.0",
+    ]
+    //version配置
+    versions = [
+            "support-version": "28.0.0",
+            "junit-version"  : "4.12",
+    ]
+    //support配置
+    support = [
+            "constraint-layout"       : "1.0.0-beta2",
+            'support-v4'              : "com.android.support:support-v4:${versions["support-version"]}",
+            'appcompat-v7'            : "com.android.support:appcompat-v7:${versions["support-version"]}",
+            'recyclerview-v7'         : "com.android.support:recyclerview-v7:${versions["support-version"]}",
+            'support-v13'             : "com.android.support:support-v13:${versions["support-version"]}",
+            'support-fragment'        : "com.android.support:support-fragment:${versions["support-version"]}",
+            'design'                  : "com.android.support:design:${versions["support-version"]}",
+            'animated-vector-drawable': "com.android.support:animated-vector-drawable:${versions["support-version"]}",
+            'junit'                   : "junit:junit:${versions["junit-version"]}",
+    ]
+    //依赖第三方配置
+    dependencies = [
+            //rxjava
+            "rxjava"                               : "io.reactivex.rxjava2:rxjava:2.2.3",
+            "rxandroid"                            : "io.reactivex.rxjava2:rxandroid:2.1.0",
+            //rx系列与View生命周期同步
+            "rxlifecycle"                          : "com.trello.rxlifecycle2:rxlifecycle:2.2.2",
+            "rxlifecycle-components"               : "com.trello.rxlifecycle2:rxlifecycle-components:2.2.2",
+            //rxbinding
+            "rxbinding"                            : "com.jakewharton.rxbinding2:rxbinding:2.1.1",
+            //rx 6.0权限请求
+            "rxpermissions"                        : "com.github.tbruyelle:rxpermissions:0.10.2",
+            //network
+            "okhttp"                               : "com.squareup.okhttp3:okhttp:3.10.0",
+            "retrofit"                             : "com.squareup.retrofit2:retrofit:2.4.0",
+            "converter-gson"                       : "com.squareup.retrofit2:converter-gson:2.4.0",
+            "adapter-rxjava"                       : "com.squareup.retrofit2:adapter-rxjava2:2.4.0",
+            //glide图片加载
+            "glide"                                : "com.github.bumptech.glide:glide:4.8.0",
+            "glide-compiler"                       : "com.github.bumptech.glide:compiler:4.8.0",
+            //json解析
+            "gson"                                 : "com.google.code.gson:gson:2.8.5",
+            //material-dialogs
+            "material-dialogs-core"                : "com.afollestad.material-dialogs:core:0.9.4.5",
+            "material-dialogs-commons"             : "com.afollestad.material-dialogs:commons:0.9.4.5",
+            //recyclerview的databinding套装
+            "bindingcollectionadapter"             : "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:2.2.0",
+            "bindingcollectionadapter-recyclerview": "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:2.2.0",
+            //Google AAC
+            "lifecycle-extensions"                 : "android.arch.lifecycle:extensions:1.1.1",
+            "lifecycle-compiler"                 : "android.arch.lifecycle:compiler:1.1.1",
+            //MVVMHabit
+            "MVVMHabit"                            : "com.github.goldze:MVVMHabit:3.1.3",
+    ]
+}
+
+

+ 15 - 0
gradle.properties

xqd
@@ -0,0 +1,15 @@
+# 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=-Xmx1536m
+# 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
+
+

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

xqd
@@ -0,0 +1,6 @@
+#Mon Nov 18 10:56:28 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip

+ 172 - 0
gradlew

xqd
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

xqd
@@ -0,0 +1,84 @@
+@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 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=
+
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+: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 %CMD_LINE_ARGS%
+
+: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

+ 1 - 0
mvvmhabit/.gitignore

xqd
@@ -0,0 +1 @@
+/build

+ 74 - 0
mvvmhabit/build.gradle

xqd
@@ -0,0 +1,74 @@
+apply plugin: 'com.android.library'
+android {
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    defaultConfig {
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+        versionCode rootProject.ext.android.versionCode
+        versionName rootProject.ext.android.versionName
+    }
+    dataBinding {
+        enabled true
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    api fileTree(include: ['*.jar'], dir: 'libs')
+    //support
+    api rootProject.ext.support["support-v4"]
+    api rootProject.ext.support["appcompat-v7"]
+    api rootProject.ext.support["recyclerview-v7"]
+    //rxjava
+    api rootProject.ext.dependencies.rxjava
+    api rootProject.ext.dependencies.rxandroid
+    //rx管理View的生命周期
+    api(rootProject.ext.dependencies.rxlifecycle) {
+        exclude group: 'com.android.support'
+    }
+    api(rootProject.ext.dependencies["rxlifecycle-components"]) {
+        exclude group: 'com.android.support'
+    }
+    //rxbinding
+    api(rootProject.ext.dependencies.rxbinding) {
+        exclude group: 'com.android.support'
+    }
+    //rx权限请求
+    api(rootProject.ext.dependencies.rxpermissions) {
+        exclude group: 'com.android.support'
+    }
+    //network
+    api rootProject.ext.dependencies.okhttp
+    api rootProject.ext.dependencies.retrofit
+    api rootProject.ext.dependencies["converter-gson"]
+    api rootProject.ext.dependencies["adapter-rxjava"]
+    //json解析
+    api rootProject.ext.dependencies.gson
+    //material-dialogs
+    api(rootProject.ext.dependencies["material-dialogs-core"]) {
+        exclude group: 'com.android.support'
+    }
+    api(rootProject.ext.dependencies["material-dialogs-commons"]) {
+        exclude group: 'com.android.support'
+    }
+    //glide图片加载库
+    api (rootProject.ext.dependencies.glide){
+        exclude group: 'com.android.support'
+    }
+    annotationProcessor rootProject.ext.dependencies["glide-compiler"]
+    //recyclerview的databinding套装
+    api(rootProject.ext.dependencies.bindingcollectionadapter) {
+        exclude group: 'com.android.support'
+    }
+    api(rootProject.ext.dependencies["bindingcollectionadapter-recyclerview"]) {
+        exclude group: 'com.android.support'
+    }
+    //Google LiveData和ViewModel组件
+    api rootProject.ext.dependencies["lifecycle-extensions"]
+    annotationProcessor rootProject.ext.dependencies["lifecycle-compiler"]
+}

+ 26 - 0
mvvmhabit/proguard-rules.pro

xqd
@@ -0,0 +1,26 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\AndroidSDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# 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
+

+ 26 - 0
mvvmhabit/src/androidTest/java/me/goldze/mvvmhabit/ExampleInstrumentedTest.java

xqd
@@ -0,0 +1,26 @@
+package me.goldze.mvvmhabit;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() throws Exception {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("me.goldze.mvvmhabit.test", appContext.getPackageName());
+    }
+}

+ 21 - 0
mvvmhabit/src/main/AndroidManifest.xml

xqd
@@ -0,0 +1,21 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+
+    package="me.goldze.mvvmhabit">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <application>
+        <activity
+            android:name=".base.ContainerActivity"
+            android:configChanges="orientation|keyboardHidden"></activity>
+        <activity
+            android:name=".crash.DefaultErrorActivity"
+            android:process=":error_activity" />
+
+        <provider
+            android:name=".crash.CaocInitProvider"
+            android:authorities="${applicationId}.customactivityoncrashinitprovider"
+            android:exported="false"
+            android:initOrder="101" />
+    </application>
+
+</manifest>

+ 199 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/AppManager.java

xqd
@@ -0,0 +1,199 @@
+package me.goldze.mvvmhabit.base;
+
+import android.app.Activity;
+import android.support.v4.app.Fragment;
+
+import java.util.Stack;
+
+/**
+ * Created by goldze on 2017/6/15.
+ * activity堆栈式管理
+ */
+public class AppManager {
+
+    private static Stack<Activity> activityStack;
+    private static Stack<Fragment> fragmentStack;
+    private static AppManager instance;
+
+    private AppManager() {
+    }
+
+    /**
+     * 单例模式
+     *
+     * @return AppManager
+     */
+    public static AppManager getAppManager() {
+        if (instance == null) {
+            instance = new AppManager();
+        }
+        return instance;
+    }
+
+    public static Stack<Activity> getActivityStack() {
+        return activityStack;
+    }
+
+    public static Stack<Fragment> getFragmentStack() {
+        return fragmentStack;
+    }
+
+
+    /**
+     * 添加Activity到堆栈
+     */
+    public void addActivity(Activity activity) {
+        if (activityStack == null) {
+            activityStack = new Stack<Activity>();
+        }
+        activityStack.add(activity);
+    }
+
+    /**
+     * 移除指定的Activity
+     */
+    public void removeActivity(Activity activity) {
+        if (activity != null) {
+            activityStack.remove(activity);
+        }
+    }
+
+
+    /**
+     * 是否有activity
+     */
+    public boolean isActivity() {
+        if (activityStack != null) {
+            return !activityStack.isEmpty();
+        }
+        return false;
+    }
+
+    /**
+     * 获取当前Activity(堆栈中最后一个压入的)
+     */
+    public Activity currentActivity() {
+        Activity activity = activityStack.lastElement();
+        return activity;
+    }
+
+    /**
+     * 结束当前Activity(堆栈中最后一个压入的)
+     */
+    public void finishActivity() {
+        Activity activity = activityStack.lastElement();
+        finishActivity(activity);
+    }
+
+    /**
+     * 结束指定的Activity
+     */
+    public void finishActivity(Activity activity) {
+        if (activity != null) {
+            if (!activity.isFinishing()) {
+                activity.finish();
+            }
+        }
+    }
+
+    /**
+     * 结束指定类名的Activity
+     */
+    public void finishActivity(Class<?> cls) {
+        for (Activity activity : activityStack) {
+            if (activity.getClass().equals(cls)) {
+                finishActivity(activity);
+                break;
+            }
+        }
+    }
+
+    /**
+     * 结束所有Activity
+     */
+    public void finishAllActivity() {
+        for (int i = 0, size = activityStack.size(); i < size; i++) {
+            if (null != activityStack.get(i)) {
+                finishActivity(activityStack.get(i));
+            }
+        }
+        activityStack.clear();
+    }
+
+    /**
+     * 获取指定的Activity
+     *
+     * @author kymjs
+     */
+    public Activity getActivity(Class<?> cls) {
+        if (activityStack != null)
+            for (Activity activity : activityStack) {
+                if (activity.getClass().equals(cls)) {
+                    return activity;
+                }
+            }
+        return null;
+    }
+
+
+    /**
+     * 添加Fragment到堆栈
+     */
+    public void addFragment(Fragment fragment) {
+        if (fragmentStack == null) {
+            fragmentStack = new Stack<Fragment>();
+        }
+        fragmentStack.add(fragment);
+    }
+
+    /**
+     * 移除指定的Fragment
+     */
+    public void removeFragment(Fragment fragment) {
+        if (fragment != null) {
+            fragmentStack.remove(fragment);
+        }
+    }
+
+
+    /**
+     * 是否有Fragment
+     */
+    public boolean isFragment() {
+        if (fragmentStack != null) {
+            return !fragmentStack.isEmpty();
+        }
+        return false;
+    }
+
+    /**
+     * 获取当前Activity(堆栈中最后一个压入的)
+     */
+    public Fragment currentFragment() {
+        if (fragmentStack != null) {
+            Fragment fragment = fragmentStack.lastElement();
+            return fragment;
+        }
+        return null;
+    }
+
+
+    /**
+     * 退出应用程序
+     */
+    public void AppExit() {
+        try {
+            finishAllActivity();
+            // 杀死该应用进程
+//          android.os.Process.killProcess(android.os.Process.myPid());
+//            调用 System.exit(n) 实际上等效于调用:
+//            Runtime.getRuntime().exit(n)
+//            finish()是Activity的类方法,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理;当调用System.exit(0)时,退出当前Activity并释放资源(内存),但是该方法不可以结束整个App如有多个Activty或者有其他组件service等不会结束。
+//            其实android的机制决定了用户无法完全退出应用,当你的application最长时间没有被用过的时候,android自身会决定将application关闭了。
+            //System.exit(0);
+        } catch (Exception e) {
+            activityStack.clear();
+            e.printStackTrace();
+        }
+    }
+}

+ 268 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseActivity.java

xqd
@@ -0,0 +1,268 @@
+package me.goldze.mvvmhabit.base;
+
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModel;
+import android.arch.lifecycle.ViewModelProviders;
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.databinding.ViewDataBinding;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import me.goldze.mvvmhabit.base.BaseViewModel.ParameterField;
+import me.goldze.mvvmhabit.bus.Messenger;
+import me.goldze.mvvmhabit.utils.MaterialDialogUtils;
+
+
+/**
+ * Created by goldze on 2017/6/15.
+ * 一个拥有DataBinding框架的基Activity
+ * 这里根据项目业务可以换成你自己熟悉的BaseActivity, 但是需要继承RxAppCompatActivity,方便LifecycleProvider管理生命周期
+ */
+public abstract class BaseActivity<V extends ViewDataBinding, VM extends BaseViewModel> extends RxAppCompatActivity implements IBaseView {
+    protected V binding;
+    protected VM viewModel;
+    private int viewModelId;
+    private MaterialDialog dialog;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        //页面接受的参数方法
+        initParam();
+        //私有的初始化Databinding和ViewModel方法
+        initViewDataBinding(savedInstanceState);
+        //私有的ViewModel与View的契约事件回调逻辑
+        registorUIChangeLiveDataCallBack();
+        //页面数据初始化方法
+        initData();
+        //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册
+        initViewObservable();
+        //注册RxBus
+        viewModel.registerRxBus();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        //解除Messenger注册
+        Messenger.getDefault().unregister(viewModel);
+        if (viewModel != null) {
+            viewModel.removeRxBus();
+        }
+        if(binding != null){
+            binding.unbind();
+        }
+    }
+
+    /**
+     * 注入绑定
+     */
+    private void initViewDataBinding(Bundle savedInstanceState) {
+        //DataBindingUtil类需要在project的build中配置 dataBinding {enabled true }, 同步后会自动关联android.databinding包
+        binding = DataBindingUtil.setContentView(this, initContentView(savedInstanceState));
+        viewModelId = initVariableId();
+        viewModel = initViewModel();
+        if (viewModel == null) {
+            Class modelClass;
+            Type type = getClass().getGenericSuperclass();
+            if (type instanceof ParameterizedType) {
+                modelClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
+            } else {
+                //如果没有指定泛型参数,则默认使用BaseViewModel
+                modelClass = BaseViewModel.class;
+            }
+            viewModel = (VM) createViewModel(this, modelClass);
+        }
+        //关联ViewModel
+        binding.setVariable(viewModelId, viewModel);
+        //让ViewModel拥有View的生命周期感应
+        getLifecycle().addObserver(viewModel);
+        //注入RxLifecycle生命周期
+        viewModel.injectLifecycleProvider(this);
+    }
+
+    //刷新布局
+    public void refreshLayout() {
+        if (viewModel != null) {
+            binding.setVariable(viewModelId, viewModel);
+        }
+    }
+
+
+    /**
+     * =====================================================================
+     **/
+    //注册ViewModel与View的契约UI回调事件
+    protected void registorUIChangeLiveDataCallBack() {
+        //加载对话框显示
+        viewModel.getUC().getShowDialogEvent().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String title) {
+                showDialog(title);
+            }
+        });
+        //加载对话框消失
+        viewModel.getUC().getDismissDialogEvent().observe(this, new Observer<Void>() {
+            @Override
+            public void onChanged(@Nullable Void v) {
+                dismissDialog();
+            }
+        });
+        //跳入新页面
+        viewModel.getUC().getStartActivityEvent().observe(this, new Observer<Map<String, Object>>() {
+            @Override
+            public void onChanged(@Nullable Map<String, Object> params) {
+                Class<?> clz = (Class<?>) params.get(ParameterField.CLASS);
+                Bundle bundle = (Bundle) params.get(ParameterField.BUNDLE);
+                startActivity(clz, bundle);
+            }
+        });
+        //跳入ContainerActivity
+        viewModel.getUC().getStartContainerActivityEvent().observe(this, new Observer<Map<String, Object>>() {
+            @Override
+            public void onChanged(@Nullable Map<String, Object> params) {
+                String canonicalName = (String) params.get(ParameterField.CANONICAL_NAME);
+                Bundle bundle = (Bundle) params.get(ParameterField.BUNDLE);
+                startContainerActivity(canonicalName, bundle);
+            }
+        });
+        //关闭界面
+        viewModel.getUC().getFinishEvent().observe(this, new Observer<Void>() {
+            @Override
+            public void onChanged(@Nullable Void v) {
+                finish();
+            }
+        });
+        //关闭上一层
+        viewModel.getUC().getOnBackPressedEvent().observe(this, new Observer<Void>() {
+            @Override
+            public void onChanged(@Nullable Void v) {
+                onBackPressed();
+            }
+        });
+    }
+
+    public void showDialog(String title) {
+        if (dialog != null) {
+            dialog = dialog.getBuilder().title(title).build();
+            dialog.show();
+        } else {
+            MaterialDialog.Builder builder = MaterialDialogUtils.showIndeterminateProgressDialog(this, title, true);
+            dialog = builder.show();
+        }
+    }
+
+    public void dismissDialog() {
+        if (dialog != null && dialog.isShowing()) {
+            dialog.dismiss();
+        }
+    }
+
+    /**
+     * 跳转页面
+     *
+     * @param clz 所跳转的目的Activity类
+     */
+    public void startActivity(Class<?> clz) {
+        startActivity(new Intent(this, clz));
+    }
+
+    /**
+     * 跳转页面
+     *
+     * @param clz    所跳转的目的Activity类
+     * @param bundle 跳转所携带的信息
+     */
+    public void startActivity(Class<?> clz, Bundle bundle) {
+        Intent intent = new Intent(this, clz);
+        if (bundle != null) {
+            intent.putExtras(bundle);
+        }
+        startActivity(intent);
+    }
+
+    /**
+     * 跳转容器页面
+     *
+     * @param canonicalName 规范名 : Fragment.class.getCanonicalName()
+     */
+    public void startContainerActivity(String canonicalName) {
+        startContainerActivity(canonicalName, null);
+    }
+
+    /**
+     * 跳转容器页面
+     *
+     * @param canonicalName 规范名 : Fragment.class.getCanonicalName()
+     * @param bundle        跳转所携带的信息
+     */
+    public void startContainerActivity(String canonicalName, Bundle bundle) {
+        Intent intent = new Intent(this, ContainerActivity.class);
+        intent.putExtra(ContainerActivity.FRAGMENT, canonicalName);
+        if (bundle != null) {
+            intent.putExtra(ContainerActivity.BUNDLE, bundle);
+        }
+        startActivity(intent);
+    }
+
+    /**
+     * =====================================================================
+     **/
+    @Override
+    public void initParam() {
+
+    }
+
+    /**
+     * 初始化根布局
+     *
+     * @return 布局layout的id
+     */
+    public abstract int initContentView(Bundle savedInstanceState);
+
+    /**
+     * 初始化ViewModel的id
+     *
+     * @return BR的id
+     */
+    public abstract int initVariableId();
+
+    /**
+     * 初始化ViewModel
+     *
+     * @return 继承BaseViewModel的ViewModel
+     */
+    public VM initViewModel() {
+        return null;
+    }
+
+    @Override
+    public void initData() {
+
+    }
+
+    @Override
+    public void initViewObservable() {
+
+    }
+
+    /**
+     * 创建ViewModel
+     *
+     * @param cls
+     * @param <T>
+     * @return
+     */
+    public <T extends ViewModel> T createViewModel(FragmentActivity activity, Class<T> cls) {
+        return ViewModelProviders.of(activity).get(cls);
+    }
+}

+ 76 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseApplication.java

xqd
@@ -0,0 +1,76 @@
+package me.goldze.mvvmhabit.base;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import me.goldze.mvvmhabit.utils.Utils;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+
+public class BaseApplication extends Application {
+    private static Application sInstance;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        setApplication(this);
+    }
+
+    /**
+     * 当主工程没有继承BaseApplication时,可以使用setApplication方法初始化BaseApplication
+     *
+     * @param application
+     */
+    public static synchronized void setApplication(@NonNull Application application) {
+        sInstance = application;
+        //初始化工具类
+        Utils.init(application);
+        //注册监听每个activity的生命周期,便于堆栈式管理
+        application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
+
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                AppManager.getAppManager().addActivity(activity);
+            }
+
+            @Override
+            public void onActivityStarted(Activity activity) {
+            }
+
+            @Override
+            public void onActivityResumed(Activity activity) {
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+            }
+
+            @Override
+            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+            }
+
+            @Override
+            public void onActivityDestroyed(Activity activity) {
+                AppManager.getAppManager().removeActivity(activity);
+            }
+        });
+    }
+
+    /**
+     * 获得当前app运行的Application
+     */
+    public static Application getInstance() {
+        if (sInstance == null) {
+            throw new NullPointerException("please inherit BaseApplication or call setApplication.");
+        }
+        return sInstance;
+    }
+}

+ 285 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseFragment.java

xqd
@@ -0,0 +1,285 @@
+package me.goldze.mvvmhabit.base;
+
+import android.arch.lifecycle.Observer;
+import android.arch.lifecycle.ViewModel;
+import android.arch.lifecycle.ViewModelProviders;
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.databinding.ViewDataBinding;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.trello.rxlifecycle2.components.support.RxFragment;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import me.goldze.mvvmhabit.base.BaseViewModel.ParameterField;
+import me.goldze.mvvmhabit.bus.Messenger;
+import me.goldze.mvvmhabit.utils.MaterialDialogUtils;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+public abstract class BaseFragment<V extends ViewDataBinding, VM extends BaseViewModel> extends RxFragment implements IBaseView {
+    protected V binding;
+    protected VM viewModel;
+    private int viewModelId;
+    private MaterialDialog dialog;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        initParam();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        binding = DataBindingUtil.inflate(inflater, initContentView(inflater, container, savedInstanceState), container, false);
+        return binding.getRoot();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        //解除Messenger注册
+        Messenger.getDefault().unregister(viewModel);
+        if (viewModel != null) {
+            viewModel.removeRxBus();
+        }
+        if (binding != null) {
+            binding.unbind();
+        }
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        //私有的初始化Databinding和ViewModel方法
+        initViewDataBinding();
+        //私有的ViewModel与View的契约事件回调逻辑
+        registorUIChangeLiveDataCallBack();
+        //页面数据初始化方法
+        initData();
+        //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册
+        initViewObservable();
+        //注册RxBus
+        viewModel.registerRxBus();
+    }
+
+    /**
+     * 注入绑定
+     */
+    private void initViewDataBinding() {
+        viewModelId = initVariableId();
+        viewModel = initViewModel();
+        if (viewModel == null) {
+            Class modelClass;
+            Type type = getClass().getGenericSuperclass();
+            if (type instanceof ParameterizedType) {
+                modelClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
+            } else {
+                //如果没有指定泛型参数,则默认使用BaseViewModel
+                modelClass = BaseViewModel.class;
+            }
+            viewModel = (VM) createViewModel(this, modelClass);
+        }
+        binding.setVariable(viewModelId, viewModel);
+        //让ViewModel拥有View的生命周期感应
+        getLifecycle().addObserver(viewModel);
+        //注入RxLifecycle生命周期
+        viewModel.injectLifecycleProvider(this);
+    }
+
+    /**
+     * =====================================================================
+     **/
+    //注册ViewModel与View的契约UI回调事件
+    protected void registorUIChangeLiveDataCallBack() {
+        //加载对话框显示
+        viewModel.getUC().getShowDialogEvent().observe(this, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String title) {
+                showDialog(title);
+            }
+        });
+        //加载对话框消失
+        viewModel.getUC().getDismissDialogEvent().observe(this, new Observer<Void>() {
+            @Override
+            public void onChanged(@Nullable Void v) {
+                dismissDialog();
+            }
+        });
+        //跳入新页面
+        viewModel.getUC().getStartActivityEvent().observe(this, new Observer<Map<String, Object>>() {
+            @Override
+            public void onChanged(@Nullable Map<String, Object> params) {
+                Class<?> clz = (Class<?>) params.get(ParameterField.CLASS);
+                Bundle bundle = (Bundle) params.get(ParameterField.BUNDLE);
+                startActivity(clz, bundle);
+            }
+        });
+        //跳入ContainerActivity
+        viewModel.getUC().getStartContainerActivityEvent().observe(this, new Observer<Map<String, Object>>() {
+            @Override
+            public void onChanged(@Nullable Map<String, Object> params) {
+                String canonicalName = (String) params.get(ParameterField.CANONICAL_NAME);
+                Bundle bundle = (Bundle) params.get(ParameterField.BUNDLE);
+                startContainerActivity(canonicalName, bundle);
+            }
+        });
+        //关闭界面
+        viewModel.getUC().getFinishEvent().observe(this, new Observer<Void>() {
+            @Override
+            public void onChanged(@Nullable Void v) {
+                getActivity().finish();
+            }
+        });
+        //关闭上一层
+        viewModel.getUC().getOnBackPressedEvent().observe(this, new Observer<Void>() {
+            @Override
+            public void onChanged(@Nullable Void v) {
+                getActivity().onBackPressed();
+            }
+        });
+    }
+
+    public void showDialog(String title) {
+        if (dialog != null) {
+            dialog = dialog.getBuilder().title(title).build();
+            dialog.show();
+        } else {
+            MaterialDialog.Builder builder = MaterialDialogUtils.showIndeterminateProgressDialog(getActivity(), title, true);
+            dialog = builder.show();
+        }
+    }
+
+    public void dismissDialog() {
+        if (dialog != null && dialog.isShowing()) {
+            dialog.dismiss();
+        }
+    }
+
+    /**
+     * 跳转页面
+     *
+     * @param clz 所跳转的目的Activity类
+     */
+    public void startActivity(Class<?> clz) {
+        startActivity(new Intent(getContext(), clz));
+    }
+
+    /**
+     * 跳转页面
+     *
+     * @param clz    所跳转的目的Activity类
+     * @param bundle 跳转所携带的信息
+     */
+    public void startActivity(Class<?> clz, Bundle bundle) {
+        Intent intent = new Intent(getContext(), clz);
+        if (bundle != null) {
+            intent.putExtras(bundle);
+        }
+        startActivity(intent);
+    }
+
+    /**
+     * 跳转容器页面
+     *
+     * @param canonicalName 规范名 : Fragment.class.getCanonicalName()
+     */
+    public void startContainerActivity(String canonicalName) {
+        startContainerActivity(canonicalName, null);
+    }
+
+    /**
+     * 跳转容器页面
+     *
+     * @param canonicalName 规范名 : Fragment.class.getCanonicalName()
+     * @param bundle        跳转所携带的信息
+     */
+    public void startContainerActivity(String canonicalName, Bundle bundle) {
+        Intent intent = new Intent(getContext(), ContainerActivity.class);
+        intent.putExtra(ContainerActivity.FRAGMENT, canonicalName);
+        if (bundle != null) {
+            intent.putExtra(ContainerActivity.BUNDLE, bundle);
+        }
+        startActivity(intent);
+    }
+
+    /**
+     * =====================================================================
+     **/
+
+    //刷新布局
+    public void refreshLayout() {
+        if (viewModel != null) {
+            binding.setVariable(viewModelId, viewModel);
+        }
+    }
+
+    @Override
+    public void initParam() {
+
+    }
+
+    /**
+     * 初始化根布局
+     *
+     * @return 布局layout的id
+     */
+    public abstract int initContentView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState);
+
+    /**
+     * 初始化ViewModel的id
+     *
+     * @return BR的id
+     */
+    public abstract int initVariableId();
+
+    /**
+     * 初始化ViewModel
+     *
+     * @return 继承BaseViewModel的ViewModel
+     */
+    public VM initViewModel() {
+        return null;
+    }
+
+    @Override
+    public void initData() {
+
+    }
+
+    @Override
+    public void initViewObservable() {
+
+    }
+
+    public boolean isBackPressed() {
+        return false;
+    }
+
+    /**
+     * 创建ViewModel
+     *
+     * @param cls
+     * @param <T>
+     * @return
+     */
+    public <T extends ViewModel> T createViewModel(Fragment fragment, Class<T> cls) {
+        return ViewModelProviders.of(fragment).get(cls);
+    }
+}

+ 15 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseModel.java

xqd
@@ -0,0 +1,15 @@
+package me.goldze.mvvmhabit.base;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+public class BaseModel implements IModel {
+
+    public BaseModel() {
+    }
+
+    @Override
+    public void onCleared() {
+
+    }
+}

+ 247 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/BaseViewModel.java

xqd
@@ -0,0 +1,247 @@
+package me.goldze.mvvmhabit.base;
+
+import android.app.Application;
+import android.arch.lifecycle.AndroidViewModel;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.Observer;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import com.trello.rxlifecycle2.LifecycleProvider;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import me.goldze.mvvmhabit.bus.event.SingleLiveEvent;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+public class BaseViewModel<M extends BaseModel> extends AndroidViewModel implements IBaseViewModel, Consumer<Disposable> {
+    protected M model;
+    private UIChangeLiveData uc;
+    //弱引用持有
+    private WeakReference<LifecycleProvider> lifecycle;
+    //管理RxJava,主要针对RxJava异步操作造成的内存泄漏
+    private CompositeDisposable mCompositeDisposable;
+
+    public BaseViewModel(@NonNull Application application) {
+        this(application, null);
+    }
+
+    public BaseViewModel(@NonNull Application application, M model) {
+        super(application);
+        this.model = model;
+        mCompositeDisposable = new CompositeDisposable();
+    }
+
+    protected void addSubscribe(Disposable disposable) {
+        if (mCompositeDisposable == null) {
+            mCompositeDisposable = new CompositeDisposable();
+        }
+        mCompositeDisposable.add(disposable);
+    }
+
+    /**
+     * 注入RxLifecycle生命周期
+     *
+     * @param lifecycle
+     */
+    public void injectLifecycleProvider(LifecycleProvider lifecycle) {
+        this.lifecycle = new WeakReference<>(lifecycle);
+    }
+
+    public LifecycleProvider getLifecycleProvider() {
+        return lifecycle.get();
+    }
+
+    public UIChangeLiveData getUC() {
+        if (uc == null) {
+            uc = new UIChangeLiveData();
+        }
+        return uc;
+    }
+
+    public void showDialog() {
+        showDialog("请稍后...");
+    }
+
+    public void showDialog(String title) {
+        uc.showDialogEvent.postValue(title);
+    }
+
+    public void dismissDialog() {
+        uc.dismissDialogEvent.call();
+    }
+
+    /**
+     * 跳转页面
+     *
+     * @param clz 所跳转的目的Activity类
+     */
+    public void startActivity(Class<?> clz) {
+        startActivity(clz, null);
+    }
+
+    /**
+     * 跳转页面
+     *
+     * @param clz    所跳转的目的Activity类
+     * @param bundle 跳转所携带的信息
+     */
+    public void startActivity(Class<?> clz, Bundle bundle) {
+        Map<String, Object> params = new HashMap<>();
+        params.put(ParameterField.CLASS, clz);
+        if (bundle != null) {
+            params.put(ParameterField.BUNDLE, bundle);
+        }
+        uc.startActivityEvent.postValue(params);
+    }
+
+    /**
+     * 跳转容器页面
+     *
+     * @param canonicalName 规范名 : Fragment.class.getCanonicalName()
+     */
+    public void startContainerActivity(String canonicalName) {
+        startContainerActivity(canonicalName, null);
+    }
+
+    /**
+     * 跳转容器页面
+     *
+     * @param canonicalName 规范名 : Fragment.class.getCanonicalName()
+     * @param bundle        跳转所携带的信息
+     */
+    public void startContainerActivity(String canonicalName, Bundle bundle) {
+        Map<String, Object> params = new HashMap<>();
+        params.put(ParameterField.CANONICAL_NAME, canonicalName);
+        if (bundle != null) {
+            params.put(ParameterField.BUNDLE, bundle);
+        }
+        uc.startContainerActivityEvent.postValue(params);
+    }
+
+    /**
+     * 关闭界面
+     */
+    public void finish() {
+        uc.finishEvent.call();
+    }
+
+    /**
+     * 返回上一层
+     */
+    public void onBackPressed() {
+        uc.onBackPressedEvent.call();
+    }
+
+    @Override
+    public void onAny(LifecycleOwner owner, Lifecycle.Event event) {
+    }
+
+    @Override
+    public void onCreate() {
+    }
+
+    @Override
+    public void onDestroy() {
+    }
+
+    @Override
+    public void onStart() {
+    }
+
+    @Override
+    public void onStop() {
+    }
+
+    @Override
+    public void onResume() {
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    @Override
+    public void registerRxBus() {
+    }
+
+    @Override
+    public void removeRxBus() {
+    }
+
+    @Override
+    protected void onCleared() {
+        super.onCleared();
+        if (model != null) {
+            model.onCleared();
+        }
+        //ViewModel销毁时会执行,同时取消所有异步任务
+        if (mCompositeDisposable != null) {
+            mCompositeDisposable.clear();
+        }
+    }
+
+    @Override
+    public void accept(Disposable disposable) throws Exception {
+        addSubscribe(disposable);
+    }
+
+    public final class UIChangeLiveData extends SingleLiveEvent {
+        private SingleLiveEvent<String> showDialogEvent;
+        private SingleLiveEvent<Void> dismissDialogEvent;
+        private SingleLiveEvent<Map<String, Object>> startActivityEvent;
+        private SingleLiveEvent<Map<String, Object>> startContainerActivityEvent;
+        private SingleLiveEvent<Void> finishEvent;
+        private SingleLiveEvent<Void> onBackPressedEvent;
+
+        public SingleLiveEvent<String> getShowDialogEvent() {
+            return showDialogEvent = createLiveData(showDialogEvent);
+        }
+
+        public SingleLiveEvent<Void> getDismissDialogEvent() {
+            return dismissDialogEvent = createLiveData(dismissDialogEvent);
+        }
+
+        public SingleLiveEvent<Map<String, Object>> getStartActivityEvent() {
+            return startActivityEvent = createLiveData(startActivityEvent);
+        }
+
+        public SingleLiveEvent<Map<String, Object>> getStartContainerActivityEvent() {
+            return startContainerActivityEvent = createLiveData(startContainerActivityEvent);
+        }
+
+        public SingleLiveEvent<Void> getFinishEvent() {
+            return finishEvent = createLiveData(finishEvent);
+        }
+
+        public SingleLiveEvent<Void> getOnBackPressedEvent() {
+            return onBackPressedEvent = createLiveData(onBackPressedEvent);
+        }
+
+        private <T> SingleLiveEvent<T> createLiveData(SingleLiveEvent<T> liveData) {
+            if (liveData == null) {
+                liveData = new SingleLiveEvent<>();
+            }
+            return liveData;
+        }
+
+        @Override
+        public void observe(LifecycleOwner owner, Observer observer) {
+            super.observe(owner, observer);
+        }
+    }
+
+    public static final class ParameterField {
+        public static String CLASS = "CLASS";
+        public static String CANONICAL_NAME = "CANONICAL_NAME";
+        public static String BUNDLE = "BUNDLE";
+    }
+}

+ 97 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/ContainerActivity.java

xqd
@@ -0,0 +1,97 @@
+package me.goldze.mvvmhabit.base;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
+
+import java.lang.ref.WeakReference;
+
+import me.goldze.mvvmhabit.R;
+
+import static android.view.View.generateViewId;
+
+
+/**
+ * 盛装Fragment的一个容器(代理)Activity
+ * 普通界面只需要编写Fragment,使用此Activity盛装,这样就不需要每个界面都在AndroidManifest中注册一遍
+ */
+public class ContainerActivity extends RxAppCompatActivity {
+    private static final String FRAGMENT_TAG = "content_fragment_tag";
+    public static final String FRAGMENT = "fragment";
+    public static final String BUNDLE = "bundle";
+    protected WeakReference<Fragment> mFragment;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_container);
+        FragmentManager fm = getSupportFragmentManager();
+        Fragment fragment = null;
+        if (savedInstanceState != null) {
+            fragment = fm.getFragment(savedInstanceState, FRAGMENT_TAG);
+        }
+        if (fragment == null) {
+            fragment = initFromIntent(getIntent());
+        }
+        FragmentTransaction trans = getSupportFragmentManager()
+                .beginTransaction();
+        trans.replace(R.id.content, fragment);
+        trans.commitAllowingStateLoss();
+        mFragment = new WeakReference<>(fragment);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        getSupportFragmentManager().putFragment(outState, FRAGMENT_TAG, mFragment.get());
+    }
+
+    protected Fragment initFromIntent(Intent data) {
+        if (data == null) {
+            throw new RuntimeException(
+                    "you must provide a page info to display");
+        }
+        try {
+            String fragmentName = data.getStringExtra(FRAGMENT);
+            if (fragmentName == null || "".equals(fragmentName)) {
+                throw new IllegalArgumentException("can not find page fragmentName");
+            }
+            Class<?> fragmentClass = Class.forName(fragmentName);
+            Fragment fragment = (Fragment) fragmentClass.newInstance();
+            Bundle args = data.getBundleExtra(BUNDLE);
+            if (args != null) {
+                fragment.setArguments(args);
+            }
+            return fragment;
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        throw new RuntimeException("fragment initialization failed!");
+    }
+
+    @Override
+    public void onBackPressed() {
+        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.content);
+        if (fragment instanceof BaseFragment) {
+            if (!((BaseFragment) fragment).isBackPressed()) {
+                super.onBackPressed();
+            }
+        } else {
+            super.onBackPressed();
+        }
+    }
+}

+ 21 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/IBaseView.java

xqd
@@ -0,0 +1,21 @@
+package me.goldze.mvvmhabit.base;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+
+public interface IBaseView {
+    /**
+     * 初始化界面传递参数
+     */
+    void initParam();
+    /**
+     * 初始化数据
+     */
+    void initData();
+
+    /**
+     * 初始化界面观察者的监听
+     */
+    void initViewObservable();
+}

+ 44 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/IBaseViewModel.java

xqd
@@ -0,0 +1,44 @@
+package me.goldze.mvvmhabit.base;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+
+public interface IBaseViewModel extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+    void onAny(LifecycleOwner owner, Lifecycle.Event event);
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    void onDestroy();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_START)
+    void onStart();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    void onResume();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    void onPause();
+
+    /**
+     * 注册RxBus
+     */
+    void registerRxBus();
+
+    /**
+     * 移除RxBus
+     */
+    void removeRxBus();
+}

+ 11 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/IModel.java

xqd
@@ -0,0 +1,11 @@
+package me.goldze.mvvmhabit.base;
+
+/**
+ * Created by goldze on 2017/6/15.
+ */
+public interface IModel {
+    /**
+     * ViewModel销毁时清除Model,与ViewModel共消亡。Model层同样不能持有长生命周期对象
+     */
+    void onCleared();
+}

+ 16 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/ItemViewModel.java

xqd
@@ -0,0 +1,16 @@
+package me.goldze.mvvmhabit.base;
+
+import android.support.annotation.NonNull;
+
+/**
+ * ItemViewModel
+ * Created by goldze on 2018/10/3.
+ */
+
+public class ItemViewModel<VM extends BaseViewModel> {
+    protected VM viewModel;
+
+    public ItemViewModel(@NonNull VM viewModel) {
+        this.viewModel = viewModel;
+    }
+}

+ 25 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/MultiItemViewModel.java

xqd
@@ -0,0 +1,25 @@
+package me.goldze.mvvmhabit.base;
+
+import android.support.annotation.NonNull;
+
+/**
+ * Create Author:goldze
+ * Create Date:2019/01/25
+ * Description:RecycleView多布局ItemViewModel是封装
+ */
+
+public class MultiItemViewModel<VM extends BaseViewModel> extends ItemViewModel<VM> {
+    protected Object multiType;
+
+    public Object getItemType() {
+        return multiType;
+    }
+
+    public void multiItemType(@NonNull Object multiType) {
+        this.multiType = multiType;
+    }
+
+    public MultiItemViewModel(@NonNull VM viewModel) {
+        super(viewModel);
+    }
+}

+ 69 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/base/ViewModelFactory.java

xqd
@@ -0,0 +1,69 @@
+package me.goldze.mvvmhabit.base;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+import android.arch.lifecycle.ViewModel;
+import android.arch.lifecycle.ViewModelProvider;
+import android.arch.lifecycle.ViewModelProviders;
+import android.support.v4.app.FragmentActivity;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Created by goldze on 2018/9/30.
+ */
+
+public class ViewModelFactory extends ViewModelProvider.NewInstanceFactory {
+    @SuppressLint("StaticFieldLeak")
+    private static volatile ViewModelFactory INSTANCE;
+
+    private final Application mApplication;
+
+    public static ViewModelFactory getInstance(Application application) {
+
+        if (INSTANCE == null) {
+            synchronized (ViewModelFactory.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new ViewModelFactory(application);
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+
+    private ViewModelFactory(Application application) {
+        mApplication = application;
+    }
+
+    @Override
+    public <T extends ViewModel> T create(Class<T> modelClass) {
+        if (modelClass.isAssignableFrom(BaseViewModel.class)) {
+            return (T) new BaseViewModel(mApplication);
+        }
+        //反射动态实例化ViewModel
+        try {
+            String className = modelClass.getCanonicalName();
+            Class<?> classViewModel = Class.forName(className);
+            Constructor<?> cons = classViewModel.getConstructor(Application.class);
+            ViewModel viewModel = (ViewModel) cons.newInstance(mApplication);
+            return (T) viewModel;
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName());
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName());
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName());
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName());
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Unknown ViewModel class: " + modelClass.getName());
+        }
+    }
+}

+ 9 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingAction.java

xqd
@@ -0,0 +1,9 @@
+package me.goldze.mvvmhabit.binding.command;
+
+/**
+ * A zero-argument action.
+ */
+
+public interface BindingAction {
+    void call();
+}

+ 75 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingCommand.java

xqd
@@ -0,0 +1,75 @@
+package me.goldze.mvvmhabit.binding.command;
+
+
+import io.reactivex.exceptions.Exceptions;
+
+/**
+ * About : kelin的ReplyCommand
+ * 执行的命令回调, 用于ViewModel与xml之间的数据绑定
+ */
+public class BindingCommand<T> {
+    private BindingAction execute;
+    private BindingConsumer<T> consumer;
+    private BindingFunction<Boolean> canExecute0;
+
+    public BindingCommand(BindingAction execute) {
+        this.execute = execute;
+    }
+
+    /**
+     * @param execute 带泛型参数的命令绑定
+     */
+    public BindingCommand(BindingConsumer<T> execute) {
+        this.consumer = execute;
+    }
+
+    /**
+     * @param execute     触发命令
+     * @param canExecute0 true则执行,反之不执行
+     */
+    public BindingCommand(BindingAction execute, BindingFunction<Boolean> canExecute0) {
+        this.execute = execute;
+        this.canExecute0 = canExecute0;
+    }
+
+    /**
+     * @param execute     带泛型参数触发命令
+     * @param canExecute0 true则执行,反之不执行
+     */
+    public BindingCommand(BindingConsumer<T> execute, BindingFunction<Boolean> canExecute0) {
+        this.consumer = execute;
+        this.canExecute0 = canExecute0;
+    }
+
+    /**
+     * 执行BindingAction命令
+     */
+    public void execute() {
+        if (execute != null && canExecute0()) {
+            execute.call();
+        }
+    }
+
+    /**
+     * 执行带泛型参数的命令
+     *
+     * @param parameter 泛型参数
+     */
+    public void execute(T parameter) {
+        if (consumer != null && canExecute0()) {
+            consumer.call(parameter);
+        }
+    }
+
+    /**
+     * 是否需要执行
+     *
+     * @return true则执行, 反之不执行
+     */
+    private boolean canExecute0() {
+        if (canExecute0 == null) {
+            return true;
+        }
+        return canExecute0.call();
+    }
+}

+ 10 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingConsumer.java

xqd
@@ -0,0 +1,10 @@
+package me.goldze.mvvmhabit.binding.command;
+
+/**
+ * A one-argument action.
+ *
+ * @param <T> the first argument type
+ */
+public interface BindingConsumer<T> {
+    void call(T t);
+}

+ 12 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/BindingFunction.java

xqd
@@ -0,0 +1,12 @@
+package me.goldze.mvvmhabit.binding.command;
+
+import me.goldze.mvvmhabit.R;
+
+/**
+ * Represents a function with zero arguments.
+ *
+ * @param <T> the result type
+ */
+public interface BindingFunction<T> {
+    T call();
+}

+ 65 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/command/ResponseCommand.java

xqd
@@ -0,0 +1,65 @@
+package me.goldze.mvvmhabit.binding.command;
+
+
+import io.reactivex.exceptions.Exceptions;
+import io.reactivex.functions.Function;
+
+/**
+ * About : kelin的ResponseCommand
+ * 执行的命令事件转换
+ */
+public class ResponseCommand<T, R> {
+
+    private BindingFunction<R> execute;
+    private Function<T, R> function;
+    private BindingFunction<Boolean> canExecute;
+
+    /**
+     * like {@link BindingCommand},but ResponseCommand can return result when command has executed!
+     *
+     * @param execute function to execute when event occur.
+     */
+    public ResponseCommand(BindingFunction<R> execute) {
+        this.execute = execute;
+    }
+
+
+    public ResponseCommand(Function<T, R> execute) {
+        this.function = execute;
+    }
+
+
+    public ResponseCommand(BindingFunction<R> execute, BindingFunction<Boolean> canExecute) {
+        this.execute = execute;
+        this.canExecute = canExecute;
+    }
+
+
+    public ResponseCommand(Function<T, R> execute, BindingFunction<Boolean> canExecute) {
+        this.function = execute;
+        this.canExecute = canExecute;
+    }
+
+
+    public R execute() {
+        if (execute != null && canExecute()) {
+            return execute.call();
+        }
+        return null;
+    }
+
+    private boolean canExecute() {
+        if (canExecute == null) {
+            return true;
+        }
+        return canExecute.call();
+    }
+
+
+    public R execute(T parameter) throws Exception {
+        if (function != null && canExecute()) {
+            return function.apply(parameter);
+        }
+        return null;
+    }
+}

+ 27 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/checkbox/ViewAdapter.java

xqd
@@ -0,0 +1,27 @@
+package me.goldze.mvvmhabit.binding.viewadapter.checkbox;
+
+import android.databinding.BindingAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/16.
+ */
+
+public class ViewAdapter {
+    /**
+     * @param bindingCommand //绑定监听
+     */
+    @SuppressWarnings("unchecked")
+    @BindingAdapter(value = {"onCheckedChangedCommand"}, requireAll = false)
+    public static void setCheckedChanged(final CheckBox checkBox, final BindingCommand<Boolean> bindingCommand) {
+        checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+                bindingCommand.execute(b);
+            }
+        });
+    }
+}

+ 55 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/edittext/ViewAdapter.java

xqd
@@ -0,0 +1,55 @@
+package me.goldze.mvvmhabit.binding.viewadapter.edittext;
+
+import android.content.Context;
+import android.databinding.BindingAdapter;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/16.
+ */
+
+public class ViewAdapter {
+    /**
+     * EditText重新获取焦点的事件绑定
+     */
+    @BindingAdapter(value = {"requestFocus"}, requireAll = false)
+    public static void requestFocusCommand(EditText editText, final Boolean needRequestFocus) {
+        if (needRequestFocus) {
+            editText.setSelection(editText.getText().length());
+            editText.requestFocus();
+            InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+            imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
+        }
+        editText.setFocusableInTouchMode(needRequestFocus);
+    }
+
+    /**
+     * EditText输入文字改变的监听
+     */
+    @BindingAdapter(value = {"textChanged"}, requireAll = false)
+    public static void addTextChangedListener(EditText editText, final BindingCommand<String> textChanged) {
+        editText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence text, int i, int i1, int i2) {
+                if (textChanged != null) {
+                    textChanged.execute(text.toString());
+                }
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+
+            }
+        });
+    }
+}

+ 28 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/image/ViewAdapter.java

xqd
@@ -0,0 +1,28 @@
+package me.goldze.mvvmhabit.binding.viewadapter.image;
+
+
+import android.databinding.BindingAdapter;
+import android.text.TextUtils;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+
+import me.goldze.mvvmhabit.R;
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+public final class ViewAdapter {
+    @BindingAdapter(value = {"url", "placeholderRes"}, requireAll = false)
+    public static void setImageUri(ImageView imageView, String url, int placeholderRes) {
+        if (!TextUtils.isEmpty(url)) {
+            //使用Glide框架加载图片
+            Glide.with(imageView.getContext())
+                    .load(url)
+                    .apply(new RequestOptions().placeholder(placeholderRes))
+                    .into(imageView);
+        }
+    }
+}
+

+ 114 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/listview/ViewAdapter.java

xqd
@@ -0,0 +1,114 @@
+package me.goldze.mvvmhabit.binding.viewadapter.listview;
+
+import android.databinding.BindingAdapter;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.functions.Consumer;
+import io.reactivex.subjects.PublishSubject;
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+public final class ViewAdapter {
+
+    @SuppressWarnings("unchecked")
+    @BindingAdapter(value = {"onScrollChangeCommand", "onScrollStateChangedCommand"}, requireAll = false)
+    public static void onScrollChangeCommand(final ListView listView,
+                                             final BindingCommand<ListViewScrollDataWrapper> onScrollChangeCommand,
+                                             final BindingCommand<Integer> onScrollStateChangedCommand) {
+        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
+            private int scrollState;
+
+            @Override
+            public void onScrollStateChanged(AbsListView view, int scrollState) {
+                this.scrollState = scrollState;
+                if (onScrollStateChangedCommand != null) {
+                    onScrollStateChangedCommand.execute(scrollState);
+                }
+            }
+
+            @Override
+            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+                if (onScrollChangeCommand != null) {
+                    onScrollChangeCommand.execute(new ListViewScrollDataWrapper(scrollState, firstVisibleItem, visibleItemCount, totalItemCount));
+                }
+            }
+        });
+
+    }
+
+
+    @BindingAdapter(value = {"onItemClickCommand"}, requireAll = false)
+    public static void onItemClickCommand(final ListView listView, final BindingCommand<Integer> onItemClickCommand) {
+        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                if (onItemClickCommand != null) {
+                    onItemClickCommand.execute(position);
+                }
+            }
+        });
+    }
+
+
+    @BindingAdapter({"onLoadMoreCommand"})
+    public static void onLoadMoreCommand(final ListView listView, final BindingCommand<Integer> onLoadMoreCommand) {
+        listView.setOnScrollListener(new OnScrollListener(listView, onLoadMoreCommand));
+
+    }
+
+    public static class OnScrollListener implements AbsListView.OnScrollListener {
+        private PublishSubject<Integer> methodInvoke = PublishSubject.create();
+        private BindingCommand<Integer> onLoadMoreCommand;
+        private ListView listView;
+
+        public OnScrollListener(ListView listView, final BindingCommand<Integer> onLoadMoreCommand) {
+            this.onLoadMoreCommand = onLoadMoreCommand;
+            this.listView = listView;
+            methodInvoke.throttleFirst(1, TimeUnit.SECONDS)
+                    .subscribe(new Consumer<Integer>() {
+                        @Override
+                        public void accept(Integer integer) throws Exception {
+                            onLoadMoreCommand.execute(integer);
+                        }
+                    });
+        }
+
+        @Override
+        public void onScrollStateChanged(AbsListView view, int scrollState) {
+
+        }
+
+        @Override
+        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+            if (firstVisibleItem + visibleItemCount >= totalItemCount
+                    && totalItemCount != 0
+                    && totalItemCount != listView.getHeaderViewsCount()
+                    + listView.getFooterViewsCount()) {
+                if (onLoadMoreCommand != null) {
+                    methodInvoke.onNext(totalItemCount);
+                }
+            }
+        }
+    }
+
+    public static class ListViewScrollDataWrapper {
+        public int firstVisibleItem;
+        public int visibleItemCount;
+        public int totalItemCount;
+        public int scrollState;
+
+        public ListViewScrollDataWrapper(int scrollState, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+            this.firstVisibleItem = firstVisibleItem;
+            this.visibleItemCount = visibleItemCount;
+            this.totalItemCount = totalItemCount;
+            this.scrollState = scrollState;
+        }
+    }
+}

+ 45 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/mswitch/ViewAdapter.java

xqd
@@ -0,0 +1,45 @@
+package me.goldze.mvvmhabit.binding.viewadapter.mswitch;
+
+import android.databinding.BindingAdapter;
+import android.text.TextUtils;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+import static android.R.attr.checked;
+import static me.goldze.mvvmhabit.R.attr.switchState;
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+
+public class ViewAdapter {
+    /**
+     * 设置开关状态
+     *
+     * @param mSwitch Switch控件
+     */
+    @BindingAdapter("switchState")
+    public static void setSwitchState(Switch mSwitch, boolean isChecked) {
+        mSwitch.setChecked(isChecked);
+    }
+
+    /**
+     * Switch的状态改变监听
+     *
+     * @param mSwitch        Switch控件
+     * @param changeListener 事件绑定命令
+     */
+    @BindingAdapter("onCheckedChangeCommand")
+    public static void onCheckedChangeCommand(final Switch mSwitch, final BindingCommand<Boolean> changeListener) {
+        if (changeListener != null) {
+            mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                @Override
+                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                    changeListener.execute(isChecked);
+                }
+            });
+        }
+    }
+}

+ 26 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/radiogroup/ViewAdapter.java

xqd
@@ -0,0 +1,26 @@
+package me.goldze.mvvmhabit.binding.viewadapter.radiogroup;
+
+import android.databinding.BindingAdapter;
+import android.support.annotation.IdRes;
+import android.text.TextUtils;
+import android.webkit.WebView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+public class ViewAdapter {
+    @BindingAdapter(value = {"onCheckedChangedCommand"}, requireAll = false)
+    public static void onCheckedChangedCommand(final RadioGroup radioGroup, final BindingCommand<String> bindingCommand) {
+        radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
+                RadioButton radioButton = (RadioButton) group.findViewById(checkedId);
+                bindingCommand.execute(radioButton.getText().toString());
+            }
+        });
+    }
+}

+ 163 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/recyclerview/DividerLine.java

xqd
@@ -0,0 +1,163 @@
+package me.goldze.mvvmhabit.binding.viewadapter.recyclerview;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * Created by goldze on 2017/6/16.
+ */
+public class DividerLine extends RecyclerView.ItemDecoration {
+    private static final String TAG = DividerLine.class.getCanonicalName();
+    //默认分隔线厚度为2dp
+    private static final int DEFAULT_DIVIDER_SIZE = 1;
+    //控制分隔线的属性,值为一个drawable
+    private static final int ATTRS[] = {android.R.attr.listDivider};
+    //divider对应的drawable
+    private Drawable dividerDrawable;
+    private Context mContext;
+    private int dividerSize;
+    //默认为null
+    private LineDrawMode mMode = null;
+
+    /**
+     * 分隔线绘制模式,水平,垂直,两者都绘制
+     */
+    public enum LineDrawMode {
+        HORIZONTAL, VERTICAL, BOTH
+    }
+
+    public DividerLine(Context context) {
+        mContext = context;
+        //获取样式中对应的属性值
+        TypedArray attrArray = context.obtainStyledAttributes(ATTRS);
+        dividerDrawable = attrArray.getDrawable(0);
+        attrArray.recycle();
+    }
+
+    public DividerLine(Context context, LineDrawMode mode) {
+        this(context);
+        mMode = mode;
+    }
+
+    public DividerLine(Context context, int dividerSize, LineDrawMode mode) {
+        this(context, mode);
+        this.dividerSize = dividerSize;
+    }
+
+    public int getDividerSize() {
+        return dividerSize;
+    }
+
+    public void setDividerSize(int dividerSize) {
+        this.dividerSize = dividerSize;
+    }
+
+    public LineDrawMode getMode() {
+        return mMode;
+    }
+
+    public void setMode(LineDrawMode mode) {
+        mMode = mode;
+    }
+
+    /**
+     * Item绘制完毕之后绘制分隔线
+     * 根据不同的模式绘制不同的分隔线
+     *
+     * @param c
+     * @param parent
+     * @param state
+     */
+    @Override
+    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        super.onDrawOver(c, parent, state);
+        if (getMode() == null) {
+            throw new IllegalStateException("assign LineDrawMode,please!");
+        }
+        switch (getMode()) {
+            case VERTICAL:
+                drawVertical(c, parent, state);
+                break;
+            case HORIZONTAL:
+                drawHorizontal(c, parent, state);
+                break;
+            case BOTH:
+                drawHorizontal(c, parent, state);
+                drawVertical(c, parent, state);
+                break;
+        }
+    }
+
+    /**
+     * 绘制垂直分隔线
+     *
+     * @param c
+     * @param parent
+     * @param state
+     */
+    private void drawVertical(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = parent.getChildAt(i);
+            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+                    .getLayoutParams();
+            final int top = child.getTop() - params.topMargin;
+            final int bottom = child.getBottom() + params.bottomMargin;
+            final int left = child.getRight() + params.rightMargin;
+            final int right = getDividerSize() == 0 ? left + dip2px(mContext, DEFAULT_DIVIDER_SIZE) : left + getDividerSize();
+            dividerDrawable.setBounds(left, top, right, bottom);
+            dividerDrawable.draw(c);
+        }
+    }
+
+    /**
+     * 绘制水平分隔线
+     *
+     * @param c
+     * @param parent
+     * @param state
+     */
+    private void drawHorizontal(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            //分别为每个item绘制分隔线,首先要计算出item的边缘在哪里,给分隔线定位,定界
+            final View child = parent.getChildAt(i);
+            //RecyclerView的LayoutManager继承自ViewGroup,支持了margin
+            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+            //child的左边缘(也是分隔线的左边)
+            final int left = child.getLeft() - params.leftMargin;
+            //child的底边缘(恰好是分隔线的顶边)
+            final int top = child.getBottom() + params.topMargin;
+            //child的右边(也是分隔线的右边)
+            final int right = child.getRight() - params.rightMargin;
+            //分隔线的底边所在的位置(那就是分隔线的顶边加上分隔线的高度)
+            final int bottom = getDividerSize() == 0 ? top + dip2px(mContext, DEFAULT_DIVIDER_SIZE) : top + getDividerSize();
+            dividerDrawable.setBounds(left, top, right, bottom);
+            //画上去
+            dividerDrawable.draw(c);
+        }
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        super.getItemOffsets(outRect, view, parent, state);
+//        outRect.bottom = getDividerSize() == 0 ? dip2px(mContext, DEFAULT_DIVIDER_SIZE) : getDividerSize();
+    }
+
+    /**
+     * 将dip或dp值转换为px值,保证尺寸大小不变
+     *
+     * @param dipValue
+     * @param context(DisplayMetrics类中属性density)
+     * @return
+     */
+    public static int dip2px(Context context, float dipValue) {
+        float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (dipValue * scale + 0.5f);
+    }
+}

+ 43 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/recyclerview/LineManagers.java

xqd
@@ -0,0 +1,43 @@
+package me.goldze.mvvmhabit.binding.viewadapter.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+
+/**
+ * Created by goldze on 2017/6/16.
+ */
+public class LineManagers {
+    protected LineManagers() {
+    }
+
+    public interface LineManagerFactory {
+        RecyclerView.ItemDecoration create(RecyclerView recyclerView);
+    }
+
+
+    public static LineManagerFactory both() {
+        return new LineManagerFactory() {
+            @Override
+            public RecyclerView.ItemDecoration create(RecyclerView recyclerView) {
+                return new DividerLine(recyclerView.getContext(), DividerLine.LineDrawMode.BOTH);
+            }
+        };
+    }
+
+    public static LineManagerFactory horizontal() {
+        return new LineManagerFactory() {
+            @Override
+            public RecyclerView.ItemDecoration create(RecyclerView recyclerView) {
+                return new DividerLine(recyclerView.getContext(), DividerLine.LineDrawMode.HORIZONTAL);
+            }
+        };
+    }
+
+    public static LineManagerFactory vertical() {
+        return new LineManagerFactory() {
+            @Override
+            public RecyclerView.ItemDecoration create(RecyclerView recyclerView) {
+                return new DividerLine(recyclerView.getContext(), DividerLine.LineDrawMode.VERTICAL);
+            }
+        };
+    }
+}

+ 113 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/recyclerview/ViewAdapter.java

xqd
@@ -0,0 +1,113 @@
+package me.goldze.mvvmhabit.binding.viewadapter.recyclerview;
+
+import android.databinding.BindingAdapter;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.functions.Consumer;
+import io.reactivex.subjects.PublishSubject;
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/16.
+ */
+public class ViewAdapter {
+
+    @BindingAdapter("lineManager")
+    public static void setLineManager(RecyclerView recyclerView, LineManagers.LineManagerFactory lineManagerFactory) {
+        recyclerView.addItemDecoration(lineManagerFactory.create(recyclerView));
+    }
+
+
+    @BindingAdapter(value = {"onScrollChangeCommand", "onScrollStateChangedCommand"}, requireAll = false)
+    public static void onScrollChangeCommand(final RecyclerView recyclerView,
+                                             final BindingCommand<ScrollDataWrapper> onScrollChangeCommand,
+                                             final BindingCommand<Integer> onScrollStateChangedCommand) {
+        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            private int state;
+
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                super.onScrolled(recyclerView, dx, dy);
+                if (onScrollChangeCommand != null) {
+                    onScrollChangeCommand.execute(new ScrollDataWrapper(dx, dy, state));
+                }
+            }
+
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                super.onScrollStateChanged(recyclerView, newState);
+                state = newState;
+                if (onScrollStateChangedCommand != null) {
+                    onScrollStateChangedCommand.execute(newState);
+                }
+            }
+        });
+
+    }
+
+    @SuppressWarnings("unchecked")
+    @BindingAdapter({"onLoadMoreCommand"})
+    public static void onLoadMoreCommand(final RecyclerView recyclerView, final BindingCommand<Integer> onLoadMoreCommand) {
+        RecyclerView.OnScrollListener listener = new OnScrollListener(onLoadMoreCommand);
+        recyclerView.addOnScrollListener(listener);
+
+    }
+
+    @BindingAdapter("itemAnimator")
+    public static void setItemAnimator(RecyclerView recyclerView, RecyclerView.ItemAnimator animator) {
+        recyclerView.setItemAnimator(animator);
+    }
+
+    public static class OnScrollListener extends RecyclerView.OnScrollListener {
+
+        private PublishSubject<Integer> methodInvoke = PublishSubject.create();
+
+        private BindingCommand<Integer> onLoadMoreCommand;
+
+        public OnScrollListener(final BindingCommand<Integer> onLoadMoreCommand) {
+            this.onLoadMoreCommand = onLoadMoreCommand;
+            methodInvoke.throttleFirst(1, TimeUnit.SECONDS)
+                    .subscribe(new Consumer<Integer>() {
+                        @Override
+                        public void accept(Integer integer) throws Exception {
+                            onLoadMoreCommand.execute(integer);
+                        }
+                    });
+        }
+
+        @Override
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+            LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
+            int visibleItemCount = layoutManager.getChildCount();
+            int totalItemCount = layoutManager.getItemCount();
+            int pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
+            if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
+                if (onLoadMoreCommand != null) {
+                    methodInvoke.onNext(recyclerView.getAdapter().getItemCount());
+                }
+            }
+        }
+
+        @Override
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+            super.onScrollStateChanged(recyclerView, newState);
+        }
+
+
+    }
+
+    public static class ScrollDataWrapper {
+        public float scrollX;
+        public float scrollY;
+        public int state;
+
+        public ScrollDataWrapper(float scrollX, float scrollY, int state) {
+            this.scrollX = scrollX;
+            this.scrollY = scrollY;
+            this.state = state;
+        }
+    }
+}

+ 64 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/scrollview/ViewAdapter.java

xqd
@@ -0,0 +1,64 @@
+package me.goldze.mvvmhabit.binding.viewadapter.scrollview;
+
+import android.databinding.BindingAdapter;
+import android.support.v4.widget.NestedScrollView;
+import android.view.ViewTreeObserver;
+import android.widget.ScrollView;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+public final class ViewAdapter {
+
+    @SuppressWarnings("unchecked")
+    @BindingAdapter({"onScrollChangeCommand"})
+    public static void onScrollChangeCommand(final NestedScrollView nestedScrollView, final BindingCommand<NestScrollDataWrapper> onScrollChangeCommand) {
+        nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
+            @Override
+            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+                if (onScrollChangeCommand != null) {
+                    onScrollChangeCommand.execute(new NestScrollDataWrapper(scrollX, scrollY, oldScrollX, oldScrollY));
+                }
+            }
+        });
+    }
+
+    @SuppressWarnings("unchecked")
+    @BindingAdapter({"onScrollChangeCommand"})
+    public static void onScrollChangeCommand(final ScrollView scrollView, final BindingCommand<ScrollDataWrapper> onScrollChangeCommand) {
+        scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
+            @Override
+            public void onScrollChanged() {
+                if (onScrollChangeCommand != null) {
+                    onScrollChangeCommand.execute(new ScrollDataWrapper(scrollView.getScrollX(), scrollView.getScrollY()));
+                }
+            }
+        });
+    }
+
+    public static class ScrollDataWrapper {
+        public float scrollX;
+        public float scrollY;
+
+        public ScrollDataWrapper(float scrollX, float scrollY) {
+            this.scrollX = scrollX;
+            this.scrollY = scrollY;
+        }
+    }
+
+    public static class NestScrollDataWrapper {
+        public int scrollX;
+        public int scrollY;
+        public int oldScrollX;
+        public int oldScrollY;
+
+        public NestScrollDataWrapper(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+            this.scrollX = scrollX;
+            this.scrollY = scrollY;
+            this.oldScrollX = oldScrollX;
+            this.oldScrollY = oldScrollY;
+        }
+    }
+}

+ 11 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/spinner/IKeyAndValue.java

xqd
@@ -0,0 +1,11 @@
+package me.goldze.mvvmhabit.binding.viewadapter.spinner;
+
+/**
+ * Created by goldze on 2017/6/18.
+ * 下拉Spinner控件的键值对, 实现该接口,返回key,value值, 在xml绑定List<IKeyAndValue>
+ */
+public interface IKeyAndValue {
+    String getKey();
+
+    String getValue();
+}

+ 70 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/spinner/ViewAdapter.java

xqd
@@ -0,0 +1,70 @@
+package me.goldze.mvvmhabit.binding.viewadapter.spinner;
+
+import android.databinding.BindingAdapter;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+public class ViewAdapter {
+    /**
+     * 双向的SpinnerViewAdapter, 可以监听选中的条目,也可以回显选中的值
+     *
+     * @param spinner        控件本身
+     * @param itemDatas      下拉条目的集合
+     * @param valueReply     回显的value
+     * @param bindingCommand 条目点击的监听
+     */
+    @BindingAdapter(value = {"itemDatas", "valueReply", "resource", "dropDownResource", "onItemSelectedCommand"}, requireAll = false)
+    public static void onItemSelectedCommand(final Spinner spinner, final List<IKeyAndValue> itemDatas, String valueReply, int resource, int dropDownResource, final BindingCommand<IKeyAndValue> bindingCommand) {
+        if (itemDatas == null) {
+            throw new NullPointerException("this itemDatas parameter is null");
+        }
+        List<String> lists = new ArrayList<>();
+        for (IKeyAndValue iKeyAndValue : itemDatas) {
+            lists.add(iKeyAndValue.getKey());
+        }
+        if (resource == 0) {
+            resource = android.R.layout.simple_spinner_item;
+        }
+        if (dropDownResource == 0) {
+            dropDownResource = android.R.layout.simple_spinner_dropdown_item;
+        }
+        ArrayAdapter<String> adapter = new ArrayAdapter(spinner.getContext(), resource, lists);
+        adapter.setDropDownViewResource(dropDownResource);
+        spinner.setAdapter(adapter);
+        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                IKeyAndValue iKeyAndValue = itemDatas.get(position);
+                //将IKeyAndValue对象交给ViewModel
+                bindingCommand.execute(iKeyAndValue);
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+
+            }
+        });
+        //回显选中的值
+        if (!TextUtils.isEmpty(valueReply)) {
+            for (int i = 0; i < itemDatas.size(); i++) {
+                IKeyAndValue iKeyAndValue = itemDatas.get(i);
+                if (valueReply.equals(iKeyAndValue.getValue())) {
+                    spinner.setSelection(i);
+                    return;
+                }
+            }
+        }
+    }
+}

+ 32 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/swiperefresh/ViewAdapter.java

xqd
@@ -0,0 +1,32 @@
+package me.goldze.mvvmhabit.binding.viewadapter.swiperefresh;
+
+import android.databinding.BindingAdapter;
+import android.support.v4.widget.SwipeRefreshLayout;
+
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+
+/**
+ * Created by goldze on 2017/6/18.
+ */
+public class ViewAdapter {
+    //下拉刷新命令
+    @BindingAdapter({"onRefreshCommand"})
+    public static void onRefreshCommand(SwipeRefreshLayout swipeRefreshLayout, final BindingCommand onRefreshCommand) {
+        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+            @Override
+            public void onRefresh() {
+                if (onRefreshCommand != null) {
+                    onRefreshCommand.execute();
+                }
+            }
+        });
+    }
+
+    //是否刷新中
+    @BindingAdapter({"refreshing"})
+    public static void setRefreshing(SwipeRefreshLayout swipeRefreshLayout, boolean refreshing) {
+        swipeRefreshLayout.setRefreshing(refreshing);
+    }
+
+}

+ 133 - 0
mvvmhabit/src/main/java/me/goldze/mvvmhabit/binding/viewadapter/view/ViewAdapter.java

xqd
@@ -0,0 +1,133 @@
+package me.goldze.mvvmhabit.binding.viewadapter.view;
+
+import android.databinding.BindingAdapter;
+import android.view.View;
+
+import com.jakewharton.rxbinding2.view.RxView;
+
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.functions.Consumer;
+import me.goldze.mvvmhabit.binding.command.BindingCommand;
+
+/**
+ * Created by goldze on 2017/6/16.
+ */
+
+public class ViewAdapter {
+    //防重复点击间隔(秒)
+    public static final int CLICK_INTERVAL = 1;
+
+    /**
+     * requireAll 是意思是是否需要绑定全部参数, false为否
+     * View的onClick事件绑定
+     * onClickCommand 绑定的命令,
+     * isThrottleFirst 是否开启防止过快点击
+     */
+    @BindingAdapter(value = {"onClickCommand", "isThrottleFirst"}, requireAll = false)
+    public static void onClickCommand(View view, final BindingCommand clickCommand, final boolean isThrottleFirst) {
+        if (isThrottleFirst) {
+            RxView.clicks(view)
+                    .subscribe(new Consumer<Object>() {
+                        @Override
+                        public void accept(Object object) throws Exception {
+                            if (clickCommand != null) {
+                                clickCommand.execute();
+                            }
+                        }
+                    });
+        } else {
+            RxView.clicks(view)
+                    .throttleFirst(CLICK_INTERVAL, TimeUnit.SECONDS)//1秒钟内只允许点击1次
+                    .subscribe(new Consumer<Object>() {
+                        @Override
+                        public void accept(Object object) throws Exception {
+                            if (clickCommand != null) {
+                                clickCommand.execute();
+                            }
+                        }
+                    });
+        }
+    }
+
+    /**
+     * view的onLongClick事件绑定
+     */
+    @BindingAdapter(value = {"onLongClickCommand"}, requireAll = false)
+    public static void onLongClickCommand(View view, final BindingCommand clickCommand) {
+        RxView.longClicks(view)
+                .subscribe(new Consumer<Object>() {
+                    @Override
+                    public void accept(Object object) throws Exception {
+                        if (clickCommand != null) {
+                            clickCommand.execute();
+                        }
+                    }
+                });
+    }
+
+    /**
+     * 回调控件本身
+     *
+     * @param currentView
+     * @param bindingCommand
+     */
+    @BindingAdapter(value = {"currentView"}, requireAll = false)
+    public static void replyCurrentView(View currentView, BindingCommand bindingCommand) {
+        if (bindingCommand != null) {
+            bindingCommand.execute(currentView);
+        }
+    }
+
+    /**
+     * view是否需要获取焦点
+     */
+    @BindingAdapter({"requestFocus"})
+    public static void requestFocusCommand(View view, final Boolean needRequestFocus) {
+        if (needRequestFocus) {
+            view.setFocusableInTouchMode(true);
+            view.requestFocus();
+        } else {
+            view.clearFocus();
+        }
+    }
+
+    /**
+     * view的焦点发生变化的事件绑定
+     */
+    @BindingAdapter({"onFocusChangeCommand"})
+    public static void onFocusChangeCommand(View view, final BindingCommand<Boolean> onFocusChangeCommand) {
+        view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (onFocusChangeCommand != null) {
+                    onFocusChangeCommand.execute(hasFocus);
+                }
+            }
+        });
+    }
+
+    /**
+     * view的显示隐藏
+     */
+    @BindingAdapter(value = {"isVisible"}, requireAll = false)
+    public static void isVisible(View view, final Boolean visibility) {
+        if (visibility) {
+            view.setVisibility(View.VISIBLE);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+//    @BindingAdapter({"onTouchCommand"})
+//    public static void onTouchCommand(View view, final ResponseCommand<MotionEvent, Boolean> onTouchCommand) {
+//        view.setOnTouchListener(new View.OnTouchListener() {
+//            @Override
+//            public boolean onTouch(View v, MotionEvent event) {
+//                if (onTouchCommand != null) {
+//                    return onTouchCommand.execute(event);
+//                }
+//                return false;
+//            }
+//        });
+//    }
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików