我如何 运行 ListenableWorker 在后台线程上工作?
How do I run a ListenableWorker work on a background thread?
因为我需要在 WorkManager 中异步执行工作,所以我需要使用 ListenableWorker
,默认情况下 运行 在主 (UI) 线程上。由于这项工作可能是一个很长的处理任务,可能会冻结界面,所以我想在后台线程上执行它。在 Working with WorkManager (Android Dev Summit '18) video 中,Google 工程师展示了如何手动配置 WorkManager 以 运行 在自定义 Executor
上工作,所以我遵循了他的指导:
1) 在 AndroidManifest 中禁用默认的 WorkManager 初始化程序:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="com.example.myapp.workmanager-init"
tools:node="remove" />
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);
}
}
现在我的实际 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();
}
为什么 isMainThread()
方法 return 为真,即使我指定 Executor
WorkManager 应该用作新的后台线程,我如何才能真正 运行 那块在后台线程上工作?
编辑: ListenableWorker
需要 CountDownLatch
.
由于我需要在每次成功时重新安排工作(PeriodicWorkRequest
的最小间隔为 15 分钟的解决方法),我需要在前一项工作 return 成功后进行,否则我会出现奇怪的行为.这是必需的,因为显然 ExistingWorkPolicy.APPEND
没有按预期工作。
用例是以相当频繁的间隔(5-10 秒)请求高精度的位置更新,即使在后台也是如此。通过短信打开和关闭,即使应用程序未 运行ning(但未强制停止),或通过按钮(这是一个大学项目)。
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 秒),你 绝对 应该使用 foreground service and not WorkManager, which is for, as per the documentation:
deferrable, asynchronous tasks
而不是需要不断运行靠近的东西。
但是,如果您继续错误地使用 WorkManager,则需要牢记以下几点:
您在主线程上的自定义 doWork
方法 运行s 因为根据 setExecutor() documentation:
An Executor for running Workers
具体来说,只有 ListenableWorker
运行 的 Worker 子类在 Executor
提供的后台线程上 - 不是 你的 ListenableWorker
实现。
根据 ListenableWorker.startWork() documentation:
This method is called on the main thread.
因为您正在使用 ListenableWorker
,您的 startWork
方法正按预期在主线程上被调用。由于您在同一个线程上调用了自己的 doWork()
方法,因此您仍然在主线程上。
在你的情况下,你不需要关心你在哪个线程上,你不需要任何 Executor
因为你调用哪个线程并不重要 getLastLocation()
上。
相反,您只需要在实际获得结果时在 ResolvableFuture
上调用 set
- 即在 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;
}
}
好吧 - 正如其他答案所提到的,如果你想做频繁的工作(比如每 60 秒一次),你应该使用前台服务。
无论如何,我会使用协程来脱离主线程。
喜欢:
runBlocking(Dispatchers.Default) {
//If you want to run blocking your code
}
或使用 GlobalScope Launch
GlobalScope.launch {
//Work in a separated thread (running not blocking)
}
这是一个使用 ListenableWorker 获取位置并在主线程外设置侦听器的实际示例。
因为我需要在 WorkManager 中异步执行工作,所以我需要使用 ListenableWorker
,默认情况下 运行 在主 (UI) 线程上。由于这项工作可能是一个很长的处理任务,可能会冻结界面,所以我想在后台线程上执行它。在 Working with WorkManager (Android Dev Summit '18) video 中,Google 工程师展示了如何手动配置 WorkManager 以 运行 在自定义 Executor
上工作,所以我遵循了他的指导:
1) 在 AndroidManifest 中禁用默认的 WorkManager 初始化程序:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="com.example.myapp.workmanager-init"
tools:node="remove" />
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);
}
}
现在我的实际 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();
}
为什么 isMainThread()
方法 return 为真,即使我指定 Executor
WorkManager 应该用作新的后台线程,我如何才能真正 运行 那块在后台线程上工作?
编辑: ListenableWorker
需要 CountDownLatch
.
由于我需要在每次成功时重新安排工作(PeriodicWorkRequest
的最小间隔为 15 分钟的解决方法),我需要在前一项工作 return 成功后进行,否则我会出现奇怪的行为.这是必需的,因为显然 ExistingWorkPolicy.APPEND
没有按预期工作。
用例是以相当频繁的间隔(5-10 秒)请求高精度的位置更新,即使在后台也是如此。通过短信打开和关闭,即使应用程序未 运行ning(但未强制停止),或通过按钮(这是一个大学项目)。
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 秒),你 绝对 应该使用 foreground service and not WorkManager, which is for, as per the documentation:
deferrable, asynchronous tasks
而不是需要不断运行靠近的东西。
但是,如果您继续错误地使用 WorkManager,则需要牢记以下几点:
您在主线程上的自定义 doWork
方法 运行s 因为根据 setExecutor() documentation:
An Executor for running Workers
具体来说,只有 ListenableWorker
运行 的 Worker 子类在 Executor
提供的后台线程上 - 不是 你的 ListenableWorker
实现。
根据 ListenableWorker.startWork() documentation:
This method is called on the main thread.
因为您正在使用 ListenableWorker
,您的 startWork
方法正按预期在主线程上被调用。由于您在同一个线程上调用了自己的 doWork()
方法,因此您仍然在主线程上。
在你的情况下,你不需要关心你在哪个线程上,你不需要任何 Executor
因为你调用哪个线程并不重要 getLastLocation()
上。
相反,您只需要在实际获得结果时在 ResolvableFuture
上调用 set
- 即在 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;
}
}
好吧 - 正如其他答案所提到的,如果你想做频繁的工作(比如每 60 秒一次),你应该使用前台服务。
无论如何,我会使用协程来脱离主线程。 喜欢:
runBlocking(Dispatchers.Default) {
//If you want to run blocking your code
}
或使用 GlobalScope Launch
GlobalScope.launch {
//Work in a separated thread (running not blocking)
}
这是一个使用 ListenableWorker 获取位置并在主线程外设置侦听器的实际示例。