为什么AndroidTestCase.getContext().getApplicationContext()返回null?

cdh*_*ker 21 junit android unit-testing android-context

更新2012年2月13日:接受了一个答案,解释说这种行为是一个错误,并指出它似乎已经比v 1.6更好地消失在模拟器上,这使得它对我们大多数人来说都不是问题.解决方法只是循环/休眠,直到getContext().getApplicationContext()返回非null.结束更新

按照android.app.Application的javadoc,我定义了一个单例(称为数据库)我所有的活动中访问了状态和持久性数据,并Database.getDatabase(上下文)通过Context.getApplicationContext获取应用程序上下文().当活动通过自己getDatabase(上下文)这个设置像宣传的那样,但是当我从AndroidTestCase的getApplicationContext(运行单元测试)经常呼叫返回null,虽然较长的测试中,更频繁地返回一个非空值.

以下代码在AndroidTestCase中重现null - 单例不是演示所必需的.

首先,要记录app-instantiation消息,在测试中的应用程序中我定义了MyApp并将其添加到清单中.

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

接下来,我定义了一个测试用例,用于报告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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是生成的logCat的一大块(通过Eclipse在SDK11 WinXP上的1.6模拟器):

INFO/TestRunner(465): started: test_exploreContext(test.foo.android.DatabaseTest)
INFO/XPLORE1(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android
INFO/XPLORE1(465): testContext.getAppCtx()=null
INFO/XPLORE1(465): appContext=android.app.ApplicationContext@437801e8 pkg=com.foo.android
INFO/XPLORE1(465): appContext.getAppCtx()=null
INFO/XPLORE2(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android
INFO/XPLORE2(465): testContext.getAppCtx()=null
INFO/XPLORE2(465): appContext=android.app.ApplicationContext@43782820 pkg=com.foo.android
INFO/XPLORE2(465): appContext.getAppCtx()=null
INFO/MYAPP(465): this=com.foo.android.MyApplication@43783830
INFO/MYAPP(465): getAppCtx()=com.foo.android.MyApplication@43783830
INFO/XPLORE3(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android
INFO/XPLORE3(465): testContext.getAppCtx()=com.foo.android.MyApplication@43783830
INFO/XPLORE3(465): appContext=android.app.ApplicationContext@43784768 pkg=com.foo.android
INFO/XPLORE3(465): appContext.getAppCtx()=com.foo.android.MyApplication@43783830
INFO/XPLORE4(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android
INFO/XPLORE4(465): testContext.getAppCtx()=com.foo.android.MyApplication@43783830
INFO/XPLORE4(465): appContext=android.app.ApplicationContext@43785778 pkg=com.foo.android
INFO/XPLORE4(465): appContext.getAppCtx()=com.foo.android.MyApplication@43783830
INFO/TestRunner(465): finished: test_exploreContext(test.foo.android.DatabaseTest)
Run Code Online (Sandbox Code Playgroud)

请注意,getApplicationContext()在一段时间内返回null,然后开始返回MyApp的实例.我一直没能得到在本次测试的不同运行同样的结果(这就是我结束了在4次迭代,睡觉,并getSharedPreferences是()调用尝试鹅应用到存在).

上面的LogCat消息块似乎最相关,但是单个测试的单个运行的整个LogCat很有趣.Android启动了4个AndroidRuntimes; 上面的块是从第4个.有趣的是,第3个运行时显示消息,指示它在进程ID 447中实例化了MyApp的不同实例:

INFO/TestRunner(447): started: test_exploreContext(test.foo.android.DatabaseTest)
INFO/MYAPP(447): this=com.foo.android.MyApplication@437809b0
INFO/MYAPP(447): getAppCtx()=com.foo.android.MyApplication@437809b0
INFO/TestRunner(447): finished: test_exploreContext(test.foo.android.DatabaseTest)
Run Code Online (Sandbox Code Playgroud)

我假设的TestRunner(447)消息来自其在过程465还是孩子家长测试线程报告,问题是:为什么Android的昏天暗地AndroidTestCase运行之前其上下文正确挂接到一个应用程序实例?

解决方法:如果我getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit();先打电话,我的一个测试似乎大多数时候都会避免空值,所以我就是这样做的.

顺便说一句:如果答案是"这是一个Android错误,你为什么不把它归档呢?哎呀,你为什么不修复它?" 然后我愿意做两件事.我还没有采取过成为bug-filer或贡献者的步骤 - 也许这是个好时机.

hac*_*bod 12

Instrumentation在主应用程序线程的单独线程中运行,因此它可以在不阻塞或中断(或被主线程阻塞)的情况下执行.如果需要与主线程同步,请使用例如:Instrumentation.waitForIdleSync()

特别是,Application对象以及Activity之类的所有其他顶级类都由主线程初始化.您的检测线程正在初始化时运行.如果您触摸任何这些对象并且没有实现自己的线程安全措施,则应该在主线程上运行此类代码,例如via:Instrumentation.runOnMainSync(java.lang.Runnable)