Как запустить работу ListenableWorker в фоновом потоке? - PullRequest
0 голосов
/ 08 января 2019

Поскольку мне нужно выполнять асинхронную работу в WorkManager, мне нужно использовать ListenableWorker, который по умолчанию выполняется в основном потоке (UI). Поскольку эта работа может быть длительной обработкой задач, которые могут заморозить интерфейс, я хотел выполнить ее в фоновом потоке. В видео Работа с WorkManager (Android Dev Summit '18) инженер Google показал, как вручную настроить WorkManager для запуска работ на пользовательском Executor, поэтому я следовал его указаниям:

1) Отключить инициализатор WorkManager по умолчанию в AndroidManifest:

<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() возвращает true, хотя я указал Executor WorkManager, который следует использовать в качестве нового фонового потока, и как я могу на самом деле запустить этот фрагмент работы в фоновом потоке?

РЕДАКТИРОВАТЬ: ListenableWorker с необходимостью CountDownLatch.
Поскольку мне нужно перепланировать работу каждый раз, когда она выполняется успешно (обходной путь для минимального интервала 15 минут для PeriodicWorkRequest), я должен сделать это после того, как предыдущая часть работы вернула успех, в противном случае у меня странное поведение. Это необходимо, потому что, по-видимому, ExistingWorkPolicy.APPEND не работает должным образом.
Вариант использования - запросить обновления местоположения с высокой точностью с довольно частым интервалом (5-10 с), даже в фоновом режиме. Включается и выключается с помощью 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;
    }
}

1 Ответ

0 голосов
/ 08 января 2019

Если вы хотите непрерывно (т. Е. Менее, чем каждые 60 секунд), вы абсолютно должны использовать службу переднего плана и не WorkManager, который за, согласно документации :

отложено, асинхронные задачи

А не то, что должно постоянно бегать.

Однако, если вы продолжите неправильно использовать WorkManager, вам следует учесть следующее:

Ваш пользовательский метод doWork выполняется в главном потоке, поскольку согласно setExecutor () документации :

Исполнитель для запуска Работник с

В частности, только Worker подкласс ListenableWorker работает в фоновом потоке, предоставленном Executor - not вашей ListenableWorker реализацией.

Согласно документации ListenableWorker.startWork () :

Этот метод вызывается в главном потоке.

Поскольку вы используете ListenableWorker, ваш метод startWork вызывается в главном потоке, как и ожидалось. Поскольку вы вызываете свой собственный метод doWork() в том же потоке, вы все равно будете в основном потоке.

В вашем случае вам не нужно заботиться о том, в каком потоке вы находитесь, и вам не нужно никакого Executor, так как не имеет значения, в каком потоке вы вызываете getLastLocation().

Вместо этого вам нужно звонить set на ваш ResolvableFuture только тогда, когда у вас действительно есть результат, то есть в обратных вызовах 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;
    }
}
...