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();
}
}