无法在工作完成后停止工作程序(ListenableWorker)任务

6
我似乎找不到正确的方法来停止我的worker任务。我在ResolvableFuture<Result>上调用.set(Result.success());,但它从未触发我的类中的onStopped(),而且其他已订阅的回调似乎仍然在我的worker内活跃。
  • 我的worker启动并尝试查找特定的BLE设备
  • 它连接到该设备并尝试使用Result.success()退出
  • 如果worker运行时间超过3分钟,则尝试退出。
在我连接到BLE设备后调用Result.success()之后,它仍会保持对connectGatt()函数内的onConnectionStateChange的活动引用。
即使操作系统强制停止我的worker,它似乎也没有调用onStopped(),因为我从未在日志中看到过。
即使创建了新的worker,在旧worker中仍然会接收到onConnectionStateChange的更新。
package test;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;

import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
import static android.os.Build.VERSION_CODES.M;

public class BluetoothScanWorker extends ListenableWorker  {

    private String TAG = "BluetoothScanWorker";
    private BluetoothAdapter bluetoothAdapter;
    private ResolvableFuture<Result> mFuture;


    public BluetoothScanWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        mFuture = ResolvableFuture.create();

        Log.d(TAG ,"startWork()");

        if (Build.VERSION.SDK_INT >= M) {
            ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build();
            List<ScanFilter> filters_v2 = new ArrayList<>();
            ScanFilter scanFilter = new ScanFilter.Builder()
                    .setDeviceAddress("B8:27:EB:B9:5C:FC")
                    .build();
            filters_v2.add(scanFilter);
            BluetoothManager bluetoothManager =
                    (BluetoothManager) this.getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE);
            bluetoothAdapter = bluetoothManager.getAdapter();
            bluetoothAdapter.getBluetoothLeScanner().startScan(filters_v2, settings, leScanCallback); // Scan for every BLE device matching my filter

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    endWork();
                }
            }, 3*60*1000); // End worker after 3 minutes
        }
        return mFuture;
    }


    private void endWork(){
        if (Build.VERSION.SDK_INT >= M) {
            Log.d(TAG, "END WORK");
            bluetoothAdapter.getBluetoothLeScanner().stopScan(leScanCallback);
            mFuture.set(Result.success());
        }
    }


    @RequiresApi(M)
    private ScanCallback leScanCallback = new ScanCallback() { // Called when I find my BLE device
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.d(TAG ,"onScanResult: " + result.getDevice().toString());
            bluetoothAdapter.getBluetoothLeScanner().stopScan(this);
            result.getDevice().connectGatt(getApplicationContext(), true, new BluetoothGattCallback() { // Connect to my BLE device
                @Override
                public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                    if (newState == BluetoothProfile.STATE_CONNECTED) {
                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            Log.d(TAG ,"GATT connected successfully");
                            endWork();
                        }
                    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                        Log.d(TAG ,"GATT disconnected");
                        endWork();
                    }

                }
            },TRANSPORT_LE);

        }
    };


    @Override
    public void onStopped() {
        Log.d(TAG, "Work service stopped");
    }

}
1个回答

4
请注意,仅当worker被取消或任务被系统抢占时,才会调用ListenableWorker.onStopped()方法。如果worker成功完成,则不会调用onStop()。文档中指出:

当告知该工作程序停止时,将调用此方法。... 这可能是由用户发出的明确取消信号引起的, 或者是因为系统决定抢占任务而引起的。...

有时即使worker成功完成,也可以调用onStop()回调,但这是一个错误,将来将进行修复。请参阅此帖子:为什么Android Worker成功完成并调用了onStopped()


那么仅返回成功或失败的结果并不能告诉工作线程它已经完成并应该停止?那么我该如何停止当前正在运行的任务(而不取消下一个重复工作的任务)呢? - somerandomusername
@somerandomusername 当您从startWork()回调中返回SUCCESS或FAIL结果时,因为工作已完成,所以工作程序被认为是完成(停止),但不会调用onStop(),因为它只设计用于在取消情况下调用。如果您希望当工作完成时调用onStop()回调,则可以在从startWork()返回结果之前手动执行它。 - Valeriy Katkov
我更关心工作线程是否仍被保留在内存中。例如,如果我在工作线程中启动计时器/延迟并返回SUCCESS/FAIL - 我的计时器任务将仍然保留引用并执行。但在销毁后活动中,情况就不是这样了。 - somerandomusername
@somerandomusername 注意,ListenableWorker.startWork() 方法在主线程中执行。如果您通过线程处理程序调度一个 Runnable,则该 Runnable 将在主线程中调度,并且当 Worker 完成时它不会被取消。Worker 甚至不知道该 Runnable 已被调度。正确的方法是存储对 Runnable 的引用,并从 onStop() 回调中取消它,该回调通知有关 Worker 取消的信息。我认为 onCanceled()onStop() 回调的更好名称,但它被称为 onStop() :) - Valeriy Katkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接