Android:正确停止异步任务

Posted

技术标签:

【中文标题】Android:正确停止异步任务【英文标题】:Android: correctly stopping an async task 【发布时间】:2016-07-16 10:40:02 【问题描述】:

我有一个应用程序,旨在每 10 毫秒记录一次传感器数据并存储到手机上的 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 类。那只是注册监听器,启动服务,这样我就可以在屏幕关闭时收集数据,计算一些传感器等等。这就是我的异步任务所在。该课程唯一有趣的部分是:

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。但是我遇到了按下停止、未注册侦听器、关闭数据库的情况,但仍有异步任务在后台运行。我的猜测是,在真正停止之前,他们仍在完成最后的任务。这让我进入了异常领域,因为异步代码正试图将数据插入到现在已关闭的数据库中。我可以抓住这些并忽略它们,但我想找出处理这种情况的正确方法

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

有没有办法判断所有异步任务何时完成?也许我可以在关闭任何东西之前将其用作onDestroy 中的条件?

或者完全放弃异步任务是否值得?我主要只是想避免在主 UI 线程中运行这些数据库插入

【问题讨论】:

在 AsynTask 的 OnDestroy 中干杯!你会知道它是否停止 【参考方案1】:

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

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

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

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

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

【讨论】:

所以我只是做了一些谷歌搜索,因为我以前从未玩过线程。但是我找不到任何名为synchronizedQueueblockingGet 的类/方法。您能否稍微扩展一下代码的实际外观和/或指出我在哪里可以找到这些方法? BlockingQueue 将是正确的接口——它的任何子集。基本上它有一个函数 take() 将返回队列的头部,或者如果队列为空,将等待直到另一个线程添加数据,然后返回。我已经用正确的名字更新了我的答案

以上是关于Android:正确停止异步任务的主要内容,如果未能解决你的问题,请参考以下文章

在android中链接2个异步任务的正确方法

Android异步任务异常

异步任务(AsyncTask)

在java中停止异步任务的onPostExecute

Django Channels 从 Celery 任务发送组消息。 Asyncio 事件循环在所有异步任务完成之前停止

android:异步任务asyncTask介绍及异步任务下载图片(带进度条)