Подтвердить что ты не робот

"android.database.sqlite.SQLiteException: нет такой таблицы" ошибка в редких случаях

У меня есть приложение, доступное в настоящее время в магазине приложений, но есть один тип отчета об ошибке, который я, похоже, не могу полностью понять. В моем приложении используется внутренняя база данных sqlite, но на некоторых устройствах (конечно, не в большинстве) иногда возникает следующая ошибка:

android.database.sqlite.SQLiteException: нет такой таблицы: image_data (код 1): при компиляции: SELECT Min (штамп) FROM image_data ГДЕ категория = 'Астрономия' AND stamp >= 1357426800 и coalesce (title_nl, '') = ''

Я уверен, что эта таблица существует, и я уверен, что этот запрос верен. Я знаю, что эта ошибка возникает только в виджетах приложения и в BroadcastReceiver, запущенном AlarmManager (приложение раз в то время пытается загрузить новые записи, так как это изображение рабочего дня).

Я думаю, что это имеет какое-то отношение к Контексту, в котором я участвую при доступе к базе данных. У меня есть класс под названием AppContextHelper, который расширяет приложение и имеет статический член, в котором я храню контекст. Этот контекст всегда используется при доступе к базе данных.

Мой вопрос: может быть, что этот контекст недействителен в некоторых случаях при приобретении базы данных в виджетах или вышеупомянутом BroadcastReceiver, выпущенном AlarmManager, и что в этом случае я должен использовать предоставленный контекст в пользу "приложения", контекст? Если да, то почему этот контекст недействителен или еще лучше, какой контекст он предоставляется?

Спасибо заранее!

Как запросил код, приводящий к этой проблеме, опять же, только на НЕКОТОРЫХ устройствах и ТОЛЬКО в виджетах или в классе AlarmManager. Я отправлю код, приводящий к ошибке в классе AlarmManager (это код с наименьшими строками)

  • Код, инициализирующий аварийный сигнал:

        Intent myIntent = new Intent(AppContextHelper.getContext(), ApodDownloader.class);
        mPendingIntent = PendingIntent.getBroadcast(AppContextHelper.getContext(), 0, myIntent, 0);
    
        AlarmManager alarmManager = (AlarmManager)AppContextHelper.getContext().getSystemService(Context.ALARM_SERVICE);
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
    
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis() + 10000, AlarmManager.INTERVAL_HOUR, mPendingIntent);
    
  • AppContextHelper.java

    public class AppContextHelper extends Application {
    
        private static Context mContext;
    
        @Override
        public void onCreate() {
            super.onCreate();
            mContext = this;
        }
    
        public static Context getContext(){
            return mContext;
        }
    }
    
  • (часть) ApodDownloader.java(это содержит строку исключаемого исключения)

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        (new AsyncTaskThreadPool<Integer, Void, Boolean>() {
    
            @Override
            protected Boolean doInBackground(Integer... params) {
                Helpers.logMessage("Checking new entries.");
    
    
                SQLiteDatabase db = FrescoDatabase.acquireReadableDatabase(AppContextHelper.getContext());
                try {
                    >>> THIS LINE THROWS THE EXCEPTION <<<
                    maxStamp = Helpers.executeScalarLong(db, "SELECT Min(stamp) FROM image_data WHERE category = 'Astronomy' AND stamp >= 1357426800 and coalesce(title_nl, '') = ''");
    
                    [...]
                } finally {
                    FrescoDatabase.releaseReadableDatabase();
                }
    
                [...] more code
    
            }
            [...] onPostExecute
        }).executeOnExecutor(AsyncTaskThreadPool.THREAD_POOL_EXECUTOR, 0);
    
  • FrescoDatabase.java База данных автоматически создается приложением при запуске, этот код работает, а также на устройствах, которые запускают исключение. Я не могу подчеркнуть, что база данных существует на проблемных устройствах, поскольку приложение работает безупречно, за исключением виджетов и BroadcastReceiver AlarmManager, поэтому, пожалуйста, не говорите мне, что db не инициализирован правильно:)

    public class FrescoDatabase extends SQLiteOpenHelper {
    
        public static final String[] OBSOLETE_DATABASE_FILE_NAMES = new String[] { "Fresco.v1.sqlite", "Fresco.v2.sqlite", "Fresco.v3.sqlite", "Fresco.v4.sqlite", "Fresco.v5.sqlite" };
        public static final String DATABASE_FILE_NAME = "Fresco.v6.sqlite";
        public  static final int DATABASE_FILE_SIZE = 15302656;
        private static final int DATABASE_VERSION = 7;
    
        private static final Lock writeLock = new ReentrantLock();
        private static SQLiteDatabase currentDB = null;
    
        public static final SQLiteDatabase acquireWritableDatabase(Context c) {
            writeLock.lock();
            currentDB = new FrescoDatabase(c).getWritableDatabase();
            return currentDB;
        }
    
        public static final void releaseWritableDatabase() {
            currentDB.close();
            currentDB = null;
            writeLock.unlock();
        }
    
        public static final SQLiteDatabase acquireReadableDatabase(Context c) {
            writeLock.lock();
            currentDB = new FrescoDatabase(c).getReadableDatabase();
            return currentDB;
        }
    
        public static final void releaseReadableDatabase() {
            currentDB.close();
            currentDB = null;
            writeLock.unlock();
        }
    
        private FrescoDatabase(Context context) {
            super(context, InitializeFrescoDatabaseTask.getDatabaseFileName(context), null, DATABASE_VERSION);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            // database is automatically generated, this should not be called
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
    
  • (часть) Helpers.java

        public class Helpers {
            [...]
            public static long executeScalarLong(SQLiteDatabase db, String query) {
                return executeScalarLong(db, query, new String[] { });
            }
            public static long executeScalarLong(SQLiteDatabase db, String query, String... parameters) {
                (line 85, see stack trace down below) Cursor cursor = db.rawQuery(query, parameters);
                try {
                    cursor.moveToNext();
                    long val = cursor.getLong(0);
                    return val;
                }   
                catch (Exception e) {
                    ;
                }
                finally {
                    cursor.close();
                }
                return -1;
            }
        }
    

Журнал исключений (по запросу):

            java.lang.RuntimeException: An error occured while executing doInBackground()
                at nl.tagpulse.utils.AsyncTaskThreadPool$3.done(AsyncTaskThreadPool.java:329)
                at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
                at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
                at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
                at java.util.concurrent.FutureTask.run(FutureTask.java:137)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
                at java.lang.Thread.run(Thread.java:856)
                Caused by: android.database.sqlite.SQLiteException: no such table: image_data (code 1): , while compiling: SELECT Min(stamp) FROM image_data WHERE category = 'Astronomy' AND stamp >= 1357426800 and coalesce(title_nl, '') = ''
                at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
                at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1012)
                at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:623)
                at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
                at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
                at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
                at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:44)
                at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1314)
                at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1253)
                at nl.tagpulse.fresco.other.Helpers.executeScalarLong(Helpers.java:85)
                at nl.tagpulse.fresco.other.Helpers.executeScalarLong(Helpers.java:82)
                at nl.tagpulse.fresco.business.FrescoDatabase.retrieveNewEntries(FrescoDatabase.java:64)
                at nl.tagpulse.fresco.business.ApodDownloader$1.doInBackground(ApodDownloader.java:192)
                at nl.tagpulse.fresco.business.ApodDownloader$1.doInBackground(ApodDownloader.java:1)
                at nl.tagpulse.utils.AsyncTaskThreadPool$2.call(AsyncTaskThreadPool.java:317)
                at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
                ... 4 more

