MvvM框架

gradle 依赖

ext {
    app_version = '8.3.2.1'
    kotlin_version = "1.3.71"
    anko_version = '0.10.8'
    lottie_version = '3.7.0'

    //Bugly
    bugly_version = '3.4.4'
    bugly_native_version = '3.9.2'
    core_ktx_version = '1.3.2'
    appcompat_version = '1.2.0'
    constraintlayout_version = '2.0.4'
    navigation_version = '2.3.5'
    jiagufix_version = '1.0.3-SNAPSHOT'
    mmkv_version = '1.2.4'
    zxing_version = '3.4.1'
    room_version = '2.3.0'
    


}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'pd-apk-sign'
apply plugin: 'kotlin-kapt'

def getCode() {
    return new Date().format("yyyyMMddHH", TimeZone.getDefault())
}

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    flavorDimensions "default"
    defaultConfig {
        applicationId 'com.pudutech.robot.firefox'
        minSdkVersion 26
        targetSdkVersion 29
        versionCode 100
        versionName "1.0"
        versionName "${project.hasProperty('VERSION_NAME') ? "$VERSION_NAME" : "$app_version"}"
        archivesBaseName = "app-${versionName}-${versionCode}-${new Date().format("yyyy-MM-dd")}"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            manifestPlaceholders = [USER_ID_CFG: "android.uid.system", CHANNEL_VALUE: "$channel_name"]
            buildConfigField("String", "APP_DEFAULT_CODE", "\"${getCode()}${defaultConfig.versionName.replace(".", "")}\"")
        }
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            manifestPlaceholders = [USER_ID_CFG: "", CHANNEL_VALUE: "$channel_name"]
            buildConfigField("String", "APP_DEFAULT_CODE", "\"${getCode()}${defaultConfig.versionName.replace(".", "")}\"")
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    packagingOptions {
        pickFirst 'lib/armeabi-v7a/libc++_shared.so'
        pickFirst 'lib/armeabi-v7a/libpudutech_log.so'
        pickFirst 'lib/armeabi-v7a/libpudutech_base.so'
        pickFirst 'lib/armeabi-v7a/libopencv_java4.so'
        pickFirst 'lib/arm64-v8a/libutils.so'
        pickFirst 'lib/arm64-v8a/libc++_shared.so'
        pickFirst 'lib/arm64-v8a/libpudutech_log.so'
        pickFirst 'lib/arm64-v8a/libpudutech_base.so'
        pickFirst 'lib/arm64-v8a/libopencv_java4.so'
        pickFirst 'lib/x86/libc++_shared.so'
        pickFirst 'lib/x86/libpudutech_log.so'
        pickFirst 'lib/x86/libpudutech_base.so'
        pickFirst 'lib/x86/libopencv_java4.so'
        pickFirst 'lib/x86_64/libc++_shared.so'
        pickFirst 'lib/x86_64/libpudutech_log.so'
        pickFirst 'lib/x86_64/libpudutech_base.so'
        pickFirst 'lib/x86_64/libopencv_java4.so'
        pickFirst 'META-INF/pudubase_release.kotlin_module'
        pickFirst 'META-INF/component_mqtt_release.kotlin_module'
        pickFirst 'META-INF/native-image/io.netty/codec-http/native-image.properties'
        pickFirst 'META-INF/native-image/io.netty/common/native-image.properties'
        pickFirst 'META-INF/native-image/io.netty/transport/native-image.properties'
        pickFirst 'META-INF/native-image/io.netty/codec-http2/native-image.properties'
        pickFirst 'META-INF/native-image/io.netty/transport/reflection-config.json'
        pickFirst 'META-INF/native-image/io.netty/buffer/native-image.properties'
        pickFirst 'META-INF/native-image/io.netty/handler/native-image.properties'
        exclude 'META-INF/INDEX.LIST'
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/io.netty.versions.properties'
        exclude 'META-INF/nanohttpd/mimetypes.properties'
        exclude 'META-INF/nanohttpd/default-mimetypes.properties'
        exclude 'bundle.properties'
    }

    sourceSets {
        main {
            res.srcDirs = [
                    "src/main/res/layouts/meituandelivery",
                    "src/main/res/layouts/calltask",
                    "src/main/res/layouts/common",
                    "src/main/res/layouts/main",
                    "src/main/res/layouts/home",
                    "src/main/res/layouts/selfcheck",
                    "src/main/res/layouts/roomdelivery",
                    "src/main/res/layouts/orderdelivery",
                    "src/main/res/layouts/cruisedelivery",
                    "src/main/res/layouts/guideguest",
                    "src/main/res/layouts/courierdelivery",
                    "src/main/res/layouts/charge",
                    "src/main/res/layouts/directtask",
                    "src/main/res/layouts/setting",
                    "src/main/res/layouts/laser",
                    "src/main/res/layouts/retail",
                    "src/main/res/layouts",
                    "src/main/res"
            ]
            jniLibs.srcDirs = ['libs']
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    productFlavors {
        mock {
            dimension "default"
            ndk {
                abiFilters 'arm64-v8a', 'x86', 'x86_64'
            }
        }

        robot {
            dimension "default"
            ndk {
                abiFilters 'arm64-v8a'
            }
        }
    }

    configurations.all {
        all*.exclude group: 'com.tencent', module: 'mmkv'
    }
}

pdSignAPk {
    if (project.hasProperty('JIA_GU_DIR')) {
        inputPaths = ["$JIA_GU_DIR"]
    } else {
        inputPaths = ["$project.buildDir/outputs/apk/robot/release"]
    }
}

tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

project.tasks.whenTaskAdded { Task task ->
    if (task.name == "assembleRobotRelease") {
        task.finalizedBy(pdSingApk)
        task.finalizedBy('recordRobotReleaseDependencise')
    }
}
configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor 1, 'seconds' // 动态版本
    resolutionStrategy.cacheChangingModulesFor 1, 'seconds' // 变化模块
}

kapt {
    arguments {
        arg("room.schemaLocation", "$projectDir/schemas".toString())
    }
}

apply from: '../record_dependencise.gradle'

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation "androidx.core:core-ktx:$core_ktx_version"
    implementation "androidx.appcompat:appcompat:$appcompat_version"
    implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version"
    implementation project(path: ':library_im')
    implementation 'androidx.work:work-runtime-ktx:2.3.2'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
    implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
    

    //第三方
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
    implementation "org.jetbrains.anko:anko:$anko_version"
    implementation "com.airbnb.android:lottie:$lottie_version"
    implementation 'me.jessyan:autosize:1.2.1'
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
    implementation "com.tencent:mmkv-static:$mmkv_version"
    implementation group: 'io.netty', name: 'netty-all', version: '4.1.42.Final'
    implementation 'com.imuxuan:floatingview:1.6'
    implementation "com.google.zxing:core:$zxing_version"


    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:2.2.6"
    implementation "androidx.room:room-ktx:2.2.6"



    implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2'
    implementation 'com.freddy:eventcenter_lib:1.0.1'

    implementation 'com.aliyun.dpa:oss-android-sdk:2.9.5'

   
    //Bugly
    implementation "com.tencent.bugly:crashreport:$bugly_version"
    //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9
    implementation "com.tencent.bugly:nativecrashreport:$bugly_native_version"
    //其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.0

//    /*MQTT*/
    implementation 'org.eclipse.paho:org.eclipse.paho.mqttv5.client:1.2.5'
    implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'

}

//    ext.kotlin_version = "1.3.71"
//    ext.anko_version = '0.10.8'
//    ext.app_version = '7.2.0.42'


//    ext.resources_version = '2.0.8'


//    ext.mqtt_version = '1.0.22-SNAPSHOT'
//    ext.slport_version = '1.0.10-SNAPSHOT'


//    ext.lease_lib_version = '1.1.4-SNAPSHOT'
//    ext.network_version = '1.2.0'
//    ext.channel_name = 'general'
//    ext.retrofit2_version = '2.5.0'
//    ext.thermometry_version = '1.0.0-SNAPSHOT'

//dependencies {
//    implementation files('libs/nineoldandroids-2.4.0.jar', 'libs/pdfbox-2.0.0-RC3.jar')
//    implementation 'androidx.fragment:fragment:1.2.5'
//    def lifecycle_version = "2.2.0"
//    def room_version = "2.2.5"
//    def fragmentVersion = "1.2.4"
//    implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
//    testImplementation 'junit:junit:4.12'
//    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
//    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//
//    implementation 'androidx.core:core-ktx:1.3.1'
//    implementation 'androidx.appcompat:appcompat:1.2.0'
//    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version")
//
//    // lifecycle
//    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
//    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
//    api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
//    api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
//    api "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
//    implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
//    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
//    implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06'
//
//    implementation 'com.google.android.material:material:1.2.1'
//    implementation "org.jetbrains.anko:anko:$anko_version"
//
//    // room
//    implementation "androidx.room:room-runtime:$room_version"
//    implementation "androidx.room:room-common:$room_version"
//    kapt "androidx.room:room-compiler:$room_version"
//    implementation "androidx.room:room-ktx:$room_version"
//
//
//    //协程
//    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
//    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
//
//    // retrofit
//    implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
//    implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
//    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version"
//
//    // rxjava
//    implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
//    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
//
//    implementation 'com.google.code.gson:gson:2.8.6'
//
//    // bugly
//    implementation 'com.tencent.bugly:crashreport:3.2.422'
//    implementation 'com.tencent.bugly:nativecrashreport:3.7.1'
//
//    // 第三方
//    implementation 'com.airbnb.android:lottie:3.3.0'
//    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.42'
//    implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-28'
//    implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2'
//    implementation 'me.jessyan:autosize:1.1.2'
//    implementation 'com.xw.repo:bubbleseekbar:3.20'
//    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
//    implementation 'com.blankj:utilcodex:1.30.5'
//    debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
//    implementation 'jp.wasabeef:blurry:4.0.0'
//    implementation 'com.github.tom200989:logma:v0.1'
//}

单个activity 多个fragment 

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/navigation_main" />

 baseFragment



import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer



abstract class BaseFragment : Fragment() {

    private var isFirstLoad: Boolean = true

    lateinit var mActivity: AppCompatActivity

    // 当前Fragment绑定的视图布局
    abstract fun layoutId(): Int

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        return inflater.inflate(layoutId(), container, false)

    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mActivity = context as AppCompatActivity
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        isFirstLoad = true
        initView(savedInstanceState)
        createObserver()
        onVisible()
        initData()
    }

    abstract fun initView(savedInstanceState: Bundle?)

    abstract fun createObserver()

    override fun onResume() {
        super.onResume()
    }

    /**
     * 是否需要懒加载
     */
    private fun onVisible() {
        if (lifecycle.currentState == Lifecycle.State.STARTED && isFirstLoad) {
            //延迟加载,避免fragment跳转动画和渲染ui同时进行,出现的卡顿
            view?.postDelayed({
                lazyLoadData()
                //在fragment中,只有懒加载过才能开启网络变化监听
                NetworkStateManager.instance.networkStateCallback.observe(
                        viewLifecycleOwner,
                        Observer {
                            if (!isFirstLoad) {
                                onNetworkStateChange(it)
                            }
                        }
                )
                isFirstLoad = false
            }, 120)
        }
    }

    /**
     * Fragment执行onCreate后触发的方法
     */

    open fun initData() {}

    /**
     * 网络变化监听,子类重写
     */

    open fun onNetworkStateChange(netState: NetState) {}

    /**
     * 懒加载
     */

    abstract fun lazyLoadData()

}




import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider



abstract class BaseVmFragment<VM : BaseViewModel> : Fragment() {

    private var isFirstLoad: Boolean = true

    lateinit var mViewModel: VM;

    lateinit var mActivity: AppCompatActivity

