android 扩展列表_扩展Android WorkManager以执行相关的定期任务

本文介绍如何扩展Android的WorkManager来执行相关联的定期任务,以确保任务按预期顺序和条件执行。
摘要由CSDN通过智能技术生成

android 扩展列表

The android work manager API runs deferrable, guaranteed background work when specified constraints are satisfied. The API also supports both asynchronous one-off and periodic tasks. For more about Android WorkManager, please read the following articles:

当满足指定的约束条件时,Android Work Manager API将运行deferrable保证后台工作。 该API还支持异步一次性任务和定期任务。 有关Android WorkManager的更多信息,请阅读以下文章:

A Periodic WorkRequest is used to perform recurring deferrable tasks. This executes periodically with the first execution happening immediately the given Constraints are satisfied. For example, you may want to periodically backup your data, download fresh content in your app, push local data to a remote server, or upload logs to a server when constraints such as network availability, battery, or storage are satisfied.

定期工作WorkRequest用于执行重复的 WorkRequest 任务 。 这将定期执行,并且第一次执行将立即满足给定的Constraints 。 例如,当满足网络可用性,电池或存储之类的限制时,您可能希望定期备份数据,下载应用程序中的新鲜内容,将本地数据推送到远程服务器或将日志上传到服务器。

A deferrable task is one that does not need to be executed immediately.

一个 可延迟任务 是一个不需要立即执行。

The WorkManager API does not support chaining of PeriodicWorkRequest which makes it quite difficult to maintain a particular order for related dependent tasks as is done for a OneTimeWorkRequest.

WorkManager API不支持PeriodicWorkRequest 链接 PeriodicWorkRequest OneTimeWorkRequest .一样,这很难维护相关依赖任务的特定顺序OneTimeWorkRequest .

In this article, I will show with an example of how we can maintain some sort of order or chaining between dependent related tasks for periodic work requests.

在本文中,我将通过一个示例展示如何针对周期性的工作请求,如何在相关的 相关任务之间维持某种顺序链接

用例 (The Use Case)

Let us consider an app to be used for the collection of personal data from a community in some remote location. Due to the anticipated network constraint of the location, the app is expected to work offline and push the data to a remote server when there is sufficient network coverage.

让我们考虑一个应用程序,该应用程序用于从某个远程位置的社区中收集个人数据。 由于该位置的预期网络限制,因此该应用程序有望脱机运行,并在网络覆盖范围足够大时将数据推送到远程服务器。

The pushing or posting of local data to a remote server when there is sufficient network coverage passes for a deferrable task and as such a Periodic Work request can be used to perform such operation.

当有足够的网络覆盖范围来执行可延期任务时,将本地数据推送或发布到远程服务器,因此可以使用“周期性工作”请求来执行此类操作。

The app is to capture each person’s data as well as information about their beneficiaries. It follows that a person can have more than one beneficiary which is a typical one-to-many relationship as can be seen below:

该应用程序将捕获每个人的数据以及有关其受益人的信息。 因此,一个人可以有多个受益人,这是典型的一对多关系,如下所示:

Image for post
Figure 1: Entity Relationship Diagram
图1:实体关系图

From figure 1, a Beneficiary cannot exist without a Person, hence, the creation of a Beneficiary presupposes that a Person must have been created.

从图1中可以看出,如果没有人,那么受益人就不会存在,因此,创建受益人的前提是必须已经创建了人。

For the offline app, the local data source(Android Room in this case) would be the single source of truth. As such, the primary key identifier for data stored locally would drive every CRUD operation we perform on the data.

对于离线应用程序,本地数据源(在这种情况下为Android Room)将是唯一的事实来源。 这样,本地存储的数据的主键标识符将驱动我们对数据执行的每个CRUD操作。

Furthermore, we can deduce the following from figure 1 for the Person entity:

