Android cursorwindow error de memoria en un fragmento específico

Tengo una aplicación para Android que utiliza un servicio para recopilar datos de los sensores cada 5 ms e insertarlo en una tabla sqlite. En una sesión típica habrá alrededor de 40 minutos de grabaciones. Todo ese código parece estar funcionando bien.

Tengo un problema extraño donde si el usuario navega a un fragmento particular consigo un CursorWindow: Window is full: requested allocation XXX error. No estoy seguro de lo que es especial sobre este fragmento específico que está causando ese error, y sólo ocurre con este fragmento

El fragmento en cuestión contiene un botón, que al hacer clic hará una serie de cosas:

  • Crea algunos directorios en el almacenamiento externo
  • Copia todos los datos del sensor de la tabla temporal e inserta en una tabla más permanente
  • Crea una copia del archivo .db completo en un almacenamiento externo
  • Toma el contenido de una tabla y lo escribe en un archivo CSV
  • Consulta otra tabla de datos de sensor y escribe todos los datos de los sensores en otro archivo CSV
  • Utiliza escáner de medios para escanear todos los archivos del directorio de exportación para que se pueda acceder a ellos mediante MTP

El código del fragmento se parece a esto (muchos de los bloques de la captura se han dejado hacia fuera para la brevedad – que sobre todo la información simplemente del registro):

 public class SaveFragment extends Fragment implements View.OnClickListener { Button saveButton; MainActivity mainActivity; DBHelper dbHelper; Boolean subjectDataExists; MediaScanner mediaScanner; static ProgressDialog dialog; public SaveFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_save, container, false); //Get save button view saveButton = (Button) view.findViewById(R.id.saveButton); saveButton.setOnClickListener(this); //Get DBHelper dbHelper = DBHelper.getInstance(getActivity(), new DatabaseHandler()); //Check if sensor data has been recorded subjectDataExists = dbHelper.checkSubjectDataExists(Short.parseShort(dbHelper.getTempSubInfo("subNum"))); // Inflate the layout for this fragment return view; } @Override public void onClick(View v) { //Alert dialog for saving/quitting AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(mainActivity); if (subjectDataExists) { alertDialogBuilder.setTitle("Save and quit?"); alertDialogBuilder.setMessage("Are you sure you want to save the data and quit the current session?"); } else { alertDialogBuilder.setTitle("Quit?"); alertDialogBuilder.setMessage("Are you sure you want to quit the current session? \n\n No data will be saved."); } alertDialogBuilder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { //Save if sensor data exists, otherwise quit if (subjectDataExists) { new ExportDatabaseCSVTask().execute(); } else { quitSession(); } } }); alertDialogBuilder.setNegativeButton("No", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); AlertDialog quitAlertDialog = alertDialogBuilder.create(); quitAlertDialog.show(); } //Quit the current session and go back to login screen private void quitSession(){ Intent intent = new Intent(getActivity(), LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); getActivity().finishAffinity(); } //Message handler class for database progress updates private static class DatabaseHandler extends Handler { @Override public void handleMessage (Message msg){ Double progressPercent = (Double) msg.obj; Integer progressValue = 40 + (int) Math.ceil(progressPercent/2); dialog.setProgress(progressValue); } } //Async class for CSV export task public class ExportDatabaseCSVTask extends AsyncTask<String, Integer, Boolean> { @Override protected void onPreExecute() { //show a progress dialog } protected Boolean doInBackground(final String... args) { //Create directories for the output csv files String pathToExternalStorage = Environment.getExternalStorageDirectory().toString(); File exportDir = new File(pathToExternalStorage, "/Data"); File subjectDataDir = new File(exportDir, "/subjects"); publishProgress(5); //The sleep is here just so the progress updates in the dialog are visually slower SystemClock.sleep(100); if (!exportDir.exists()) { Boolean created = exportDir.mkdirs(); } publishProgress(10); SystemClock.sleep(100); if (!subjectDataDir.exists()) { Boolean created = subjectDataDir.mkdirs(); } publishProgress(15); SystemClock.sleep(100); //If all directories have been created successfully if (exportDir.exists() && subjectDataDir.exists()) { try { //Copy temp subject and sensor data to persistent db tables dbHelper.copyTempData(); publishProgress(20); SystemClock.sleep(200); //Backup the SQL DB file File data = Environment.getDataDirectory(); String currentDBPath = "//data//com.example.app//databases//" + DBHelper.DATABASE_NAME; File currentDB = new File(data, currentDBPath); File destDB = new File(exportDir, DBHelper.DATABASE_NAME); publishProgress(25); SystemClock.sleep(100); if (exportDir.canWrite()) { if (currentDB.exists()) { FileChannel src = new FileInputStream(currentDB).getChannel(); FileChannel dst = new FileOutputStream(destDB).getChannel(); dst.transferFrom(src, 0, src.size()); src.close(); dst.close(); } } publishProgress(35); SystemClock.sleep(300); //Export subjects table/tracking sheet File trackingSheet = new File(exportDir, "trackingSheet.csv"); try{ dbHelper.exportTrackingSheet(trackingSheet); } catch (SQLException | IOException e){ } publishProgress(40); SystemClock.sleep(300); //Export individual subject data String subNum = dbHelper.getTempSubInfo("subNum"); File subjectFile = new File(subjectDataDir, subNum + ".csv"); try{ dbHelper.exportSubjectData(subjectFile, subNum); } catch (SQLException | IOException e){ } publishProgress(90); SystemClock.sleep(300); //Scan all files for MTP List<String> fileList = getListFiles(exportDir); String[] allFiles = new String[fileList.size()]; allFiles = fileList.toArray(allFiles); mediaScanner = new MediaScanner(); try{ mediaScanner.scanFile(getContext(), allFiles, null, mainActivity.logger); } catch (Exception e) { } publishProgress(100); SystemClock.sleep(400); return true; } catch (SQLException | IOException e) { } else { //Directories don't exist if (!exportDir.exists()) { } else if (!subjectDataDir.exists()) { return false; } } public void onProgressUpdate(Integer ... progress){ dialog.setProgress(progress[0]); if (progress[0] == 100){ dialog.setMessage("Quitting..."); } } protected void onPostExecute(final Boolean success) { if (dialog.isShowing()) { dialog.dismiss(); } if (success) { //Restart app and go back to login screen quitSession(); } } //Recursive file lister for MTP private List<String> getListFiles(File parentDir) { ArrayList<String> inFiles = new ArrayList<>(); File[] files = parentDir.listFiles(); //Loop through everything in base directory, including folders for (File file : files) { if (file.isDirectory()) { //Recursively add files from subdirectories inFiles.addAll(getListFiles(file)); } else { inFiles.add(file.getAbsolutePath()); } } return inFiles; } } } 