    // 当前Fragment绑定的视图布局
    abstract fun layoutId(): Int

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        return inflater.inflate(layoutId(), container, false)

    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mActivity = context as AppCompatActivity
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        isFirstLoad = true
        mViewModel = createViewModel()
        initView(savedInstanceState)
        createObserver()
        onVisible()
        registerDefaultUiChange()
        initData()
    }

    abstract fun initView(savedInstanceState: Bundle?)

    abstract fun createObserver()

    override fun onResume() {
        super.onResume()
    }

    /**
     * 是否需要懒加载
     */
    private fun onVisible() {
        if (lifecycle.currentState == Lifecycle.State.STARTED && isFirstLoad) {
            //延迟加载,避免fragment跳转动画和渲染ui同时进行,出现的卡顿
            view?.postDelayed({
                lazyLoadData()
                //在fragment中,只有懒加载过才能开启网络变化监听
                NetworkStateManager.instance.networkStateCallback.observe(
                        viewLifecycleOwner,
                        Observer {
                            if (!isFirstLoad) {
                                onNetworkStateChange(it)
                            }
                        }
                )
                isFirstLoad = false
            }, 120)
        }
    }

    /**
     * Fragment执行onCreate后触发的方法
     */

    open fun initData() {}

    /**
     * 网络变化监听,子类重写
     */

    open fun onNetworkStateChange(netState: NetState) {}

    abstract fun showLoading(message: String = "请求网络中...")

    abstract fun dismissLoading()

    /**
     * 注册UI事件
     *
     */

    private fun registerDefaultUiChange() {
        mViewModel.loadingChange.showDialog.observe(viewLifecycleOwner, Observer {
            showLoading()
        })

        mViewModel.loadingChange.dismissDialog.observe(viewLifecycleOwner, Observer {
            dismissLoading()
        })
    }

    /**
     * 懒加载
     */

    abstract fun lazyLoadData()

    /**
     * 创建ViewModel
     */

    private fun createViewModel(): VM {
        return ViewModelProvider(this).get(getVmClazz(this))
    }

}




import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import android.view.View
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import java.util.*

/**
 * ViewModelActivity基类 把ViewModel注入进来
 */
abstract class BaseVmActivity<VM : BaseViewModel> : AppCompatActivity() {

    lateinit var mViewModel: VM

    abstract fun layoutId(): Int

    abstract fun initView(saveInstanceState: Bundle?)

    abstract fun showLoading(message: String = "网络请求中....")

    abstract fun dismissLoading()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
        window.decorView.setOnSystemUiVisibilityChangeListener { translucent() }
//        hideNavigationBarWithoutBack()
        InputMethodUtil.changeInputMethodIfNeed(this, InputMethodUtil.INPUT_NAME_GOKEY)
        setContentView(layoutId())
        init(savedInstanceState)

    }

    private fun init(saveInstanceState: Bundle?) {
        mViewModel = createViewModel()
        initView(saveInstanceState)
        createObserver()
        NetworkStateManager.instance.networkStateCallback.observe(this, Observer {
            onNetWorkStateChanged(it)
        })
    }

    /**
     * 创建LiveData数据观察者
     */
    abstract fun createObserver()

    /**
     * 网络变化监听,子类重写
     */
    open fun onNetWorkStateChanged(netState: NetState) {}

    /**
     * 创建ViewModel
     */
    private fun createViewModel(): VM {
        return ViewModelProvider(this).get(getVmClazz(this))
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        translucent()
    }

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(updateBaseContext(newBase));
    }

    private fun updateBaseContext(context: Context): Context {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            updateResource(context);
        } else
            context;
    }

    @TargetApi(Build.VERSION_CODES.N)
    private fun updateResource(context: Context): Context {
        val resources = context.resources
        val locale = Locale.getDefault()
        val configuration = resources.configuration
        configuration.setLocale(locale)
        var localeList = LocaleList(locale)
        configuration.setLocales(localeList)
        return context.createConfigurationContext(configuration)
    }

    protected fun translucent() {
        if (Build.VERSION.SDK_INT >= 19) {
            val decorView = window.decorView
            decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_IMMERSIVE)
        }
    }
    private fun hideNavigationBarWithoutBack() {
//        NavigationBar.statusBarDisable(NavigationBar.ONLY_BACK_MASK, applicationContext)
    }
}





import android.annotation.TargetApi
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import android.view.MotionEvent
import android.view.View
import android.view.Window
import androidx.annotation.NonNull
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.observe
import androidx.navigation.fragment.NavHostFragment
import com.imuxuan.floatingview.FloatingView

import kotlinx.coroutines.*
import java.util.*

abstract class BaseActivity<VM : BaseViewModel> : AppCompatActivity() {

    companion object {
        private const val TAG = "BaseActivity"
    }

    protected var mViewModel: VM? = null
        private set
        get() {
            field ?: let { field = provideViewModel()?.value }
            return field
        }

    protected val appViewModel by lazy {
        RobotApp.instance().getAppViewModelProvider().get(AppViewModel::class.java)
    }

    /**
     * 待机倒计时
     */
    protected val idleLiveData = MutableLiveData<Boolean>()

    var robotChargingDialog: RobotChargingDialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
        window.decorView.setOnSystemUiVisibilityChangeListener { translucent() }
        hideNavigationBarWithoutBack()
        InputMethodUtil.changeInputMethodIfNeed(this, InputMethodUtil.INPUT_NAME_GOKEY)
        if (layoutId() != 0) {
            setContentView(layoutId())
        }
        appViewModel.registerWifiConnectStatusReceiver()
        appViewModel.registerPhoneStateListener()
        init(savedInstanceState)
        FLog.i(TAG, "${javaClass.simpleName} onCreate")
    }

    private var isInit = false
    private fun init(savedInstanceState: Bundle?) {
        initView(savedInstanceState)
        createObserver()
        addBatteryObserver()
        if (AppInitProcessor.isTest) {
            FloatingView.get().icon(R.mipmap.ic_app_launcher)
                .customView(R.layout.view_global_floating)
                .add()
        }
    }

    override fun onStart() {
        super.onStart()
        FLog.i(TAG, "${javaClass.simpleName} onStart")
        if (AppInitProcessor.isTest) {
            FloatingView.get().attach(this)
        }
        TimerManager.INSTANCE.registerOnCompleteListener(
            TimerManager.KEY_CHARGE
        ) {
            if (it == TimerManager.KEY_CHARGE) {
                // 是否是充电中状态并且是使用充电桩方式
                FLog.i(
                    TAG,
                    "$it 计时结束 isCharging:${BatteryInfoManager.isCharging}} "
                )
                if (BatteryInfoManager.isCharging) {
                    showChargeDialog()
                }
            }
        }
        GlobalLiveData.getInstance().with(MotionEvent::class.java)
            .observe(this, Observer {
                if (it != null) {
                    TimerManager.INSTANCE.stopCountDown(TimerManager.KEY_CHARGE)
                    if (BatteryInfoManager.isCharging) {
                        TimerManager.INSTANCE.startCountDown(TimerManager.KEY_CHARGE, 60)
                    }
                }
            })

        GlobalLiveData.getInstance().with(GlobalElevatorEvent::class.java)
            .observe(this, Observer {
                FLog.d(
                    TAG,
                    "${javaClass.simpleName} createObserver GlobalElevatorEvent: [$it]"
                )
                handleElevatorState(it.state, it.param)
            })

        GlobalLiveData.getInstance().with(GlobalCrashEvent::class.java)
            .observe(this, Observer {
                FLog.d(
                    TAG,
                    "${javaClass.simpleName} createObserver GlobalCrashEvent: [$it]"
                )
                if (it.crash) {
                    showCrashDialog()
                }
            })

        TimerManager.INSTANCE.registerOnCompleteListener(
            TimerManager.KEY_USER_INTERACTION
        ) {
            if (it == TimerManager.KEY_USER_INTERACTION) {
                FLog.d(TAG, "no user interaction")
                idleLiveData.postValue(true)
            }
        }
        startNoUserInteractionCountdown()
    }

    abstract fun showCrashDialog()

    override fun onStop() {
        super.onStop()
        robotChargingDialog?.dismissDialog()
        robotChargingDialog = null
        FLog.i(TAG, "${javaClass.simpleName} onStop")
        if (AppInitProcessor.isTest) {
            FloatingView.get().detach(this)
        }
        TimerManager.INSTANCE.unRegisterOnCompleteListener(TimerManager.KEY_USER_INTERACTION)
        TimerManager.INSTANCE.unRegisterOnCompleteListener(TimerManager.KEY_CHARGE)
        TimerManager.INSTANCE.stopAll()
    }

    override fun onResume() {
        super.onResume()
        FLog.i(TAG, "${javaClass.simpleName} onResume")
        NavigationBarUtil.hideNavigationBar(window)
    }

    override fun onPause() {
        super.onPause()
        FLog.i(TAG, "${javaClass.simpleName} onPause")
    }

    override fun onUserInteraction() {
        //有用户交互
        FLog.i(TAG, "onUserInteraction")
        startNoUserInteractionCountdown()
    }

    protected fun startNoUserInteractionCountdown() {
        idleLiveData.value = false
        TimerManager.INSTANCE.startCountDown(
            TimerManager.KEY_USER_INTERACTION,
            appViewModel.userNoInteractionTime
        )
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        FLog.i(TAG, "${javaClass.simpleName} onConfigurationChanged")
        translucent()
    }

    override fun onDestroy() {
        appViewModel.destroy()
        FLog.i(TAG, "${javaClass.simpleName} onDestroy")
        removeObservers(
            appViewModel.chargingErrorEvent,
            appViewModel.chargingEvent,
            appViewModel.chargingTypeEvent,
            appViewModel.powerEvent,
            appViewModel.shutdownEvent,
            appViewModel.dataConnectEvent,
            appViewModel.dataNetworkType,
            appViewModel.signalLevelEvent,
            appViewModel.wifiConnectEvent,
            appViewModel.wifiLevelEvent
        )
        super.onDestroy()
    }

    /**
     * 屏蔽返回按键
     */
    /*override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
        if (event?.keyCode == KeyEvent.KEYCODE_BACK) {
            return true
        }
        return super.dispatchKeyEvent(event)
    }*/

    private fun hideNavigationBarWithoutBack() {
        NavigationBar.statusBarDisable(
            NavigationBar.ONLY_BACK_MASK, applicationContext
        )
    }

    fun addIdleObserver(@NonNull owner: LifecycleOwner, observer: (Boolean) -> Unit) {
        idleLiveData.observe(owner, observer)
    }

    fun removeIdleObserver(@NonNull owner: LifecycleOwner) {
        idleLiveData.removeObservers(owner)
    }

    private fun translucent() {
        val decorView = window.decorView
        decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_IMMERSIVE)
    }


    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        if (ev != null) {
            GlobalLiveData.getInstance().with(MotionEvent::class.java).postValue(ev)
            currentEvent = ev.action
        }
        return super.dispatchTouchEvent(ev)
    }

    /**
     * 全局公共的监听充电,如果发现有充电事件响应需要跳转到展示充电页面
     */
    private fun addBatteryObserver() {
        observe(appViewModel.canShowChargeDialog) {
            it?.let {
                if (it) {
                    if (BatteryInfoManager.isCharging) {
                        showChargeDialog()
                    }
                }
            }
        }
        observe(appViewModel.chargingTypeEvent) {

        }
        observe(appViewModel.chargingErrorEvent) {
            FLog.w(TAG, "charging error event:${it.name}")
            ReportDataManager.reportChargeError(it)
            when (it) {
                ChargeState.ChargeErrorContact -> {
                    showChargerError(getString(R.string.pdStr6_3))
                }

                ChargeState.ErrorBatteryPackComm -> {
                    showChargerError(getString(R.string.pdStr6_4))
                }

                ChargeState.ErrorOverVolt -> {
                    showChargerError(getString(R.string.pdStr6_5))
                }

                ChargeState.ErrorOverElectric, ChargeState.ChargeErrorElectric -> {
                    showChargerError(getString(R.string.pdStr6_6))
                }

                ChargeState.ErrorOverTemperature, ChargeState.ErrorLowTemperature -> {
                    showChargerError(getString(R.string.pdStr6_7))
                }

                ChargeState.ErrorOverTime -> {
                    showChargerError(getString(R.string.pdStr6_8))
                }
                else -> {

                }
            }
        }
    }

    fun showChargeDialog() {
        //跳转充电fragment
        FLog.i(
            TAG,
            "show ChargingDialog canShowChargeDialog:${appViewModel.canShowChargeDialog.value} power:${appViewModel.powerEvent.value}"
        )
        if (appViewModel.canShowChargeDialog.value == true) {
            appViewModel.showChargeDialog.postValue(true)
            robotChargingDialog ?: let {
                appViewModel.powerEvent.value?.let {
                    robotChargingDialog = RobotChargingDialog.newInstance(it)
                    robotChargingDialog?.setDismissListener {
                        FLog.d(TAG, "ChargingDialog dismiss")
                        appViewModel.showChargeDialog.postValue(false)
                        TimerManager.INSTANCE.startCountDown(TimerManager.KEY_CHARGE, 60)
                        getFragment(HomeFragment::class.java)?.checkDeliveryException()
                        robotChargingDialog = null
                    }
                }
            }
            robotChargingDialog?.let {
                if (!it.isAdded && !it.isVisible) {
                    it.showDialog(supportFragmentManager, "RobotChargingDialog")
                }
            }
        }
    }

    private fun showChargerError(msg: String) {
        showTipsDialog(tips = msg, onDismissCallback = {
            stopErrorVoice()
        })
        playErrorVoice()
    }

    private var playErrorJob: Job? = null

    private fun stopErrorVoice() {
        lifecycleScope.launch {
            playErrorJob?.cancelAndJoin()
            playErrorJob = null
        }
    }

    private fun playErrorVoice() {
        if (playErrorJob == null) {
            PlaySound.playChargingVoice()
            playErrorJob = lifecycleScope.launch {
                while (isActive) {
                    delay(15_000)
                    PlaySound.playChargingVoice()
                }
            }
        }
    }

    /**
     * 判断处于哪个fragment
     */
    fun <F : Fragment> AppCompatActivity.getFragment(fragmentClass: Class<F>): F? {
        val navHostFragment = this.supportFragmentManager.fragments.first() as NavHostFragment
        if (navHostFragment.isAdded) {
            navHostFragment.childFragmentManager.fragments.forEach {
                if (fragmentClass.isAssignableFrom(it.javaClass)) {
                    return it as F
                }
            }
        }
        return null
    }

    private var tipsDialog: ShowTipsDialog? = null

    fun showTipsDialog(tips: String, onDismissCallback: () -> Unit) {
        if (tipsDialog == null) {
            tipsDialog = ShowTipsDialog(this)
        }
        tipsDialog?.setDialogType(ShowTipsDialog.TITLE_TIPS_AND_CLOSE)
        tipsDialog?.setTitle(getString(R.string.pdStr5_1))
        tipsDialog?.setTips(tips)
        tipsDialog?.setButTips(getString(R.string.pdStr8_1))
        tipsDialog?.setOnBtnClickListener {
            tipsDialog?.dismiss()
        }
        tipsDialog?.setOnDismissListener {
            onDismissCallback.invoke()
            tipsDialog = null
        }
        tipsDialog?.let {
            if (!it.isShowing) {
                it.show()
            }
        }
    }

    protected abstract fun layoutId(): Int

    protected abstract fun initView(saveInstanceState: Bundle?)

    protected abstract fun createObserver()

    protected abstract fun provideViewModel(): Lazy<VM>?

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(updateBaseContext(newBase))
    }

    private fun updateBaseContext(context: Context): Context {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            updateResource(context)
        } else
            context
    }

    @TargetApi(Build.VERSION_CODES.N)
    private fun updateResource(context: Context): Context {
        FLog.i("LanguageUtils", "updateResource")
        val resources = context.resources
        val locale = Locale.getDefault()
        val configuration = resources.configuration
        configuration.setLocale(locale)
        var localeList = LocaleList(locale)
        configuration.setLocales(localeList)
        return context.createConfigurationContext(configuration)
    }

    abstract fun showElevatorUpgradeDialog(
        elevatorUtilizeState: ElevatorUtilizeState,
        param: ElevatorEventParam?
    )


}


