14

我目前在应用商店中有一个应用程序可用,但有一种类型的错误报告我似乎无法完全弄清楚。我的应用程序使用内部 sqlite 数据库,但在某些设备上(当然不是大多数设备)有时会出现以下错误:

android.database.sqlite.SQLiteException: no such table: image_data (code 1): , while compile: SELECT Min(stamp) FROM image_data WHERE category = 'Astronomy' AND stamp >= 1357426800 and coalesce(title_nl, '') = ' '

我确信这个表存在并且我确信这个查询是正确的。我知道这个错误只发生在应用程序的小部件和 AlarmManager 触发的 BroadcastReceiver 中(应用程序偶尔会尝试下载新条目,因为它是当天应用程序的图片)。

我认为这与我在访问数据库时所处的上下文有关。我有一个名为 AppContextHelper 的类,它扩展了 Application 并有一个静态成员用于存储上下文。访问数据库时始终使用该上下文。

我的问题:在某些情况下,在获取小部件中的数据库或上述由 AlarmManager 触发的 BroadcastReceiver 时,上下文是否无效,在这种情况下,我应该使用提供的上下文来支持“应用程序”上下文?如果是这样,为什么该上下文无效或更好,提供的是哪个上下文?

提前致谢!

根据要求,导致问题的代码再次仅在某些设备上且仅在小部件或 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 数据库由应用程序在启动时自动生成,此代码在触发异常的设备上也有效。我不能足够强调数据库存在于有问题的设备上,因为除了小部件和 AlarmManager 的 BroadcastReceiver 之外,应用程序运行完美,所以请不要告诉我数据库未正确初始化:)

    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

我在 Helpers.java 块中添加了第 85 行标记。

希望这可以帮助!

4

1 回答 1

4

这是正在发生的事情:

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) 调用将返回一个空字符串,它自然会打开错误的数据库。因为您在 FrescoDatabase 类的 onCreate() 中什么都不做,所以数据库没有正确初始化并且找不到表。但是,即使您创建了表,它也会是错误的数据库,尽管崩溃已经消失,但它不会显示任何数据。

为了防止这种情况发生,您需要在 onReceive() 正在运行的同一线程中完成所有工作,或者如果这需要太长时间启动服务来完成繁重的工作。

我在分析代码时注意到的另一件事是,您在创建警报时使用应用程序上下文来创建意图(new Intent(AppContextHelper.getContext(), ApodDownloader.class);)。执行此操作时,您需要设置 FLAG_ACTIVITY_NEW_TASK 或者您可能会收到“android.util.AndroidRuntimeException:从 Activity 上下文外部调用 startActivity() 需要 FLAG_ACTIVITY_NEW_TASK 标志。这真的是您想要的吗?” 这可能会或可能不会使应用程序崩溃。

于 2013-04-24T19:10:03.710 回答