此外,我们可以从图1推断出Person实体:

  • localId : This is the primary key for the “person” entity. It identifies the record in the local database.

    localId :这是“个人”实体的主键。 它标识本地数据库中的记录。

  • id : This is the primary key for the record in the remote database. It is null until the record is pushed to the remote database after which it is updated locally with the record id value from the remote.

    id :这是远程数据库中记录的主键。 在记录被推送到远程数据库之前,它为null ,然后使用来自远程的记录ID值在本地对其进行更新。

  • state : This is used to know whether or not the local data has been pushed to the remote. It is set to pending when the data is resident in the local storage after which it is updated to synced state once the data is pushed to the remote.

    state :用于了解本地数据是否已推送到远程。 当数据驻留在本地存储中时,它将设置为pending ,一旦将数据推送到远程,数据将更新为synced状态。

The data class for Person is shown below:

Person的数据类如下所示:

package com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.model


import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName


@Entity
data class Person(
    @Expose(serialize = false)
    @PrimaryKey val localId: Int?, // local Person id
    @Expose(serialize = false)
    var id: Long?,  // remote Person id
    @SerializedName("firstName")
    @Expose
    var firstName: String,
    @SerializedName("lastName")
    @Expose
    var lastName: String,
    @Expose(serialize = false)
    var state: String?
) {
    constructor(firstName: String, lastName: String, state: String?) : this(
        null,
        null,
        firstName,
        lastName,
        state
    )
}

Similarly, we can deduce the following from figure 1 for the Beneficiary entity:

同样,我们可以从图1中得出受益实体的以下内容:

  • localId : This is the primary key for the “beneficiary” entity. It identifies the record in the local database.

    localId :这是“受益人”实体的主键。 它标识本地数据库中的记录。

  • id : This primary key for the record in the remote database.

    id :远程数据库中记录的此主键。

  • personLocalId : This represents the foreign key in the person-to-beneficiary relationship locally.

    personLocalId :这表示本地人与受益人关系中的外键。

  • state : This is used to know whether or not the local data has been pushed to the remote. It could either be pending or synced

    state :用于了解本地数据是否已推送到远程。 它可以处于pendingsynced

The data class for Beneficiary is shown below:

受益人的数据类别如下所示:

package com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.model


import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName


@Entity
data class Beneficiary(
    @Expose(serialize = false)
    @PrimaryKey val localId: Int?, //local Beneficiary id


    @SerializedName("firstName")
    @Expose
    var firstName: String,


    @SerializedName("lastName")
    @Expose
    var lastName: String,


    @SerializedName("relationship")
    @Expose
    var relationship: String,


    @Expose(serialize = false)
    var personLocalId: Int?, //local Person id


    @Expose(serialize = false)
    var id: Long?,   // remote Beneficiary id


    @SerializedName("personId")
    @Expose
    var personId: Long?, // remote Person id


    @Expose(serialize = false)
    var state: String?
) {
    constructor(
        personLocalId: Int?,
        firstName: String,
        lastName: String,
        relationship: String,
        state: String?
    ) : this(null, firstName, lastName, relationship, personLocalId, null, null, state)
}

挑战 (The Challenge)

Since we have resolved to use a Periodic WorkRequest, how then do we ensure some form of order for the parent-child relationship that exists between the Person and Beneficiary entities?

既然我们决定使用“定期工作请求”,那么我们如何确保受益人实体之间存在的父子关系某种形式的顺序?

That is, how do we ensure that the Person(parent) entity is pushed to the remote before the Beneficiary(child) entity is pushed.

也就是说,我们如何确保在将受益人 (子实体)推送之前将“ 人” (父)实体推送到远程。

This is necessary because the remote backend API requires that a Beneficiary is created with the remote id of the Person entity.

这是必需的,因为远程后端API要求使用Person实体的远程id创建受益人。

The above presents a daunting challenge since periodic work requests cannot be chained. To solve this, we need to device a means to ensure that the order of precedence as explained in the preceding paragraph is followed.

上面提出了一个艰巨的挑战,因为周期性的工作要求不能被束缚。 为了解决这个问题,我们需要采用一种手段来确保遵循上一段所述的优先顺序。

拟议的解决方案 (The Proposed Solution)

To solve this problem, we will take the following steps:

