diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskPagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskPagingAdapter.kt index 72aa124e..51c895d5 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskPagingAdapter.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskPagingAdapter.kt @@ -1,7 +1,6 @@ package com.idormy.sms.forwarder.adapter import android.annotation.SuppressLint -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -48,9 +47,9 @@ class TaskPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa holder.binding.layoutActionsIcons.removeAllViews() if (item.actions.isNotEmpty()) { val actionList = Gson().fromJson(item.actions, Array::class.java).toMutableList() - Log.d("TaskPagingAdapter", "actionList:$actionList") + //Log.d("TaskPagingAdapter", "actionList:$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 ivActionIcon = layoutActionItem.findViewById(R.id.iv_setting_icon) ivActionIcon.setImageResource(action.iconId) diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt index b1011aa2..6a64e999 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt @@ -10,6 +10,7 @@ import androidx.room.Update import androidx.sqlite.db.SupportSQLiteQuery import com.idormy.sms.forwarder.database.entity.Task import io.reactivex.Single +import java.util.Date @Dao interface TaskDao { @@ -23,6 +24,9 @@ interface TaskDao { @Query("SELECT * FROM Task ORDER BY id DESC") fun getAll(): List + @Query("SELECT * FROM Task where type = 1000 ORDER BY id DESC") + fun getAllCron(): List + @Query("SELECT * FROM Task where type < 1000 ORDER BY id DESC") fun pagingSourceFixed(): PagingSource @@ -34,7 +38,7 @@ interface TaskDao { fun getAllRaw(query: SupportSQLiteQuery): List @Query("SELECT * FROM Task WHERE type = :taskType") - fun getByType(taskType: String): List + fun getByType(taskType: Int): List //TODO:根据条件查询,不推荐使用 @Query("SELECT * FROM Task WHERE type = :taskType AND conditions LIKE '%' || :conditionKey || '%' AND conditions LIKE '%' || :conditionValue || '%'") @@ -46,6 +50,9 @@ interface TaskDao { @Update 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") fun delete(taskId: Long) diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt index 74ebf20c..032dfe85 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt @@ -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.TaskSetting 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.xpage.annotation.Page import com.xuexiang.xpage.base.XPageFragment @@ -338,10 +338,14 @@ class TasksEditFragment : BaseFragment(), View.OnClic when (task.type) { //定时任务 TASK_CONDITION_CRON -> { - //取消旧任务的定时器 - CronUtils.cancelAlarm(task) - //设置新的定时器 - CronUtils.scheduleAlarm(task) + //取消旧任务的定时器 & 设置新的定时器 + //CronUtils.cancelAlarm(task) + //CronUtils.scheduleAlarm(task) + + //val uuid = App.TaskIdToWorkerIdMap[task.id] + //uuid?.let { CronJobScheduler.cancelTask(it) } + CronJobScheduler.cancelTask(task.id) + CronJobScheduler.scheduleTask(task) } } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt index 15de3bf0..e32f1953 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt @@ -22,12 +22,14 @@ import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.entity.LocationInfo import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.task.CronJobScheduler import com.idormy.sms.forwarder.workers.LoadAppListWorker import com.jeremyliao.liveeventbus.LiveEventBus import com.king.location.LocationClient import com.king.location.LocationErrorCode import com.king.location.OnExceptionListener import com.king.location.OnLocationListener +import com.xuexiang.xaop.util.PermissionUtils import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.file.FileUtils import frpclib.Frpclib @@ -141,6 +143,16 @@ class ForegroundService : Service() { 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 信息 if (SettingUtils.enableLoadAppList) { val request = OneTimeWorkRequestBuilder().build() @@ -169,8 +181,10 @@ class ForegroundService : Service() { } } - //远程找手机 - if (SettingUtils.enableLocationTag || HttpServerUtils.enableApiLocation) { + //远程找手机 TODO: 判断权限 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION + 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)//设置位置精度:高精度 .setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/task/CronJobScheduler.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/task/CronJobScheduler.kt new file mode 100644 index 00000000..388571f7 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/task/CronJobScheduler.kt @@ -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() + .setInputData(inputData) + .build() + } else { + Log.d(TAG, "延迟 $delayInMillis 毫秒执行任务${task.id}") + OneTimeWorkRequestBuilder() + .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) + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/TaskWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/TaskWorker.kt new file mode 100644 index 00000000..e1a43ec0 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/TaskWorker.kt @@ -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::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() + } + +}