Проблема жизненного цикла Android и нулевого указателя - PullRequest
3 голосов
/ 01 июня 2011

У меня есть приложение, которое использует базу данных 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, который проверит на ноль и при необходимости повторно инициализирует.

Заранее спасибо, Барри

Ответы [ 6 ]

8 голосов
/ 10 марта 2012

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

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

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

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

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

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

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

0 голосов
/ 10 марта 2012

Вы должны вызывать 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();
    }
   ...
  }

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

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

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

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

0 голосов
/ 10 марта 2012

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

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

public static Cursor getVehicles()

, объект uKMpgData имеет значение null, поскольку вы не вызывали метод 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 () методы.Есть хороший трюк, который вы можете использовать.

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

0 голосов
/ 07 марта 2012

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

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

0 голосов
/ 01 июня 2011

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

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

0 голосов
/ 01 июня 2011

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

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

...