在某些情况下,我想在仿真器或测试期间的设备上使用我的Android应用程序中的Assert关键字来销毁我的应用程序。这可能吗?
看来,仿真器只是忽略了我的断言。
Answers:
该API提供了JUnit Assert。
你可以做
import static junit.framework.Assert.*;
现在,您可以使用junit框架中提供的所有函数,例如assertTrue,assertEquals,assertNull。
注意不要通过eclipse导入Junit4框架,那将是org.junit软件包。您必须使用junit.framework包才能使其在android设备或仿真器上正常工作。
请参阅嵌入式VM控制文档(源树中的原始HTML 或格式良好的副本)。
基本上,即使.dex字节代码包含执行检查的代码,Dalvik VM仍默认设置为忽略断言检查。检查断言以两种方式之一打开:
(1)通过以下方式设置系统属性“ debug.assert”:
adb shell setprop debug.assert 1
只要您在执行此操作后重新安装应用程序,我就可以验证该文件是否可以正常工作
(2)通过将命令行参数“ --enable-assert”发送到dalvik VM,这可能不是应用程序开发人员可能能够做到的(如果我在这里错了,请有人纠正我)。
基本上,有一个标志可以全局设置,在包级别或在类级别设置,以启用相应级别的声明。该标志默认情况下处于关闭状态,因此跳过断言检查。
我在示例活动中编写了以下代码:
public class AssertActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
int x = 2 + 3;
assert x == 4;
}
}
对于此代码,生成的dalvik字节代码为(对于Android 2.3.3):
// Static constructor for the class
000318: |[000318] com.example.asserttest.AssertActivity.:()V
000328: 1c00 0300 |0000: const-class v0, Lcom/example/asserttest/AssertActivity; // class@0003
00032c: 6e10 0c00 0000 |0002: invoke-virtual {v0}, Ljava/lang/Class;.desiredAssertionStatus:()Z // method@000c
000332: 0a00 |0005: move-result v0
000334: 3900 0600 |0006: if-nez v0, 000c // +0006
000338: 1210 |0008: const/4 v0, #int 1 // #1
00033a: 6a00 0000 |0009: sput-boolean v0, Lcom/example/asserttest/AssertActivity;.$assertionsDisabled:Z // field@0000
00033e: 0e00 |000b: return-void
000340: 1200 |000c: const/4 v0, #int 0 // #0
000342: 28fc |000d: goto 0009 // -0004
:
:
// onCreate()
00035c: |[00035c] com.example.asserttest.AssertActivity.onCreate:(Landroid/os/Bundle;)V
00036c: 6f20 0100 3200 |0000: invoke-super {v2, v3}, Landroid/app/Activity;.onCreate:(Landroid/os/Bundle;)V // method@0001
000372: 1501 037f |0003: const/high16 v1, #int 2130903040 // #7f03
000376: 6e20 0500 1200 |0005: invoke-virtual {v2, v1}, Lcom/example/asserttest/AssertActivity;.setContentView:(I)V // method@0005
00037c: 1250 |0008: const/4 v0, #int 5 // #5
00037e: 6301 0000 |0009: sget-boolean v1, Lcom/example/asserttest/AssertActivity;.$assertionsDisabled:Z // field@0000
000382: 3901 0b00 |000b: if-nez v1, 0016 // +000b
000386: 1251 |000d: const/4 v1, #int 5 // #5
000388: 3210 0800 |000e: if-eq v0, v1, 0016 // +0008
00038c: 2201 0c00 |0010: new-instance v1, Ljava/lang/AssertionError; // class@000c
000390: 7010 0b00 0100 |0012: invoke-direct {v1}, Ljava/lang/AssertionError;.:()V // method@000b
000396: 2701 |0015: throw v1
000398: 0e00 |0016: return-void
注意静态构造函数如何在Class对象上调用methodAssertAssertionStatus并设置类范围变量$ assertionsDisabled;。还应注意,在onCreate()中,所有引发java.lang.AssertionError的代码都已编译,但其执行取决于$ assertionsDisabled的值,该值是为静态构造函数中的Class对象设置的。
似乎主要使用JUnit的Assert类,因此使用它可能是一个安全的选择。assert关键字的灵活性是能够在开发时打开断言,然后将其关闭以发送位,而可以正常地失败。
希望这可以帮助。
import static junit.framework.Assert.*
然后使用它的一种方法(如)assertNotNull("It's null!", someObject);
,则该声明是否在装运位中关闭了?
adb shell setprop debug.assert 1
在Eclipse中有办法吗?
su
,然后setprop debug.assert 1
。请注意,您显示的反汇编代码将保留在发布版本中(stackoverflow.com/a/5590378/506073)。我不相信可以告诉Javac编译器不要发出断言,因此需要以某种方式将其删除。一个简单的解决方案是将关键字assert包裹在您自己的函数中,proguard可以为您剥离。
启用断言后,assert
关键字仅AssertionError
在布尔表达式为时抛出false
。
因此,IMO,最好的选择,尤其是。如果您不愿意依赖junit,则要AssertionError
显式地抛出如下图所示:
assert x == 0 : "x = " + x;
上述声明的替代方法是:
Utils._assert(x == 0, "x = " + x);
该方法定义为:
public static void _assert(boolean condition, String message) {
if (!condition) {
throw new AssertionError(message);
}
}
Oracle Java文档建议将AssertionError
替换为可接受的替代方法。
我猜您可以配置Proguard去除对生产代码的调用。
在“ Android实践”中,建议使用:
$adb shell setprop dalvik.vm.enableassertions all
如果此设置未在手机上保留,则可以使用以下属性创建/data/local.prop文件:
dalvik.vm.enableassertions=all
chmod 644
)。
直到我在google上检查了问题,我的断言才起作用,这使我烦恼了……我放弃了简单的断言,将使用Junits断言方法。
为了方便起见,我使用:
导入静态junit.framework.Assert。*;
由于静态导入,我以后可以写:
assertTrue(...); 而不是Assert.assertTrue(...);
如果您担心带有JUnit断言的代码(或其他任何类路径)的运输,可以使用ProGuard config选项“ assumenosideeffects”,它会假定删除该类路径对代码没有任何作用,从而去除了一个类路径。 。
例如。
-assumenosideeffects junit.framework.Assert {
*;
}
我有一个通用的调试库,将所有测试方法都放入了该库中,然后使用此选项将其从发布的应用程序中剥离。
这也消除了很难发现的问题,这些问题是从未在发行代码中使用过的被操纵的字符串。例如,如果您编写一个调试日志方法,并且在该方法中记录日志字符串之前检查调试模式,那么您仍在构造字符串,分配内存,调用该方法,但随后却什么也不做。剥离该类,然后完全删除调用,这意味着只要在方法调用中构造了您的字符串,它也会消失。
但是,请确保只删除线段是真正安全的,因为这样做无需检查ProGuard的零件。删除任何void返回方法都可以,但是,如果要从要删除的内容中获取任何返回值,请确保没有将它们用于实际的操作逻辑。
-assumenosideeffects class junit.framework.Assert { *; }
您可以使用断言,但是要可靠地使用它们需要一些工作。系统属性debug.assert
不可靠;看问题175697,65183,36786和17324。
一种方法是将每个assert
语句转换为任何运行时都可以处理的内容。使用Java编译器前面的源预处理器执行此操作。例如,使用以下语句:
assert x == 0: "Failure message";
对于调试版本,预处理器会将以上内容转换为if
语句:
{ if( !(x == 0) ) throw new AssertionError( "Failure message" ); }
对于生产版本,为空语句:
;
注意,这将在运行时控制声明,而不是运行时(通常的做法)。
要增加Zulaxia关于剥离Junit的答案,Proguard已经是Android SDK / Eclipse的一部分,下面的页面告诉您如何启用它。
http://developer.android.com/guide/developing/tools/proguard.html
上面的方法也不能与最新的默认proguard配置一起使用,因为它使用了-dontoptimize标志,必须将其删除并启用某些优化。