2011-06-01 30 views
3

我有一個使用SQL數據庫的應用程序。這是由SQLiteOpenHelper類封裝的。當啓動屏幕啓動時,它將調用DataProvider類的init,該類存儲SQLiteOpenHelper的受保護靜態實例。初始化只是調用SQLiteOpenHelper的構造函數:Android生命週期和空指針問題

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,如果需要,它將檢查null並重新初始化。

由於提前, 巴里

回答

8

當殺死的Android應用程序回收內存,應用程序/活動對象都卸載,將不復存在。但是,Android會保存某些信息,以便在您嘗試再次運行應用程序時可以將其恢復到近似狀態。

其中的一部分內容包括有關活動堆棧狀態(即正在運行哪些活動)的信息,在其銷燬之前。正如你注意到的,Android將你的應用程序恢復到上次運行的Activity,而不是你的應用程序的開始(啓動屏幕)。這是一個'功能',我只能假設這是爲了讓用戶甚至不應該注意到應用程序已卸載而提供無縫體驗的嘗試。

在重新創建當前Activity之前,Android沒有理由重新創建啓動畫面(即使它仍然存在於Activity堆棧中)。不鼓勵這種依賴性,並且不適合Android加載/卸載它認爲合適的活動。通常,在以前的活動中初始化靜態是在這種情況下遇到問題的一種可靠方法。由於沒有重新創建初始屏幕,因此在其他活動中使用它之前,它不會初始化UKMPGDataProvider,因此NullPointerException

你可以用兩種方法解決這個問題。

  1. 初始化使用它的每個Activity的Activity.onCreate(Bundle)中的UKMPGDataProvider。如果其中一項活動正在恢復,則保證將回撥至onCreate,因此數據提供者將始終被初始化。

  2. 初始化UKMPGDataProvider裏面Application.onCreate()。這可以保證在任何Activity啓動之前調用,即使在內存回收/恢復時也是如此。如果您還沒有自定義類Application,則應在AndroidManifest.xml中創建一個子類,然後使用point to it以供使用。

順便說一句,實現Activity.onSaveInstanceState(Bundle)是保存應用程序相關的附加狀態,這將還給你Activity.onCreate(Bundle)一種方式(它是Bundle參數)在恢復時。它僅用於臨時狀態,例如某些用戶操作之後出現的某種視圖狀態(特別是自定義視圖的狀態)。它不適合這種情況,因爲您可以輕鬆生成新的UKMPGDataProvider,並且它沒有任何狀態鏈接到前一個會話。

+0

這聽起來像我正在尋找的答案。謝謝。 – barry 2012-03-10 14:13:47

0

這將是最好的投放在onStart()或的onResume()中的所有數據初始化,而不是的onCreate()。如果您將這些初始化設置移至其中一個區域,則可能會更好。請記住,這些可以在你提到的實例中調用,如果應用停止一段時間然後又回來。但是相應的onStop()或onPause()會先被調用。

我懷疑你的內存部分正在被回收,但不是全部回收,如果按照我上面的建議操作,它會解決你的問題。

+0

感謝您的回覆。這肯定會起作用,但當用戶在應用程序中導航時,它不會真正感覺正確,因爲活動會反覆地初始化一個db對象。我希望這隻需要在每次運行應用程序時執行一次。 – barry 2011-06-01 19:20:32

0

我建議你在你活動的onCreate初始化SQLiteOpenHelper對象(),關閉它在的onDestroy()並通過您活動上下文反對。

這樣你SQLiteOpenHelper對象將正確地與您的活動的生命週期相關

+0

問題是,爲什麼android會這樣做?是將一些對象歸零,但不會破壞應用程序。 – barry 2012-03-07 22:50:15

+0

我相信你遇到了內存泄漏,就像這樣http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html – 2012-03-08 08:59:32

+0

但我正在傳遞一個應用程序上下文,就像文章中的建議。 – barry 2012-03-08 21:41:41

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

+0

但是,爲什麼我們在清除應用程序時清理了一些對象?如果Android想要回收內存,爲什麼不殺掉應用程序而不是回收某些對象? – barry 2012-03-10 00:46:31

+0

當應用程序被終止時,所有對象都會被清理。但是,舉例來說,您可以在ActivityA中啓動程序,然後調用init()方法,然後在應用程序暫停並終止後進入ActivityB。在ActivityB中恢復應用程序時,所有對象都是新的,但尚未調用init()方法,這就是Nul​​lPointer的原因 – Addev 2012-03-10 09:20:19

0

您應該在您的活動onResume()方法中調用VehicleDataProvider.init()。

,你不應該在之前的生活cylcle 的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的環境時,此行爲並不常見。

希望這會有所幫助