要解决此问题,我们将采取以下步骤:

  1. Create an abstract Worker Contract class that extends Android Worker and defines our custom rules.

    创建一个抽象的Worker Contract类,以扩展Android Worker并定义我们的自定义规则。

  2. Create worker classes for Person and Beneficiary that extends from the Worker Contract class defined in step 1.

    为人员和受益人创建工人类,该类来自步骤1中定义的“工人合同”类。
  3. Create Periodic work requests using the worker classes created in step 2.

    使用在步骤2中创建的工作程序类创建定期工作请求。

Let’s dive into the details of each step.

让我们深入研究每个步骤的细节。

第1步:创建工人合同 (Step 1: Create a Worker Contract)

Image for post
Figure 2: Worker Contract that Extends from Android Worker
图2:从Android Worker扩展的Worker合同

This is an abstract class that defines the rules for pushing data stored locally to the remote data source. The contract shows the various conditions that must be met before an entity can be pushed to the remote. The class extends Android Worker and overrides the doWork() method wherein the rules and order of execution of the periodic tasks are defined as can be seen in the code snippet below. It also contains various methods as explained with the comments shown in the code snippet below:

这是一个抽象类,定义了将本地存储的数据推送到远程数据源的规则。 合同显示了将实体推送到远程对象之前必须满足的各种条件。 该类扩展了Android Worker并覆盖了doWork ()方法,该方法定义了定期任务的执行规则和执行顺序,如下面的代码片段所示。 它还包含各种方法,如下面的代码片段中所示的注释所述:

package com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.worker


import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.dependency.Injector
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.util.AppExecutor
import retrofit2.Call
import retrofit2.Response
import timber.log.Timber


/**
 * A generic class that defines the contract for synchronization of local data stored in {@link androidx.room.Room} database with a remote data source.
 * The contract shows the various conditions that must be met before an [Entity] can be synchronized with the remote.
 * <p>
 * It extends the {@link Worker} class that performs work synchronously on a background thread provided.
 * The aim is to leverage the {@link Worker} to perform Periodic task of synchronization of local data with the remote data source
 *
 * @param <ResultType>   This is the Generic Entity present in the local database
 * @param <ResponseType> This is the Generic Response Entity from the remote
 */
