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

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

问题描述

因为我需要在 WorkManager 中异步执行工作,所以我需要使用 ListenableWorker,它默认在主 (UI) 线程上运行.由于这项工作可能是一个很长的处理任务,可能会冻结界面,因此我想在后台线程上执行它.在使用 WorkManager(Android 开发者峰会 '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-10 秒)以高精度请求位置更新,即使在后台也是如此.即使应用程序未运行(但未强制停止)或通过按钮(这是一个大学项目),也可以通过 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 秒),你绝对应该使用 前台服务not WorkManager,根据 文档:

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 方法在主线程上运行,因为根据 setExecutor() 文档:

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

用于运行 Workers

具体来说,只有 ListenableWorkerWorker 子类> 在 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() on.

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 回调.这是给 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天全站免登陆