1

У меня есть приложение, предназначенное для записи данных датчиков каждые 10 мс и хранения в SQLite db на телефоне. Я делаю вставки db как задачу async, потому что они происходят так быстро, и их так много, что они заметно замедляют навигацию. Тем не менее, я иногда сталкиваюсь с проблемами при попытке остановить запись.Android: правильная остановка задачи async

В одном из моих фрагментов есть кнопка начала остановки. Нажмите его для записи. Нажмите ее еще раз, чтобы остановить запись. 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. Однако я сталкиваюсь с условиями остановки, прослушиватели незарегистрированы, базы данных закрыты, но в фоновом режиме все еще выполняются задачи асинхронного программирования. Я предполагаю, что они до сих пор завершают свои окончательные задачи, прежде чем остановить их. Это приводит меня в зону исключения, потому что тогда асинхронный код пытается вставить данные в базу данных, которая теперь закрыта. Я мог бы просто поймать их и проигнорировать их, но я хочу выяснить правильный способ справиться с такой ситуацией.

Как мне реорганизовать мой код, чтобы разрешить выполнение задач async? Поскольку это не одна большая работа, а несколько тысяч небольших вставных заданий, я бы предположил, что они могут остановиться довольно быстро, поэтому я удивлен тем, что я продолжаю сталкиваться с этими проблемами с закрытыми исключениями db.

Есть ли способ рассказывать, когда все асинхронные задачи выполняются? Может быть, я могу использовать это как условие в onDestroy, прежде чем что-нибудь закрывается?

Или стоило ли вообще отказаться от асинхронных задач? Я в основном просто хочу избежать использования этих вставок db в основной резьбе UI

+0

положить тост в OnDestroy AsynTask! вы узнаете, остановится ли это или нет. – Jois

ответ

2

Так что это может быть хуже. Когда вы вызываете execute(), вы фактически добавляете задачу в очередь. Один поток проходит через очередь и запускает задачи по одному. Таким образом, у вас может быть несколько задач в очереди, которые не будут отменены. Кстати, это 1 общий поток для всех асинхронных задач, поэтому, если у вас есть другие задачи, они также могут задержать вещи.

Здесь два решения. Первый заключается в том, чтобы на уровне обслуживания была переменная isCanceled, на которую все асинхронные задачи смотрят в начале doInBackground и немедленно выходят, если они установлены.

Второй, я думаю, лучшее решение. Создать тему. Нить должна выглядеть следующим образом:

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

Ваш обратный вызов данные датчика может добавить элемент в эту очередь, и ваш OnStop может просто отменить нить и очистить очередь.

+0

Итак, я просто сделал несколько поисковых запросов, поскольку я никогда не играл с нитями раньше. Однако я не мог найти никаких классов/методов, называемых 'synchronizedQueue' или' blockingGet'. Не могли бы вы немного рассказать о том, как будет выглядеть код и/или указать мне в направлении того, где я найду эти методы? – Simon

+0

BlockingQueue будет правильным интерфейсом - любым его подмножеством.В основном у него есть функция take(), которая либо вернет голову очереди, либо если пустая очередь будет ждать, пока данные не будут добавлены другим потоком, а затем верните это. Я обновил свой ответ с правильными именами –