Commit 255716f8 authored by 姜天宇's avatar 姜天宇

feat(轮询JOB):增加日志轮询上传任务

parent 51c65b50
......@@ -3,8 +3,8 @@ plugins {
id 'org.jetbrains.kotlin.android'
}
def APP_VERSION_CODE = 1000204
def APP_VERSION_NAME = "1.0.2.4"
def APP_VERSION_CODE = 1000205
def APP_VERSION_NAME = "1.0.2.5"
android {
namespace 'com.wmdigit.cateringdetect'
......@@ -106,6 +106,7 @@ dependencies {
implementation project(path: ':core-network')
implementation project(path: ':core-storage')
implementation project(path: ':service-aidl')
implementation project(path: ':feature-worker-schedule')
implementation 'com.alibaba:arouter-api:1.5.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
......
......@@ -5,6 +5,7 @@ import com.wmdigit.core.NetworkModule;
import com.wmdigit.common.CommonModule;
import com.wmdigit.feature.ai.CoreModule;
import com.wmdigit.core.storage.LocalDataModule;
import com.wmdigit.feature.schedule.WorkerScheduleModule;
import com.wmdigit.service.ServiceModule;
/**
......@@ -48,5 +49,7 @@ public class Application extends android.app.Application {
NetworkModule.init(this);
// 初始化服务模块
ServiceModule.init(this);
// 初始化worker模块
WorkerScheduleModule.INSTANCE.init(this);
}
}
package com.wmdigit.core.network.repository;
import android.annotation.SuppressLint;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.text.TextUtils;
import com.elvishew.xlog.XLog;
import com.wmdigit.common.utils.DateUtils;
import com.wmdigit.common.utils.ZipUtils;
import com.wmdigit.core.NetworkModule;
import com.wmdigit.common.base.mvvm.BaseMvvmNetworkRepository;
import com.wmdigit.common.base.mvvm.SingleLiveEvent;
import com.wmdigit.common.utils.DeviceUtils;
import com.wmdigit.core.network.bean.request.QueryCommandParam;
import com.wmdigit.core.network.bean.request.ReplyCommandList;
import com.wmdigit.core.network.bean.request.SaveCommandResult;
import com.wmdigit.core.network.bean.response.BasePosResponse;
import com.wmdigit.core.network.bean.response.SimplePosCommandDTO;
import com.wmdigit.core.network.oss.OSSManager;
import com.wmdigit.core.storage.mmkv.repository.UserLocalRepository;
import com.wmdigit.core.network.R;
import com.wmdigit.core.network.bean.request.UnbindSnParam;
......@@ -18,9 +30,19 @@ import com.wmdigit.core.network.bean.response.PosRegisterInfo;
import com.wmdigit.core.network.exception.ServerException;
import com.wmdigit.core.network.utils.RxHelper;
import java.io.File;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
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;
import io.reactivex.schedulers.Schedulers;
import retrofit2.HttpException;
/**
......@@ -189,5 +211,127 @@ public class UserRemoteRepository extends BaseMvvmNetworkRepository implements I
});
}
/**
* 查询服务器指令,根据查询结果决定是否上传日志
* @return
*/
@Override
public Disposable queryServerCommandAndUploadLog() {
// 查询本地注册信息
String[] array = UserLocalRepository.getInstance().getUser();
if (TextUtils.isEmpty(array[0]) || TextUtils.isEmpty(array[1])){
return null;
}
String posCode = array[0] + "_" + array[1];
QueryCommandParam queryCommandParam = new QueryCommandParam();
// 设备CODE
queryCommandParam.setPosCode(posCode);
// 模型版本号,暂时没用
queryCommandParam.setAiModelVersion("20250519");
PackageInfo info = null;
try {
String packageName = NetworkModule.getAppContext().getPackageName();
info = NetworkModule.getAppContext().getPackageManager().getPackageInfo(packageName,0);
} catch (PackageManager.NameNotFoundException e) {
XLog.e(e.toString());
}
// App版本号
if (info != null){
queryCommandParam.setPosVersion(info.versionName);
}
else{
queryCommandParam.setPosVersion("");
}
// 系统版本号
queryCommandParam.setOsVersion(android.os.Build.VERSION.RELEASE);
return ServiceFactory.getServiceFactory().getPosService()
// 查询服务器指令
.getCommandByPosCode(queryCommandParam)
.compose(RxHelper.syncHandlePosResult())
.flatMap(obj -> {
List<SimplePosCommandDTO> simplePosCommandDTOs = (List<SimplePosCommandDTO>) obj;
return Observable.create(emitter -> {
if (simplePosCommandDTOs != null && simplePosCommandDTOs.size() > 0){
// 遍历后台指令
for (SimplePosCommandDTO dto : simplePosCommandDTOs){
// 先回复命令
replyServerCommand(dto.getId());
// 判断后台是否需要上传日志
if (dto.getType() == SimplePosCommandDTO.TypeEnum.UPLOAD_LOG){
uploadLog(dto);
}
}
}
});
}).subscribe(unUsed->{
}, throwable -> {
XLog.e("轮询上传日志任务失败: %s", throwable);
});
}
/**
* 回复后台指令
* @param commandId
*/
@SuppressLint("CheckResult")
private void replyServerCommand(Long commandId){
if (commandId == null){
return;
}
// 组装请求参数
ReplyCommandList replyCommandList = new ReplyCommandList();
List<Long> list = new ArrayList<>();
list.add(commandId);
replyCommandList.setCommandIds(list);
// 回复后台
ServiceFactory.getServiceFactory().getPosService()
.replyCommand(replyCommandList)
.compose(RxHelper.syncHandlePosResult())
.subscribe(unused -> {
}, throwable -> {
XLog.e(throwable);
});
}
/**
* 上传日志
* @param dto
*/
private void uploadLog(SimplePosCommandDTO dto){
String date = DateUtils.getTodayDate();
if (!TextUtils.isEmpty(dto.getParameters())) {
date = dto.getParameters();
}
// 日志文件
String path = NetworkModule.getAppContext().getExternalFilesDir("Log").getPath() + File.separator + date;
File logFile = new File(path);
if (!logFile.exists()){
return;
}
// 压缩文件
String zipPath = path + ".zip";
ZipUtils.zipFile(path ,zipPath);
File zipFile = new File(zipPath);
Disposable disposable = Observable.create(emitter -> {
// 上传压缩文件
String url = OSSManager.getInstance().syncUploadFileToOSS(zipFile);
// 组装请求参数
SaveCommandResult saveCommandResult = new SaveCommandResult();
saveCommandResult.setCommandId(dto.getId());
saveCommandResult.setSuccess(true);
saveCommandResult.setResult(url);
emitter.onNext(saveCommandResult);
}).flatMap(obj->{
// 保存命令执行结果
SaveCommandResult saveCommandResult = (SaveCommandResult) obj;
return ServiceFactory.getServiceFactory().getPosService().saveCommandResult(saveCommandResult);
}).subscribe(response -> {
XLog.i("上传日志code:%s, message:%s", response.getCode(), response.getMessage());
}, throwable -> {
XLog.e(throwable);
});
}
}
......@@ -28,4 +28,10 @@ public interface IUserRemoteRepository {
* @return
*/
Disposable unbindPos();
/**
* 查询服务器指令,根据查询结果决定是否上传日志
* @return
*/
Disposable queryServerCommandAndUploadLog();
}
......@@ -41,6 +41,27 @@ public class RxHelper {
};
}
/**
* 同步去壳,不切换线程
* @return
* @param <T>
*/
public static <T> ObservableTransformer<BasePosResponse<T>,T> syncHandlePosResult(){
return upstream -> {
Observable<Object> observable = upstream.flatMap((Function<BasePosResponse<T>, ObservableSource<?>>) tBaseResponse -> {
int code=tBaseResponse.getCode();
String desc=tBaseResponse.getMessage();
T data=tBaseResponse.getData();
if(code == ServiceFactory.NET_SUCCESS_CODE){
return createObservable(data);
}else{
return createErrorObservable(new ServerException(code, desc));
}
});
return (ObservableSource<T>)observable;
};
}
/**
* 创建指定数据源的Observable
* 后端传来的数据,如果为空,则则直接走 onComplete(); 否则,走onNext(),即传数据
......
/build
\ No newline at end of file
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.wmdigit.feature.schedule"
compileSdk = 33
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(project(":core-common"))
implementation(project(":core-network"))
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
implementation("androidx.work:work-runtime:2.8.1")
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.wmdigit.feature.schedule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.wmdigit.feature.workerschedule.test", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
\ No newline at end of file
package com.wmdigit.feature.schedule
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import com.wmdigit.feature.schedule.workers.UploadLogWorker
import java.util.concurrent.TimeUnit
/**
* WorkerScheduleModule 是一个用于调度后台任务的模块,主要用于初始化和调度日志上传等后台工作.
*/
object WorkerScheduleModule {
// 应用上下文,用于全局初始化和访问
private var appContext: Context? = null
/**
* 初始化WorkerScheduleModule.
*
* @param context 应用上下文,用于全局初始化
*/
fun init(context: Context){
// 首次初始化时,设置appContext并构建日志上传的后台任务
if (appContext == null) {
appContext = context
buildSimpleWorkerSchedule(UploadLogWorker::class.java, 15, 10)
}
}
/**
* 获取全局应用上下文.
*
* @return 应用上下文,如果未初始化则抛出异常
*/
fun getContext(): Context {
// 如果appContext为空,表示模块未初始化,抛出异常
return appContext ?: throw IllegalStateException("WorkerScheduleModule has not been initialized")
}
/**
* 构建并调度一个周期性后台任务.
*
* @param workerClass 后台任务的类,必须是ListenableWorker的子类
* @param repeatIntervalMinutes 任务重复执行的间隔时间(分钟)
* @param initialDelaySeconds 任务首次执行的延迟时间(秒)
*/
private fun buildSimpleWorkerSchedule(workerClass: Class<out ListenableWorker>, repeatIntervalMinutes: Long, initialDelaySeconds: Long){
// 构建任务约束条件,包括网络状态、电池状态、存储状态和充电状态
val constraints: Constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresStorageNotLow(false)
.setRequiresCharging(true)
.build()
// 使用worker类的简单名称作为标签
val workerTag: String = workerClass.simpleName
// 构建周期性任务请求,包括任务执行的间隔时间、约束条件、初始延迟等
val request: PeriodicWorkRequest = PeriodicWorkRequest
.Builder(workerClass, repeatIntervalMinutes, TimeUnit.MINUTES)
.setConstraints(constraints)
.setInitialDelay(initialDelaySeconds, TimeUnit.SECONDS)
// 退避策略(已注释)
// .setBackoffCriteria(BackoffPolicy.LINEAR, 5, TimeUnit.MINUTES)
.addTag(workerTag)
.build()
// 如果appContext不为空,获取WorkManager实例并调度任务
appContext?.let {
WorkManager.getInstance(it).enqueueUniquePeriodicWork(workerTag, ExistingPeriodicWorkPolicy.UPDATE, request)
}
}
}
package com.wmdigit.feature.schedule.base
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.elvishew.xlog.XLog
/**
* Worker基类
*/
abstract class BaseWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams){
override fun doWork(): Result {
val startTime = System.currentTimeMillis()
val jobName = this::class.simpleName ?: "unknown"
XLog.i("%s JOB准备执行", jobName)
synchronized(this::class){
try {
return doJob()
} finally {
val endTime = System.currentTimeMillis()
XLog.i("%s JOB执行耗时:%sms", jobName, endTime - startTime)
}
}
}
protected abstract fun doJob():Result;
}
\ No newline at end of file
package com.wmdigit.feature.schedule.workers
import android.content.Context
import androidx.work.WorkerParameters
import com.wmdigit.core.network.repository.UserRemoteRepository
import com.wmdigit.feature.schedule.base.BaseWorker
import io.reactivex.Observer
/**
* 上传日志worker
* 15分钟一次
*/
class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
BaseWorker(context, workerParams) {
override fun doJob(): Result {
UserRemoteRepository.getInstance().queryServerCommandAndUploadLog()
return Result.success()
}
}
\ No newline at end of file
package com.wmdigit.feature.schedule
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
......@@ -29,7 +29,6 @@ v1.0.2.3 2025/05/08 1.增加GPU推理
3.学习页增加重置背景功能
4.修复部分设备USB相机格式不兼容的问题
v1.0.2.4 2025/05/13 1.增加arm64支持
v1.0.2.5 2025/05/22 1.增加日志轮询上传任务
todo 增加轮询JOB
todo 限制学习列表图片展示数量
......@@ -36,3 +36,4 @@ include ':service-aidl'
include ':library-opencv'
include ':aidl-sdk'
include ':ui-upgrade'
include ':feature-worker-schedule'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment