为什么缩小会导致错误 "JSONObject["rewards"] not a string"?

Why does minify cause error "JSONObject["rewards"] not a string"?

不知何故,从一个应用程序发布到另一个应用程序,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

修复方法是向混淆器脚本应用一个新行。

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

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

之所以会发生此问题,是因为当您将 r8 收缩器应用于 Android 应用时,它会重命名 class 中定义的一些内部字段和方法。如果这些 methods/fields 是代码中其他地方的引用,那么它将具有未定义/意外的行为。要解决此问题,请尝试在混淆文件中添加此规则,这将阻止 R8 重命名公开引用的字段和方法

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

我能找到的关于错误日志中异常文本的唯一参考来自这里: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 的实现。您只能通过这种方式读取对象的字符串值。任何其他值都将被拒绝并导致异常。由于您正在阅读 JSONArray 这将不起作用。

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

它在没有 ProGuard 的情况下工作的原因是系统已经提供了 Androids 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;
    }