1

我有一個應用程序設計爲每10ms記錄傳感器數據並存儲到手機上的SQLite數據庫。我正在做數據庫插入作爲異步任務,因爲他們發生得太快了,而且他們中有很多人會顯着減慢導航速度。但是,在嘗試停止錄製時,偶爾會遇到問題。Android:正確地停止異步任務

在我的一個片段中有一個開始停止按鈕。按下它來記錄。再按一次停止錄製。該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; 
      } 
     } 
    } 
} 

當我按下停止按鈕,會立即撥打在onClickstopService,我相信會打電話給在SensorServiceonDestroy。然而,我遇到了停止按下的情況,監聽器未註冊,數據庫關閉,但仍有異步任務在後臺運行。我的猜測是他們在實際停止之前仍然完成了最終的任務。這讓我進入異常領域,因爲異步代碼試圖將數據插入到現在已關閉的數據庫中。我可以抓住這些並忽略它們,但我想找出正確的方法來處理這種情況

我應該如何重構我的代碼以允許異步任務完成?因爲這不是一個大工作,而是成千上萬的小型數據庫插入工作,我會想象他們可以很快停下來,所以我很驚訝,我一直遇到這些封閉的數據庫異常問題

有沒有辦法告訴所有異步任務何時完成?也許我可以用它作爲onDestroy中的一個條件,然後關閉任何東西?

還是值得從異步任務完全撤離?我主要是想避免在主UI線程中運行這些分貝插入

+0

在OnDestroy的AsynTask烤麪包!你會知道它是否停止 – Jois

回答

2

所以它可能比這更糟糕。當你調用execute()時,你實際上將一個任務添加到隊列中。一個線程遍歷隊列並一次運行一個任務。所以你可以有多個排隊的任務不會被取消。順便說一下,這是所有異步任務的共享線程,所以如果你有其他任務,他們也可以阻止。

這裏有兩種解決方案。首先是在服務級別有一個isCanceled變量,所有異步任務在doInBackground的開始處查看並立即退出(如果已設置)。

第二個是我認爲更好的解決方案。創建一個線程。線程應該是這樣的:那麼

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

你的傳感器數據的回調可以添加項目到這個隊列中,你的onStop只需取消線程並清空隊列。

+0

所以我只是做了一些谷歌搜索,因爲我以前從來沒有玩過線程。但是我找不到任何叫做'synchronizedQueue'或'blockingGet'的類/方法。你可以擴展一下代碼的實際外觀和/或指向我可以找到這些方法的方向嗎? – Simon

+0

BlockingQueue將是正確的界面 - 任何子集。基本上它有一個函數take(),它將返回隊列的頭部,或者如果隊列爲空,則會等待數據被另一個線程添加,然後返回該數據。我用正確的名字更新了我的答案 –