新增:自动任务·快捷指令 —— 定时任务(CoroutineWorker方案) #279

This commit is contained in:
pppscn 2023-12-07 00:21:09 +08:00
parent 51149c95cd
commit af63302df6
6 changed files with 165 additions and 11 deletions

View File

@ -1,7 +1,6 @@
package com.idormy.sms.forwarder.adapter package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -48,9 +47,9 @@ class TaskPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
holder.binding.layoutActionsIcons.removeAllViews() holder.binding.layoutActionsIcons.removeAllViews()
if (item.actions.isNotEmpty()) { if (item.actions.isNotEmpty()) {
val actionList = Gson().fromJson(item.actions, Array<TaskSetting>::class.java).toMutableList() val actionList = Gson().fromJson(item.actions, Array<TaskSetting>::class.java).toMutableList()
Log.d("TaskPagingAdapter", "actionList:$actionList") //Log.d("TaskPagingAdapter", "actionList:$actionList")
for (action in actionList) { for (action in actionList) {
Log.d("TaskPagingAdapter", "action:$action") //Log.d("TaskPagingAdapter", "action:$action")
val layoutActionItem = View.inflate(App.context, R.layout.item_setting, null) as LinearLayout val layoutActionItem = View.inflate(App.context, R.layout.item_setting, null) as LinearLayout
val ivActionIcon = layoutActionItem.findViewById<ImageView>(R.id.iv_setting_icon) val ivActionIcon = layoutActionItem.findViewById<ImageView>(R.id.iv_setting_icon)
ivActionIcon.setImageResource(action.iconId) ivActionIcon.setImageResource(action.iconId)

View File

@ -10,6 +10,7 @@ import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery
import com.idormy.sms.forwarder.database.entity.Task import com.idormy.sms.forwarder.database.entity.Task
import io.reactivex.Single import io.reactivex.Single
import java.util.Date
@Dao @Dao
interface TaskDao { interface TaskDao {
@ -23,6 +24,9 @@ interface TaskDao {
@Query("SELECT * FROM Task ORDER BY id DESC") @Query("SELECT * FROM Task ORDER BY id DESC")
fun getAll(): List<Task> fun getAll(): List<Task>
@Query("SELECT * FROM Task where type = 1000 ORDER BY id DESC")
fun getAllCron(): List<Task>
@Query("SELECT * FROM Task where type < 1000 ORDER BY id DESC") @Query("SELECT * FROM Task where type < 1000 ORDER BY id DESC")
fun pagingSourceFixed(): PagingSource<Int, Task> fun pagingSourceFixed(): PagingSource<Int, Task>
@ -34,7 +38,7 @@ interface TaskDao {
fun getAllRaw(query: SupportSQLiteQuery): List<Task> fun getAllRaw(query: SupportSQLiteQuery): List<Task>
@Query("SELECT * FROM Task WHERE type = :taskType") @Query("SELECT * FROM Task WHERE type = :taskType")
fun getByType(taskType: String): List<Task> fun getByType(taskType: Int): List<Task>
//TODO:根据条件查询,不推荐使用 //TODO:根据条件查询,不推荐使用
@Query("SELECT * FROM Task WHERE type = :taskType AND conditions LIKE '%' || :conditionKey || '%' AND conditions LIKE '%' || :conditionValue || '%'") @Query("SELECT * FROM Task WHERE type = :taskType AND conditions LIKE '%' || :conditionKey || '%' AND conditions LIKE '%' || :conditionValue || '%'")
@ -46,6 +50,9 @@ interface TaskDao {
@Update @Update
fun update(task: Task) fun update(task: Task)
@Query("UPDATE Task SET last_exec_time = :lastExecTime, next_exec_time = :nextExecTime, status = :status WHERE id = :taskId")
fun updateExecTime(taskId: Long, lastExecTime: Date, nextExecTime: Date, status: Int)
@Query("DELETE FROM Task WHERE id = :taskId") @Query("DELETE FROM Task WHERE id = :taskId")
fun delete(taskId: Long) fun delete(taskId: Long)

View File

@ -26,7 +26,7 @@ import com.idormy.sms.forwarder.databinding.FragmentTasksEditBinding
import com.idormy.sms.forwarder.entity.task.CronSetting import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.task.CronUtils import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageFragment import com.xuexiang.xpage.base.XPageFragment
@ -338,10 +338,14 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
when (task.type) { when (task.type) {
//定时任务 //定时任务
TASK_CONDITION_CRON -> { TASK_CONDITION_CRON -> {
//取消旧任务的定时器 //取消旧任务的定时器 & 设置新的定时器
CronUtils.cancelAlarm(task) //CronUtils.cancelAlarm(task)
//设置新的定时器 //CronUtils.scheduleAlarm(task)
CronUtils.scheduleAlarm(task)
//val uuid = App.TaskIdToWorkerIdMap[task.id]
//uuid?.let { CronJobScheduler.cancelTask(it) }
CronJobScheduler.cancelTask(task.id)
CronJobScheduler.scheduleTask(task)
} }
} }
} }

View File

@ -22,12 +22,14 @@ import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.LocationInfo import com.idormy.sms.forwarder.entity.LocationInfo
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import com.idormy.sms.forwarder.workers.LoadAppListWorker import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.king.location.LocationClient import com.king.location.LocationClient
import com.king.location.LocationErrorCode import com.king.location.LocationErrorCode
import com.king.location.OnExceptionListener import com.king.location.OnExceptionListener
import com.king.location.OnLocationListener import com.king.location.OnLocationListener
import com.xuexiang.xaop.util.PermissionUtils
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib import frpclib.Frpclib
@ -141,6 +143,16 @@ class ForegroundService : Service() {
CommonUtils.toggleNotificationListenerService(this) CommonUtils.toggleNotificationListenerService(this)
} }
//启动定时任务
GlobalScope.async(Dispatchers.IO) {
val taskList = AppDatabase.getInstance(App.context).taskDao().getByType(TASK_CONDITION_CRON)
taskList.forEach { task ->
Log.d(TAG, "task = $task")
CronJobScheduler.cancelTask(task.id)
CronJobScheduler.scheduleTask(task)
}
}
//异步获取所有已安装 App 信息 //异步获取所有已安装 App 信息
if (SettingUtils.enableLoadAppList) { if (SettingUtils.enableLoadAppList) {
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build() val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
@ -169,8 +181,10 @@ class ForegroundService : Service() {
} }
} }
//远程找手机 //远程找手机 TODO: 判断权限 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION
if (SettingUtils.enableLocationTag || HttpServerUtils.enableApiLocation) { if ((SettingUtils.enableLocationTag || HttpServerUtils.enableApiLocation)
&& PermissionUtils.isGranted(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
) {
//可根据具体需求设置定位配置参数(这里只列出一些主要的参数) //可根据具体需求设置定位配置参数(这里只列出一些主要的参数)
val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度 val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度
.setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗 .setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗

View File

@ -0,0 +1,50 @@
package com.idormy.sms.forwarder.utils.task
import android.util.Log
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.workers.TaskWorker
import java.util.concurrent.TimeUnit
@Suppress("DEPRECATION")
class CronJobScheduler {
companion object {
private const val TAG: String = "CronJobScheduler"
fun scheduleTask(task: Task) {
val currentTimeMillis = System.currentTimeMillis()
val delayInMillis = task.nextExecTime.time / 1000 * 1000 - currentTimeMillis
val inputData = Data.Builder().putLong("taskId", task.id).build()
val taskRequest = if (delayInMillis <= 0L) {
Log.d(TAG, "立即执行任务${task.id}delayInMillis = $delayInMillis")
OneTimeWorkRequestBuilder<TaskWorker>()
.setInputData(inputData)
.build()
} else {
Log.d(TAG, "延迟 $delayInMillis 毫秒执行任务${task.id}")
OneTimeWorkRequestBuilder<TaskWorker>()
.setInitialDelay(delayInMillis, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.build()
}
// 确保同一个任务 ID 的 Worker 在同一时间只会执行一个实例
val uniqueTaskName = "$TAG-${task.id}"
WorkManager.getInstance().beginUniqueWork(
uniqueTaskName, // 给任务设置一个唯一的名称
ExistingWorkPolicy.KEEP, // 设置任务存在时的策略
taskRequest
).enqueue()
}
fun cancelTask(taskId: Long) {
val uniqueTaskName = "$TAG-$taskId"
WorkManager.getInstance().cancelUniqueWork(uniqueTaskName)
}
}
}

View File

@ -0,0 +1,80 @@
package com.idormy.sms.forwarder.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import gatewayapps.crondroid.CronExpression
import kotlinx.coroutines.delay
import java.util.Date
@Suppress("PrivatePropertyName")
class TaskWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = TaskWorker::class.java.simpleName
override suspend fun doWork(): Result {
val taskId = inputData.getLong("taskId", -1L)
if (taskId == -1L) {
Log.d(TAG, "taskId is -1L")
return Result.failure()
}
val task = AppDatabase.getInstance(App.context).taskDao().getOne(taskId)
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
if (conditionList.isEmpty()) {
Log.d(TAG, "onReceive conditionList is empty")
return Result.failure()
}
val firstCondition = conditionList.firstOrNull()
if (firstCondition == null) {
Log.d(TAG, "onReceive firstCondition is null")
return Result.failure()
}
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
if (cronSetting == null) {
Log.d(TAG, "onReceive cronSetting is null")
return Result.failure()
}
// 更新任务的上次执行时间和下次执行时间
val now = Date()
task.lastExecTime = task.nextExecTime
val cronExpression = CronExpression(cronSetting.expression)
val nextExecTime = cronExpression.getNextValidTimeAfter(now)
// 将 nextExecTime 的毫秒部分设置为 0避免因为毫秒部分不同导致的任务重复执行
nextExecTime.time = nextExecTime.time / 1000 * 1000
task.nextExecTime = nextExecTime
Log.d(TAG, "lastExecTime = ${task.lastExecTime}, nextExecTime = ${task.nextExecTime}")
// 自动禁用任务
if (task.nextExecTime.time / 1000 < now.time / 1000) {
task.status = 0
}
// 更新任务信息
AppDatabase.getInstance(App.context).taskDao().updateExecTime(task.id, task.lastExecTime, task.nextExecTime, task.status)
if (task.status == 0) {
Log.d(TAG, "onReceive task is disabled")
return Result.success()
}
// TODO: 执行具体任务
Log.d(TAG, "执行具体任务,耗时 200 毫秒")
delay(200L)
// 为新的 nextExecTime 调度下一次任务执行
CronJobScheduler.cancelTask(task.id)
CronJobScheduler.scheduleTask(task)
return Result.success()
}
}