如何在后台线程上运行ListenableWorker工作? [英] How do I run a ListenableWorker work on a background thread?

查看:209
本文介绍了如何在后台线程上运行ListenableWorker工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于我需要在WorkManager中异步执行工作,因此需要使用ListenableWorker,该默认情况下在主(UI)线程上运行.由于这项工作可能是很长的处理任务,可能会冻结接口,因此我想在后台线程上执行它.在使用WorkManager(Android Dev Summit '18)视频中, Google工程师展示了如何手动配置WorkManager以在自定义Executor上运行作品,因此我遵循了他的指导:

Since I need to perform work asynchronously in WorkManager, I need to use the ListenableWorker, which by default runs on the main (UI) thread. Since this work could be a long processing tasks that could freeze the interface, I wanted to perform it on a background thread. In the Working with WorkManager (Android Dev Summit '18) video, the Google engineer showed how to manually configure WorkManager to run works on a custom Executor, so I followed his guidance:

1)在AndroidManifest中禁用默认的WorkManager初始化程序:

1) Disable the default WorkManager initializer in the AndroidManifest:

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="com.example.myapp.workmanager-init"
    tools:node="remove" />

2)在Application.onCreate中,使用自定义配置初始化WorkManager,在我的情况下是这样的:

2) In Application.onCreate, initialize WorkManager with the custom configuration, which in my case is this:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration = new Configuration.Builder().setExecutor(Executors.newSingleThreadExecutor()).build();
        WorkManager.initialize(this, configuration);
    }
}

现在我的实际ListenableWorker是这样的:

Now my actual ListenableWorker is this:

@NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        Log.d(TAG, "Work started.");
        mFuture = ResolvableFuture.create();
        Result result = doWork();
        mFuture.set(result);
        return mFuture;
    }

    private Result doWork() {
        Log.d(TAG, "isMainThread? " + isMainThread());
        mFusedLocationProviderClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
            @Override
            public void onSuccess(Location location) {
                if (location != null) {
                    // Since I still don't know how to communicate with the UI, I will just log the location
                    Log.d(TAG, "Last location: " + location);
                    return Result.success();
                } else {
                    return Result.failure();
                }
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                e.printStackTrace();
                return Result.failure();
            }
        });
    }

    private boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

即使我指定Executor WorkManager应该用作新的后台线程,为什么isMainThread()方法返回true,为什么我实际上如何在后台线程上运行该工作呢?

Why does the isMainThread() method return true even though I specified the Executor WorkManager should use as a new background thread and how can I actually run that piece of work on a background thread?

编辑:ListenableWorker,需要CountDownLatch.
由于我需要在每次工作成功时重新安排工作的时间(PeriodicWorkRequest的最小间隔为15分钟的变通方法),因此我需要在上一部分工作返回成功后再进行工作,否则我的行为会很奇怪.这是必需的,因为显然ExistingWorkPolicy.APPEND不能按预期工作.
用例是即使在后台,也要以相当频繁的间隔(5-10s)高精度请求位置更新.即使应用程序未运行(但不是强制停止),也可以通过SMS来打开和关闭,或者通过按钮(这是一个大学项目)来打开和关闭.

ListenableWorker with the need of a CountDownLatch.
Since I need to reschedule the work everytime it succeeds (workaround for the 15 minutes minimum interval for PeriodicWorkRequest), I need to do it after the previous piece of work returns success, otherwise I have weird behavior. This is needed because, apparently, ExistingWorkPolicy.APPEND doesn't work as expected.
Use case is to request location updates with high accuracy at a pretty frequent interval (5-10s), even in background. Turned on and off by an SMS, even when the app is not running (but not force-stopped), or through a button (It's an university project).

public class LocationWorker extends ListenableWorker {

    static final String UNIQUE_WORK_NAME = "LocationWorker";
    static final String KEY_NEW_LOCATION = "new_location";
    private static final String TAG = "LocationWorker";
    private ResolvableFuture<Result> mFuture;
    private LocationCallback mLocationCallback;
    private CountDownLatch mLatch;
    private Context mContext;

    public LocationWorker(@NonNull final Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
        mContext = appContext;
        Utils.setRequestingLocationUpdates(mContext, true);
        mLatch = new CountDownLatch(1);
        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                LocationUtils.getInstance(mContext).removeLocationUpdates(this);
                Location location = locationResult.getLastLocation();
                Log.d(TAG, "Work " + getId() + " returned: " + location);
                mFuture.set(Result.success(Utils.getOutputData(location)));
                // Rescheduling work
                OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(LocationWorker.class).setInitialDelay(10, TimeUnit.SECONDS).build();
                WorkManager.getInstance().enqueueUniqueWork(LocationWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request);
                Log.d(TAG, "Rescheduling work. New ID: " + request.getId());
                // Relase lock
                mLatch.countDown();
            }
        };
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        Log.d(TAG, "Starting work " + getId());
        mFuture = ResolvableFuture.create();
        LocationUtils.getInstance(mContext).requestSingleUpdate(mLocationCallback, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                LocationUtils.getInstance(mContext).removeLocationUpdates(mLocationCallback);
                Utils.setRequestingLocationUpdates(mContext, false);
                WorkManager.getInstance().cancelUniqueWork(UNIQUE_WORK_NAME);
                mFuture.set(Result.failure());
                // Relase lock
                mLatch.countDown();
            }
        });
        try {
            mLatch.await(5L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return mFuture;
    }
}

