14

現在、アプリ ストアでアプリを入手できますが、完全に把握できないエラー レポートが 1 つあります。私のアプリは内部の sqlite データベースを使用していますが、一部のデバイス (確かに大部分ではありません) では、次のエラーが発生することがあります。

android.database.sqlite.SQLiteException: そのようなテーブルはありません: image_data (コード 1): 、コンパイル中: SELECT Min(stamp) FROM image_data WHERE カテゴリ = '天文学' AND スタンプ >= 1357426800 および 合体 (title_nl, '') = ' '

このテーブルが存在すると確信しており、このクエリが正しいと確信しています。このエラーは、アプリのウィジェットと、AlarmManager によって起動される BroadcastReceiver でのみ発生することがわかっています (アプリは、その日のアプリの画像であるため、時々新しいエントリをダウンロードしようとします)。

データベースにアクセスするときのコンテキストと関係があると思います。Application を拡張し、コンテキストを格納する静的メンバーを持つ AppContextHelper というクラスがあります。そのコンテキストは、データベースにアクセスするときに常に使用されます。

私の質問: 場合によっては、ウィジェットでデータベースを取得するとき、または 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() には引き続きアクセスできますが、プロセスを削除したときに古いクラスが強制終了されたため、新しくロードされたクラスになります。つまり、返されるコンテキストは null になります。これは、Android が実際にプロセスを強制終了するというまれなケースでのみ発生しますが、これは私の経験ではあまりありません。

AppContextHelper が提供するコンテキストがまだ初期化されていない場合、InitializeFrescoDatabaseTask.getDatabaseFileName(context) 呼び出しは空の文字列を返し、当然間違ったデータベースを開きます。FrescoDatabase クラスの onCreate() で何もしないため、データベースが正しく初期化されず、テーブルが見つかりません。しかし、テーブルを作成したとしても、それは間違ったデータベースであり、クラッシュはなくなったものの、データは表示されません。

これが起こらないようにするには、 onReceive() が実行されている同じスレッドですべての作業を行う必要があります。時間がかかりすぎる場合は、サービスを開始して面倒な作業を行う必要があります。

コードを分析したときに気付いたもう 1 つのことは、アプリケーション コンテキストを使用して、アラームを作成するときにインテントを作成していることです (new Intent(AppContextHelper.getContext(), ApodDownloader.class);)。これを行うときは、FLAG_ACTIVITY_NEW_TASK を設定する必要があります。そうしないと、「android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?」が発生する可能性があります。アプリがクラッシュする場合としない場合があります。

于 2013-04-24T19:10:03.710 に答える