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

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

android.database.sqlite.SQLiteException: нет такой таблицы: image_data (код 1): при компиляции: SELECT Min(stamp) FROM image_data WHERE category = 'Astronomy' AND штамп >= 1357426800 и объединение (title_nl, '') = ' '

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

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

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

Заранее спасибо!

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

  1. Код инициализации тревоги:

        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);
    
  2. 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;
        }
    }
    
  3. (часть) 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);
    
  4. FrescoDatabase.java База данных автоматически создается приложением при запуске, этот код работает, в том числе на устройствах, которые запускают исключение. Я не могу не подчеркнуть, что база данных существует на проблемных устройствах, поскольку приложение работает безупречно, за исключением виджетов и BroadcastReceiver в AlarmManager, поэтому, пожалуйста, не говорите мне, что база данных неправильно инициализирована :)

    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) {
        }
    }
    
  5. (часть) 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.

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


person Toverbal    schedule 11.04.2013    source источник
comment
Пожалуйста, выложите свой java-код, без него мы ничего не сможем сделать.   -  person Simon Dorociak    schedule 11.04.2013
comment
Пытался уточнить, добавляя код. Надеюсь, поможет.   -  person Toverbal    schedule 11.04.2013
comment
опубликуйте свой журнал ошибок тоже, пожалуйста.   -  person kdehairy    schedule 12.04.2013
comment
Я добавил трассировку стека и добавил маркер строки 85 в Helpers.js. Надеюсь, это прояснит еще больше :) Таблица определяется следующим образом (но я должен добавить (снова), что код работает, когда не в событии, запущенном AlarmManager или в виджетах): CREATE TABLE "image_data" ( "image_source" TEXT, "category" TEXT, "title" TEXT, "title_nl" TEXT, "description" TEXT, "description_nl" TEXT, "image_thumb" TEXT, "stamp" INTEGER, "random_tag" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY ("image_source","category") )   -  person Toverbal    schedule 12.04.2013
comment
У меня похожая ситуация, получаю то же исключение. Вы нашли решение? Есть ли у вас сведения об устройстве/версии/ПЗУ, в которых вы столкнулись с этой проблемой?   -  person Raanan    schedule 17.04.2013
comment
@Raanan, похоже, это происходит на разных устройствах. Я попытаюсь проверить свои подозрения (контекст неверен), но это займет некоторое время, потому что я знаю только одного парня, у которого есть эта проблема, и он занят в ближайшие недели, поэтому потребуется некоторое время, чтобы подтвердить это (хотя я очень надеюсь, что смогу это подтвердить :))   -  person Toverbal    schedule 18.04.2013
comment
@Raanan, не могли бы вы предоставить мне некоторую информацию о вашей проблеме, это может на самом деле дать подсказку (я только что обнаружил, что вы не можете связываться с пользователями в частном порядке, используя этот сайт, поэтому я прокомментировал это здесь :)).   -  person Toverbal    schedule 23.04.2013
comment
Я добавлю комментарий с более подробной информацией позже ~ (10 часов)   -  person Raanan    schedule 23.04.2013
comment
Итак, мое приложение (RefreshMe) также какое-то время находится в Play, у меня немного сложная настройка, поскольку оно использует службы и действия в разных процессах, поэтому БД обернута поставщиком контента. Пользователь пожаловался, что приложение вообще не работает, вылетает. В его журналах android.database.sqlite.SQLiteException: такое исключение таблицы всегда не является причиной. Я использую функцию onCreate для создания БД, поэтому нет возможности получить доступ к БД до создания таблиц. Пока что только один пользователь сообщил, что он использует Galaxy S2, 4.1.2, используя Resurrection Remix JB v3.1.2 ROM. Идеи?   -  person Raanan    schedule 24.04.2013
comment
Просто дикий удар в темноте, но не думали ли вы, что это может быть проблема с разрешением? Я мог бы быть так, что виджет каким-то образом считается за пределами приложения и поэтому не может получить доступ к поставщику контента (просто случайная идея, ничего, чтобы поддержать это)   -  person Corey Scott    schedule 24.04.2013
comment
В дополнение к тому, что говорит Кори, являетесь ли вы контекстом приложения, как и я, Раанан? Я все еще подозреваю, что контекст приложения может отличаться в службах/виджетах/событиях алармменеджера/и т.д. Проблема в том, что я не могу проверить это, так как проблема на самом деле не воспроизводима.   -  person Toverbal    schedule 24.04.2013
comment
В моем случае я не думаю, что это как-то связано с контекстом, поскольку я обращаюсь к базе данных через ContentProvider.   -  person Raanan    schedule 24.04.2013
comment
Можете ли вы опубликовать метод InitializeFrescoDatabaseTask.getDatabaseFileName(context)?   -  person Emanuel Moecklin    schedule 25.04.2013
comment
@Raanan: вы правы в том, что BroadcastReceivers запускаются после вызова Application.onCreate(). С другой стороны, ContentProviders может запускаться до вызова Application.onCreate(), так что, может быть, в вашем случае действительно есть состояние гонки?   -  person Emanuel Moecklin    schedule 25.04.2013
comment
@Emanuel: Я думаю, ты попал в точку! Этот метод проходит через несколько циклов и, наконец, вызывает перегруженный метод, который делает следующее: return (context == null ? "" : "/data/data/" + context.getPackageName() + "/databases/");. Может ли быть так, что контекст равен нулю? Я помню, вчера вечером был опубликован еще один ответ, в котором обсуждалось, когда создается контекст приложения, но, похоже, он был удален?   -  person Toverbal    schedule 25.04.2013
comment
@Toverbal: это был мой ответ, но он был написан с ложным предположением, что, как и ContentProviders, BroadcastReceivers могут быть запущены до вызова Application.onCreate(). В случае Раанана этот ответ все еще может быть правильным, но я сначала сосредоточусь на вашем случае, потому что информации о проблеме Раанана недостаточно, чтобы дать квалифицированный совет.   -  person Emanuel Moecklin    schedule 25.04.2013
comment
@Toverbal: если контекст имеет значение null, то он, безусловно, не будет иметь доступа к правильной базе данных и может объяснить исключение, которое получают ваши пользователи. Я мог бы придумать несколько сценариев, которые могли бы объяснить, что контекст является нулевым, например. что Кори упоминает о разрешениях или виджете, работающем в другом процессе (используя android:process в вашем манифесте). Но это приведет к надежному сбою приложения, а не только время от времени.   -  person Emanuel Moecklin    schedule 25.04.2013
comment
@Toverbal: я заметил одну вещь: вы используете контекст приложения для создания намерения при создании будильника (новое намерение (AppContextHelper.getContext(), ApodDownloader.class);). При этом вам НЕОБХОДИМО установить FLAG_ACTIVITY_NEW_TASK, иначе вы можете получить исключение android.util.AndroidRuntimeException: для вызова startActivity() из-за пределов контекста Activity требуется флаг FLAG_ACTIVITY_NEW_TASK. Это действительно то, что вы хотите?   -  person Emanuel Moecklin    schedule 25.04.2013
comment
@Toverbal: возможно, я только что обнаружил вашу настоящую проблему. Вы запускаете какую-то AsyncTask в своем onReceive(). Документация ясно говорит об этом:   -  person Emanuel Moecklin    schedule 25.04.2013
comment
Объект BroadcastReceiver действителен только на время вызова onReceive(). Как только ваш код возвращается из этой функции, система считает, что объект завершен и больше не активен. Это имеет важные последствия для того, что вы можете сделать в реализации onReceive(): все, что требует асинхронной операции, недоступно, потому что вам нужно будет вернуться из функции для обработки асинхронной операции, но в этот момент BroadcastReceiver больше не активен. и, таким образом, система может завершить свой процесс до завершения асинхронной операции.   -  person Emanuel Moecklin    schedule 25.04.2013
comment
Это объясняет, почему это происходит не так часто. Android может или не может убить процесс. Если это так, контекст приложения больше недоступен, и поэтому база данных будет создана в памяти без каких-либо таблиц. Я напишу ответ на ваш вопрос.   -  person Emanuel Moecklin    schedule 25.04.2013
comment
@Emanual: вау, какое потрясающее откровение! Должен признаться, что я, вероятно, читал это один раз, но я не могу его вспомнить, а также я бы не читал его для решения моей проблемы, если бы вы этого не заметили! Когда я буду свободен от работы, я проверю эту теорию на своем коде и дам вам знать, если смогу найти что-нибудь, что сломает ее, но на самом деле я совершенно уверен, что вы абсолютно правы. Я обещаю, что приму ответ в течение времени щедрости. Еще раз спасибо, и я с нетерпением жду нажатия кнопки «Принять»!   -  person Toverbal    schedule 25.04.2013
comment
Извините, я имел в виду Эмануэля вместо Эмануэля.   -  person Toverbal    schedule 25.04.2013


