首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >带安全参数的Android去丑化java.time.LocalDate

带安全参数的Android去丑化java.time.LocalDate
EN

Stack Overflow用户
提问于 2021-08-26 16:06:55
回答 3查看 300关注 0票数 0

尝试在Android项目中从threetenabp迁移到使用去糖化。https://developer.android.com/studio/write/java8-support#library-desugaring

我遇到的问题是安全参数和导航库,并且有一个片段参数。

E.g

代码语言:javascript
运行
复制
<argument
            android:name="lastTime"
            android:defaultValue="@null"
            app:argType="java.time.LocalDate"
            app:nullable="true"
            />

不幸的是,这会在较低的API级别上启动应用程序时产生崩溃。在API21和API25之间,但在API26和更高版本上工作,其中支持java.time而不去糖化。

代码语言:javascript
运行
复制
    Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: java.time.LocalDate
        at androidx.navigation.NavType.fromArgType(NavType.java:181)
        at androidx.navigation.NavInflater.inflateArgument(NavInflater.java:191)
        at androidx.navigation.NavInflater.inflateArgumentForDestination(NavInflater.java:155)
        at androidx.navigation.NavInflater.inflate(NavInflater.java:128)
        at androidx.navigation.NavInflater.inflate(NavInflater.java:141)
        at androidx.navigation.NavInflater.inflate(NavInflater.java:88)
            ... 39 more
     Caused by: java.lang.ClassNotFoundException: java.time.LocalDate
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:400)
        at java.lang.Class.forName(Class.java:326)
        at androidx.navigation.NavType.fromArgType(NavType.java:169)
            ... 44 more
     Caused by: java.lang.ClassNotFoundException: Didn't find class "java.time.LocalDate" on path: DexPathList[[zip file "/data/app/myfancypackagename-1/base.apk"],nativeLibraryDirectories=[/data/app/com.medco.myfancypackagename--1/lib/x86, /system/lib, /vendor/lib]]
EN

回答 3

Stack Overflow用户

发布于 2021-08-26 23:56:16

我认为这不是safe args plugin的问题。

NavType表示可以在NavArgument中使用的类型。有针对原始类型的内置NavTypes,例如int、long、boolean、float和strings、可打包和可序列化的类(包括枚举),以及每种支持类型的数组。

在本例中,您通过全名类java.time.LocalDate使用了参数类型,该类是可序列化的类。在API26或更高版本上,通过fromArgType方法返回其对象,成功地对其进行了分类和初始化。我们知道在API26中添加了java.time.LocalDate

但是desugaring不能解决这样的ClassNotFoundException问题,而在较低的接口上通过fromArgType将你的参数类型解析为NavType,我不知道为什么。但您可以在传统设备上以不同的方式使用该API。假设textView.setText(LocalDate.now().getMonth().toString());

你可以检查NavType类,看看它是如何在解析argtype时跳过所有作用域的。

代码语言:javascript
运行
复制
    package com.example.stackoverflow;
  import android.os.Bundle;
import android.os.Parcelable;
 import androidx.annotation.AnyRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Serializable;
import java.text.ParseException;


public abstract class NavType<T> {
    private final boolean mNullableAllowed;

    NavType(boolean nullableAllowed) {
        this.mNullableAllowed = nullableAllowed;
    }


    public boolean isNullableAllowed() {
        return mNullableAllowed;
    }


    public abstract void put(@NonNull Bundle bundle, @NonNull String key, @Nullable T value);

    @Nullable
    public abstract T get(@NonNull Bundle bundle, @NonNull String key);


    @NonNull
    public abstract T parseValue(@NonNull String value);

    @NonNull
    T parseAndPut(@NonNull Bundle bundle, @NonNull String key, @NonNull String value) {
        T parsedValue = parseValue(value);
        put(bundle, key, parsedValue);
        return parsedValue;
    }
   
    @NonNull
    public abstract String getName();

    @Override
    @NonNull
    public String toString() {
        return getName();
    }
 