import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager

abstract class BaseFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId) {

    protected var onStatusBarControlListener: IStatusBarControlListener? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        onStatusBarControlListener = context as IStatusBarControlListener
        FLog.i("BaseFragment", "${javaClass.simpleName} onAttach")
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        FLog.i("BaseFragment", "${javaClass.simpleName} onCreateView")
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        FLog.i("BaseFragment", "${javaClass.simpleName} onViewCreated")
        initView(savedInstanceState)
        createObserver()
        initData()
        onStatusBarControlListener?.apply {
            showHideStatusBar(isShowStatusBar())
            if (isShowStatusBar()) {
                switchTheme(getStatusBarLayoutMode())
            }
        }
    }

    override fun onStart() {
        FLog.i("BaseFragment", "${javaClass.simpleName} onStart")
        super.onStart()
    }

    override fun onResume() {
        FLog.i("BaseFragment", "${javaClass.simpleName} onResume")
        fullScreen(activity?.window)
        super.onResume()
    }

    override fun onPause() {
        super.onPause()
        FLog.d("BaseFragment", "${javaClass.simpleName} onPause")
    }

    override fun onStop() {
        super.onStop()
        FLog.d("BaseFragment", "${javaClass.simpleName} onStop")
    }

    override fun onDestroyView() {
        FLog.i("BaseFragment", "${javaClass.simpleName} onDestroyView")
        super.onDestroyView()
    }

    override fun onDestroy() {
        FLog.i("BaseFragment", "${javaClass.simpleName} onDestroy")
        super.onDestroy()
    }

    override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        FLog.i("BaseFragment", "${javaClass.simpleName} onHiddenChanged $hidden")
    }

    override fun onDetach() {
        super.onDetach()
        FLog.i("BaseFragment", "${javaClass.simpleName} onDetach")
    }

    protected open fun isShowStatusBar(): Boolean {
        return true
    }

    protected open fun getStatusBarLayoutMode(): Int {
        return FirefoxStatusBar.THEME_DARK
    }

    protected open fun initData() {}

    protected abstract fun initView(savedInstanceState: Bundle?)

    protected open fun createObserver() {}

    fun getSupportFragmentManager(): FragmentManager? {
        val currentActivity = KtxActivityManager.currentActivity
        if (currentActivity is FragmentActivity)
            return currentActivity.supportFragmentManager
        return null
    }

    private var powerRemindDialog: PowerRemindDialog? = null
    fun showPowerDisconnectDialog() {
        getSupportFragmentManager()?.let {
            powerRemindDialog ?: let {
                powerRemindDialog =
                    PowerRemindDialog.newInstance(PowerRemindDialog.TYPE_POWER_DISCONNECT)
            }
            powerRemindDialog?.apply {
                FLog.d("BaseFragment", "${javaClass.simpleName} 显示断开充电DC弹窗")
                if (this.isAdded || this.isVisible || this.isRemoving) {
                    return@apply
                } else {
                    setDialogCanceledOnTouchOutside(false)
                    setDismissListener { powerRemindDialog = null }
                    showDialog(it, "PowerRemindDialog")
                }
            }
        }
    }

    fun observerIdle() {
        (activity as MainActivity).reset()
        (activity as MainActivity).addIdleObserver(this) {
            if (it) {
                FLog.d(javaClass.simpleName, "to Idle")
                val navController = nav()
                if (navController?.currentDestination?.getAction(R.id.to_idle_face) != null) {
                    (activity as MainActivity).removeIdleObserver(this)
                    navController.navigate(R.id.to_idle_face)
                }
            }
        }
    }

  
}


import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*

/**
 * BaseViewModel 请求协程封装
 */

/**
 * 显示页面状态
 *
 * @param resultState 接口返回值
 * @param onLoading 加载中
 * @param onSuccess 成功回调
 * @param onError 失败回调
 */
fun <T> BaseVmActivity<*>.handleResultState(
    resultState: ResultState<T>,
    onSuccess: (T) -> Unit,
    onError: ((AppException) -> Unit)? = null,
    onLoading: (() -> Unit)? = null

) {
    when (resultState) {
        is ResultState.Loading -> {
            showLoading(resultState.loadingMessage)
            onLoading?.run {
                this
            }
        }
        is ResultState.Success -> {
            dismissLoading()
            onSuccess(resultState.data)
        }
        is ResultState.Error -> {
            dismissLoading()
            onError?.run {
                this(resultState.error)
            }
        }
    }

}

/**
 * 所有的ViewModel接收到的ResultState处理方式都是调用handleResultState方法来进行
 * 内部已经封装好了成功的,失败,以及处理中的回调函数,
 * 可供调用者进行调用处理
 * @param onSuccess  将T作为参数回调出去
 * @param onError   将AppException作为参数回调出去
 * @param onLoading 可选,调用者可以在显示loading
 */
fun <T> BaseVmFragment<*>.handleResultState(
    resultState: ResultState<T>,
    onSuccess: (T) -> Unit,
    onError: ((AppException) -> Unit)? = null,
    onLoading: (() -> Unit)? = null
) {
    when (resultState) {
        is ResultState.Loading -> {
            showLoading(resultState.loadingMessage)
            onLoading?.invoke()
        }
        is ResultState.Success -> {
            dismissLoading()
            onSuccess(resultState.data)
        }

        is ResultState.Error -> {
            dismissLoading()
            onError?.run {
                this(resultState.error)
            }
        }
    }
}

/**
 * 网络请求,不校验请求结果数据是否是成功
 *
 */
fun <T> BaseViewModel.request(
    block: suspend () -> BaseResponse<T>,
    resultState: MutableLiveData<ResultState<T>>,
    isShowDialog: Boolean = false,
    loadingMessage: String = "请求网络中...."
): Job {
    return viewModelScope.launch {
        runCatching {
            if (isShowDialog) resultState.value = ResultState.onLoading(loadingMessage)
            //请求体
            block()
        }.onSuccess {
            // 处理block返回的Response
            resultState.parseResult(it)
        }.onFailure {
            it.message?.logE()
            resultState.parseException(it)

        }
    }
}

/**
 * 过滤服务器结果,失败抛出异常
 * @param block 请求体方法 suspend
 * @param success 成功回调
 * @param error 失败回调
 * @param isShowDialog 是否显示加载框
 * @param loadingMessage 加载框提示内容
 */

fun <T> BaseViewModel.request(
    block: suspend () -> BaseResponse<T>,
    success: (T) -> Unit, // 函数为 void success(T)
    error: (AppException) -> Unit = {},
    isShowDialog: Boolean = true,
    loadingMessage: String = "请求网络中..."
): Job {
    return viewModelScope.launch {
        runCatching {
            if (isShowDialog) loadingChange.showDialog.postValue(loadingMessage)
            //请求体
            block()
        }.onSuccess {
            //网络请求成功 关闭弹窗
            loadingChange.dismissDialog.postValue(false)
            runCatching {
                //校验请求结果是否成功,
                executeResponse(it) { t ->
                    //成功回调
                    success(t)
                    Pdlog.d( "BaseViewModel","request success $t")
                }

            }.onFailure { e ->
                //抛出异常,执行到这里
              Pdlog.e( "BaseViewModel","request fail ${e.message}")
                //失败回调
                error(ExceptionHandle.handleException(e))

            }
        }.onFailure {
            //网络请求异常,关闭弹窗 // LiveData  通知数据变化
            loadingChange.dismissDialog.postValue(false)
            //打印错误信息
            it.message?.logE()
            Pdlog.e( "BaseViewModel","request fail ${it.message}")
            //失败回调
            error(ExceptionHandle.handleException(it))
        }
    }
}

/**
 * 不过滤请求结果 ===> 主要是针对的是本地数据处理 如果需要使用过滤请求结果的[BaseViewModel.request]函数
 * @param block     该函数可以是一个阻塞函数,用于进行相关的数据处理操作
 * @param success   等到block函数执行完毕之后,成功的回调
 * @param error     block函数执行过程中可能出现异常的回调
 */

fun <T> BaseViewModel.requestSkipCheck(
    block: suspend () -> T,
    success: (T) -> Unit = {},
    error: (AppException) -> Unit = {},
    isShowDialog: Boolean = false,
    loadingMessage: String = "请求网络中..."

): Job {
    if (isShowDialog) loadingChange.showDialog.postValue(loadingMessage)
    return viewModelScope.launch {
        runCatching {
            block()
        }.onSuccess {
            loadingChange.dismissDialog.postValue(false)
            success(it)

        }.onFailure {
            loadingChange.dismissDialog.postValue(false)
            it.message?.logE()
            error(ExceptionHandle.handleException(it))
        }
    }
}

/**
 * 请求结果过滤,判断请求服务器结果是否成功,不成功会抛出异常
 */
suspend fun <T> executeResponse(
    response: BaseResponse<T>,
    success: suspend CoroutineScope.(T) -> Unit
) {
    coroutineScope {
        if (response.isSuccess()) success(response.getResponseData())
        else throw AppException(
                response.getResponseCode(),
                response.getResponseMsg(),
                response.getResponseMsg()
        )
    }

}

/**
 * 调用协程
 *
 * @param block 操作耗时操作任务
 * @param success 成功回调
 * @param error 失败回调
 */
fun <T> BaseViewModel.launch(
    block: () -> T,
    success: (T) -> Unit,
    error: (Throwable) -> Unit = {}
) {
    viewModelScope.launch {
        kotlin.runCatching {
            withContext(Dispatchers.IO)
            {
                block()
            }
        }.onSuccess {
            success(it)
        }.onFailure {
            error(it)
        }
    }
}


import androidx.lifecycle.ViewModel

/**
 * Created by Hash on 2020/9/25.
 */
open class BaseViewModel : ViewModel() {

    val loadingChange: UiLoadingChange by lazy { UiLoadingChange() }

    inner class UiLoadingChange {
        //显示加载框  TODO
        val showDialog by lazy { EventLiveData<String>() }

        //隐藏 TODO
        val dismissDialog by lazy { EventLiveData<Boolean>() }
    }

}