Ответы (1)


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

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

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

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

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

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

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

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

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

person Emanuel Moecklin    schedule 24.04.2013
comment
Я согласен, что это может быть ответом на проблему, с которой столкнулся Товербал. Не решает мою проблему, но дал мне несколько новых идей для проверки. - person Raanan; 25.04.2013
comment
@Raanan: если вы не предоставите некоторую информацию о своем проекте, вы ничем не сможете помочь. Может быть, открыть свой вопрос? - person Emanuel Moecklin; 25.04.2013
comment
Да, я согласен, но сначала я проведу еще несколько тестов, прежде чем задать свой вопрос. Если ваш ответ будет принят Товербалом, я награжу вас наградой. - person Raanan; 25.04.2013
comment
Я все проверил, и все симптомы указывают на то, что проблема именно в этом, спасибо всем за поддержку в этом поиске! О намерении предупреждения: я изменю контекст или использую FLAG_ACTIVITY_NEW_TASK, когда контекст недоступен, спасибо, что заметили! Кроме того, если вы открываете проблему, пожалуйста, сделайте ссылку здесь, Раанан, люди могут прочитать это в будущем, и тогда они сразу же перейдут к вашей проблеме :) - person Toverbal; 25.04.2013
comment
Вам нужно использовать FLAG_ACTIVITY_NEW_TASK всякий раз, когда вы используете контекст приложения вместо контекста действия. Я не могу точно сказать, в каком контексте вы устанавливаете будильник, но если это не действие, вы должны установить этот флаг. - person Emanuel Moecklin; 25.04.2013
comment
Я рад, что потратил так много времени на поиск правильного ответа на вопрос, не получив награду, в то время как кто-то другой получает награду в 25 репутации за неправильный ответ. Это большой стимул отвечать на больше вопросов о SO. - person Emanuel Moecklin; 26.04.2013
comment
@EmanuelMoecklin извините, меня отвлекли личные проблемы, вы должны получить хотя бы эту награду, я попытаюсь решить эту проблему. - person Raanan; 26.04.2013