Проблема жизненного цикла Android и нулевой указатель

У меня есть приложение, которое использует базу данных SQL. Это инкапсулируется классом SQLiteOpenHelper. Когда запускается экран заставки, он вызывает init в классе DataProvider, который хранит защищенный статический экземпляр SQLiteOpenHelper. Init просто вызывает конструктор SQLiteOpenHelper:

public class UKMPGData extends SQLiteOpenHelper { public UKMPGData(Context context, String databaseName) { super(context, databaseName, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { //create table and set up triggers etc } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { onCreate(db); } } public class UKMPGDataProvider { protected static UKMPGData uKMpgData; public static void init(Context aApplicationContext, String aDatabaseName) { uKMpgData = new UKMPGData(applicationContext, databaseName); } public static void close() { uKMpgData.close(); } } 

Затем у меня было еще два класса, которые расширили UKMPGDataProvider и, следовательно, имели доступ к uKMpgData. Эти классы извлекают и хранят определенные типы данных из базы данных. Например.

 public class VehicleDataProvider extends UKMPGDataProvider { public static Cursor getVehicles() { Cursor cursor = null; SQLiteDatabase db = uKMpgData.getReadableDatabase(); cursor = db.query(VEHICLE_TABLE_NAME, GET_VEHICLES_FROM_CLAUSE, null, null, null, null, ORDER_BY); return cursor; } //... } 

Все это, казалось, отлично работало, пока я не заметил, что если приложение запускалось, а затем принудительно переходило на задний план, если его оставляли на несколько часов, когда приложение было возвращено на передний план, я бы получил нулевой указатель в Activity Класс, который называется getVehicles () (см. Выше). Оказывается, uKMpgData больше не ссылается на объект.

Я понимаю, что Android может убивать процессы, когда это считается необходимым, но не понимаю, что случилось с моим приложением, чтобы получить нулевой указатель. Если процесс моего приложения был убит, не будет ли запущен новый экземпляр приложения? Другими словами, новый SplashScreen инициализирует объект базы данных и, следовательно, исключает исключение нулевого указателя.

Мне нужно что-то упустить – какое состояние было для моего приложения, чтобы восстановить память (ссылку на объект базы данных), но отобразить последнюю видимую активность после перезапуска.

Кстати, ошибка теперь исправлена. VehicleDataProvider и другой подобный класс больше не расширяют поставщика данных суперкласса (UKMPGDataProvider), который теперь имеет частную ссылку на ukMpgData. Все методы, которые касаются базы данных, теперь проходят через UKMPGDataProvider, который в случае необходимости проверяет наличие нулевого значения и повторно инициализирует.

Спасибо заранее, Барри

Когда Android убивает ваше приложение для восстановления памяти, ваши объекты Application / Activity все выгружаются и больше не будут существовать. Однако Android сохраняет определенную информацию, чтобы ваше приложение можно было восстановить в этом приблизительном состоянии, когда вы попытаетесь запустить его снова.

Часть этого включает информацию о состоянии стека активности (то есть, какие действия выполнялись) до его уничтожения. Как вы заметили, Android восстанавливает ваше приложение в последней активности, а не в начале вашего приложения (заставки). Это «функция», и я могу только предположить, что это попытка обеспечить бесшовный опыт для пользователя, который не должен даже заметить, что приложение было выгружено.

Нет причин, по которым Android должен воссоздать ваш заставку (даже если он все еще существовал в стеке активности) до воссоздания текущей Activity. Такая зависимость не поощряется, и на нее нельзя положиться, так как Android загружает / выгружает действия по своему усмотрению. В общем случае статическое состояние, которое инициализируется в предыдущих действиях, является верным способом столкнуться с проблемами в этой ситуации. Поскольку заставка не была воссоздана, она не будет инициализирована UKMPGDataProvider прежде чем вы будете использовать ее в другом UKMPGDataProvider , следовательно, NullPointerException .

Вы можете решить это двумя способами.

  1. Инициализируйте UKMPGDataProvider внутри Activity.onCreate (Bundle) каждого действия, которое его использует. Если одна из этих onCreate восстанавливается, гарантируется получение обратного вызова onCreate и, следовательно, поставщик данных всегда будет инициализирован.

  2. Инициализируйте UKMPGDataProvider внутри Application.onCreate () . Это гарантированно будет вызываться до начала любой активности даже в случае восстановления / восстановления памяти. Если у вас еще нет собственного класса Application , вы должны создать подкласс, а затем указать его в вашем AndroidManifest.xml для его использования.

В стороне, реализация Activity.onSaveInstanceState (Bundle) является одним из способов сохранения дополнительного состояния, соответствующего вашему приложению, которое будет возвращено вам в Activity.onCreate(Bundle) (это аргумент Bundle ) после восстановления. Он предназначен только для состояния переходного процесса, например, для некоторого состояния представления, возникшего после некоторых действий пользователя (в частности, состояния пользовательских представлений). Это не подходит для этого случая, так как вы можете легко создать новый UKMPGDataProvider и не иметь никакого состояния, связанного с предыдущим сеансом.

Было бы лучше всего инициализировать все данные в onStart () или onResume (), а не onCreate (). Если вы переместите эти инициализации в одну из этих областей, это, скорее всего, будет работать лучше. Помните, что они могут быть вызваны именно в тех случаях, о которых вы упомянули, если приложение останавливается на некоторое время, а затем возвращается. Но соответствующие вызовы onStop () или onPause () будут вызваны первыми.

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

Вы не показываете ни одного из ваших обработчиков жизненного цикла вашей деятельности. Проверьте это: http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

В вашем случае вы хотите сначала посмотреть onStop () и onRestart ().

Я предлагаю вам инициализировать ваш объект SQLiteOpenHelper в вашем Activity onCreate () , закройте его в onDestroy () и передайте ему объект Activity Context .

Таким образом, ваш объект SQLiteOpenHelper будет правильно связан с вашим жизненным циклом активности

Проблема в том, что когда ваше приложение возобновляется, ваших объектов больше нет, потому что они очищаются, чтобы получить память для других приложений.

Поэтому, когда вы звоните

 public static Cursor getVehicles() 

Объект uKMpgData является нулевым, потому что вы не вызывали метод init. У вас есть некоторые решения для этого. Чем проще, тем лучше, если у вас есть доступ к контексту init значение перед его вызовом. Выполнение чего-то вроде:

 Cursor cursor = null; SQLiteDatabase db = getuKMpgData().getReadableDatabase(); ..... 

И имея метод

 public static UKMPGData getuKMpgData(){ if(uKMpgData==null){ Context cnt= //Some method for retrieving the context String dbname="myDB"; init(cnt,dbname) } return uKMpgData; } 

Если вы не знаете, как извлечь контекст из места, где вы не можете вызвать методы getContext () или getApplicationContext (). Есть хороший трюк, который вы можете использовать.

Проверьте ЭТО ОТВЕТ

Вы должны вызвать VehicleDataProvider.init () в своих действиях onResume ().

И вы НЕ должны обращаться к VehicleDataProvider перед onResume в жизненном цикле, и не больше после onPause .

На самом деле не рекомендуется использовать статические элементы в android. Я бы рекомендовал общий редизайн, чтобы избавиться от статики (я надеюсь, что нет опечаток)

 public class UKMPGDataProvider { protected UKMPGData uKMpgData; protected UKMPGDataProvider(Context aApplicationContext, String aDatabaseName) { uKMpgData = new UKMPGData(applicationContext, databaseName); } public void close() { uKMpgData.close(); } } 

И теперь ваш VehicleDataProvider

 public class VehicleDataProvider extends UKMPGDataProvider { public VehicleDataProvider(Context aApplicationContext, String aDatabaseName) { super (aApplicationContext, aDatabaseName); } public Cursor getVehicles() { Cursor cursor = null; SQLiteDatabase db = uKMpgData.getReadableDatabase(); cursor = db.query(VEHICLE_TABLE_NAME, GET_VEHICLES_FROM_CLAUSE, null, null, null, null, ORDER_BY); return cursor; } //... } 

И теперь ваша деятельность

 .. extends Activity { private VehicleDataProvider vehicle; @Override protected void onResume() { super.onResume(); vehicle = new VehicleDataProvider (this, "database.db"); ... } @Override protected void onPause() { vehicle.close (); vehicle = null; super.onPause(); } ... } 

Когда вы получаете исключение из null-указателя, это очень похоже на то, что вы получаете доступ к транспортному средству рано или поздно.

Абсолютно необходимо освободить базу данных в onPause. OnPause происходит, например, когда другая активность становится видимой. В этом случае любая память, занимаемая базой данных, должна быть освобождена.

Android разработан как память с низкой памятью, освобождает память как можно скорее. Такое поведение необычно, когда вы приходят из других java-сред.

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