    @SuppressWarnings("unchecked")
    @NonNull
    public static NavType<?> fromArgType(@Nullable String type, @Nullable String packageName) {
        if (IntType.getName().equals(type)) {
            return IntType;
        } else if (IntArrayType.getName().equals(type)) {
            return IntArrayType;
        } else if (LongType.getName().equals(type)) {
            return LongType;
        } else if (LongArrayType.getName().equals(type)) {
            return LongArrayType;
        } else if (BoolType.getName().equals(type)) {
            return BoolType;
        } else if (BoolArrayType.getName().equals(type)) {
            return BoolArrayType;
        } else if (StringType.getName().equals(type)) {
            return StringType;
        } else if (StringArrayType.getName().equals(type)) {
            return StringArrayType;
        } else if (FloatType.getName().equals(type)) {
            return FloatType;
        } else if (FloatArrayType.getName().equals(type)) {
            return FloatArrayType;
        } else if (ReferenceType.getName().equals(type)) {
            return ReferenceType;
        } else if (type != null && !type.isEmpty()) {
            try {
                String className;
                if (type.startsWith(".") && packageName != null) {
                    className = packageName + type;
                } else {
                    className = type;
                }

                if (type.endsWith("[]")) {
                    className = className.substring(0, className.length() - 2);
                    Class<?> clazz = Class.forName(className);
                    if (Parcelable.class.isAssignableFrom(clazz)) {
                        return new ParcelableArrayType(clazz);
                    } else if (Serializable.class.isAssignableFrom(clazz)) {
                        return new SerializableArrayType(clazz);
                    }
                } else {
                    Class<?> clazz = Class.forName(className);
                    if (Parcelable.class.isAssignableFrom(clazz)) {
                        return new ParcelableType(clazz);
                    } else if (Enum.class.isAssignableFrom(clazz)) {
                        return new EnumType(clazz);
                    } else if (Serializable.class.isAssignableFrom(clazz)) {
                        return new SerializableType(clazz);
                    }
                }
                throw new IllegalArgumentException(className + " is not Serializable or "
                        + "Parcelable.");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return StringType;
    }

    @NonNull
    static NavType inferFromValue(@NonNull String value) {
        //because we allow Long literals without the L suffix at runtime,
        //the order of IntType and LongType parsing has to be reversed compared to Safe Args
        try {
            IntType.parseValue(value);
            return IntType;
        } catch (IllegalArgumentException e) {
            //ignored, proceed to check next type
        }
        try {
            LongType.parseValue(value);
            return LongType;
        } catch (IllegalArgumentException e) {
            //ignored, proceed to check next type
        }

        try {
            FloatType.parseValue(value);
            return FloatType;
        } catch (IllegalArgumentException e) {
            //ignored, proceed to check next type
        }

        try {
            BoolType.parseValue(value);
            return BoolType;
        } catch (IllegalArgumentException e) {
            //ignored, proceed to check next type
        }

        return StringType;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    static NavType inferFromValueType(@Nullable Object value) {
        if (value instanceof Integer) {
            return IntType;
        } else if (value instanceof int[]) {
            return IntArrayType;
        } else if (value instanceof Long) {
            return LongType;
        } else if (value instanceof long[]) {
            return LongArrayType;
        } else if (value instanceof Float) {
            return FloatType;
        } else if (value instanceof float[]) {
            return FloatArrayType;
        } else if (value instanceof Boolean) {
            return BoolType;
        } else if (value instanceof boolean[]) {
            return BoolArrayType;
        } else if (value instanceof String || value == null) {
            return StringType;
        } else if (value instanceof String[]) {
            return StringArrayType;
        } else if (value.getClass().isArray()
                && Parcelable.class.isAssignableFrom(value.getClass().getComponentType())) {
            return new ParcelableArrayType(value.getClass().getComponentType());
        } else if (value.getClass().isArray()
                && Serializable.class.isAssignableFrom(value.getClass().getComponentType())) {
            return new SerializableArrayType(value.getClass().getComponentType());
        } else if (value instanceof Parcelable) {
            return new ParcelableType(value.getClass());
        } else if (value instanceof Enum) {
            return new EnumType(value.getClass());
        } else if (value instanceof Serializable) {
            return new SerializableType(value.getClass());
        } else {
            throw new IllegalArgumentException("Object of type " + value.getClass().getName()
                    + " is not supported for navigation arguments.");
        }
    }
  
    @NonNull
    public static final NavType<Integer> IntType = new NavType<Integer>(false) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @NonNull Integer value) {
            bundle.putInt(key, value);
        }

        @Override
        public Integer get(@NonNull Bundle bundle, @NonNull String key) {
            return (Integer) bundle.get(key);
        }

        @NonNull
        @Override
        public Integer parseValue(@NonNull String value) {
            if (value.startsWith("0x")) {
                return Integer.parseInt(value.substring(2), 16);
            } else {
                return Integer.parseInt(value);
            }
        }

        @NonNull
        @Override
        public String getName() {
            return "integer";
        }
    };
    
    @NonNull
    public static final NavType<Integer> ReferenceType = new NavType<Integer>(false) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key,
                        @NonNull @AnyRes Integer value) {
            bundle.putInt(key, value);
        }

