Android proguard 混淆的代码在实际上不应该导致 NullPointerException 的情况下导致了 NullPointerException

发布于 2024-12-07 19:47:56 字数 4001 浏览 1 评论 0原文

我已经在 Android Marketplace 上分发了一个应用程序。我从一小部分用户(可能是 2%)那里收到了错误报告,他们在没有逻辑意义的情况下得到了 NullPointerExceptions。

我自己从来无法复制这一点。该代码相对简单,是每个用户都必须遵循的通用代码路径。实际上,我已经获取了可能创建 NPE 的每一行单独的代码,并将其包装在 try-catch 块中并抛出自定义运行时异常,但我仍然遇到未捕获的 NullPointerException 错误。

此时,我唯一能想象到的是与我的 Proguard 混淆有关的东西。我看过其他一些文章,讨论如果您发现奇怪的行为,请删除 -overloadaggressively 选项,但据我所知,我没有使用该选项。

有没有其他人使用 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 是另一个类中的最终变量:

game.setScreen(new MainMenuScreen(game));

调度程序在上面对 super 的调用中设置。

getSettings() 返回一个设置对象,该对象在应用程序一开始就设置,是私有的并且永远不会取消设置。这个方法之前也用过几次。

没有自动装箱原语。

这是混淆器配置:

-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 *;
}

I have distributed an application on the Android Marketplace. I am getting error reports back in from a small handful of users (maybe 2%) where they are getting NullPointerExceptions where it doesn't make logical sense.

I have never been able to replicate this myself. The code is relatively straightforward and is a common code path that EVERY user has to follow. I've actually taken every separate line of code that could possibly be creating the NPE and wrapped it in a try-catch block and throw a custom runtime exception, but I'm still getting NullPointerException errors not caught.

At this point, the only thing I can imagine it would be is something related to my Proguard obfuscation. I have seen some other article talking about taking out the -overloadaggressively option if you notice odd behavior, but as far as I can tell, I'm not using that option.

Has anybody else experienced mysterious NPEs using android and proguard. Are there any other settings people can recommend to dial down the optimizations that might be causing this issue?

Any other ideas?

For reference, here is the unobfuscated function that is getting the 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();


}

So the only possibilities I can see are game, dispatcher and settings.

game gets set via this code in another class. game is a final variable in that other class:

game.setScreen(new MainMenuScreen(game));

dispatcher gets set within in the call to super above.

getSettings() returns a settings object that gets set at the very start of the application, is private and never gets unset. Its also used before this method several times.

There are not auto-boxing primitives.

here is the proguard config:

-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 *;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

云归处 2024-12-14 19:47:56

好吧,我想我已经找到了问题/混乱的根源。

proguard 所做的事情之一是内联一些方法。因此,构造函数底部的 setupContinue() 函数的全部内容都直接添加到构造函数的内容中。所以现在我有更多的代码需要审查,并且我确实看到了 NPE 的更多可能性。我很确定我会弄清楚我的问题的真相。

我通过获取 proguard 生成的 obfuscated.jar 并通过反编译器运行它来解决这个问题。这是一个有趣的练习,因为您可以更深入地了解 proguard 的内部工作原理。我强烈推荐那些想要更好地了解 proguard 对代码影响的人们。

OK, I think I got to the root of the issue/confusion.

One of the things proguard does is in-line some methods. Because of this, the entire contents of my setupContinue() function at the bottom of my constructor was added directly into the contents of my constructor. So now I have a bunch more code to review, and I do see some more possibilities for NPEs. I'm pretty sure I'll get to the bottom of my issue.

I figured this out by taking the obfuscated.jar that proguard produces, and running it through a decompiler. Its an interesting exercise as you get get little more insight into the inner workings of proguard. I highly recommend it for people wanting to better understand the impacts that proguard has on their code.

带刺的爱情 2024-12-14 19:47:56

最好的选择是使用mapping.txt 文件和retrace 工具来查找错误的确切位置。
从那里可以更容易地理解它是否确实是 Proguard 或者其他你没有想到的最终情况。

为此,您需要将堆栈跟踪从开发人员控制台复制到另一个文件,我们假设它被称为

c:\trace.txt

现在,在您的项目中,您将找到一个包含 4 个文件的 Proguard 文件夹。
假设您的项目位于

c:\项目

您需要做的是运行位于(更改为 Android Sdk 文件夹的位置)的 retrace 工具(使用批处理文件以方便使用):

c:\android-sdk-windows\tools\proguard\bin\retrace.bat

转到该文件夹​​并运行:

回溯c:\project\proguard\mapping.txt c:\trace.txt

从这里开始,找出异常的确切行并可能找到错误就会容易得多。

根据我的经验,唯一可能搞砸的是第三方库。对于我所有的项目来说,普通的 Android 代码从未因混淆而受到损害。

Your best bet would be to use the mapping.txt file and the retrace tool to find the exact location of the error.
From there it would be easier to understand if it's indeed Proguard or some other end-case you didn't think of.

To do this, you need to copy the stack trace from the developer's console to another file, let's assume it's called

c:\trace.txt

Now, in your project, you'll find a Proguard folder with 4 files.
Let's assume your project is in

c:\project

What you'll need to do is run the retrace tool (using the batch file for easier use) located at (change to the location of your Android Sdk folder) :

c:\android-sdk-windows\tools\proguard\bin\retrace.bat

Go to that folder and run:

retrace c:\project\proguard\mapping.txt c:\trace.txt

From there on, it would be much easier to figure our the exact line of the exception and to probably find the error.

From my experience, the only things that could get messed up are third party libraries. Normal Android code, for all my projects, was never harmed from obfuscation.

咆哮 2024-12-14 19:47:56

抱歉,我还不能发表评论(我是新手)。

这可能是我自己遇到的另一个问题。在某些手机上,有时会出现缺少 Android 库(例如 Json 库)的问题。

我建议您仔细查看哪些手机实际上获得了 NPE - 可能有一些相似之处。

就我而言,它是 HTC Desire Z,它缺少 JSon 库,因此每次调用 JSon 部分时应用程序都会强制关闭。 HTC 后来通过 Desire Z ROM 的修补程序解决了这个问题。

Sorry I can't post comments yet (I'm new to SO).

This could be another problem I've encountered myself. On some phones there was a problem at some point with missing Android libraries like a JSon library.

I'd recommend you take a closer look at what phones that actually get the NPE - there might be some similarities.

In my case it was the HTC Desire Z, that was missing the JSon library and so the app force closed every time the JSon part was called. The problem was fixed by HTC later on with a hotfix to the Desire Z's rom.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文