Я добавил маркер строки 85 в блок Helpers.java.

Надеюсь, это поможет!

4b9b3361

Ответ 1

Вот что происходит:

ApodDownloader - это BroadcastReceiver, а в onReceive() вы запускаете AsyncTask, который не разрешен. В документации указано:

Объект BroadcastReceiver действителен только на время вызова to onReceive (Контекст, намерение). Как только ваш код вернется из этого функции, система считает объект законченным и больше не активным.

Это имеет важные последствия для того, что вы можете сделать в onReceive (Context, Intent): все, что требует асинхронная операция недоступна, поскольку вам нужно будет возврат из функции для обработки асинхронной операции, но при что точка BroadcastReceiver больше не активна, и, следовательно, система может убить свой процесс до асинхронной операции завершается.

http://developer.android.com/reference/android/content/BroadcastReceiver.html#ReceiverLifecycle

Когда процесс убит, контекст приложения больше не доступен. Вы все равно можете получить доступ к AppContextHelper.getContext(), но это будет новый класс, потому что старый был убит при устранении этого процесса. Другими словами, возвращаемый контекст был бы нулевым. Это происходит только в редком случае, когда Android на самом деле убивает процесс, который не очень часто возникает из моего опыта.

Если Контекст, который предоставляет ваш AppContextHelper, еще не был инициализирован, ваш вызов InitializeFrescoDatabaseTask.getDatabaseFileName(контекст) возвращает пустую строку, и она, естественно, откроет неправильную базу данных. Поскольку вы не делаете ничего в onCreate() вашего класса FrescoDatabase, база данных не инициализируется правильно и она не найдет таблицы. Но даже если вы создадите таблицы, это будет неправильная база данных, и хотя сбоев не было, данные не будут отображаться.

Чтобы этого не произошло, вам необходимо выполнить всю работу в том же потоке, в котором работает onReceive(), или если слишком долго запускается сервис для тяжелого подъема.

Еще одна вещь, которую я заметил при анализе кода, заключается в том, что вы используете контекст приложения для создания намерения при создании своего будильника (новый Intent (AppContextHelper.getContext(), ApodDownloader.class);). При выполнении этого вам НЕОБХОДИМО установить FLAG_ACTIVITY_NEW_TASK, или вы можете получить "android.util.AndroidRuntimeException: Calling startActivity() извне контекста Activity требует флаг FLAG_ACTIVITY_NEW_TASK. Это действительно то, что вы хотите?" который может или не может привести к сбою приложения.