Android: 正确停止异步任务
Android: correctly stopping an async task
我有一个应用程序,旨在每 10 毫秒记录一次传感器数据并将其存储到 phone 上的 SQLite 数据库中。我将数据库插入作为异步任务执行,因为它们发生得如此之快,而且数量如此之多,以至于它们显着减慢了导航速度。但是,我偶尔会 运行 在尝试停止录制时遇到问题。
在我的一个片段中有一个开始停止按钮。按下它进行录音。再按一次停止录音。 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
class被调出。这只是注册侦听器、启动服务以便我可以在屏幕关闭时收集数据、计算一些传感器等。这就是我的异步任务所在。 class 唯一有趣的部分是:
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;
}
}
}
}
当我按下停止按钮时,将立即在 onClick
中调用 stopService
,我相信它会在 SensorService
中调用 onDestroy
。然而,我 运行 进入停止被按下、监听器未注册、数据库已关闭但后台仍有异步任务 运行ning 的情况。我的猜测是他们在真正停下来之前仍在完成最后的任务。这 运行 让我陷入了异常领域,因为异步代码试图将数据插入现在已关闭的数据库中。我可以抓住那些并忽略它们,但我想找出处理这种情况的正确方法
我应该如何重构我的代码以允许异步任务完成?因为这不是一项大工作,而是数千个小型数据库插入作业,我本以为它们会很快停止,所以令我惊讶的是我一直运行关注这些关闭的数据库异常问题
有没有办法判断所有异步任务何时完成?也许我可以在 onDestroy
关闭任何内容之前将其用作条件?
或者是否值得完全放弃异步任务?我主要只是想避免 运行 在主线程 UI 中插入这些数据库
所以情况可能比这更糟。当您调用 execute() 时,实际上是将任务添加到队列中。单个线程通过队列并一次运行一个任务。所以你可以有多个任务排队,不会被取消。顺便说一句,这是所有异步任务的 1 个共享线程,因此如果您有其他任务,它们也可以支持。
这里有两个解决方案。第一个是在服务级别有一个 isCanceled 变量,所有异步任务都会在 doInBackground 开始时查看该变量,如果已设置则立即退出。
第二个是我认为更好的解决方案。创建线程。线程应如下所示:
while(!isCanceled) {
insertData = BlockingQueue.take()
//insert insertData
}
然后您的传感器数据回调可以向该队列添加一个项目,您的 onStop 可以取消线程并清空队列。
我有一个应用程序,旨在每 10 毫秒记录一次传感器数据并将其存储到 phone 上的 SQLite 数据库中。我将数据库插入作为异步任务执行,因为它们发生得如此之快,而且数量如此之多,以至于它们显着减慢了导航速度。但是,我偶尔会 运行 在尝试停止录制时遇到问题。
在我的一个片段中有一个开始停止按钮。按下它进行录音。再按一次停止录音。 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
class被调出。这只是注册侦听器、启动服务以便我可以在屏幕关闭时收集数据、计算一些传感器等。这就是我的异步任务所在。 class 唯一有趣的部分是:
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;
}
}
}
}
当我按下停止按钮时,将立即在 onClick
中调用 stopService
,我相信它会在 SensorService
中调用 onDestroy
。然而,我 运行 进入停止被按下、监听器未注册、数据库已关闭但后台仍有异步任务 运行ning 的情况。我的猜测是他们在真正停下来之前仍在完成最后的任务。这 运行 让我陷入了异常领域,因为异步代码试图将数据插入现在已关闭的数据库中。我可以抓住那些并忽略它们,但我想找出处理这种情况的正确方法
我应该如何重构我的代码以允许异步任务完成?因为这不是一项大工作,而是数千个小型数据库插入作业,我本以为它们会很快停止,所以令我惊讶的是我一直运行关注这些关闭的数据库异常问题
有没有办法判断所有异步任务何时完成?也许我可以在 onDestroy
关闭任何内容之前将其用作条件?
或者是否值得完全放弃异步任务?我主要只是想避免 运行 在主线程 UI 中插入这些数据库
所以情况可能比这更糟。当您调用 execute() 时,实际上是将任务添加到队列中。单个线程通过队列并一次运行一个任务。所以你可以有多个任务排队,不会被取消。顺便说一句,这是所有异步任务的 1 个共享线程,因此如果您有其他任务,它们也可以支持。
这里有两个解决方案。第一个是在服务级别有一个 isCanceled 变量,所有异步任务都会在 doInBackground 开始时查看该变量,如果已设置则立即退出。
第二个是我认为更好的解决方案。创建线程。线程应如下所示:
while(!isCanceled) {
insertData = BlockingQueue.take()
//insert insertData
}
然后您的传感器数据回调可以向该队列添加一个项目,您的 onStop 可以取消线程并清空队列。