import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.content.Context
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import com.tencent.bugly.crashreport.CrashReport
import com.tencent.bugly.crashreport.CrashReport.UserStrategy
import kotlinx.coroutines.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class RobotApp : Application(), ViewModelStoreOwner {

    companion object {
        private var instance: RobotApp by NotNullSingleValueVar()
        const val TAG = "RobotApp"

        @JvmStatic
        fun instance() = instance
    }

    private val appViewModelStore: ViewModelStore by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        ViewModelStore()
    }

    private val appViewModelFactory: ViewModelProvider.Factory by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        ViewModelProvider.AndroidViewModelFactory.getInstance(this)
    }

    override fun attachBaseContext(base: Context?) {
        System.setProperty("kotlinx.coroutines.scheduler", "off")
        instance = this
        super.attachBaseContext(base)
    }

    var applicationScope: CoroutineScope? = null

    override fun onCreate() {
        super.onCreate()
        if (PackageUtil.isMainProcess(this)) {
            killMirSdk(this)
        }
        initPdlog()
        CrashHandler(
            appContext = this,
            crashPath = "/sdcard/pudu/crash/",
            crashFileName = "crash-robot-firefox.log",
            crashContentShow = CrashHandler.CRASH_TIP_COMMON,
            isRestartApp = false,
            classOfFirstActivity = MainActivity::class.java,
            crashAction = { t, ex ->
                FLog.d(TAG, "crashAction: [$t, $ex]")
                GlobalLiveData.getInstance().with(GlobalCrashEvent::class.java).postValue(GlobalCrashEvent(true))
            }
        ).build()
        RobotVoicePlayer.init(this, LanguageUtils(this).current.locale)
        AppInitProcessor.INSTANCE.init(this)
        RobotInitProcessor.INSTANCE.init(this)

        LightPlayManager.playInit()

        TtsVoiceManager.init(this)
        //  TtsVoiceManager.init(this)

        if (BuildConfig.FLAVOR == "mock") {
            FLog.d("RobotApp", "mock mode not init bugly crash report")
        } else {
            // 设置是否为上报进程
            val strategy = UserStrategy(this)
            strategy.appVersion = BuildConfig.VERSION_NAME
            strategy.appChannel = BuildConfig.FLAVOR.plus("_").plus(BuildConfig.BUILD_TYPE)
            CrashReport.initCrashReport(applicationContext, "5ae5abf4dc", true, strategy)
            val mac = getMac()
            FLog.d("CrashReport", "init CrashReport mac:$mac")
            if (mac.isNotEmpty()) {
                CrashReport.setUserId(mac)
            }
        }
        applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
        registerActivityLifecycle()
    }

    private fun initPdlog() {
        CoroutineScope(Dispatchers.IO).launch {
            val systemProperty = SystemProperty(this@RobotApp)
            val sysBuildId = systemProperty.getProperty("ro.build.id")
            Pdlog.init(
                this@RobotApp,
                getPdlogName(),
                BuildConfig.DEBUG,
                BuildConfig.VERSION_NAME,
                sysBuildId
            )
        }
    }

    private fun getPdlogName(): String {
        var name = PackageUtil.getCurrentProcessName(this@RobotApp) ?: "robot"
        name = name.replace('.', '-').replace("com-pudutech", "pudutech")
        return name
    }

    //定义一个属性管理类,处理非空和重复复制的问题
    private class NotNullSingleValueVar<T> : ReadWriteProperty<Any?, T> {

        private var value: T? = null
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            this.value =
                if (this.value == null) value else throw IllegalStateException("application already initialized")
        }

        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
            return value ?: throw IllegalStateException("application not initialized")
        }
    }

    override fun getViewModelStore(): ViewModelStore {
        return appViewModelStore
    }

    fun getAppViewModelProvider(): ViewModelProvider {
        return ViewModelProvider(this, getAppFactory())
    }

    private fun getAppFactory(): ViewModelProvider.Factory {
        return appViewModelFactory
    }

    /**
     * 需要先停止mirsdk才启动
     */
    fun killMirSdk(context: Context) {
        val mActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val mList = mActivityManager.runningAppProcesses
        for (runningAppProcessInfo in mList) {
            FLog.d(
                "MyBaseActivity", "runningAppProcessInfo = " + runningAppProcessInfo.processName
            )
            if ("com.pudutech.mirsdk" == runningAppProcessInfo.processName) {
                FLog.d(
                    "MyBaseActivity",
                    "kill = " + runningAppProcessInfo.processName + " ; pid = " + runningAppProcessInfo.pid
                )
                android.os.Process.killProcess(runningAppProcessInfo.pid)
            }
        }
    }

    var mMainActivityVisible = false

    /**
     * 全局监听Activity生命周期
     */
    private fun registerActivityLifecycle() {
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityCreated(p0: Activity, p1: Bundle?) {

            }

            override fun onActivityStarted(p0: Activity) {

            }

            override fun onActivityResumed(activity: Activity) {
                if (activity is MainActivity) {
                    Pdlog.d(TAG, "MainActivity onActivityResumed")
                    mMainActivityVisible = true
                }
            }

            override fun onActivityPaused(activity: Activity) {
                if (activity is MainActivity) {
                    Pdlog.d(TAG, "MainActivity onActivityPaused")
                    mMainActivityVisible = false
                }

            }

            override fun onActivityStopped(p0: Activity) {

            }

            override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {

            }

            override fun onActivityDestroyed(p0: Activity) {

            }

        })
    }

}


import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Build.VERSION;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.Format;
import java.text.SimpleDateFormat;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class Pdlog {
    public static final int V = 2;
    public static final int D = 3;
    public static final int I = 4;
    public static final int W = 5;
    public static final int E = 6;
    public static final int A = 7;
    private static final char[] T;
    private static final int FILE = 16;
    private static final int JSON = 32;
    private static final int XML = 48;
    private static final String FILE_SEP;
    private static final String LINE_SEP;
    private static final String TOP_CORNER = "┌";
    private static final String MIDDLE_CORNER = "├";
    private static final String LEFT_BORDER = "│ ";
    private static final String BOTTOM_CORNER = "└";
    private static final String SIDE_DIVIDER = "────────────────────────────────────────────────────────";
    private static final String MIDDLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
    private static final String TOP_BORDER = "┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────";
    private static final String MIDDLE_BORDER = "├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
    private static final String BOTTOM_BORDER = "└────────────────────────────────────────────────────────────────────────────────────────────────────────────────";
    private static final int MAX_LEN = 3000;
    @SuppressLint({"SimpleDateFormat"})
    private static final Format FORMAT;
    private static final Format FORMAT_TIME;
    private static final String NOTHING = "log nothing";
    private static final String NULL = "null";
    private static final String ARGS = "args";
    private static final String PLACEHOLDER = " ";
    private static Context sAppContext;
    private static Pdlog.Config sConfig;
    private static long sLastFlush;
    private static long sLastCoutMilli;
    private static final int s_flush_interval = 100;
    private static String TAG;

    private Mylog() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    public static native void nativeLog(int var0, String var1, String var2);

    public static native void initNativeLog(String var0, String var1, String var2, boolean var3, String var4, String var5);

    public static native void setPuduLogHighestLevel(int var0);

    public static native void resetGitHash(String var0);

    public static native void releaseLog();

    public static Pdlog.Config init(Context context, String moduleName, boolean printToLogcat, String softwareVersion, String systemVersion) {
        sAppContext = context;
        if (VERSION.SDK_INT < 24) {
            mountTmpCheck();
            initNativeLog("/sdcard/PuduRobotMap/logconfig.json", "/tmp", moduleName, printToLogcat, softwareVersion, systemVersion);
        } else {
            File folder = new File("/sdcard/pudu/log");
            boolean success = false;
            if (!folder.exists()) {
                success = folder.mkdirs();
            }

            if (success) {
            }

            initNativeLog("/sdcard/PuduRobotMap/logconfig.json", "/sdcard/pudu/log", moduleName, printToLogcat, softwareVersion, systemVersion);
        }

        sConfig.setConsoleFilter(2);
        setPuduLogHighestLevel(2);
        resetGitHash("pudubase: commit: 528570b, auth: “zhengbangqiang”<“zhengbangqiang@pudutech.com”>, time: “Fri Oct 15 16:55:41 2021 +0800”");
        return sConfig;
    }

    private static String readFile(String path) {
        File file = new File(path);
        StringBuilder text = new StringBuilder();

        try {
            BufferedReader br = new BufferedReader(new FileReader(file));

            String line;
            while((line = br.readLine()) != null) {
                text.append(line);
                text.append('\n');
            }

            br.close();
        } catch (IOException var5) {
        }

        return text.toString();
    }

    private static void mountTmpCheck() {
        String mounts = readFile("/proc/mounts");
        if (mounts.contains("tmpfs /tmp tmpfs")) {
            i(TAG, "tmpfs /tmp mount point exsit");
        } else {
            Tools.execCommand("mount -o remount,rw /;mkdir /tmp;mount -o size=300m,mode=777 -t tmpfs tmpfs /tmp;mount -o remount,ro /", true);
            Tools.execCommand("echo 20 > /sys/bus/spi/drivers/mcp251x/spi1.0/net/can0/tx_queue_len", true);
            mounts = readFile("/proc/mounts");
            if (mounts.contains("tmpfs /tmp tmpfs")) {
                i(TAG, "tmpfs /tmp mount point create success");
            } else {
                sConfig.mLog2FileSwitch = false;
                Log.w(TAG, "tmpfs /tmp create fail, close log to file");
            }
        }

    }

    public static Pdlog.Config getConfig() {
        if (sConfig == null) {
            throw new NullPointerException("U should init first.");
        } else {
            return sConfig;
        }
    }

    public static void v(String tag, Object... contents) {
        log(2, tag, contents);
    }

    public static void d(String tag, Object... contents) {
        log(3, tag, contents);
    }

    public static void i(String tag, Object... contents) {
        log(4, tag, contents);
    }

    public static void w(String tag, Object... contents) {
        log(5, tag, contents);
    }

    public static void e(String tag, Object... contents) {
        log(6, tag, contents);
    }

    public static void a(String tag, Object... contents) {
        log(7, tag, contents);
    }

    public static void file(String tag, Object content) {
        log(19, tag, content);
    }

    public static void file(int type, String tag, Object content) {
        log(16 | type, tag, content);
    }

    public static void json(String tag, String content) {
        log(35, tag, content);
    }

    public static void json(int type, String tag, String content) {
        log(32 | type, tag, content);
    }

    public static void xml(String tag, String content) {
        log(51, tag, content);
    }

    public static void xml(int type, String tag, String content) {
        log(48 | type, tag, content);
    }

    public static void log(int type, String tag, Object... contents) {
        if (sConfig.mLogSwitch && (sConfig.mLog2ConsoleSwitch || sConfig.mLog2FileSwitch)) {
            int type_low = type & 15;
            int type_high = type & 240;
            if (type_low >= sConfig.mConsoleFilter || type_low >= sConfig.mFileFilter) {
                String body = processBody(type_high, contents);
                if ((sConfig.mLog2FileSwitch || type_high == 16) && type_low >= sConfig.mFileFilter) {
                    nativeLog(type_low, tag, body);
                }

            }
        }
    }

    private static String processBody(int type, Object... contents) {
        String body = "null";
        if (contents != null) {
            if (contents.length == 1) {
                Object object = contents[0];
                if (object != null) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(object.toString());
                    body = sb.toString();
                }

                if (type == 32) {
                    body = formatJson(body);
                } else if (type == 48) {
                    body = formatXml(body);
                }
            } else {
                StringBuilder sb = new StringBuilder();
                int i = 0;

                for(int len = contents.length; i < len; ++i) {
                    Object content = contents[i];
                    sb.append(content == null ? "null" : content.toString()).append(" ");
                }

                body = sb.toString();
            }
        }

        return body.length() == 0 ? "log nothing" : body;
    }

    private static String formatJson(String json) {
        try {
            if (json.startsWith("{")) {
                json = (new JSONObject(json)).toString(4);
            } else if (json.startsWith("[")) {
                json = (new JSONArray(json)).toString(4);
            }
        } catch (JSONException var2) {
            var2.printStackTrace();
        }

        return json;
    }

    private static String formatXml(String xml) {
        try {
            Source xmlInput = new StreamSource(new StringReader(xml));
            StreamResult xmlOutput = new StreamResult(new StringWriter());
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty("indent", "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            transformer.transform(xmlInput, xmlOutput);
            xml = xmlOutput.getWriter().toString().replaceFirst(">", ">" + LINE_SEP);
        } catch (Exception var4) {
            var4.printStackTrace();
        }

        return xml;
    }

    private static boolean createOrExistsFile(String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            return file.isFile();
        } else if (!createOrExistsDir(file.getParentFile())) {
            return false;
        } else {
            try {
                boolean isCreate = file.createNewFile();
                file.setReadable(true, false);
                file.setExecutable(true, false);
                file.setWritable(true, false);
                return isCreate;
            } catch (IOException var3) {
                var3.printStackTrace();
                return false;
            }
        }
    }

    private static void printDeviceInfo() {
        String versionName = "";
        long versionCode = 0L;

        try {
            PackageInfo pi = sAppContext.getPackageManager().getPackageInfo(sAppContext.getPackageName(), 0);
            if (pi != null) {
                versionName = pi.versionName;
                versionCode = (long)pi.versionCode;
            }
        } catch (NameNotFoundException var4) {
            var4.printStackTrace();
        }

        String head = "************* Log Head ****************\nDevice Manufacturer: " + Build.MANUFACTURER + "\nDevice Model       : " + Build.MODEL + "\nAndroid Version    : " + VERSION.RELEASE + "\nAndroid SDK        : " + VERSION.SDK_INT + "\nApp VersionName    : " + versionName + "\nApp VersionCode    : " + versionCode + "\n************* Log Head ****************\n\n";
        i(TAG, head);
    }

    private static boolean createOrExistsDir(File file) {
        boolean var10000;
        label25: {
            if (file != null) {
                if (file.exists()) {
                    if (file.isDirectory()) {
                        break label25;
                    }
                } else if (file.mkdirs()) {
                    break label25;
                }
            }

            var10000 = false;
            return var10000;
        }

        var10000 = true;
        return var10000;
    }

    private static boolean isSpace(String s) {
        if (s == null) {
            return true;
        } else {
            int i = 0;

            for(int len = s.length(); i < len; ++i) {
                if (!Character.isWhitespace(s.charAt(i))) {
                    return false;
                }
            }

            return true;
        }
    }

    static {
        System.loadLibrary("pudutech_log");
        T = new char[]{'V', 'D', 'I', 'W', 'E', 'A'};
        FILE_SEP = System.getProperty("file.separator");
        LINE_SEP = System.getProperty("line.separator");
        FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS ");
        FORMAT_TIME = new SimpleDateFormat("HH:mm:ss.SSS ");
        sConfig = new Pdlog.Config();
        sLastFlush = 0L;
        sLastCoutMilli = 0L;
        TAG = "pdlog";
    }

    public static class Config {
        private String mDefaultDir;
        private String mDir;
        private String mFilePrefix;
        private boolean mLogSwitch;
        private boolean mLog2ConsoleSwitch;
        private boolean mLog2FileSwitch;
        private int mConsoleFilter;
        private int mFileFilter;
        private int mStackDeep;
        private int mStackOffset;

        private Config() {
            this.mDefaultDir = "/tmp/";
            this.mFilePrefix = Pdlog.TAG;
            this.mLogSwitch = true;
            this.mLog2ConsoleSwitch = true;
            this.mLog2FileSwitch = true;
            this.mConsoleFilter = 2;
            this.mFileFilter = 2;
            this.mStackDeep = 1;
            this.mStackOffset = 0;
            if (this.mDefaultDir == null) {
                if ("mounted".equals(Environment.getExternalStorageState()) && Pdlog.sAppContext.getExternalCacheDir() != null) {
                    this.mDefaultDir = Pdlog.sAppContext.getExternalCacheDir() + Pdlog.FILE_SEP + "log" + Pdlog.FILE_SEP;
                } else {
                    this.mDefaultDir = Pdlog.sAppContext.getCacheDir() + Pdlog.FILE_SEP + "log" + Pdlog.FILE_SEP;
                }

            }
        }

        public Pdlog.Config setLogSwitch(boolean logSwitch) {
            this.mLogSwitch = logSwitch;
            return this;
        }

        public Pdlog.Config setConsoleSwitch(boolean consoleSwitch) {
            this.mLog2ConsoleSwitch = consoleSwitch;
            return this;
        }

        public Pdlog.Config setLog2FileSwitch(boolean log2FileSwitch) {
            this.mLog2FileSwitch = log2FileSwitch;
            return this;
        }

        public Pdlog.Config setDir(String dir) {
            if (Pdlog.isSpace(dir)) {
                this.mDir = null;
            } else {
                this.mDir = dir.endsWith(Pdlog.FILE_SEP) ? dir : dir + Pdlog.FILE_SEP;
            }

            return this;
        }

        public Pdlog.Config setDir(File dir) {
            this.mDir = dir == null ? null : dir.getAbsolutePath() + Pdlog.FILE_SEP;
            return this;
        }

        public Pdlog.Config setFilePrefix(String filePrefix) {
            if (Pdlog.isSpace(filePrefix)) {
                this.mFilePrefix = "util";
            } else {
                this.mFilePrefix = filePrefix;
            }

            return this;
        }

        public Pdlog.Config setConsoleFilter(int consoleFilter) {
            this.mConsoleFilter = consoleFilter;
            return this;
        }

        public Pdlog.Config setFileFilter(int fileFilter) {
            this.mFileFilter = fileFilter;
            return this;
        }

        public String toString() {
            return "switch: " + this.mLogSwitch + Pdlog.LINE_SEP + "console: " + this.mLog2ConsoleSwitch + Pdlog.LINE_SEP + "file: " + this.mLog2FileSwitch + Pdlog.LINE_SEP + "dir: " + (this.mDir == null ? this.mDefaultDir : this.mDir) + Pdlog.LINE_SEP + "filePrefix: " + this.mFilePrefix + Pdlog.LINE_SEP + "consoleFilter: " + Pdlog.T[this.mConsoleFilter - 2] + Pdlog.LINE_SEP + "fileFilter: " + Pdlog.T[this.mFileFilter - 2] + Pdlog.LINE_SEP + "stackDeep: " + this.mStackDeep + Pdlog.LINE_SEP + "mStackOffset: " + this.mStackOffset;
        }
    }

    @Retention(RetentionPolicy.SOURCE)
    public @interface TYPE {
    }
}


