更新2/13/2012:接受了一个答案,解释了这个行为是一个错误,并注意到它似乎在模拟器上消失得比v1.6好,这使得它对我们大多数人来说是一个不成问题的问题。解决方法是循环/休眠,直到getContext().getApplicationContext()返回非空。结束更新
根据android.app.Application javadoc,我定义了一个单例(称为数据库),我所有的活动访问状态和持久数据,Database.getDatabase(上下文)通过Context.getApplicationContext()获取应用程序上下文。当活动传递给getDatabase(上下文)时,这个设置可以正常工作,但是当我从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());
}
}接下来,我定义了一个测试用例,用于4次报告AndroidTestCase.getContext(),用一些休眠和一个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的一部分(通过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)注意,getApplicationContext()返回了一段时间的null,然后开始返回一个MyApp实例。在这个测试的不同运行中,我还没有得到完全相同的结果(这就是为什么我在4次迭代、休眠和调用getSharedPreferences()试图使应用程序成熟起来)。
上面的LogCat消息块似乎是最相关的,但是单个测试的单个运行的整个LogCat很有趣。安卓启动了4 AndroidRuntimes,上面的部分是从第4位开始的。有趣的是,第三个运行时显示的消息表明它在进程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)我假设TestRunner(447)消息来自父测试线程,该线程报告了进程465中的子进程。不过,问题是:为什么安卓让AndroidTestCase在上下文正确连接到应用程序实例之前运行?
解决方案:如果我首先调用getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit();,那么我的一个测试在大多数情况下似乎都避免了无效,所以我将这样做。
BTW:如果答案是“这是一个安卓bug,你为什么不把它归档;见鬼,你为什么不修复它?”那我愿意两者兼得。我还没有采取的步骤,成为一个错误的申报者或贡献者--也许这是一个好时机。
发布于 2011-11-29 08:57:58
工具运行在一个独立于主应用程序线程的线程中,这样它就可以在不阻塞或中断(或被主线程阻塞)的情况下执行。如果需要与主线程同步,请使用例如:Instrumentation.waitForIdleSync()
特别是,Application对象以及所有其他顶级类(如Activity )都由主线程初始化。您的工具线程正在运行,而这些线程正在初始化。如果您正在接触这些对象中的任何一个,并且没有实现您自己的线程安全度量,那么您可能应该在主线程上运行这样的代码,比如via:Instrumentation.runOnMainSync(java.lang.Runnable)。
发布于 2013-11-27 08:27:01
正如问题和Dianne的回答(@hackbod)中所提到的,该工具运行在一个单独的线程上。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);
}
}
}轮询和睡眠时间是基于经验的,必要时可以进行调整。
https://stackoverflow.com/questions/6516441
复制相似问题