如何在后台线程上运行ListenableWorker工作?

Yan*_*rin 3 performance android background android-architecture-components android-workmanager

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

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

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="com.example.myapp.workmanager-init"
    tools:node="remove" />
Run Code Online (Sandbox Code Playgroud)

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

public class MyApplication extends Application {

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

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

@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();
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

ian*_*ake 8

如果要连续(例如,少于60秒),则绝对应使用前台服务,不要使用 WorkManager,根据文档

可延迟的异步任务

而不是需要连续运行的东西。

但是,如果您确实继续错误使用WorkManager,则需要牢记以下几点:

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

运行执行人工人小号

具体来说,只有Worker的子类ListenableWorkerExecutor- 而不是您的ListenableWorker实现提供的后台线程上运行。

根据ListenableWorker.startWork()文档

在主线程上调用此方法。

因为您正在使用ListenableWorkerstartWork所以按预期在主线程上调用了您的方法。由于您doWork()在同一线程上调用自己的方法,因此您仍将位于主线程上。

在您的情况下,您无需关心正在使用的线程,也不需要任何Executor线程,因为您调用的线程无关紧要getLastLocation()

相反,您只需要setResolvableFuture实际有结果时即在onSuccess()onFailure回调中调用即可。这WorkManager表明您实际上已经完成工作:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)