import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

class SingleEventLiveData<T> : MutableLiveData<T>() {
    private val mPending = AtomicBoolean(false)
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner, Observer { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}

LiveDataBus



import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer


class LiveDataBus<T> : MutableLiveData<T>() {

    //这里重写更新数据的版本号
    var mVersion = START_VERSION

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner, ObserverWrapper(observer, this))
    }

    companion object {
        const val START_VERSION = -1
    }

    override fun postValue(value: T) {
        mVersion++
        super.postValue(value)
    }

    var mStickEvent: T? = null

    fun setStickValue(value: T) {
        mStickEvent = value;
        setValue(value)
    }

    fun postStickValue(value: T) {
        mStickEvent = value;
        postValue(value)

    }

    fun removeStickEvent() {
        mStickEvent = null
    }

    private class ObserverWrapper<T>(val observer: Observer<in T>, val liveData: LiveDataBus<in T>) : Observer<T> {

        var sticky: Boolean = false

        constructor(observer: Observer<in T>, liveData: LiveDataBus<in T>, stick: Boolean) : this(observer, liveData) {
            this.sticky = stick
        }

        ///
        private var mLastVersion = liveData.mVersion

        override fun onChanged(t: T) {
            if (mLastVersion >= liveData.mVersion) {
                if (sticky && liveData.mStickEvent != null) {
                    observer.onChanged(liveData.mStickEvent as T)
                }
                return
            }
            mLastVersion = liveData.mVersion
            observer.onChanged(t)
        }

    }
}


import java.util.concurrent.ConcurrentHashMap

/**
 *
 * TODO 这里会存在一个问题,就是比如发生基本数据类型,Int、Float、Double 是有问题的,因为该LiveData是全局发射的,
 * 因此发射的数据类型必须是唯一的,所有需要将发射的数据封装成对应的Event实体Bean对象
 */

class GlobalLiveData private constructor() {

    protected val busMap by lazy { ConcurrentHashMap<Class<*>, LiveDataBus<*>>() }

    protected fun <T> bus(clazz: Class<T>) = busMap.getOrPut(clazz) {
        LiveDataBus<T>()
    }

    fun <T> with(clazz: Class<T>) = bus(clazz) as LiveDataBus<T>

    companion object {
        @Volatile
        private var instance: GlobalLiveData? = null

        @JvmStatic
        fun getInstance() = instance ?: synchronized(this)
        {
            instance ?: GlobalLiveData().also {
                instance = it
            }
        }
    }
}

用法

//发射
GlobalLiveData.getInstance().with(MotionEvent::class.java).postValue(ev!!)

//接收
  GlobalLiveData.getInstance().with(TimeDisinfectTaskEntity::class.java).observe(this,
            Observer {
                Log.d(TAG, "onViewCreated: timerTask = ${Gson().toJson(it)}")
                
            })

room 用法



import android.content.Context
import androidx.annotation.NonNull
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import java.util.*

/**
 * @author created by zhoujianxiong
 * @description
 * @date 2022/7/1
 */
@Database(entities = [TaskReportEntity::class], version = 1, exportSchema = false)
abstract class FoxdisinfectDatabase : RoomDatabase() {
    abstract fun taskReportDao(): TaskReportDao

    companion object {
        private const val TAG = "FoxdisinfectDatabase"
        private const val DB_NAME = "fox_disinfect.db"

        private var instance: FoxdisinfectDatabase? = null

        get() {
            if (field==null){
                field=buildDatabase()
            }
            return field
        }
        @Synchronized
        fun get():FoxdisinfectDatabase?{
            return instance
        }

        private fun buildDatabase() =
            Room.databaseBuilder(RobotContext.context, FoxdisinfectDatabase::class.java, DB_NAME)
                .addMigrations(MIGRATION_VERSION_1_TO_2)
                .addCallback(object : RoomDatabase.Callback() {
                    override fun onCreate(db: SupportSQLiteDatabase) {
                        Pdlog.d(TAG, "onCreate $db")
                        db.setLocale(Locale.CHINESE)
                        super.onCreate(db)

                    }

                    override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
                        Pdlog.d(TAG, "onDestructiveMigration $db")
                        db.setLocale(Locale.CHINESE)
                        super.onDestructiveMigration(db)
                    }

                    override fun onOpen(db: SupportSQLiteDatabase) {
                        Pdlog.d(TAG, "onOpen $db")
                        db.setLocale(Locale.CHINESE)
                        super.onOpen(db)
                    }

                })
                .build()

        private val MIGRATION_VERSION_1_TO_2: Migration = object : Migration(1, 2) {
            override fun migrate(@NonNull database: SupportSQLiteDatabase) {
                //cruise_disinfect_task 新加 start_time 任务开始的时间 字段
                // cruise_disinfect_stay_point add column point_disinfect_task_status
                //database.execSQL("ALTER TABLE task_report ADD COLUMN point_disinfect_task_status TEXT")
                //没有迁移就不做处理
            }
        }
    }
}


import androidx.room.*

/**
 * @author created by zhoujianxiong
 * @description
 * @date 2022/7/1
 */
@Dao
interface TaskReportDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(taskReportEntity: TaskReportEntity)

    /**
     * 按降序获取所有任务报告
     */
    @Query("select * from task_report order by task_start_time desc")
    suspend fun getAll():List<TaskReportEntity>

    /**
     * 删除
     */
    @Query("delete from task_report where task_id = (:taskId)")
    suspend fun delete(taskId:String)

//    /**
//     * 查询当天的任务报告
//     */
//    @Query("select * from task_report where to_days(task_start_time) = to_days(now())")
//    suspend fun getOneDay():List<TaskReportEntity>

//    /**
//     * 获取7天数据
//     */
//    @Query("select * from task_report where date_sub(curdate(),'interval 7 day') <= date(task_start_time)")
//    suspend fun getSevenDay():List<TaskReportEntity>
//
//    /**
//     * 获取30天数据
//     */
//    @Query("select * from task_report where date_sub(curdate(),'interval 30 day') <= date(task_start_time)")
//    suspend fun getThirtyDay():List<TaskReportEntity>

}


import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize

/**
 * @author created by zhoujianxiong
 * @description
 * @date 2022/7/1
 */
@Parcelize
@Entity(tableName = "task_report")
data class TaskReportEntity(
    @PrimaryKey
    @ColumnInfo(name = "task_id")
    var task_id:String,//任务id
    @ColumnInfo(name = "task_name")
    var task_name:String,//消毒任务名称
    @ColumnInfo(name = "task_type")
    var task_type:String,//消毒类型
    @ColumnInfo(name = "task_mileage")
    var task_mileage:String,//里程
    @ColumnInfo(name = "task_consumption_liquid")
    var task_consumption_liquid:String,//消耗液体量
    @ColumnInfo(name = "task_start_time")
    var task_start_time:Long,//开始时间
    @ColumnInfo(name = "task_end_time")
    var task_end_time:Long,//结束时间
    @ColumnInfo(name = "task_state")
    var task_state:String//任务状态  完成/中断/取消
):Parcelable

