Android WorkManager

2019-04-13  本文已影响0人  葫芦娃大战屎壳郎

本文主要内容

WorkManager使用详细介绍
自定义Worker的几种实现
使用注意事项

特性

关键类

使用方法

添加WorkManager依赖

dependencies {
    def work_version = 2.0.1
    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"
    
    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"
    
    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"
    
    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
 }

WorkManager 初始化

默认系统已经给我们初始化了WorkManager,无需自己初始化。但如果要自己初始化,需要在AndroidManifest.xml文件里面添加以下标签删除默认初始化:

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove" />

然后在Application.onCreate()中初始化WorkManager,通过WorkManager.getInstance()获取实例,初始化如下:

// provide custom configuration
Configuration myConfig = new Configuration.Builder()
    // 设置日志级别
    .setMinimumLoggingLevel(android.util.Log.INFO)
    // 设置后台任务线程池
    .setExecutor(Executors.newFixedThreadPool(8))
    .build();

// 初始化 WorkManager
WorkManager.initialize(this, myConfig);
// 获取实例
WorkManager workManager = WorkManager.getInstance()

执行一次性任务: OneTimeWorkRequest

public class MyWorker extends Worker {

    public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @Override
    public Result doWork() {
        Log.d(TAG, "I am working");
        // 任务结果
        Data data = new Data.Builder().putString("result", "I am result from MyWork").build();
        // 成功返回任务结果,实际结果包含在data对象里面,如果无需返回结果返回:Result.success()
        // 失败返回:Result.failure()
        // 需要重新执行返回:Result.retry()
        return Result.success(data);
    }
}
// 每个WorkRequest会初始化一个UUID作为唯一任务ID,查询任务状态或者取消任务都需要通过这个ID来实现
OneTimeWorkRequest request = new OneTimeWorkRequest
    // 任务为MyWork
    .Builder(MyWork.class)
    // 设置执行条件为充电时才执行
    .setConstraints(new Constraints.Builder().setRequiresCharging(true).build())
    // 设置延迟5分钟执行
    .setInitialDelay(5, TimeUnit.MINUTES)
    // 添加tag,用于标识任务,可以通过tag查询任务状态,取消任务等操作
    .addTag("one_time_request_tag")
    // 设置输入data,有点像输入参数,可以在Worker中通过getInputData()方法获取
    .setInputData(new Data.Builder().putString("input_data", "I am input data").build())
    .build();
// 添加任务到队列,任务会在条件满足的情况下才会执行 
WorkManager.getInstance().enqueue(request);

添加周期性任务:PeriodicWorkRequest

步骤跟添加一次性任务一样,只是WorkRequest从OneTimeRequest改为PeriodicWorkRequest,示例如下:

// 创建每隔30分钟执行一次的周期性任务请求
PeriodicWorkRequest request = new PeriodicWorkRequest
    .Builder(MyWork.class, 30, TimeUnit.MINUTES)
    .build();
// 添加任务
WorkManager.getInstance().enqueue(request);

\color{#FF0000}{注意:PeriodicWorkRequest周期性任务最小间隔时间为15分钟}

任务链(组合任务,关联任务)

比如有四个任务A,B,C,D,我们需要先执行完A和B两个任务,在执行C任务,最后在执行D任务,代码如下:

WorkManager.getInstance()
    // 先执行 A,B,并行执行
    .beginWith(Arrays.asList(A, B))
    // 等 A 和 B 都执行完成后,再执行 C
    .then(C)
    // 等C执行完,再执行 D
    .then(D)
    // Don't forget to enqueue()
    .enqueue();

上面提到任务之间数据的输入输出关系,如果某个任务的前置任务是由多人任务组成,比如上面的例子:任务A和B的结果都会传递到任务C中,但任务结果Data对象是通过key-value的方式存储数据,如果存在相同的key结果如何组合传递给C,所以这里需要一个组合策略InputMerger,在这里WorkManager提供了两种实现:

我们在创建任务C的WorkRequest时可通过setInputMerger()方法设置输入数据组合规则

unique 任务(唯一的任务)

unique任务在某种场景下会很实用,有点类似单例,指定名称的任务/任务链只有一个;再次添加相同名称的任务时,可以有几种处理方式:

使用方法如下,主要调用:enqueueUniqueWork方法

// 唯一任务
WorkManager.getInstance()
    // my_work 唯一任务标识,ExistingWorkPolicy(KEEP,REPLACE,APPEND)
    .enqueueUniqueWork("unique_work_name", ExistingWorkPolicy.REPLACE, request);

// 唯一任务链
WorkManager.getInstance()
    .beginUniqueWork("unique_work_chain_name", ExistingWorkPolicy.REPLACE, Arrays.asList(requestA, requestB))
    .then(requestC)
    .then(requestD)
    .enqueue();

获取任务状态,结果,取消任务

在任务整个生命周期中,有如下一些状态:

获取任务信息:WorkInfo
public WorkInfo(UUID id, State state, Data outputData, List<String> tags) {
    mId = id; // WorkRequest 的ID
    mState = state; // 任务当前状态
    mOutputData = outputData; // 任务执行结果
    mTags = new HashSet<>(tags); // 任务tag,在创建WorkRequest设置的
}

WorkManager提供了以下的方法查询WorkInfo信息,并提供了ListenableFuture和LiveData两种结果返回方式:

ListenableFuture<WorkInfo> getWorkInfoById(UUID id)
LiveData<WorkInfo> getWorkInfoByIdLiveData(UUID id)

ListenableFuture<List<WorkInfo>> getWorkInfosByTag(String tag)
LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(String tag)

ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(String uniqueWorkName);
LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData(String uniqueWorkName)

获取任务信息结合LiveData:

WorkManager.getInstance().getWorkInfoByIdLiveData(myWorkRequest.getId())
    .observe(lifecycleOwner, new Observer<WorkInfo>() {
        @Override
        public void onChanged(@Nullable WorkInfo workInfo) {
          if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
              displayMessage("You did good job!")
          }
        }
    });