        @AnyRes
        @Override
        public Integer get(@NonNull Bundle bundle, @NonNull String key) {
            return (Integer) bundle.get(key);
        }

        @NonNull
        @Override
        public Integer parseValue(@NonNull String value) {
            throw new UnsupportedOperationException(
                    "References don't support parsing string values.");
        }

        @NonNull
        @Override
        public String getName() {
            return "reference";
        }
    };
   
    @NonNull
    public static final NavType<int[]> IntArrayType = new NavType<int[]>(true) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable int[] value) {
            bundle.putIntArray(key, value);
        }

        @Override
        public int[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (int[]) bundle.get(key);
        }

        @NonNull
        @Override
        public int[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @NonNull
        @Override
        public String getName() {
            return "integer[]";
        }
    };
 
    @NonNull
    public static final NavType<Long> LongType = new NavType<Long>(false) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @NonNull Long value) {
            bundle.putLong(key, value);
        }

        @Override
        public Long get(@NonNull Bundle bundle, @NonNull String key) {
            return (Long) bundle.get(key);
        }

        @NonNull
        @Override
        public Long parseValue(@NonNull String value) {
            //At runtime the L suffix is optional, contrary to the Safe Args plugin.
            //This is in order to be able to parse long numbers passed as deep link URL parameters
            if (value.endsWith("L")) {
                value = value.substring(0, value.length() - 1);
            }
            if (value.startsWith("0x")) {
                return Long.parseLong(value.substring(2), 16);
            } else {
                return Long.parseLong(value);
            }
        }    
        @NonNull
        @Override
        public String getName() {
            return "long";
        }
    };
   
    @NonNull
    public static final NavType<long[]> LongArrayType = new NavType<long[]>(true) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable long[] value) {
            bundle.putLongArray(key, value);
        }

        @Override
        public long[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (long[]) bundle.get(key);
        }

        @NonNull
        @Override
        public long[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @NonNull
        @Override
        public String getName() {
            return "long[]";
        }
    };
    @NonNull
    public static final NavType<Float> FloatType = new NavType<Float>(false) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @NonNull Float value) {
            bundle.putFloat(key, value);
        }

        @Override
        public Float get(@NonNull Bundle bundle, @NonNull String key) {
            return (Float) bundle.get(key);
        }

        @NonNull
        @Override
        public Float parseValue(@NonNull String value) {
            return Float.parseFloat(value);
        }

        @NonNull
        @Override
        public String getName() {
            return "float";
        }
    };
 
    @NonNull
    public static final NavType<float[]> FloatArrayType = new NavType<float[]>(true) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable float[] value) {
            bundle.putFloatArray(key, value);
        }

        @Override
        public float[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (float[]) bundle.get(key);
        }

        @NonNull
        @Override
        public float[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @NonNull
        @Override
        public String getName() {
            return "float[]";
        }
    };


    @NonNull
    public static final NavType<Boolean> BoolType = new NavType<Boolean>(false) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @NonNull Boolean value) {
            bundle.putBoolean(key, value);
        }

        @Override
        public Boolean get(@NonNull Bundle bundle, @NonNull String key) {
            return (Boolean) bundle.get(key);
        }

        @NonNull
        @Override
        public Boolean parseValue(@NonNull String value) {
            if ("true".equals(value)) {
                return true;
            } else if ("false".equals(value)) {
                return false;
            } else {
                throw new IllegalArgumentException(
                        "A boolean NavType only accepts \"true\" or \"false\" values.");
            }
        }

        @NonNull
        @Override
        public String getName() {
            return "boolean";
        }
    };


    @NonNull
    public static final NavType<boolean[]> BoolArrayType = new NavType<boolean[]>(true) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable boolean[] value) {
            bundle.putBooleanArray(key, value);
        }

        @Override
        public boolean[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (boolean[]) bundle.get(key);
        }

        @NonNull
        @Override
        public boolean[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @NonNull
        @Override
        public String getName() {
            return "boolean[]";
        }
    };

    @NonNull
    public static final NavType<String> StringType = new NavType<String>(true) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable String value) {
            bundle.putString(key, value);
        }

        @Override
        public String get(@NonNull Bundle bundle, @NonNull String key) {
            return (String) bundle.get(key);
        }

        @NonNull
        @Override
        public String parseValue(@NonNull String value) {
            return value;
        }

        @NonNull
        @Override
        public String getName() {
            return "string";
        }
    };
  
    @NonNull
    public static final NavType<String[]> StringArrayType = new NavType<String[]>(true) {
        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable String[] value) {
            bundle.putStringArray(key, value);
        }

        @Override
        public String[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (String[]) bundle.get(key);
        }

        @NonNull
        @Override
        public String[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @NonNull
        @Override
        public String getName() {
            return "string[]";
        }
    };

    public static final class ParcelableType<D> extends NavType<D> {
        @NonNull
        private final Class<D> mType;
        public ParcelableType(@NonNull Class<D> type) {
            super(true);
            if (!Parcelable.class.isAssignableFrom(type)
                    && !Serializable.class.isAssignableFrom(type)) {
                throw new IllegalArgumentException(
                        type + " does not implement Parcelable or Serializable.");
            }
            this.mType = type;
        }

        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable D value) {
            mType.cast(value);
            if (value == null || value instanceof Parcelable) {
                bundle.putParcelable(key, (Parcelable) value);
            } else if (value instanceof Serializable) {
                bundle.putSerializable(key, (Serializable) value);
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        @Nullable
        public D get(@NonNull Bundle bundle, @NonNull String key) {
            return (D) bundle.get(key);
        }

        @NonNull
        @Override
        public D parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Parcelables don't support default values.");
        }

        @Override
        @NonNull
        public String getName() {
            return mType.getName();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ParcelableType<?> that = (ParcelableType<?>) o;

            return mType.equals(that.mType);
        }

        @Override
        public int hashCode() {
            return mType.hashCode();
        }
    }


    public static final class ParcelableArrayType<D extends Parcelable> extends NavType<D[]> {
        @NonNull
        private final Class<D[]> mArrayType;

        /**
         * Constructs a NavType that supports arrays of a given Parcelable type.
         * @param type class that is a subtype of Parcelable
         */
        @SuppressWarnings("unchecked")
        public ParcelableArrayType(@NonNull Class<D> type) {
            super(true);
            if (!Parcelable.class.isAssignableFrom(type)) {
                throw new IllegalArgumentException(
                        type + " does not implement Parcelable.");
            }

            Class<D[]> arrayType;
            try {
                arrayType = (Class<D[]>) Class.forName("[L" + type.getName() + ";");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e); //should never happen
            }
            this.mArrayType = arrayType;
        }

        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable D[] value) {
            mArrayType.cast(value);
            bundle.putParcelableArray(key, value);
        }

        @SuppressWarnings("unchecked")
        @Override
        @Nullable
        public D[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (D[]) bundle.get(key);
        }

        @NonNull
        @Override
        public D[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @Override
        @NonNull
        public String getName() {
            return mArrayType.getName();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ParcelableArrayType<?> that = (ParcelableArrayType<?>) o;

            return mArrayType.equals(that.mArrayType);
        }

        @Override
        public int hashCode() {
            return mArrayType.hashCode();
        }
    }

    public static class SerializableType<D extends Serializable> extends NavType<D> {
        @NonNull
        private final Class<D> mType;

        /**
         * Constructs a NavType that supports a given Serializable type.
         * @param type class that is a subtype of Serializable
         */
        public SerializableType(@NonNull Class<D> type) {
            super(true);
            if (!Serializable.class.isAssignableFrom(type)) {
                throw new IllegalArgumentException(
                        type + " does not implement Serializable.");
            }
            if (type.isEnum()) {
                throw new IllegalArgumentException(
                        type + " is an Enum. You should use EnumType instead.");
            }
            this.mType = type;
        }

        SerializableType(boolean nullableAllowed, @NonNull Class<D> type) {
            super(nullableAllowed);
            if (!Serializable.class.isAssignableFrom(type)) {
                throw new IllegalArgumentException(
                        type + " does not implement Serializable.");
            }
            this.mType = type;
        }

        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable D value) {
            mType.cast(value);
            bundle.putSerializable(key, value);
        }

        @SuppressWarnings("unchecked")
        @Override
        @Nullable
        public D get(@NonNull Bundle bundle, @NonNull String key) {
            return (D) bundle.get(key);
        }

        @NonNull
        @Override
        public D parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Serializables don't support default values.");
        }

        @Override
        @NonNull
        public String getName() {
            return mType.getName();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof SerializableType)) return false;

            SerializableType<?> that = (SerializableType<?>) o;

            return mType.equals(that.mType);
        }

        @Override
        public int hashCode() {
            return mType.hashCode();
        }
    }
    public static final class EnumType<D extends Enum> extends SerializableType<D> {
        @NonNull
        private final Class<D> mType;

        /**
         * Constructs a NavType that supports a given Enum type.
         * @param type class that is an Enum
         */
        public EnumType(@NonNull Class<D> type) {
            super(false, type);
            if (!type.isEnum()) {
                throw new IllegalArgumentException(
                        type + " is not an Enum type.");
            }
            mType = type;
        }

        @SuppressWarnings("unchecked")
        @NonNull
        @Override
        public D parseValue(@NonNull String value) {
            for (Object constant : mType.getEnumConstants()) {
                if (((Enum) constant).name().equals(value)) {
                    return (D) constant;
                }
            }
            throw new IllegalArgumentException("Enum value " + value + " not found for type "
                    + mType.getName() + ".");
        }

        @Override
        @NonNull
        public String getName() {
            return mType.getName();
        }
    }


    public static final class SerializableArrayType<D extends Serializable> extends NavType<D[]> {
        @NonNull
        private final Class<D[]> mArrayType;

        /**
         * Constructs a NavType that supports arrays of a given Serializable type.
         * @param type class that is a subtype of Serializable
         */
        @SuppressWarnings("unchecked")
        public SerializableArrayType(@NonNull Class<D> type) {
            super(true);
            if (!Serializable.class.isAssignableFrom(type)) {
                throw new IllegalArgumentException(
                        type + " does not implement Serializable.");
            }

            Class<D[]> arrayType;
            try {
                arrayType = (Class<D[]>) Class.forName("[L" + type.getName() + ";");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e); //should never happen
            }
            this.mArrayType = arrayType;
        }

        @Override
        public void put(@NonNull Bundle bundle, @NonNull String key, @Nullable D[] value) {
            mArrayType.cast(value);
            bundle.putSerializable(key, value);
        }

        @SuppressWarnings("unchecked")
        @Override
        @Nullable
        public D[] get(@NonNull Bundle bundle, @NonNull String key) {
            return (D[]) bundle.get(key);
        }

        @NonNull
        @Override
        public D[] parseValue(@NonNull String value) {
            throw new UnsupportedOperationException("Arrays don't support default values.");
        }

        @Override
        @NonNull
        public String getName() {
            return mArrayType.getName();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            SerializableArrayType<?> that = (SerializableArrayType<?>) o;

            return mArrayType.equals(that.mArrayType);
        }

        @Override
        public int hashCode() {
            return mArrayType.hashCode();
        }
    }
}

来自另一个活动的用法示例-

代码语言:javascript
运行
复制
NavType.fromArgType("java.time.LocalDate","java.time");

尽管您启用了desugaring或D8编译的8+ API,但在更低的API上也会看到相同的错误。

票数 1
EN

Stack Overflow用户

发布于 2021-08-26 21:20:30

终于找到了问题跟踪器的链接。看起来这个插件有一些问题,目前唯一的方法是将LocalDate包装在一个非去糖化的对象中,或者使用LocalDate的不同表示(例如,long或string)

希望这个问题很快就能解决,我们都可以使用带有安全args插件的java.time.LocalDate。

https://issuetracker.google.com/u/1/issues/160257645

票数 0
EN

Stack Overflow用户

发布于 2021-08-26 17:49:22

SafeArgs只能是可以放在捆绑包中的东西。这就是值类型(int、double、Boolean等)和Serializables和Parcelables。

因为LocalDate不是其中的任何一个,所以您必须找到一种方法来转换LocalDate,然后再将其转换回来。

我认为Date是可打包的,或者你可以使用字符串形式。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68941948

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档