我在Android Marketplace上发布了一个应用程序。我从少数用户(也许2%)那里得到错误报告,他们得到NullPointerExceptions,这在逻辑上没有意义。
我自己从来没能复制这个。代码相对简单,是每个用户都必须遵循的常见代码路径。我实际上已经采取了可能创建NPE的每一行单独的代码,并将其包装在一个try-catch块中,并抛出一个自定义运行时异常,但我仍然得到未捕获的NullPointerException错误。
在这一点上,我唯一能想到的是与我的Proguard混淆有关的东西。我看过其他一些文章,讨论如果您注意到奇怪的行为,请删除-overloadaggressive选项,但据我所知,我没有使用该选项。有没有人在使用android和proguard时遇到过神秘的npe ?人们是否可以推荐其他设置来降低可能导致此问题的优化?
还有其他想法吗?
作为参考,下面是获得NPE的未混淆函数:
public MainMenuScreen(final HauntedCarnival game) {
super(game);
game.startMusic("data/music/intro.mp3");
stage = new Stage(Screen.SCREEN_WIDTH, Screen.SCREEN_HEIGHT,true);
stage.addActor(new Image("background", Assets.mainMenuBackground));
Image title = new Image("title", Assets.mainMenuTitle);
title.x = 0;
title.y = 340;
resetEyeBlink();
stage.addActor(title);
dispatcher.registerInputProcessor(stage);
settings = game.getSettings();
eyeBlinkImage = new Image("eyeBlink", Assets.eyeBlink);
if (settings.getPlayers().isEmpty()) {
settings.addPlayer("Player One");
settings.save(game);
}
setupContinue();
}
所以我能看到的唯一可能是game, dispatcher和settings。
在另一个类中通过这段代码设置game。Game是另一个类中的最后一个变量:
game.setScreen(new MainMenuScreen(game));
dispatcher在上面的super调用中被设置。
getSettings()返回一个设置对象,该对象在应用程序的一开始就被设置,是私有的并且永远不会被取消设置。
没有自动装箱原语。
下面是proguard的配置:
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keepattributes Signature
-keep public class com.alkilabs.hauntedcarnival.settings.Settings
-keep public class com.alkilabs.hauntedcarnival.settings.Settings {
*;
}
-keep public class com.alkilabs.hauntedcarnival.settings.Player
-keep public class com.alkilabs.hauntedcarnival.settings.Player {
*;
}
-keepnames public class com.alkilabs.hauntedcarnival.world.World
-keepnames public class * extends com.alkilabs.hauntedcarnival.world.upgrades.Upgrade
-keepnames public class * extends com.alkilabs.hauntedcarnival.world.achievments.Achievement
-keepnames public class com.alkilabs.hauntedcarnival.world.monsters.MonsterType
-keepclassmembers class * extends com.alkilabs.hauntedcarnival.world.monsters.Monster {
public <init>(com.alkilabs.hauntedcarnival.world.monsters.MonsterType, java.lang.Integer, com.alkilabs.hauntedcarnival.world.World);
}
-keepnames public class com.alkilabs.hauntedcarnival.world.items.ItemType
-keepclassmembers class * extends com.alkilabs.hauntedcarnival.world.items.Item {
public <init>(com.alkilabs.hauntedcarnival.world.World, java.lang.Integer, java.lang.Integer);
}
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-dontwarn com.badlogic.gdx.scenes.scene2d.ui.utils.DesktopClipboard
-dontwarn com.badlogic.gdx.utils.JsonWriter
-dontwarn com.badlogic.gdx.utils.XmlWriter
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
好了,我想我找到问题的根源了。
proguard做的一件事是内联一些方法。因此,构造函数底部的setupContinue()函数的整个内容被直接添加到构造函数的内容中。所以现在我有更多的代码要审查,我确实看到了npe的更多可能性。我敢肯定我会把问题搞清楚的。
我通过使用proguard生成的obfusated .jar并通过反编译器运行它来解决这个问题。这是一个有趣的练习,因为你会对proguard的内部工作原理有更多的了解。我强烈推荐给那些想要更好地理解proguard对代码的影响的人。
最好的方法是使用mapping.txt文件和回溯工具来查找错误的确切位置。从这里可以更容易地理解,如果它确实是Proguard或其他一些你没有想到的结束情况。
要做到这一点,您需要将堆栈跟踪从开发人员的控制台复制到另一个文件,让我们假设它被称为c: trace.txt
现在,在你的项目中,你会发现一个包含4个文件的Proguard文件夹。假设项目位于
目录下项目c:
你需要做的是运行retrace工具(使用批处理文件更容易使用)位于(更改到Android Sdk文件夹的位置):
c: android-sdk-windows 工具混淆器 bin retrace.bat
进入该文件夹并运行:
c:projectproguardmapping.txt c:trace.txt
从这里开始,就更容易找出异常的确切行,并可能找到错误。
根据我的经验,唯一可能搞砸的是第三方库。在我所有的项目中,正常的Android代码从来没有因为混淆而受到损害。
对不起,我还不能发表评论(我是新手)。
这可能是我自己遇到的另一个问题。在某些手机上,有时会出现缺少Android库(如JSon库)的问题。
我建议你仔细看看哪些手机真正得到了NPE——可能有一些相似之处。
在我的例子中是HTC Desire Z,缺少JSon库,因此每次调用JSon部分时应用程序都会强制关闭。这个问题后来由HTC通过对Desire Z的rom进行热修复修复。