取消任务

取消任务方法如下:

WorkManager.cancelAllWork()  取消所有任务
WorkManager.cancelWorkById(UUID id)  通过ID取消任务
WorkManager.cancelAllWorkByTag(String tag) 通过tag取消相关任务
WorkManager.cancelUniqueWork(String uniqueWorkName) 通过标识取消唯一任务

WorkManager的Work实现

WorkManager提供了4种Work:Worker, CoroutineWorker, RxWorker, ListenableWorker

WorkManager会在后台线程执行我们的任务,后台线程来自默认初始化时Configuration创建的Executor,当然我们也可以自己初始化WorkManager,在上面初始化WorkManager有讲到:

WorkManager.initialize(context,
    new Configuration.Builder()
        .setExecutor(Executors.newFixedThreadPool(8))
            .build());

CoroutineWorker是Kotlin提供优秀的协程实现,示例如下:

class CoroutineDownloadWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result = coroutineScope {
        val jobs = (0 until 100).map {
            async {
                downloadSynchronously("https://www.google.com")
            }
        }

        // awaitAll will throw an exception if a download fails, which CoroutineWorker will treat as a failure
        jobs.awaitAll()
        Result.success()
    }
}

在这里doWork()是阻塞方法,CoroutineWorker跟Worker不一样,它不在Executor中执行,而是通过CoroutineDispatcher执行,默认提供了Dispatchers.IO来执行后台任务,当然你也可以自定义CoroutineDispatcher(通过重载CoroutineWorker变量:open val coroutineContext 实现)

顾名思义是为RxJava提供的一种实现,通过继承RxWorker实现createWork()方法,返回的是RxJava中可订阅对象:Single<Result>,跟Observable差不多

public class RxDownloadWorker extends RxWorker {

    public RxDownloadWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Single<Result> createWork() {
        return Observable.range(0, 100)
            .flatMap { download("https://www.google.com") }
            .toList()
            .map { return Result.success() };
    }
}

注意createWork()方法是在主线程调用,返回结果在后台线程,通过重载 RxWorker.getBackgroundScheduler()自定义后台线程

ListenableWorker提供更底层的任务执行细节,其任务执行的入口方法是startWork()

public abstract @NonNull ListenableFuture<Result> startWork();

上面三种Worker都继承自它,通过实现startWork方法,执行任务不同调度方式,并抽象了具体任务doWork()方法,比如Worker的实现是:

public abstract @NonNull Result doWork();

@Override
public final @NonNull ListenableFuture<Result> startWork() {
    mFuture = SettableFuture.create();
    getBackgroundExecutor().execute(new Runnable() {
        @Override
        public void run() {
            try {
                Result result = doWork();
                mFuture.set(result);
            } catch (Throwable throwable) {
                mFuture.setException(throwable);
            }
        }
    });
    return mFuture;
}

如果你需要自己控制任务的执行(执行任务的线程),可以直接继承ListenableWorker,并是现实startWork()方法,示例:

public class MyThreadWorker extends ListenableWorker {

    public MyThreadWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        @SuppressLint("RestrictedApi")
        final SettableFuture<Result> result = SettableFuture.create();
        new Thread(new Runnable() {
            @SuppressLint("RestrictedApi")
            @Override
            public void run() {
                Log.d(TAG, "i am doing work");
                result.set(Result.success());
            }
        }).run();
        return result;
    }
}

注意事项

更多内容见 官方文档官方Demo

上一篇下一篇

猜你喜欢

热点阅读