我有一个Android应用程序,它使用服务每隔5ms收集传感器数据并将其插入到sqlite表中。在典型的会话中,将有大约40分钟的录音。所有这些代码似乎都运行良好。
我有一个奇怪的问题,如果用户导航到特定片段,我会收到CursorWindow: Window is full: requested allocation XXX
错误。我不确定导致该错误的特定片段有什么特别之处,并且只发生在这个片段
有问题的片段包含一个按钮,点击该按钮会做很多事情:
.db
文件的副本创建到外部存储片段代码看起来像这样(为简洁起见,许多catch块都被省略了 - 它们大多只是记录信息):
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;
}
}
}
在记录了大量传感器数据后,每次用户导航到片段时都会收到错误。但是当点击按钮时,我每隔2秒就会连续出现错误。
传感器录制服务代码可以在我的另一个问题中找到:Android sending messages between fragment and service
这个片段从我的DBHelper类(设置为单例)中调用了许多方法:
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();
}
}
DBHelper和任何SQL连接在我的主要活动的onDestroy
中关闭
我的媒体扫描仪课程也非常简单:
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
}
}
);
}
}
任何人都可以看到导致此光标窗口错误的片段代码有什么特别之处吗?