推荐答案

如果要连续(即少于60秒),则绝对应该使用文档:

If you want to continuously (i.e., less than every 60 seconds), you absolutely should be using a foreground service and not WorkManager, which is for, as per the documentation:

可延迟的异步任务

不需要连续运行的东西.

And not something that needs to run near continously.

但是,如果您确实不正确地使用WorkManager,则需要牢记以下几点:

However, if you do proceed to incorrectly use WorkManager, you'd want to keep the following in mind:

您的自定义doWork方法在主线程上运行,因为根据

Your custom doWork method runs on the main thread because as per the setExecutor() documentation:

用于运行 Worker s

具体来说,只有ListenableWorker Worker 子类在后台运行Executor-不是您的ListenableWorker实现提供的线程.

Specifically, only the Worker subclass of ListenableWorker runs on a background thread provided by the Executor - not your ListenableWorker implementation.

根据 ListenableWorker.startWork()文档:

在主线程上调用此方法.

This method is called on the main thread.

由于使用的是ListenableWorker,因此将按预期在主线程上调用startWork方法.由于您在同一线程上调用自己的doWork()方法,因此您仍将位于主线程上.

Because you're using ListenableWorker, your startWork method is being called on the main thread, as expected. Since you call your own doWork() method on the same thread, you'll still be on the main thread.

在您的情况下,您无需关心正在使用的线程,也不需要任何Executor,因为您在哪个线程上调用getLastLocation()都没有关系.

In your case, you don't need to care about what thread you're on and you don't need any Executor since it doesn't matter what thread you call getLastLocation() on.

相反,只有在实际有结果时,即在onSuccess()onFailure回调中,才需要在ResolvableFuture上调用set.这是向WorkManager发出信号的信号,表明您实际上已经完成工作:

Instead, you need to only call set on your ResolvableFuture when you actually have a result - i.e., in the onSuccess() or onFailure callbacks. This is the signal to WorkManager that you're actually done with your work:

public class LocationWorker extends ListenableWorker {

    static final String UNIQUE_WORK_NAME = "LocationWorker";
    static final String KEY_NEW_LOCATION = "new_location";
    private static final String TAG = "LocationWorker";
    private ResolvableFuture<Result> mFuture;
    private LocationCallback mLocationCallback;

    public LocationWorker(@NonNull final Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        Log.d(TAG, "Starting work " + getId());
        mFuture = ResolvableFuture.create();
        Utils.setRequestingLocationUpdates(getApplicationContext(), true);
        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                LocationUtils.getInstance(getApplicationContext()).removeLocationUpdates(this);
                Location location = locationResult.getLastLocation();
                Log.d(TAG, "Work " + getId() + " returned: " + location);
                // Rescheduling work
                OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(LocationWorker.class).setInitialDelay(10, TimeUnit.SECONDS).build();
                WorkManager.getInstance().enqueueUniqueWork(LocationWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request);
                Log.d(TAG, "Rescheduling work. New ID: " + request.getId());

                // Always set the result as the last operation
                mFuture.set(Result.success(Utils.getOutputData(location)));
            }
        };
        LocationUtils.getInstance(getApplicationContext()).requestSingleUpdate(mLocationCallback, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                LocationUtils.getInstance(getApplicationContext()).removeLocationUpdates(mLocationCallback);
                Utils.setRequestingLocationUpdates(getApplicationContext(), false);
                WorkManager.getInstance().cancelUniqueWork(UNIQUE_WORK_NAME);
                mFuture.set(Result.failure());
            }
        });
        return mFuture;
    }
}

这篇关于如何在后台线程上运行ListenableWorker工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