Tov*_*bal 14 android android-widget android-sqlite
我在应用程序商店中有一个应用程序,但有一种类型的错误报告,我似乎无法完全弄清楚.我的应用程序使用内部sqlite数据库,但在某些设备上(当然不是大多数)有时会发生以下错误:
android.database.sqlite.SQLiteException:没有这样的表:image_data(代码1):,同时编译:SELECT Min(标记)FROM image_data WHERE category ='Astronomy'AND stamp> = 1357426800 and coalesce(title_nl,'')=' "
我确信这个表存在,我确信这个查询是正确的.我知道这个错误只发生在应用程序的小部件和AlarmManager触发的BroadcastReceiver中(应用程序偶尔尝试下载新条目,因为它是当天应用程序的图片).
我认为它与访问数据库时我所处的Context有关.我有一个名为AppContextHelper的类,它扩展了Application并且有一个静态成员,我在其中存储上下文.访问数据库时始终使用该上下文.
我的问题:在某些情况下,当获取小部件中的数据库或前面提到的由AlarmManager触发的BroadcastReceiver时,这个上下文是无效的吗?在这种情况下,我应该使用提供的Context来支持'application'上下文?如果是这样,为什么该上下文无效或更好,哪个上下文是提供的?
提前致谢!
正如请求导致问题的代码一样,仅在某些设备上,而且仅在小部件或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);
Run Code Online (Sandbox Code Playgroud)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;
}
}
Run Code Online (Sandbox Code Playgroud)(部分)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);
Run Code Online (Sandbox Code Playgroud)FrescoDatabase.java数据库是由应用程序在启动时自动生成的,此代码也可以在触发异常的设备上运行.我无法强调数据库存在于有问题的设备上,因为除了AlarmManager的小部件和BroadcastReceiver之外,app运行完美,所以请不要告诉我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) {
}
}
Run Code Online (Sandbox Code Playgroud)(部分)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;
}
}
Run Code Online (Sandbox Code Playgroud)异常日志(根据要求):
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
Run Code Online (Sandbox Code Playgroud)
我在Helpers.java块添加了一个85行标记.
希望这可以帮助!
这是发生的事情:
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 提供的 Context 尚未初始化,那么您的 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 标志。这真的是您想要的吗?” 这可能会或可能不会使应用程序崩溃。