2011-06-29 26 views
20

UPDATE 2/13/2012:接受了一個答案,解釋說這種行爲是一個錯誤,並指出它似乎已經在仿真器上消失得比1.6版更好,這對我們大多數人來說都不是問題。解決方法是簡單地循環/休眠直到getContext()。getApplicationContext()返回非空。 END UPDATE爲什麼AndroidTestCase.getContext()。getApplicationContext()返回null?

根據android.app.Application javadoc,我定義了一個單例(稱爲數據庫),我的所有活動都可以訪問狀態數據和持久數據,而Database.getDatabase(Context)通過上下文獲取應用程序上下文。 getApplicationContext()。此設置按照活動傳遞給getDatabase(Context)時的通告方式工作,但是當我從AndroidTestCase運行單元測試時,getApplicationContext()調用通常返回null,儘管測試時間越長,返回非空的頻率越高值。

以下代碼重現了AndroidTestCase中的null - 單例不是演示必需的。

首先,爲了記錄應用程序實例化消息,在測試的應用程序中,我定義了MyApp並將其添加到清單中。

public class MyApplication extends Application { 
    @Override 
    public void onCreate() { 
     super.onCreate(); 
     Log.i("MYAPP", "this=" + this); 
     Log.i("MYAPP", "getAppCtx()=" + getApplicationContext()); 
    } 
} 

