美文网首页
Android WorkManager

Android WorkManager

作者: 葫芦娃大战屎壳郎 | 来源:发表于2019-04-13 12:15 被阅读0次

    本文主要内容

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

    特性

    • 兼容Android API 14+
    • API 23+使用JobScheduler
    • API 14-22 使用BroadcastReceiver + AlarmManager
    • 可配置任务执行约束条件(比如在有网络时,充电时,电量充足时等)
    • 执行一次性任务和周期性任务
    • 跟踪管理任务:取消任务,获取任务结果
    • 支持串联执行任务,可设置任务执行顺序
    • 保证任务执行,即使是app或者设备重启了

    关键类

    • WorkManager
      采用单例模式(WorkManager.getInstance()),用于任务管理(添加,取消,获取任状态果,结果等)
    • Worker
      抽象任务类,任务抽象方法为:doWork()
    • WorkRequest :
      任务请求抽象类,WorkManager通过WorkRequest添加任务,其实现类有:OneTimeWorkRequest和PeriodicWorkRequest
    • OneTimeWorkRequest
      一次性任务请求
    • PeriodicWorkRequest
      周期性任务请求(注意:周期性任务最小间隔时间为15分钟)
    • Constraints
      执行任务的约束条件,比如需要充电状态才执行(mRequiresCharging),电量充足时才执行(mRequiresBatteryNotLow)等等
    • WorkInfo
      任务相关信息,包括WorkRequest产生的ID(UUID),当前任务状态,结果数据,标识Tag,通过:WorkManager.getWorkInfoById(UUID) or WorkManager.getWorkInfoByIdLiveData(UUID)获得

    使用方法

    添加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

    • 执行任务前,首先需自定任务,继承Worker抽象类,实现抽象方法doWork()即可,doWork()就是任务方法(有点类似Runnable接口的run()方法):
    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,执行一次性任务使用:OneTimeWorkRequest
    // 每个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();
    
    • 第三步将OneTimeWorkRequest添加到任务队列
    // 添加任务到队列,任务会在条件满足的情况下才会执行 
    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分钟}

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

    • 可以将多个任务组合起来,可设置执行的先后顺序
    • 任务之间的结果又输入输出关系,上一个任务的输出数据会是下一个任务输入数据,可通过Worker.getInputData()获取输入数据,doWork()方法的返回值为输出数据:Result.success(data)
    • 任务链只能用于OneTimeWorkRequest

    比如有四个任务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提供了两种实现:

    • OverwritingInputMerger 如果存在相同的key,就会覆盖
    • ArrayCreatingInputMerger 合并结果,组合成一个列表

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

    unique 任务(唯一的任务)

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

    • KEEP :保留原来的任务,新添加的任务会被忽略
    • REPLACE :替换原来的任务,原来的任务会被取消掉
    • APPEND :等原来的任务执行完后,再执行新添加的任务(这里貌似对唯一性的理解有些牵强,可以理解成正执行任务唯一性)

    使用方法如下,主要调用: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();
    

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

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

    • ENQUEUED :进入队列状态,在条件满足的时候就会执行
    • RUNNING :正在执行
    • SUCCEEDED :doWork()返回Result.success() 后的状态,表明任务执行成功,这种状态只会在OneTimeWorkRequest中出现
    • FAILED :doWork()返回Result.failure() 后的状态,表示任务执行失败,也只会在OneTimeWorkRequest中出现
    • BLOCKED : 阻塞状态,一般出现在前一个任务还没有执行完(在unique任务的APPEND操作中)
    • CANCELLED :当通过WorkManager取消一个没有执行完成的任务后的状态
    获取任务信息:WorkInfo
    • WorkInfo包含了: 任务id(UUID), 任务状态(State),任务结果(Data),任务tag(List<String>)
    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!")
              }
            }
        });
    
    取消任务
    • 取消任务框架并不能保证任务不被执行(比如有些任务已经开始执行了或者已经执行完成了)
    • 如果是耗时任务,在我们实现Worker的doWork()方法时可通过isStopped()方法检测任务是否已被停止,如果被停止了则结束任务执行,并回收资源

    取消任务方法如下:

    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

    • Worker

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

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

    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 实现)

    • RxWorker

    顾名思义是为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

    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;
        }
    }
    

    注意事项

    • Worker 都是通过反射实例化的,所以要注意自定义Worker的访问权限,构造函数为public,内部类记得加上 public static 修饰符
    • WorkManager 适用于延期执行任务,或者是需要在app退出或者设备重启后也能确保任务执行
    • WorkManager不适用场景:正在运行的进程提供后台任务,需要立即执行的后台任务
    • PeriodicWorkRequest最小间隔时间为15分钟

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

    相关文章

      网友评论

          本文标题:Android WorkManager

          本文链接:https://www.haomeiwen.com/subject/hrylwqtx.html