一些常用的ext



import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.ResultReceiver
import android.renderscript.ScriptGroup
import android.view.View
import android.view.inputmethod.InputMethodManager

/**
 * Created by Hash on 2020/9/27.
 */

/**
 * activity===》隐藏软键盘
 */
fun hideSoftKeyboard(activity: Activity?) {
    activity?.let { activity ->
        val view = activity.currentFocus
        view?.let {
            val inputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
            inputMethodManager.hideSoftInputFromWindow(
                    view.windowToken,
                    InputMethodManager.HIDE_NOT_ALWAYS
            )

        }
    }
}

/**
 * view --> 隐藏软键盘
 */

fun hideSoftInput(view: View?) {
    var imm = RobotContext.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    if (imm == null) return;
    imm.hideSoftInputFromWindow(view?.windowToken, 0)
}

/**
 *View ===》显示软键盘
 */
fun showSoftKeyboard(view: View?) {

    var imm = view?.context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;

    if (imm == null) return;
    view.isFocusable = true
    view.isFocusableInTouchMode = true
    view.requestFocus();
    imm.showSoftInput(view, 0, object : ResultReceiver(Handler()) {
        override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
            if (resultCode == InputMethodManager.RESULT_UNCHANGED_HIDDEN
                || resultCode == InputMethodManager.RESULT_HIDDEN
            ) {
                toggleSoftInput();
            }
        }
    })
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,InputMethodManager.HIDE_IMPLICIT_ONLY)
}

fun toggleSoftInput() {
    var imm = RobotContext.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    if (imm == null) return;
    imm.toggleSoftInput(0, 0)

}



import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer


fun <T> LifecycleOwner.observe(liveData: LiveData<T>?, observer: (t: T) -> Unit) {
    liveData?.observe(this, Observer(observer))
}

fun <T> LifecycleOwner.removeObserver(liveData: LiveData<T>?) {
    liveData?.removeObservers(this)
}

fun LifecycleOwner.removeObservers(vararg liveData: LiveData<*>) {
    liveData.forEach { it.removeObservers(this) }
}


import android.content.Context
import android.content.res.Resources
import android.text.Html
import android.text.Spanned
import android.view.View


/**
 * 获取屏幕宽度
 */
val Context.screenWidth
    get() = resources.displayMetrics.widthPixels

/**
 * 获取屏幕高度
 */

val Context.screenHeight
    get() = resources.displayMetrics.heightPixels

/**
 * 判断是否为空,并传入相关操作??
 *
 */
inline fun <reified T> T?.notNull(notNullAction: (T) -> Unit, nullAction: () -> Unit = {}) {
    if (this != null) {
        notNullAction.invoke(this)
    } else {
        nullAction.invoke()
    }
}

/**
 * Context dp值转换为px
 */
fun Context.dp2px(dp: Int): Int {
    val scale = resources.displayMetrics.density
    return (dp * scale + 0.5f).toInt()
}

/**
 * Context px值转化为dp
 */
fun Context.px2dp(px: Int): Int {
    val scale = resources.displayMetrics.density
    return (px / scale + 0.5f).toInt()
}

/**
 * View dp值转换为px
 */
fun View.dp2px(dp: Int): Int {
    val scale = resources.displayMetrics.density
    return (dp * scale + 0.5f).toInt()
}

/**
 * View px值转化为dp
 */
fun View.px2dp(px: Int): Int {
    val scale = resources.displayMetrics.density
    return (px / scale + 0.5).toInt()
}

/**
 * Float dp值转换为px
 */
val Float.px: Int
    get() = (this*Resources.getSystem().displayMetrics.density + 0.5f).toInt()

/**
 * Int dp值转换为px
 */
val Int.px: Int
    get() = (this*Resources.getSystem().displayMetrics.density + 0.5f).toInt()

/**
 * 设置点击时间
 * @param views 需要设置点击的View
 * @param onClick 点击触发的方法
 */

fun setOnClick(vararg views: View?, onClick: (View) -> Unit) {
    views.forEach {
        it?.setOnClickListener { view ->
            onClick.invoke(view)
        }
    }
}

/**
 * 设置防止重复点击事件
 * @param views 需要设置点击事件的view集合
 * @param internal 时间间隔 默认为0.5秒
 * @param onClick 点击触发的方法
 */
fun setOnClick(vararg views: View?, interval: Long = 500, onClick: (View) -> Unit) {
    views.forEach {
        it?.clickNotRepeat(interval = interval) { view ->
            onClick.invoke(view)
        }
    }
}

/**
 * 将String字符串转换为Html
 */
fun String.toHtml(flag: Int = Html.FROM_HTML_MODE_LEGACY): Spanned {
    return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
        Html.fromHtml(this, flag)
    } else {
        Html.fromHtml(this)
    }
}


import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.Navigation


fun Fragment.nav(): NavController {
    return NavHostFragment.findNavController(this)
}

fun nav(view: View): NavController {
    return Navigation.findNavController(view)
}

var lastNavTime = 0L

/**
 * 防止短时间内多次快速跳转Fragment出现的问题
 * @param resId 跳转的action Id
 * @param bundle 传递的参数
 * @param interval 多少毫秒内不可重复点击 默认为0。5秒
 */
fun NavController.navigateAction(resId: Int, bundle: Bundle? = null, interval: Long = 500) {
    val currentTime = System.currentTimeMillis()
    if (currentTime >= lastNavTime + interval) {
        lastNavTime = currentTime
        navigate(resId, bundle)
    }
}

fun Fragment.navigate(resId: Int) {
    try {
        NavHostFragment.findNavController(this).navigate(resId)
    } catch (e: Exception) {
        Pdlog.d("Navigation","navigate: resId = $resId")
        Pdlog.d("Navigation","navigate: "+e)
    }
}

fun Fragment.navigate(resId: Int, args: Bundle) {
    try {
        NavHostFragment.findNavController(this).navigate(resId, args)
    } catch (e: Exception) {
        Pdlog.d("Navigation","navigate: resId = $resId args = $args")
        Pdlog.d("Navigation","navigate: "+e)
    }
}


import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import java.lang.NullPointerException
import java.lang.reflect.ParameterizedType

/**
 * Created by Hash on 2020/9/25.
 *
 */

//获取当前类绑定的泛型ViewModel-Clazz
@Suppress("UNCHECKED_CAST")
fun <VM> getVmClazz(obj: Any): VM {
    return (obj.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as VM
}

/**
 * 在activity中得到Application上下文的ViewModel
 */
inline fun <reified VM : BaseViewModel> AppCompatActivity.getAppViewModel(): VM {
    (this.application as? BaseApp).let {
        if (it == null) {
            throw NullPointerException("please check your base app is instance of Application")
        } else {
            return it.getAppViewModelProvider().get(VM::class.java)
        }
    }
}

/**
 * 在Fragment中得到Application上下文的ViewModel
 * 提示:在Fragment中调用该方法的时候,请在Fragment onCreate以后调用或使用by lazy方式进行懒加载初始化调用,
 * 不然会提示requireActivity没有  Fragment " + this + " not attached to an activity.
 * {@link androidx.fragment.app.FragmentActivity#requireActivity}
 */
inline fun <reified VM : BaseViewModel> Fragment.getAppViewModel(): VM {
    (this.requireActivity().application as? BaseApp).let {
        if (it == null) {
            throw NullPointerException("please check your base app is instance of Application")
        } else {
            return it.getAppViewModelProvider().get(VM::class.java)
        }
    }
}

/**
 * 得到当前Activity上下文的ViewModel
 */
@Deprecated("deprecated function now can use ktx function by viewModels() can acquire")
inline fun <reified VM : BaseViewModel> AppCompatActivity.getViewModel(): VM {
    return ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
        .get(VM::class.java)
}

/**
 * 在Fragment中得到父类Activity共享的viewModel
 */
@Deprecated("deprecated function now can use ktx function by activityViewModels() acquire")
inline fun <reified VM : BaseViewModel> Fragment.getActivityViewModel(): VM {
    return ViewModelProvider(requireActivity(),
            ViewModelProvider.AndroidViewModelFactory(this.requireActivity().application)
    ).get(VM::class.java)
}


文件下载



import android.text.TextUtils
import com.pudutech.base.Pdlog
import io.reactivex.Observable
import io.reactivex.ObservableOnSubscribe
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.TimeUnit

/**
 * 自定义的文件下载管理器
 * 基于OkHttp/RxJava实现
 */
class CDownloadFileManager private constructor() :
    IFileDownloadInterface {

    private var okHttpClient: OkHttpClient

    companion object {
        const val TAG = "CDownloadFileManager"
        val INSTANCE: CDownloadFileManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            CDownloadFileManager()
        }
    }

    init {
        val okHttpClientBuilder = OkHttpClient.Builder()
            .connectTimeout(15 * 1000, TimeUnit.MILLISECONDS)
            .writeTimeout(15 * 1000, TimeUnit.MILLISECONDS)
            .readTimeout(15 * 1000, TimeUnit.MILLISECONDS)
        okHttpClient = okHttpClientBuilder.build()
    }

    /**
     * 下载文件
     */
    override fun downloadFile(
        fileUrl: String?,
        fileName: String?,
        directoryPath: String,
        listener: OnDownloadFileListener?
    ) {
        if (fileUrl.isNullOrEmpty()) {
            Pdlog.e(
                    TAG,
                    "OSSUploadFileManager#downloadFile() failure, reason: fileUrl is null or empty."
            )
            return
        }

        var inputStream: InputStream? = null
        var file: File? = null
        var fos: FileOutputStream? = null
        listener?.onStart(fileUrl)

        Observable.create(ObservableOnSubscribe<String> { emitter ->

            try {
                if (TextUtils.isEmpty(directoryPath)) {
                    listener?.onFailure(fileUrl, "directoryPath is null or empty.")
                    return@ObservableOnSubscribe
                }

                Pdlog.d(TAG, "directoryPath[$directoryPath]")
                val directory = File(directoryPath)
                if (!directory.exists()) {
                    directory.mkdirs()
                }
                file =
                    File(directory.path.plus("/").plus(fileUrl.substring(fileUrl.lastIndexOf("/"))))
                if (file?.exists()!!) {
                    file?.delete()
                }
                file?.createNewFile()
                val contentLength = getContentLength(fileUrl)
                if (contentLength == 0L) {
                    Pdlog.e(TAG, "download file failure, reason: contentLength = 0")
                    emitter.onError(Throwable("download file failure, reason: contentLength = 0"))
                } else {
                    fos = FileOutputStream(file)
                    val request = Request.Builder().url(fileUrl).build()
                    val response = okHttpClient.newCall(request).execute()
                    if (response.isSuccessful) {
                        inputStream = response.body()?.byteStream()!!
                        val bytes = ByteArray(1024)
                        var total = 0
                        var len: Int
                        var lastProgress = 0
                        while (inputStream?.read(bytes).also { len = it ?: 0 } != -1) {
                            total += len
                            fos?.write(bytes, 0, len)
                            val progress = (total * 1.0f / contentLength * 100).toInt()
                            if (progress % 5 == 0 && progress != lastProgress) {
                                Pdlog.d(TAG, "progress[$progress]")
                                lastProgress = progress
                                listener?.onProgress(fileUrl, progress)
                            }
                        }
                        fos?.flush()
                        response.body()?.close()
                        emitter.onNext(fileUrl)
                    }
                }
            } catch (e: IOException) {
                e.printStackTrace()
                emitter.onError(Throwable(e))
            } finally {
                try {
                    inputStream?.close()
                } catch (e: Exception) {
                    e.printStackTrace()
                }
                try {
                    fos?.close()
                } catch (e: Exception) {
                    e.printStackTrace()
                }
                emitter.onComplete()
            }
        }).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : Observer<String> {

                private var disposable: Disposable? = null
                override fun onSubscribe(d: Disposable) {
                    disposable = d
                    Pdlog.d(TAG, "downloadFile#onSubscribe() disposable[$disposable]")
                }

                override fun onNext(fileUrl: String) {
                    Pdlog.d(TAG, "downloadFile#onNext() fileUrl[$fileUrl]")
                    listener?.onSuccessful(file?.path, fileUrl)
                }

                override fun onError(t: Throwable) {
                    Pdlog.d(TAG, "downloadFile#onError() throwable[$t]")
                    disposable?.dispose()
                    listener?.onFailure(fileUrl, t.message)
                }

                override fun onComplete() {
                    Pdlog.d(TAG, "downloadFile#onComplete() fileUrl[$fileUrl]")
                }
            })
    }

    /**
     * 获取下载文件长度
     */
    private fun getContentLength(fileUrl: String): Long {
        try {
            val request = Request.Builder().url(fileUrl).addHeader("Accept-Encoding", "identity").build()
            val response = okHttpClient.newCall(request).execute()
            if (response.isSuccessful) {
                val contentLength = response.body()?.contentLength()
                response.body()?.close()
                return contentLength ?: 0
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }

        return 0
    }
}


