Android:正确停止异步任务

时间:2016-03-29 03:16:09

标签: android android-asynctask android-service android-sqlite android-sensors

我有一个应用程序,旨在每10毫秒记录一次传感器数据并存储到手机上的SQLite数据库中。我将db插入作为异步任务进行操作,因为它们发生的速度非常快,而且有很多这样的插入会明显减慢导航速度。但是,在尝试停止录制时,我偶尔会遇到问题。

在我的一个片段中有一个开始停止按钮。按它进行录制。再次按此按钮可停止录制。 onClick看起来像这样:

@Override
    public void onClick(View v) {
        if (!recordingStarted){

            recordingStarted = true;
            mainActivity.startService(new Intent(mainActivity, SensorService.class));
            startButton.setText(getResources().getString(R.string.start_button_label_stop));
            Snackbar.make(coordinatorLayout, "Recording...", Snackbar.LENGTH_SHORT).show();
        } else {
            mainActivity.stopService(new Intent(mainActivity, SensorService.class));
            startButton.setEnabled(false);
            Snackbar.make(coordinatorLayout, "Recording stopped.", Snackbar.LENGTH_SHORT).show();
        }
    }

开始录制时,会调出SensorService课程。这只是注册监听器,启动服务,所以我可以在屏幕关闭时收集数据,计算一些传感器的东西等。这是我的异步任务所在。该课程中唯一有趣的部分是:

public class SensorService extends Service implements SensorEventListener {

    public BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "onReceive("+intent+")");

            if (!intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                return;
            }

            Runnable runnable = new Runnable() {
                public void run() {
                    Log.i(TAG, "Runnable executing...");
                    unregisterListener();
                    registerListener();
                }
            };

            new Handler().postDelayed(runnable, SCREEN_OFF_RECEIVER_DELAY);
        }
    };

    public void onSensorChanged(SensorEvent event) {
        sensor = event.sensor;

        int i = sensor.getType();
        if (i == MainActivity.TYPE_ACCELEROMETER) {
            accelerometerMatrix = event.values;
        } else if (i == MainActivity.TYPE_GYROSCOPE) {
            gyroscopeMatrix = event.values;
        } else if (i == MainActivity.TYPE_GRAVITY) {
            gravityMatrix = event.values;
        } else if (i == MainActivity.TYPE_MAGNETIC) {
            magneticMatrix = event.values;
        }

        long curTime = System.currentTimeMillis();
        long diffTime = (curTime - lastUpdate);

        // only allow one update every POLL_FREQUENCY.
        if(diffTime > POLL_FREQUENCY) {
            lastUpdate = curTime;

            //cut a bunch of stuff here to save space

            //insert into database
            new InsertSensorDataTask().execute();
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();

        dbHelper = new DBHelper(getApplicationContext());

        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        accelerometer = sensorManager.getDefaultSensor(MainActivity.TYPE_ACCELEROMETER);
        gyroscope = sensorManager.getDefaultSensor(MainActivity.TYPE_GYROSCOPE);
        gravity = sensorManager.getDefaultSensor(MainActivity.TYPE_GRAVITY);
        magnetic = sensorManager.getDefaultSensor(MainActivity.TYPE_MAGNETIC);

        PowerManager manager =
                (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);

        registerReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(receiver);
        unregisterListener();
        wakeLock.release();
        dbHelper.close();
        stopForeground(true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        startForeground(Process.myPid(), new Notification());
        registerListener();
        wakeLock.acquire();

        return START_STICKY;
    }

    private class InsertSensorDataTask extends AsyncTask<String, String, Boolean> {
        @Override
        protected Boolean doInBackground(String... params) {
            try {
                dbHelper.insertData(Short.parseShort(MainActivity.subInfo.get("subNum")), System.currentTimeMillis(),
                        accelerometerMatrix[0], accelerometerMatrix[1], accelerometerMatrix[2],
                        accelerometerWorldMatrix[0], accelerometerWorldMatrix[1], accelerometerWorldMatrix[2],
                        gyroscopeMatrix[0], gyroscopeMatrix[1], gyroscopeMatrix[2]);
                return true;
            } catch (SQLException e) {
                Log.e(TAG, "insertData: " + e.getMessage(), e);
                return false;
            }
        }
    }
}

当我点击停止按钮时,会立即拨打stopService中的onClick,我相信会在onDestroy中拨打SensorService。但是我遇到了按下停止,监听器未注册,数据库关闭但仍然在后台运行异步任务的情况。我的猜测是他们在实际停止之前仍然完成了他们的最终任务。这使我进入异常区域,因为异步代码试图将数据插入到现在关闭的数据库中。我可以抓住那些并忽略它们,但我想弄清楚处理这种情况的正确方法

我应该如何重构代码以允许异步任务完成?因为这不是一件大事,而是成千上万的小型数据库插入工作,我会想象他们可以很快停止,所以我很惊讶我一直遇到这些封闭的数据库异常问题

有没有办法告诉所有异步任务何时完成?也许我可以在任何事情结束之前将其用作onDestroy中的一个条件?

还是值得完全摆脱异步任务?我大多只想避免在主UI线程中运行这些db插入

1 个答案:

答案 0 :(得分:2)

所以可能比这更糟糕。当您调用execute()时,实际上是将一个任务添加到队列中。单个线程通过队列并一次运行一个任务。因此,排队的多个任务可能无法取消。顺便说一句,这是所有异步任务的1个共享线程,所以如果你有其他任务,它们也可以阻止它们。

这里有两个解决方案。第一个是在服务级别有一个isCanceled变量,所有异步任务在doInBackground的开头查看,如果设置了则立即退出。

第二个是我认为更好的解决方案。创建一个线程。线程应如下所示:

while(!isCanceled) {
  insertData = BlockingQueue.take()
  //insert insertData
}

您的传感器数据回调可以将一个项目添加到此队列,您的onStop可以取消该线程并清空队列。