接下來,我所定義的測試用例來上AndroidTestCase.getContext()4次報告,由一些睡覺和getSharedPreferences)分開(呼叫:

public class DatabaseTest extends AndroidTestCase { 
    public void test_exploreContext() { 
     exploreContexts("XPLORE1"); 
     getContext().getSharedPreferences("foo", Context.MODE_PRIVATE); 
     exploreContexts("XPLORE2"); 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     exploreContexts("XPLORE3"); 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     exploreContexts("XPLORE4"); 
    } 
    public void exploreContexts(String tag) { 
     Context testContext = getContext(); 
     Log.i(tag, "testCtx=" + testContext + 
       " pkg=" + testContext.getApplicationInfo().packageName); 
     Log.i(tag, "testContext.getAppCtx()=" + testContext.getApplicationContext()); 
     try { 
      Context appContext = testContext.createPackageContext("com.foo.android", 0); 
      ApplicationInfo appInfo = appContext.getApplicationInfo(); 
      Log.i(tag, "appContext=" + appContext + 
        " pkg=" + appContext.getApplicationInfo().packageName); 
      Log.i(tag, "appContext.getAppCtx()=" + appContext.getApplicationContext()); 
     } catch (NameNotFoundException e) { 
      Log.i(tag, "Can't get app context."); 
     } 
    } 
} 

這是的一個塊導致logcat的(通過Eclipse 1.6模擬器上SDK11的WinXP):在getApplicationContext()一會兒返回null,然後開始返回MyApp的實例

INFO/TestRunner(465): started: test_exploreContext(test.foo.android.DatabaseTest) 
INFO/XPLORE1(465): [email protected] pkg=com.foo.android 
INFO/XPLORE1(465): testContext.getAppCtx()=null 
INFO/XPLORE1(465): [email protected] pkg=com.foo.android 
INFO/XPLORE1(465): appContext.getAppCtx()=null 
INFO/XPLORE2(465): [email protected] pkg=com.foo.android 
INFO/XPLORE2(465): testContext.getAppCtx()=null 
INFO/XPLORE2(465): [email protected] pkg=com.foo.android 
INFO/XPLORE2(465): appContext.getAppCtx()=null 
INFO/MYAPP(465): [email protected] 
INFO/MYAPP(465): getAppCtx()[email protected] 
INFO/XPLORE3(465): [email protected] pkg=com.foo.android 
INFO/XPLORE3(465): testContext.getAppCtx()[email protected] 
INFO/XPLORE3(465): [email protected] pkg=com.foo.android 
INFO/XPLORE3(465): appContext.getAppCtx()[email protected] 
INFO/XPLORE4(465): [email protected] pkg=com.foo.android 
INFO/XPLORE4(465): testContext.getAppCtx()[email protected] 
INFO/XPLORE4(465): [email protected] pkg=com.foo.android 
INFO/XPLORE4(465): appContext.getAppCtx()[email protected] 
INFO/TestRunner(465): finished: test_exploreContext(test.foo.android.DatabaseTest) 

通知。在這個測試的不同運行中,我一直無法得到完全相同的結果(這就是我最終在4次迭代,睡眠以及getSharedPreferences()的嘗試嘗試讓應用程序存在的結果)。

上面的LogCat消息塊似乎最相關,但單個測試的單次運行的整個LogCat很有趣。 Android開始了4個AndroidRuntimes;上面的塊是從第四個。有趣的是,第三運行時顯示錶示其實例MyApp的的不同實例在進程ID 447的消息:

INFO/TestRunner(447): started: test_exploreContext(test.foo.android.DatabaseTest) 
INFO/MYAPP(447): [email protected] 
INFO/MYAPP(447): getAppCtx()[email protected] 
INFO/TestRunner(447): finished: test_exploreContext(test.foo.android.DatabaseTest) 

我假定TestRunner的(447)消息是從在過程465在其孩子父母測試線程報告。問題仍然存在:爲什麼Android會在其上下文正確連接到Application實例之前讓AndroidTestCase運行?

解決方法:如果我先撥打getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit();,我的一個測試似乎在大多數情況下避免了空值,所以我正在使用它。

BTW:如果答案是「這是一個Android錯誤,爲什麼不提交它;哎呀,你爲什麼不修復它?那麼我願意做這兩件事。我還沒有成爲一個bug編制者或貢獻者的步驟 - 也許這是一個好時機。

+0

我注意到AndroidTestCase也有一些奇怪的行爲。通常情況下,每個測試運行多次,但只向Eclipse報告一次。例如,我有時會在'onDestroy'中得到'Nullpointer'異常,而所有實例字段都在'onCreate'中正確初始化。你可以通過在'onDestroy'中放置一個斷點來檢驗這一點,並且看到執行至少有四次,而測試只運行一次。 – siamii

+1

我在Android 4.0.4模擬器上遇到了同樣的問題,ApplicationContext有時會在那裏,有時候不會。你的解決方法,直到它在那裏似乎工作。 – Ralf

+0

對於使用SDK 10在模擬器HAX上運行的測試,我也會看到它。否則,看起來錯誤不能再生成。 – Snicolas

回答

12

工具在與主應用程序線程分離的線程中運行,以便它可以在不阻塞或干擾主線程的情況下執行。如果您需要與主線程同步,請使用例如:Instrumentation.waitForIdleSync()

特別是,Application對象以及所有其他頂級類(如Activity)都由主線程初始化。您的檢測線程正在初始化的同時運行。如果你正在觸摸任何這些對象並且沒有實現你自己的線程安全措施,你應該可以在主線程上運行這樣的代碼,例如:Instrumentation.runOnMainSync(java.lang.Runnable)

+0

啊哈!我會給它一個鏡頭(然後接受你的答案)。 +1現在。謝謝! – cdhabecker

+0

我認爲你正確地確定了行爲的原因,所以我接受了這個答案。 行爲仍然是一個缺陷;框架類應該在測試用例方法運行之前完成設置。換句話說,AndroidTestCase.getContext()javadoc沒有列出任何前置請求;實際上,getContext()返回一個值,它是getContext()。getApplicationContext(),它沒有(一段時間)。 儀表同步的想法很好,但不適用於AndroidTestCase。不管怎麼說,還是要謝謝你。 最後 - 我可以在1.6模擬器上重現這一點,但不能在2.1,2.3.3或3.0上重現。所以,讓我們稱它爲一天! :-) – cdhabecker

4

正如問題和Dianne的回答(@ hackbod),Instrumentation在單獨的線程上運行。 AndroidTestCase或者有一個實現缺陷(缺少同步)或沒有正確記錄。不幸的是,沒有辦法從這個特定的測試用例類中調用Instrumentation.waitForIdleSync(),因爲這個工具不能從它訪問。

這個子類可以用來添加同步,可以輪詢getApplicationContext(),直到它返回一個非空值:

public class MyAndroidTestCase extends AndroidTestCase { 

    @Override 
    public void setContext(Context context) { 
     super.setContext(context); 

     long endTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(2); 

     while (null == context.getApplicationContext()) { 

      if (SystemClock.elapsedRealtime() >= endTime) { 
       fail(); 
      } 

      SystemClock.sleep(16); 
     } 
    } 
} 

輪詢和睡眠的持續時間是根據經驗,必要時可以調整。

+1

此問題似乎影響API 16 Jelly Bean下面的設備。 –