本文介绍了为什么 AndroidTestCase.getContext().getApplicationContext() 返回 null?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

UPDATE 2/13/2012: Accepted an answer, explained that this behavior is a bug, and noted that it appears to have disappeared on emulators better than v 1.6, which makes it a non-issue for most of us. The workaround is simply to loop/sleep until getContext().getApplicationContext() returns non-null.END UPDATE

根据 android.app.Application javadoc,我定义了一个单例(称为数据库),我的所有活动都可以访问状态和持久数据,Database.getDatabase(Context) 通过 Context.getApplicationContext() 获取应用程序上下文.当 Activity 将自身传递给 getDatabase(Context) 时,此设置按宣传的那样工作,但是当我从 AndroidTestCase 运行单元测试时,getApplicationContext() 调用通常返回 null,尽管测试时间越长,它返回非 null 的频率越高价值.

As per android.app.Application javadoc, I defined a singleton (called Database) that all of my activities access for state and persistent data, and Database.getDatabase(Context) gets the application context via Context.getApplicationContext(). This setup works as advertised when activities pass themselves to getDatabase(Context), but when I run a unit test from an AndroidTestCase, the getApplicationContext() call often returns null, though the longer the test, the more frequently it returns a non-null value.

以下代码在 AndroidTestCase 中重现了 null -- 演示不需要单例.

The following code reproduces the null within an AndroidTestCase -- the singleton isn't necessary for the demonstration.

首先,为了记录应用程序实例化消息,在被测应用程序中,我定义了 MyApp 并将其添加到清单中.

First, to log app-instantiation messages, in the app-under-test I defined MyApp and added it to the manifest.

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

接下来,我定义了一个测试用例来报告 AndroidTestCase.getContext() 4 次,由一些 sleeps 和 getSharedPreferences() 调用分隔:

Next, I defined a test case to report on AndroidTestCase.getContext() 4 times, separated by some sleeps and a getSharedPreferences() call:

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 的一大块(SDK11 WinXP 上的 1.6 模拟器,通过 Eclipse):

And this is a chunk of the resulting logCat (1.6 emulator on SDK11 WinXP via Eclipse):

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)

注意 getApplicationContext() 返回 null 一段时间,然后开始返回 MyApp 的实例.在此测试的不同运行中,我无法获得完全相同的结果(这就是我在 4 次迭代、睡眠和调用 getSharedPreferences() 以尝试使应用程序存在的方式结束).

Notice that getApplicationContext() returned null for a while, then started returning an instance of MyApp. I have not been able to get the exact same results in different runs of this test (that's how I ended up at 4 iterations, sleeps, and that call to getSharedPreferences() to try to goose the app into existence).

上面的 LogCat 消息块似乎最相关,但该单个测试的单次运行的整个 LogCat 很有趣.Android 启动了 4 个 AndroidRuntime;上面的块是从 4 号开始的.有趣的是,第三个运行时显示的消息表明它在进程 ID 447 中实例化了一个不同的 MyApp 实例:

The chunk of LogCat messages above seemed most relevant, but the entire LogCat for that single run of that single test was interesting. Android started 4 AndroidRuntimes; the chunk above was from the 4th. Interestingly, the 3rd runtime displayed messages indicating that it instantiated a different instance of MyApp in process ID 447:

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)

我假设 TestRunner(447) 消息来自父测试线程在进程 465 中报告其子进程.不过,问题是:为什么 Android 在其上下文正确连接到应用程序实例之前让 AndroidTestCase 运行?

I assume that the TestRunner(447) messages are from a parent test thread reporting on its children in process 465. Still, the question is: why does Android let an AndroidTestCase run before its context is properly hooked up to an Application instance?

解决方法:如果我调用 getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear,我的一项测试似乎在大多数情况下都避免了空值().commit(); 首先,所以我要这样做.

Workaround: One of my tests seemed to avoid nulls most of the time if I called getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit(); first, so I'm going with that.

顺便说一句:如果答案是这是一个 Android 错误,你为什么不提交它;哎呀,你为什么不修复它?"那么我愿意两者都做.我还没有迈出成为 bug 提交者或贡献者的步伐——也许现在是个好时机.

BTW: If the answer is "it's an Android bug, why don't you file it; heck, why don't you fix it?" then I'd be willing to do both. I haven't taken the step of being a bug-filer or contributor yet -- maybe this is a good time.

推荐答案

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

Instrumentation runs in a separate thread from the main app thread, so that it can execute without blocking or disrupting (or being blocked by) the main thread. If you need to synchronize with the main thread, use for example: Instrumentation.waitForIdleSync()

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

In particular, the Application object as well as all other top-level classes like Activity are initialized by the main thread. Your instrumentation thread is running at the same time those are initializing. This if you are touching any of those objects and are not implementing your own measures of thread safety, you should probably have such code run on the main thread such as via: Instrumentation.runOnMainSync(java.lang.Runnable)

这篇关于为什么 AndroidTestCase.getContext().getApplicationContext() 返回 null?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-23 21:43