Android生命周期和空指针问题

时间:2011-06-01 19:07:24

标签: android nullpointerexception lifecycle

我有一个使用SQL数据库的应用程序。这是由SQLiteOpenHelper类封装的。启动启动画面时,它会在DataProvider类上调用init,该类存储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;
}
//...
}

这一切似乎工作正常,直到我注意到如果应用程序正在运行然后被强制到后台,如果它被放置了几个小时,当应用程序被带回到前台时我会得到一个空指针在一个名为getVehicles()的Activity类中(见上文)。事实证明,uKMpgData不再引用对象。

我知道Android可以在必要时杀死进程,但是不明白我的应用程序发生了什么以获取空指针 - 如果我的应用程序的进程被杀死,那么应用程序的新实例就不会推出?换句话说,新的SplashScreen会初始化数据库对象,因此没有空指针异常。

我必须遗漏一些东西 - 我的应用程序状态是回收内存(对数据库对象的引用),但是在重新启动时显示最后一个可见活动。

顺便提一下,该错误现已修复。 VehicleDataProvider和其他类似的类不再扩展超类数据提供程序(UKMPGDataProvider),它现在拥有对ukMpgData的私有引用。触摸数据库的所有方法现在都通过UKMPGDataProvider,它将检查是否为空,并在必要时重新初始化。

提前致谢, 百里

6 个答案:

答案 0 :(得分:8)

当Android杀死您的应用程序以回收内存时,您的Application / Activity对象都将被卸载并且将不再存在。但是,Android会保存某些信息,以便在您尝试再次运行时将应用程序恢复到该近似状态。

其中一部分包括有关活动堆栈状态的信息(即哪些活动正在运行),然后才会销毁。正如您所注意到的,Android会将您的应用程序还原到上次运行的Activity,而不是应用程序的开头(启动屏幕)。这是一个“功能”,我只能假设它是为那些甚至不会注意到应用程序已卸载的用户提供无缝体验的尝试。

在重新创建当前活动之前,Android没有理由重新创建启动画面(即使它仍然存在于活动堆栈中)。这种依赖性不受鼓励,并且不是您可以依赖的东西,因为Android会根据需要加载/卸载活动。通常,在先前的活动中初始化的静态是在这种情况下遇到问题的可靠方法。由于未重新创建启动画面,因此在您在另一个活动中使用它之前,它不会初始化UKMPGDataProvider,因此NullPointerException

您可以通过两种方式解决此问题。

  1. 初始化使用它的每个Activity的UKMPGDataProvideronCreate。如果正在恢复其中一个活动,则可以保证回调UKMPGDataProvider,因此数据提供程序将始终初始化。

  2. 初始化Activity.onCreate(Bundle)内的Application。保证在任何Activity启动之前调用它,即使在内存恢复/恢复中也是如此。如果您还没有自定义AndroidManifest.xml课程,则应创建一个子类,然后在Activity.onCreate(Bundle)中创建Application.onCreate()以便使用它。

  3. 顺便说一下,实现point to it是保存与应用程序相关的其他状态的一种方法,它将在Bundle(它是UKMPGDataProvider参数)中返回给您恢复。它仅用于临时状态,例如在某些用户操作(特别是自定义视图的状态)之后出现的某种视图状态。它不适用于这种情况,因为您可以轻松生成新的{{1}},并且没有任何状态链接到上一个会话。

答案 1 :(得分:0)

最好将所有数据初始化放在onStart()或onResume()中,而不是onCreate()。如果将这些初始化移动到其中一个区域,它可能会更好。请记住,如果应用程序停止一段时间然后返回,则可以在您提到的实例中调用这些实例。但是首先会调用相应的onStop()或onPause()。

我怀疑你的部分记忆正在被回收,但不是全部,并且如果按照我上面的建议行事,它会解决你的问题。

答案 2 :(得分:0)

您没有显示任何活动生命周期处理程序。 看一下这个: http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

在您的情况下,您首先要查看onStop()和onRestart()。

答案 3 :(得分:0)

我建议您在Activity onCreate()中初始化 SQLiteOpenHelper 对象,在 onDestroy()中将其关闭并传递活动上下文对象。

这样,您的 SQLiteOpenHelper 对象将与您的活动生命周期正确关联

答案 4 :(得分:0)

问题在于,当您的应用程序恢复时,您的对象不再存在,因为它们已清理,以便为其他应用程序获取内存。

所以当你打电话

public static Cursor getVehicles()

对象uKMpgData为null,因为您尚未调用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()方法的位置检索上下文。你可以使用一个很好的技巧。

检查THIS ANSWER

答案 5 :(得分:0)

您应该在活动onResume()方法中调用VehicleDataProvider.init()。

你不应该在生命周期中的 onResume 之前访问VehicleDataProvider,而不能在 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的环境时,此行为并不常见。

希望这有帮助