【问题标题】:Why does minify cause error "JSONObject["rewards"] not a string"?为什么缩小会导致错误“JSONObject [”奖励“]不是字符串”?
【发布时间】:2021-03-12 14:15:46
【问题描述】:

不知何故,从一个应用程序发布到另一个应用程序的内部 org.json 库损坏了。它不能再将键的内容作为字符串读取。这是一个在启用 minify/R8/proguard 时可以正常工作的代码示例。

    try {
        parseJson("{\"rewards\":[{\"amount\":1,\"name\":\"Add Audio\"}]}");
    } catch (Exception e) {
        Log.w("TEST", "Error parsing rewarded currencies JSON header!!!", e);
    }
    
    private void parseJson(@NonNull String rewardedCurrencies) throws JSONException {
        final Map<String, String> rewardsMap = jsonStringToMap(rewardedCurrencies);

        for (Map.Entry<String, String> entry : rewardsMap.entrySet()) {
            Log.e("TEST", entry.getKey() + ":" + entry.getValue());
        }
    }

    public Map<String, String> jsonStringToMap(String jsonParams) throws JSONException {
        Map<String, String> jsonMap = new HashMap<String, String>();

        if (TextUtils.isEmpty(jsonParams)) return jsonMap;

        JSONObject jsonObject = (JSONObject) new JSONTokener(jsonParams).nextValue();
        Iterator<String> keys = jsonObject.keys();

        Log.e("TEST", "jsonStringToMap() -> jsonObject: "+jsonObject.toString());

        while (keys.hasNext()) {
            String key = (String) keys.next();
            Log.e("TEST", "jsonStringToMap() -> key: "+key);
            jsonMap.put(key, jsonObject.getString(key));
        }

        return jsonMap;
    }

失败的日志输出:

jsonStringToMap() -> jsonObject: {"rewards":[{"amount":1,"name":"Add Audio"}]}
jsonStringToMap() -> key: rewards
Error parsing rewarded currencies JSON header!!!
    j.b.b: JSONObject["rewards"] not a string.
        at j.b.d.getString(SourceFile:4)

那么自上次发布有效的应用程序以来,该项目发生了哪些变化?

targetSdkVersion 28 -> 29

sourceCompatibility JavaVersion.VERSION_1_7 -> JavaVersion.VERSION_1_8

'com.android.tools.build:gradle:4.0.0' -> 'com.android.tools.build:gradle:4.1.1'

distributionUrl -> gradle-6.1.1-all.zip -> gradle-6.5-all.zip

解决方法是在 proguard 脚本中应用一个新行。

-keep class org.json.** { *; }

现在我很担心。为什么我需要将此应用于proguard?是否有其他内部库可能破坏?我似乎无法弄清楚这是什么原因造成的,以及这是否是一个潜在的大问题?

【问题讨论】:

  • 你能告诉我们堆栈跟踪并告诉我们发生在哪一行吗?
  • 我也无法找出程序的预期输出。你能解释一下这个函数的作用吗?也许这将指向根本问题
  • @JensV 感谢您的提醒!忘记添加日志了。
  • 堆栈跟踪中缺少异常名称
  • 突然想到:你能看看你的依赖关系吗?可能有一个提供 org.json 的库,您正在使用它而不是标准库,然后被保护掉......

标签: android android-studio proguard minify android-r8


【解决方案1】:

我能在您的错误日志中找到的关于异常文本的唯一参考来自这里:JSONObject not a String Error

我在 Android org.json 实现中找不到会导致该异常的代码路径。这意味着org.json 以某种方式被打包到您的应用程序中。这也意味着这不是您担心的标准库问题。

进一步强化这一点:https://github.com/stleary/JSON-java/blob/master/src/main/java/org/json/JSONObject.java#L849-L864

这完全解释了您的问题。 JSONObject#getString(String key) 的实现与 Android 的实现不同。您只能以这种方式读取 Object 的 String 值。任何其他值都将被拒绝并导致异常。由于您正在阅读JSONArray,这将不起作用。

无论您使用的是 Android 标准库还是拉取的依赖项,我下面的示例都将解决此问题。

不使用 ProGuard 的原因是系统已经提供了 Android 的 org.json。缩小时,它会重命名依赖包 org.json 包,从而强制你使用它。

我建议摆脱 org.json 依赖项。或者编写适用于两者的代码...您可能想要切换到GsonJackson,因为它更易于使用。

    public static Map<String, String> jsonStringToMap(String jsonParams) throws JSONException {
        Map<String, String> jsonMap = new HashMap<>();
        if (TextUtils.isEmpty(jsonParams)) return jsonMap;

        // Use JSONObject constructor
        JSONObject jsonObject = new JSONObject(jsonParams);

        Iterator<String> keys = jsonObject.keys();

        Log.e("TEST", "jsonStringToMap() -> jsonObject: " + jsonObject.toString());

        while (keys.hasNext()) {
            String key = keys.next();
            Log.e("TEST", "jsonStringToMap() -> key: " + key);
            // Type safe way to get the String value of any type within the key
            // Probably needs to handle null values correctly
            jsonMap.put(key, String.valueOf(jsonObject.get(key)));
        }

        return jsonMap;
    }

【讨论】:

  • 你摇滚!这完全有道理。我会做一些调查,然后用我的发现回过头来。谢谢詹斯V! :)
  • 原来第三方库以某种方式包含了 org.json 库。我无法确切确认他们是如何做到的,但删除他们的库解决了这个问题。再次感谢您的帮助!
【解决方案2】:

这个问题可能会发生,因为当您将 r8 收缩器应用到您的 Android 应用程序时,它会重命名该类中定义的一些内部字段和方法。如果这些方法/字段是代码中其他位置的引用,那么它将具有未定义/意外行为。要解决此问题,请尝试在您的 proguard 文件中添加此规则,以防止 R8 重命名公开引用的字段和方法

-keep class org.json.** { *; }

【讨论】:

  • 我认为问题更多的是“为什么会发生这种情况”,他的 proguard 规则已经有效
  • 有趣的是,这条规则实际上并没有解决它。但是更激进的一个 -keep class org.json.** { *; } 确实解决了这个问题。
  • 这条规则实际上会阻止重命名 org.json 包下的所有类/方法/字段。
  • 更有趣的是,我无法让它在启用 R8 的测试项目中中断
  • 也尝试使用 JSONObject 构造函数来解析 json 字符串,而不是使用 tokener。
猜你喜欢
  • 1970-01-01
  • 2017-01-19
  • 2018-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
相关资源
最近更新 更多