/**
 * Created by ChenS on 2019/11/8.
 * chenshichao@outlook.com
 */
interface IFileDownloadInterface {

    fun downloadFile(fileUrl: String?, fileName: String?, directoryPath: String, listener: OnDownloadFileListener?)
}


/**
 * Created by ChenS on 2019/11/7.
 * chenshichao@outlook.com
 */
interface OnDownloadFileListener {

    fun onStart(fileUrl: String?)

    /**
     * 下载进度回调,此方法跑在子线程,若需要更新ui,请自行切换线程操作
     */
    fun onProgress(fileUrl: String?, progress: Int)

    fun onSuccessful(filePath: String?, fileUrl: String?)

    fun onFailure(fileUrl: String?, errMsg: String?)
}

网络请求模块



import me.jessyan.retrofiturlmanager.RetrofitUrlManager
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit

/**
 * 网络请求构建基类
 */

abstract class BaseNetworkApi {

    protected var isTest = false

    companion object {
        private const val TAG = "BaseNetworkApi"
    }

    fun <T> getApi(serviceClass: Class<T>, baseUrl: String): T {
        var retrofitBuilder = Retrofit.Builder().baseUrl(baseUrl)
            .client(okHttpClient)
        return setRetrofitBuilder(retrofitBuilder).build().create(serviceClass)
    }

    fun setTest(boolean: Boolean): BaseNetworkApi {
        isTest = boolean
        return this
    }

    /**
     * 重写父类方法可以添加拦截器,可以对Builder做相关操作
     */
    abstract fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder

    /**
     * 为Retrofit做相关操作,比如添加GSON解析器 Protocol
     */
    abstract fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder

    /**
     * 配置Http
     */

    private val okHttpClient: OkHttpClient
        get() {
            var builder = RetrofitUrlManager.getInstance().with(OkHttpClient.Builder())
            builder = setHttpClientBuilder(builder).addInterceptor(
                HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message -> //访问网络请求,和服务端响应请求时。将数据拦截并输出
                    Pdlog.d(TAG, "$message")
                }).setLevel(HttpLoggingInterceptor.Level.BODY)
            ) //Log等级
            return builder.build()
        }
}



import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import javax.security.cert.CertificateException

/**
 * 云平台网络请求协议,设置证书相关内容
 */
class CloudNet : BaseNetworkApi() {

    companion object{
        private const val TAG ="CloudNet"
    }

    override fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
        "setHttpClientBuilder".logD()
        builder?.apply {
//            cache(Cache(File(appContext.cacheDir, "cache_net"), 10 * 1024 * 1024)) //10M缓存配置
//            cookieJar(cookieJar) // 添加CookieJar自动持久化
//            addInterceptor(CacheInterceptor()) // 添加缓存拦截器,可传入缓存天数,默认7天
            addInterceptor(LogInterceptor()) // 日志拦截器
            // 添加证书
            if (!isTest){
                try {
                    val ins = PuduHttpsUtils.getCertificateInput(appContext, HttpsServiceType.Cloud)
                    ins?.let {
                        PuduHttpsUtils.setSuportHttpsParams(appContext, builder, ins, true)
                    }
                    // builder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
                    builder.hostnameVerifier { hostname, session -> //还可以对域名啥的进行验证
                        Pdlog.d(TAG,
                                "hostname: " + hostname + " session protocol: " + session.protocol
                        )
                        true
                    }
                } catch (e: Exception) {
                    "setHttpClientBuilder setSuportHttpsParams error , $e".logE()
                }
            }
//            sslSocketFactory(getSSLSocketFactory()).hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
            connectTimeout(10, TimeUnit.SECONDS)
            readTimeout(5, TimeUnit.SECONDS)
            writeTimeout(5, TimeUnit.SECONDS)

        }
        return builder

    }

    /**
     * 跳过Https校验
     */
    @Throws(Exception::class)
    fun getSSLSocketFactory(): SSLSocketFactory? {
        //创建一个不验证证书链的证书信任管理器。
        val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
            @Throws(CertificateException::class)
            override fun checkClientTrusted(
                chain: Array<X509Certificate>,
                authType: String) {
            }

            @Throws(CertificateException::class)
            override fun checkServerTrusted(
                chain: Array<X509Certificate>,
                authType: String) {
            }

            override fun getAcceptedIssuers(): Array<X509Certificate?> {
                return arrayOfNulls(0)
            }
        })

        // Install the all-trusting trust manager
        val sslContext: SSLContext = SSLContext.getInstance("TLS")
        sslContext.init(null, trustAllCerts,
                SecureRandom()
        )
        // Create an ssl socket factory with our all-trusting manager
        return sslContext
            .getSocketFactory()
    }

    // 添加gson解析器
    override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
        return builder.apply {
            addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
        }
    }

}


import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import javax.security.cert.CertificateException

/**
 * 一般的网络请求,不需要特殊证书的
 */
class NormalNet : BaseNetworkApi() {

    companion object{
        private const val TAG ="NormalNet"
    }

    override fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
        "setHttpClientBuilder".logD()
        builder?.apply {
//            cache(Cache(File(appContext.cacheDir, "cache_net"), 10 * 1024 * 1024)) //10M缓存配置
//            cookieJar(cookieJar) // 添加CookieJar自动持久化
//            addInterceptor(CacheInterceptor()) // 添加缓存拦截器,可传入缓存天数,默认7天
            addInterceptor(LogInterceptor()) // 日志拦截器
            connectTimeout(10, TimeUnit.SECONDS)
            readTimeout(5, TimeUnit.SECONDS)
            writeTimeout(5, TimeUnit.SECONDS)

        }
        return builder

    }

    /**
     * 跳过Https校验
     */
    @Throws(Exception::class)
    fun getSSLSocketFactory(): SSLSocketFactory? {
        //创建一个不验证证书链的证书信任管理器。
        val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
            @Throws(CertificateException::class)
            override fun checkClientTrusted(
                chain: Array<X509Certificate>,
                authType: String) {
            }

            @Throws(CertificateException::class)
            override fun checkServerTrusted(
                chain: Array<X509Certificate>,
                authType: String) {
            }

            override fun getAcceptedIssuers(): Array<X509Certificate?> {
                return arrayOfNulls(0)
            }
        })

        // Install the all-trusting trust manager
        val sslContext: SSLContext = SSLContext.getInstance("TLS")
        sslContext.init(null, trustAllCerts,
            SecureRandom()
        )
        // Create an ssl socket factory with our all-trusting manager
        return sslContext
            .getSocketFactory()
    }

    // 添加gson解析器
    override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
        return builder.apply {
            addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
        }
    }

}

import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url

/**
 * 运力平台 api
 */
object TransportApiManager {
    @Volatile
    var mAccessToken:String?=null