abstract class WorkerContract<ResultType, ResponseType>(
    context: Context,
    workerParams: WorkerParameters
) : Worker(context, workerParams) {
    private var appExecutor: AppExecutor = Injector.provideExecutors()


    override fun doWork(): Result {
        return try {
            val pendingItems: MutableList<ResultType> = loadPendingItemsFromDb()
            when {
                pendingItems.isNotEmpty() -> {
                    //loop through and check if conditions for posting are met
                    for (resultType in pendingItems) {
                        //checks if the Entity has a parent relationship
                        if (hasParentRelationship(resultType)) {
                            if (checkParentStatus(resultType))  //checks if the Parent Entity state is [SYNCED] or [PENDING] before posting
                                postToRemote(resultType) //posts the data to the remote if and only if all posting conditions have been met
                        } else {
                            //Entity does not have a Parent relationship, most likely it is a Parent to other Entities
                            postToRemote(resultType) //posts the data to the remote if and only if all posting conditions have been met
                        }
                    }
                    Result.success()
                }
                else -> {
                    //No pending items were found, so we return Result.retry() so it should be retried with backoff specified.
                    Timber.i("Pending items is empty for entity, retrying worker.....")
                    Result.retry()
                }
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
            //return FAILURE if there were errors
            Timber.e(ex, "Error executing worker ")
            Result.failure()
        }
    }


    @Throws(java.lang.Exception::class)
    private fun postToRemote(resultType: ResultType) {
        val apiResponse: Response<ResponseType> = createCall(resultType).execute()
        if (apiResponse.isSuccessful) {
            appExecutor.diskIO().execute {
                //process response if necessary
                val processedResponse: ResponseType = processResponse(apiResponse)
                //saves the result/data from network to db, this is done in a background thread
                saveCallResult(processedResponse, resultType)
            }
        } else {
            //posting was not successful, so we return failure
            onPostFailed(resultType)
        }
    }


    /**
     * This queries the local database for items that are in the [pending] state
     * It is these items that are eligible for posting
     *
     * @return List of pending items  from the local database to be posted.
     * The entities must be in the [pending] state
     */
    protected abstract fun loadPendingItemsFromDb(): MutableList<ResultType>


    /**
     * This checks whether the Entity is a Parent or Child.
     * It presupposes that the developer knows which Entity is a Parent or Child
     * as such the concrete class returns the appropriate boolean
     *
     * @param resultType Entity to be checked
     * @return [true] or [false] if the Entity has a child of a parent Entity or not
     */
    protected abstract fun hasParentRelationship(resultType: ResultType): Boolean


    /**
     * This checks the Parent Entity to ascertain if has been synchronized with the remote.
     * For Posting to occur, the Parent Entity must be in the [synced] state
     *
     * @param resultType Entity to be checked
     * @return [true] or [false] if the Parent Entity is [synced] or not
     */
    protected abstract fun checkParentStatus(resultType: ResultType): Boolean


    /**
     * This creates a call to post the local data to the remote
     *
     * @param resultType local data to be posted
     * @return ResponseType from the remote
     */
    protected abstract fun createCall(resultType: ResultType): Call<ResponseType>


    /**
     * This saves the result from the network operation to the database.
     * It basically updates the existing local data state
     *
     * @param responseData the response data from the remote source
     * @param localData    the existing local data
     */
    protected abstract fun saveCallResult(responseData: ResponseType, localData: ResultType)


    protected open fun processResponse(response: Response<ResponseType>): ResponseType {
        return response.body()!!
    }


    /**
     * Called if you need to inform the user of the failure of the operation e.g to make a notification
     *
     * @param resultType
     */
    protected open fun onPostFailed(resultType: ResultType) {}
}

The WorkerContract is the “engine” of this solution, as it brings all the pieces together and ensures that the required order of precedence is adhered to strictly.

WorkerContract是此解决方案的“引擎”,因为它将所有要素组合在一起,并确保严格遵守所需的优先顺序。

Depending on your scenario, your contract rules might be different.

根据您的方案,您的合同规则可能会有所不同。

步骤2:创建工作者类 (Step 2: Create Worker classes)

With the worker contract well defined, we need to create worker classes that provide a concrete implementation of the WorkerContract for both Person and Beneficiary entities.

定义好工人合同后,我们需要创建工人类, WorkerContract和受益人实体提供具体的工人合同实施。

The PersonWorker extends from the WorkerContract and provides the logic for pushing Person data to the remote server. The code snippet below explains the PersonWorker class.

所述PersonWorker从延伸WorkerContract并提供逻辑用于推动Person数据发送到远程服务器。 下面的代码片段说明了PersonWorker类。

package com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.worker


import android.content.Context
import androidx.work.WorkerParameters
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.CommunityApp
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.db.CommunityDao
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.model.Person
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.service.CommunityService
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.util.Constant
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.util.WorkerUtils
import retrofit2.Call
import timber.log.Timber


class PersonWorker(context: Context, workerParams: WorkerParameters) :
    WorkerContract<Person, Person>(context, workerParams) {
    private val tag: String = PersonWorker::class.java.simpleName
    private var communityService: CommunityService
    private var communityDao: CommunityDao


    init {
        val communityApp: CommunityApp = CommunityApp.instance
        communityService = communityApp.getEmployeeService()
        communityDao = communityApp.getEmployeeDao()
    }


    override fun loadPendingItemsFromDb(): MutableList<Person> {
        Timber.i("loading pending People from db..........")
        return communityDao.findPendingPeople()
    }


    override fun hasParentRelationship(resultType: Person): Boolean {
        //return false since this is the parent Entity
        return false
    }


    override fun checkParentStatus(resultType: Person): Boolean {
        //return false since this is the parent Entity
        return false
    }


    override fun createCall(resultType: Person): Call<Person>{
        Timber.i("%s: creating call...", tag)
        return communityService.createPerson(resultType)
    }


    /* updates the local data with the remote ID as well as the state to [SYNCED] **/
    override fun saveCallResult(responseData: Person, localData: Person) {
        localData.id = responseData.id
        localData.state = Constant.SYNCED
        communityDao.savePerson(localData)
    }


    override fun onPostFailed(resultType: Person) {
        WorkerUtils.makeStatusNotification(
            """failed to save ${resultType.firstName} ${resultType.lastName}""",
            applicationContext
        )
    }
}

As can be seen from the saveCallResult method, once the local data is pushed successfully, the local data state and the remote id are updated accordingly.

saveCallResult方法可以看出,一旦成功推送本地数据,本地数据state和远程id地更新。

Similarly, the BeneficiaryWorker implementation is shown below. Here, the hasParentRelationship method is set to return true because it is the child entity. Once the hasParentRelationship method returns true , we need to check if the Person entity associated with the Beneficiary via the personLocalId field has been pushed to the remote. This is done via the checkParentStatus() method which returns a boolean value depending on the state of the parent entity. This is shown in the code snippet below:

同样,下面显示了BeneficiaryWorker实现。 在这里, hasParentRelationship方法设置为返回true因为它是子实体。 一旦hasParentRelationship方法返回true ,我们需要检查通过personLocalId字段与Beneficiary关联的Person实体是否已被推送到远程。 这是通过checkParentStatus()方法完成的,该方法根据父实体的状态返回布尔值。 这显示在下面的代码片段中:

package com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.worker


import android.content.Context
import androidx.work.WorkerParameters
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.CommunityApp
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.db.CommunityDao
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.model.Beneficiary
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.model.Person
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.service.CommunityService
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.util.Constant
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.util.WorkerUtils
import retrofit2.Call
import timber.log.Timber


class BeneficiaryWorker(context: Context, workerParams: WorkerParameters) :
    WorkerContract<Beneficiary, Beneficiary>(context, workerParams) {
    private val tag: String = BeneficiaryWorker::class.java.simpleName
    private var communityService: CommunityService
    private var communityDao: CommunityDao


    init {
        val communityApp: CommunityApp = CommunityApp.instance
        communityService = communityApp.getEmployeeService()
        communityDao = communityApp.getEmployeeDao()
    }


    override fun loadPendingItemsFromDb(): MutableList<Beneficiary> {
        Timber.i("loading pending Beneficiaries from db..........")
        return communityDao.findPendingBeneficiaries()
    }


    override fun hasParentRelationship(resultType: Beneficiary): Boolean {
        //return true since it is a child Entity
        return true
    }


    override fun checkParentStatus(resultType: Beneficiary): Boolean {
        Timber.i("%s: in checkParentStatus.........", tag)
        val person: Person = communityDao.findPeopleByLocalId(resultType.personLocalId!!)
        return person.state.equals(Constant.SYNCED, ignoreCase = true)
    }


    override fun createCall(resultType: Beneficiary): Call<Beneficiary> {
        Timber.i("%s: creating call...", tag)
        //get the remote id of the parent entity
        val person = communityDao.findPeopleByLocalId(resultType.personLocalId!!)
        resultType.personId = person.id
        return communityService.createBeneficiary(resultType)
    }


    /* updates the local data with the remote ID as well as the state to [SYNCED] **/
    override fun saveCallResult(responseData: Beneficiary, localData: Beneficiary) {
        localData.id = responseData.id
        localData.personId = responseData.personId
        localData.state = Constant.SYNCED
        communityDao.saveBeneficiary(localData)
    }


    override fun onPostFailed(resultType: Beneficiary) {
        WorkerUtils.makeStatusNotification(
            """failed to save ${resultType.firstName} ${resultType.lastName}""",
            applicationContext
        )
    }
}

步骤3:建立定期工作要求 (Step 3: Create Periodic Work Requests)

In this step, we will create periodic work requests for each worker since we have successfully defined our rules via the WorkerContract and also created concrete implementations of the WorkerContract.

在此步骤中,由于我们已通过WorkerContract成功定义了规则并创建了WorkerContract.具体实现,因此我们将为每个工作者创建定期工作请求WorkerContract.

First, we need to define the network constraints as shown below. That is, we want to push the local data once there is network connection.

首先,我们需要定义网络约束,如下所示。 也就是说,我们要在建立网络连接后推送本地数据。

val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()

The periodic work request for saving a person is shown below.

保存人的定期工作要求如下所示。

val periodicSyncDataWork =
            PeriodicWorkRequest.Builder(PersonWorker::class.java, 15, TimeUnit.MINUTES)
                .addTag(Constant.TAG_SYNC_PERSON)
                .setConstraints(constraints) // setting a backoff on case the work needs to retry
                .setBackoffCriteria(
                    BackoffPolicy.LINEAR,
                    PeriodicWorkRequest.MIN_BACKOFF_MILLIS,
                    TimeUnit.MILLISECONDS
                ).build()
        getWorkManager()!!.enqueueUniquePeriodicWork(
            Constant.SYNC_PERSON_WORK_NAME,
            ExistingPeriodicWorkPolicy.KEEP,  //Existing Periodic Work policy
            periodicSyncDataWork //work request
        )

Similarly, the Beneficiary work request is shown below:

同样,受益人的工作要求如下所示:

val periodicSyncDataWork = PeriodicWorkRequest.Builder(BeneficiaryWorker::class.java, 15, TimeUnit.MINUTES)
            .addTag(Constant.TAG_SYNC_BENEFICIARY)
            .setConstraints(constraints) // setting a backoff on case the work needs to retry
            .setBackoffCriteria(
                BackoffPolicy.LINEAR,
                PeriodicWorkRequest.MIN_BACKOFF_MILLIS,
                TimeUnit.MILLISECONDS
            )
            .build()
        getWorkManager()!!.enqueueUniquePeriodicWork(
            Constant.SYNC_BENEFICIARY_WORK_NAME,
            ExistingPeriodicWorkPolicy.KEEP,  //Existing Periodic Work policy
            periodicSyncDataWork //work request
        )

From the work requests above, note that we used a unique periodic work request to ensure that only one PeriodicWorkRequest of a particular name can be active at a time. For our case, we only want one operation to be active at a time; if there is a pending one, we will let it run to completion, otherwise, we insert the new work.

从上面的工作请求中,请注意,我们使用了唯一的定期工作请求,以确保一次只能激活一个具有特定名称的PeriodicWorkRequest 。 对于我们的情况,我们一次只希望一个操作处于活动状态。 如果有一个待处理的项目,我们将使其运行完成,否则,将插入新工作。

结果 (Result)

Essentially, the person and beneficiary workers would start at the same time and will be executed as soon as the defined constraints are met. However, the Beneficiary worker would not be able to push data to the remote data source until the related person entity has been pushed as specified by the entity-relationship and the Worker Contract. By this, we can achieve a sense of order and precedence in the execution of the periodic tasks.

本质上,人员和受益人将同时开始工作,并在满足定义的约束条件后立即执行。 但是,直到实体关系和“工人合同”指定的相关人员实体已被推送,收款人工人才能将数据推送到远程数据源。 这样,我们可以在执行周期性任务时获得顺序感和优先级。

Note that the solution presented in this article is by no means a general one. It is only specific to this use case. However, it gives a general idea as to how problems of the kind may be solved.

请注意,本文介绍的解决方案绝不是通用解决方案。 它仅特定于此用例。 但是,它给出了有关如何解决此类问题的一般思路。

输出量 (Output)

Image for post

You can find the source code for the android client and spring-boot backend in the repository below.

您可以在下面的存储库中找到android客户端和spring-boot后端的源代码。

Thanks for reading and feel free to leave your comments and suggestions.

感谢您的阅读,并随时留下您的意见和建议。

翻译自: https://medium.com/swlh/extending-android-workmanager-for-dependent-periodic-tasks-7b14ab440d0c

android 扩展列表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值