Después de que una gran cantidad de datos de los sensores se ha grabado, obtengo el error cada vez que el usuario navega al fragmento. Pero cuando se hace clic en el botón, obtengo el error continuamente cada 2 segundos.

El sensor de grabación de código de servicio se puede encontrar en mi otra pregunta: Android enviar mensajes entre el fragmento y el servicio

Este fragmento llama a una serie de métodos de mi clase DBHelper (que se configura como singleton):

 public class DBHelper extends SQLiteOpenHelper { SQLiteDatabase db; CSVWriter csvWrite; Cursor curCSV; static Handler messageHandler; private static DBHelper sInstance; public static synchronized DBHelper getInstance(Context context) { if (sInstance == null) { sInstance = new DBHelper(context.getApplicationContext()); Log.d(TAG, "New DBHelper created"); } return sInstance; } public static synchronized DBHelper getInstance(Context context, Handler handler) { if (sInstance == null) { sInstance = new DBHelper(context.getApplicationContext()); Log.d(TAG, "New DBHelper created"); } messageHandler = handler; return sInstance; } private DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); db = this.getWritableDatabase(); } public boolean checkSubjectDataExists(Short subNum) throws SQLException { //Check if sensor data, for this subject, exists in the temp data table String query = "SELECT * FROM " + DATA_TABLE_NAME_TEMP + " WHERE " + DATA_SUBJECT + "=" + subNum; Cursor c = db.rawQuery(query, null); boolean exists = (c.getCount() > 0); c.close(); return exists; } public void copyTempData() throws SQLException{ String copySubjectSQL = "INSERT INTO " + SUBJECTS_TABLE_NAME + " SELECT * FROM " + SUBJECTS_TABLE_NAME_TEMP; db.execSQL(copySubjectSQL); String copyDataSQL = "INSERT INTO " + DATA_TABLE_NAME + " SELECT * FROM " + DATA_TABLE_NAME_TEMP; db.execSQL(copyDataSQL); } public void exportTrackingSheet(File outputFile) throws SQLException, IOException { csvWrite = new CSVWriter(new FileWriter(outputFile)); curCSV = db.rawQuery("SELECT * FROM " + SUBJECTS_TABLE_NAME, null); csvWrite.writeNext(curCSV.getColumnNames()); while (curCSV.moveToNext()) { String arrStr[] = {curCSV.getString(0), curCSV.getString(1), curCSV.getString(2), curCSV.getString(3), curCSV.getString(4), curCSV.getString(5), curCSV.getString(6)}; csvWrite.writeNext(arrStr); } csvWrite.close(); curCSV.close(); } public void exportSubjectData(File outputFile, String subNum) throws IOException, SQLException { csvWrite = new CSVWriter(new FileWriter(outputFile)); curCSV = db.rawQuery("SELECT * FROM " + DATA_TABLE_NAME + " WHERE id = " + subNum, null); csvWrite.writeNext(curCSV.getColumnNames()); Integer writeCounter = 0; Integer numRows = curCSV.getCount(); while (curCSV.moveToNext()) { writeCounter++; String arrStr[] = {curCSV.getString(0), curCSV.getString(1), curCSV.getString(2), curCSV.getString(3), curCSV.getString(4), curCSV.getString(5), curCSV.getString(6), curCSV.getString(7), curCSV.getString(8), curCSV.getString(9), curCSV.getString(10), curCSV.getString(11), curCSV.getString(12), curCSV.getString(13), curCSV.getString(14), curCSV.getString(15), curCSV.getString(16), curCSV.getString(17), curCSV.getString(18), curCSV.getString(19), curCSV.getString(20), curCSV.getString(21), curCSV.getString(22), curCSV.getString(23), curCSV.getString(24), curCSV.getString(25)}; csvWrite.writeNext(arrStr); if ((writeCounter % 1000) == 0){ csvWrite.flush(); } Double progressPercent = Math.ceil(((float) writeCounter / (float) numRows)*100); Message msg = Message.obtain(); msg.obj = progressPercent; msg.setTarget(messageHandler); msg.sendToTarget(); } csvWrite.close(); curCSV.close(); } } 

Las conexiones DBHelper y SQL están cerradas en onDestroy de mi actividad principal

Mi clase de escáner multimedia también es bastante sencilla:

 public class MediaScanner { protected void scanFile(final Context context, String[] files, String[] mimeTypes, final Logger logger) { MediaScannerConnection.scanFile(context, files, mimeTypes, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { //Log some info } } ); } } 

¿Puede alguien ver algo especial sobre mi código de fragmento que está causando este error de ventana de cursor?

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.