    private val orderStatusService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        NormalNet().setTest(isTestServer).getApi(
            OrderStatusService::class.java,
            if (isTestServer) OrderStatusService.DEBUG_URL else OrderStatusService.PRODUCT_URL
        )
    }

    fun getOrderStatusNet():OrderStatusService{
        return orderStatusService
    }

    interface OrderStatusService {
        companion object {

            //生产环境
            const val PRODUCT_URL = "https://********/"
            //测试环境
            const val DEBUG_URL = "https://********/"

            //机器上报取得商品
            const val OBTAINED_GOODS = "api/********"

            //机器上报未取货
            const val UNRECEIVED = "api/********"

            //机器上报已取货
            const val RECEIVING = "api/********"

            //机器上报到达目的地
            const val ARRIVED_DESTINATION = "api/********"

           ......

        }

        /**
         * 机器上报取得商品
         */
        @POST
        suspend fun obtainedGoods(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + OBTAINED_GOODS,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 机器上报未取货
         */
        @POST
        suspend fun unreceived(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + UNRECEIVED,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 机器上报已取货
         */
        @POST
        suspend fun receiving(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + RECEIVING,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 机器上报到达目的地
         */
        @POST
        suspend fun arrivedDestination(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + ARRIVED_DESTINATION,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 上报机器状态
         * @param work_status  -1是空闲中,1是工作中
         */
        @POST
        suspend fun workingStatus(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + WORKING_STATUS,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 上报收到任务但不执行,让任务重新排队
         * @param work_status  -1是空闲中,1是工作中
         */
        @POST
        suspend fun queueUpdate(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + QUEUE_UPDATE,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 开机检测订单任务  重新分配任务
         */
        @POST
        suspend fun redispatch(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + REDISPATCH,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>

        /**
         * 确认接收任务
         */
        @POST
        suspend fun commit(
            @Url url: String = (if (isTestServer) DEBUG_URL else PRODUCT_URL) + COMMIT,
            @Body orderDistributionReq: OrderDistributionReq
        ): TransportResponse<Any>
    }
}


import com.google.gson.Gson
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
 * 订单任务请
 */
object OrderTaskRepository {
    private val TAG = "OrderTaskRepository"

    val NUMDER_LIMIT=1000  //小于10000为货柜单编号  10000及以上事人工取货

    const val ROBOT_FREE=-1  //-1是空闲中
    const val ROBOT_WORKING=1  //1是工作中

    var currentWorkingStatus= ROBOT_WORKING

    fun onFree(){
        val cabinetLastPickUpSucceed=MMKVPreference.decodeBoolean(CABINET_LAST_PICKUP_STATE,true)
        Pdlog.d(TAG,"cabinetLastPickUpSucceed is $cabinetLastPickUpSucceed")
        //上次已取货 测才能上在线状态
        if (cabinetLastPickUpSucceed==true){
            GlobalScope.launch {
                workingStatus(ROBOT_FREE)
            }
        }
    }

    fun onWorking(){
        GlobalScope.launch {
            workingStatus(ROBOT_WORKING)
        }
    }

    /**
     * 机器上报取得商品
     * @param taskSn
     * @param receivingCode 开仓码
     */
    suspend fun obtainedGoods(taskSn: String?){
        MMKVPreference.encode(CABINET_LAST_PICKUP_STATE, false)
        try {
            val receivingCode= MMKVPreference.decodeString(CallConfig.RECEIVING_CODE)
            Pdlog.d(TAG, "obtainedGoods taskSn = $taskSn receivingCode = $receivingCode")
            val resp = TransportApiManager.getOrderStatusNet()
                .obtainedGoods(orderDistributionReq = OrderDistributionReq(task_sn = taskSn,receiving_code = receivingCode))
            Pdlog.d(TAG, "obtainedGoods ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "obtainedGoods $e")
        }
    }


    /**
     * 机器上报未取货
     * @param taskSn
     */
    suspend fun unreceived(taskSn: String){
        try {
            val resp = TransportApiManager.getOrderStatusNet()
                .unreceived(orderDistributionReq = OrderDistributionReq(task_sn = taskSn))
            Pdlog.d(TAG, "unreceived ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "unreceived $e")
        }
    }

    /**
     * 机器上报已取货
     * @param taskSn
     */
    suspend fun receiving(taskSn: String){
        MMKVPreference.encode(CABINET_LAST_PICKUP_STATE, true)
        try {
            val resp = TransportApiManager.getOrderStatusNet()
                .receiving(orderDistributionReq = OrderDistributionReq(task_sn= taskSn))
            Pdlog.d(TAG, "receiving ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "receiving $e")
        }
    }

    /**
     * 机器上报到达目的地
     * @param taskSn
     */
    suspend fun arrivedDestination(taskSn: String){
        try {
            val resp = TransportApiManager.getOrderStatusNet()
                .arrivedDestination(orderDistributionReq = OrderDistributionReq(task_sn =taskSn))
            Pdlog.d(TAG, "arrivedDestination ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "arrivedDestination $e")
        }
    }

    /**
     * 上报机器人状态
     * @param taskSn
     */
    suspend fun workingStatus(workStatus: Int){
        try {
            currentWorkingStatus =workStatus
            val resp = TransportApiManager.getOrderStatusNet()
                .workingStatus(orderDistributionReq = OrderDistributionReq(working_status = workStatus,device_name = RobotApp.instance().getMac()))
            Pdlog.d(TAG, "workingStatus workStatus = $workStatus  ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "workingStatus $e")
        }
    }
    /**
     * 上报收到任务但不执行,让任务重新排队
     * @param taskSn
     */
    suspend fun queueUpdate(taskSn: String?) {
        try {
            val resp = TransportApiManager.getOrderStatusNet()
                .queueUpdate(orderDistributionReq = OrderDistributionReq(task_sn = taskSn))
            Pdlog.d(TAG, "queueUpdate ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "queueUpdate $e")
        }
    }

    /**
     * 开机检测订单任务  重新分配任务
     * @param taskSn
     */
    suspend fun redispatch() {
        try {
            val resp = TransportApiManager.getOrderStatusNet()
                .redispatch(orderDistributionReq = OrderDistributionReq(mac = RobotApp.instance().getMac()))
            Pdlog.d(TAG, "redispatch ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "redispatch $e")
        }
    }

    /**
     *上报 mqtt 接收到派发订单任务
     * @param taskSn
     */
    suspend fun commit(taskSn: String?) {
        try {
            val resp = TransportApiManager.getOrderStatusNet()
                .commit(orderDistributionReq = OrderDistributionReq(task_sn = taskSn))
            Pdlog.d(TAG, "commit ${Gson().toJson(resp)}")
        } catch (e: Exception) {
            Pdlog.e(TAG, "commit $e")
        }
    }
}


import com.google.gson.annotations.SerializedName

data class OrderDistributionReq(
    @SerializedName("task_sn")
    var task_sn: String? = null,
    @SerializedName("receiving_code")
    var receiving_code: String? = null,
    @SerializedName("working_status")
    var working_status: Int? = null,
    @SerializedName("device_name")
    var device_name: String? = null,
    @SerializedName("mac")
    var mac: String? = null
)


import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

/**
 *
 * 用于网络请求的响应结果  运力平台
 */
data class TransportResponse<T>(var code: Int, var message: String, var data: T?)

MMKVPreference用法



import android.os.Parcelable
import com.tencent.mmkv.MMKV
import java.util.*

/**
 *
 * @link https://github.com/Tencent/MMKV/wiki/android_tutorial
 *
 * MMKV的简单封装
 * encode 相当于是Sp中的putXX()
 * decodeXXX 相当于是SP中的getXX()
 *
 * Supported Types 支持类型
 *
 * - 基本数据类型
 *   boolean、 int 、long、 float、 double 、byte[]
 *
 * - 类 & 集合:

 *  String  === Set<String>
 *
 *  实现Parcelable接口的类型
 *
 *  - 删除和查询功能
 *
 *  ```
 *     删除
 *           var mmkv = MMKV.defaultMMKV()
 *           mmkv?.removeValueForKey("a") //
 *           mmkv?.removeValueForKeys(arrayOf("int", "long"))
 *     查询
 *          var hasBool = mmkv.containsKey("bool") as Boolean
 *
 *  ```
 *
 */

object MMKVPreference {

    var mmkv: MMKV? = null

    init {
        val dir = MMKV.initialize(appContext)
        "mmkv dir $dir".logD()
        mmkv = MMKV.defaultMMKV()

    }

    ///存值操作/
    /**
     * 存储基本数据类型
     */
    fun encode(key: String, value: Any?) {
        when (value) {
            is String -> mmkv?.encode(key, value)
            is Float -> mmkv?.encode(key, value)
            is Boolean -> mmkv?.encode(key, value)
            is Int -> mmkv?.encode(key, value)
            is Long -> mmkv?.encode(key, value)
            is Double -> mmkv?.encode(key, value)
            is ByteArray -> mmkv?.encode(key, value)
            is Nothing -> return
        }
    }

    /**
     * 存储序列化数据类型
     */
    fun <T : Parcelable> encode(key: String, t: T?) {
        if (t == null) {
            return
        }
        mmkv?.encode(key, t)
    }

    /**
     * 存储集合 Set<string>
     */
    fun <T : Parcelable> encode(key: String, sets: Set<String>?) {
        if (sets == null) {
            return
        }
        mmkv?.encode(key, sets)
    }

    取值操作

    fun decodeInt(key: String, i: Int): Int? {
        return mmkv?.decodeInt(key, i)
    }

    fun decodeFloat(key: String): Float? {
        return mmkv?.decodeFloat(key, 0F)
    }

    fun decodeFloat(key:String,value:Float):Float?{
        return mmkv?.decodeFloat(key,value)
    }

    fun decodeDouble(key: String): Double? {
        return mmkv?.decodeDouble(key, 0.00)
    }

    fun decodeLong(key: String): Long? {
        return mmkv?.decodeLong(key, 0L)
    }

    fun decodeBoolean(key: String): Boolean? {
        return mmkv?.decodeBool(key, false)
    }
    fun decodeBoolean(key:String,flag:Boolean):Boolean?{
        return mmkv?.decodeBool(key,flag)
    }

    fun decodeByteArray(key: String): ByteArray? {
        return mmkv?.decodeBytes(key)
    }

    fun decodeString(key: String): String? {
        return mmkv?.decodeString(key, "")
    }
    fun decodeString(key: String,value:String): String? {
        return mmkv?.decodeString(key, value)
    }

    fun decodeStringSet(key: String): Set<String>? {
        return mmkv?.decodeStringSet(key, Collections.emptySet())
    }

    fun <T : Parcelable> decodeParcelable(key: String, clazz: Class<T>): T? {
        return mmkv?.decodeParcelable(key, clazz)
    }

    ///移除
    fun removeKey(key: String) = mmkv?.removeValueForKey(key)

    fun containsKey(key: String): Boolean? = mmkv?.containsKey(key)

    fun clearAll() {
        mmkv?.clearAll()
    }
}


RxTime 使用



import android.view.View;
import android.widget.TextView;

import androidx.annotation.NonNull;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;

/**
 * Rxjava2.x实现轮询定时器.
 * @author zjx
 */
public class RxTimerUtils {

    /**
     * 作用1、避免重复执行相同name定时器2,计时结束后取消订阅
     */
    private static Map mDisposableMap = new HashMap();

    /**
     * 是否存在定时器
     *
     * @param name
     */
    public static boolean hasTimer(String name) {
        return mDisposableMap.containsKey(name);
    }

    /**
     * x秒后执行next操作
     */
    public static void timer(long seconds, final String name, final IListener next) {
        if (mDisposableMap.containsKey(name)) {
            return;
        }
        Observable.timer(seconds, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable disposable) {
                        mDisposableMap.put(name, disposable);
                    }

                    @Override
                    public void onNext(@NonNull Long number) {
                        if (next != null) {
                            next.onComplete();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        //取消订阅
                        cancel(name);
                    }

                    @Override
                    public void onComplete() {
                        //取消订阅
                        cancel(name);

                    }
                });
    }

    /**
     * xx后执行next操作
     */
    public static void timer(long seconds, final String name, TimeUnit timeUnit, final IListener next) {
        if (mDisposableMap.containsKey(name)) {
            //ALog.e(TextUtils.concat("已经有定时器【", name, "】在执行了,本次重复定时器不在执行").toString());
            return;
        }
        Observable.timer(seconds, timeUnit)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable disposable) {

                        mDisposableMap.put(name, disposable);
                    }

                    @Override
                    public void onNext(@NonNull Long number) {
                        if (next != null) {
                            next.onComplete();
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        //取消订阅
                        cancel(name);
                    }

                    @Override
                    public void onComplete() {
                        //取消订阅
                        cancel(name);

                    }
                });
    }

    /**
     * 每隔milliseconds秒后执行next操作
     *
     * @param milliseconds
     * @param name         给当前定时器命名
     * @param next
     */
    public static void interval(long milliseconds, final String name, final IRxNext next) {
        if (mDisposableMap.containsKey(name)) {
            //ALog.e(TextUtils.concat("------已经有定时器【", name, "】在执行了,本次重复定时器不在执行").toString());
            return;
        }
        Observable.interval(milliseconds, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable disposable) {
                        mDisposableMap.put(name, disposable);
                    }

                    @Override
                    public void onNext(@NonNull Long number) {
                        if (next != null) {
                            next.doNext(number);
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        cancel(name);
                        //ALog.e("---onError---");
                    }

                    @Override
                    public void onComplete() {
                        cancel(name);
                    }
                });
    }


    /**
     * 每隔xx后执行next操作
     */
    public static void interval(long milliseconds, TimeUnit unit, final String name, final IRxNext next) {
        if (mDisposableMap.containsKey(name)) {
            //ALog.v(TextUtils.concat("已经有定时器【", name, "】在执行了,本次重复定时器不在执行").toString());
            return;
        }
        Observable.interval(milliseconds, unit)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable disposable) {
                        mDisposableMap.put(name, disposable);
                    }

                    @Override
                    public void onNext(@NonNull Long number) {
                        if (next != null) {
                            next.doNext(number);
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        cancel(name);
                    }

                    @Override
                    public void onComplete() {
                        cancel(name);
                    }
                });
    }


    /**
     * 隔xx后执行next操作
     */
    public static void countDownTimer(final long seconds, final String name, TextView tv, ITimer iTimer) {
        if (mDisposableMap.containsKey(name)) {
            //ALog.e(TextUtils.concat("已经有定时器【", name, "】在执行了,本次重复定时器不在执行").toString());
            return;
        }
        Observable.interval(0, 1, TimeUnit.SECONDS)
                .take(seconds + 1)
                .map(new Function<Long, Long>() {
                    @Override
                    public Long apply(Long aLong) throws Exception {
                        return seconds - aLong;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())//ui线程中进行控件更新
                .doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {
                        if (tv != null) {
                            tv.setVisibility(View.VISIBLE);
//                            CommUtils.setTextColor(tv, R.color.color_write);
                        }

                    }
                })
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(Disposable disposable) {
                        mDisposableMap.put(name, disposable);
                    }

                    @Override
                    public void onNext(Long num) {
                        //tv.setText("剩余" + num + "秒");

                        if (tv != null) {
                            if (num <= 3) {
//                                CommUtils.setTextColor(tv, R.color.color_red);
                            }
                            tv.setText(String.valueOf(num));
                        }

                        boolean bool = num == 0;
                        iTimer.doNext(num, bool);
                        if (bool) {
                            cancel(name);
                        }

                    }

                    @Override
                    public void onError(Throwable e) {
                        cancel(name);
                    }

                    @Override
                    public void onComplete() {
                        //回复原来初始状态
                        // tv.setEnabled(true);
                        // tv.setText("发送验证码");
                        cancel(name);
                        if (tv != null) {
                            tv.setVisibility(View.GONE);
                        }

                    }
                });
    }


    /**
     * 隔xx后执行next操作
     */
    public static void countDownTimer(final long seconds, TimeUnit timeUnit, final String name, ITimer iTimer) {
        if (mDisposableMap.containsKey(name)) {
            //ALog.e(TextUtils.concat("已经有定时器【", name, "】在执行了,本次重复定时器不在执行").toString());
            return;
        }
        Observable.interval(0, 1, timeUnit)
                .take(seconds + 1)
                .map(new Function<Long, Long>() {
                    @Override
                    public Long apply(Long aLong) throws Exception {
                        return seconds - aLong;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())//ui线程中进行控件更新
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(Disposable disposable) {
                        mDisposableMap.put(name, disposable);
                    }

                    @Override
                    public void onNext(Long num) {
                        boolean bool = num == 0;
                        if (bool) {
                            cancel(name);
                        }
                        iTimer.doNext(num, bool);
                    }

                    @Override
                    public void onError(Throwable e) {
                        cancel(name);
                    }

                    @Override
                    public void onComplete() {
                        cancel(name);
                        // iTimer.onComplete();
                    }
                });
    }


    /**
     * 取消订阅
     */
    public static void cancel(@NonNull String... timerNames) {
        if (timerNames != null && timerNames.length > 0) {
            for (String name : timerNames) {
                Disposable mDisposable = (Disposable) mDisposableMap.get(name);
                if (mDisposable != null) {
                    mDisposableMap.remove(name);
                    if (!mDisposable.isDisposed()) {
                        mDisposable.dispose();
//                        Log.w("RxTimerUtils", "---Rx定时器【" + name + "】取消---");
                    }
                }
            }
        }

    }

    public static void cancel(Listener listener, @NonNull String... timerNames) {
        if (timerNames != null && timerNames.length > 0) {
            for (String name : timerNames) {
                Disposable mDisposable = (Disposable) mDisposableMap.get(name);
                if (mDisposable != null) {
                    mDisposableMap.remove(name);
                    if (!mDisposable.isDisposed()) {
                        mDisposable.dispose();
//                        Log.w("RxTimerUtils", "---Rx定时器【" + name + "】取消---");
                    }
                }
            }
            listener.onResult();
        }

    }

    public interface IRxNext {
        void doNext(long number);
    }

    public interface ITimer {
        void doNext(long number, boolean complete);
    }

    public interface IListener {
        void onComplete();
    }

    public interface Listener {
        public void onResult();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值