尝试在Android项目中从threetenabp迁移到使用去糖化。https://developer.android.com/studio/write/java8-support#library-desugaring
我遇到的问题是安全参数和导航库,并且有一个片段参数。
E.g
<argument
android:name="lastTime"
android:defaultValue="@null"
app:argType="java.time.LocalDate"
app:nullable="true"
/>
不幸的是,这会在较低的API级别上启动应用程序时产生崩溃。在API21和API25之间,但在API26和更高版本上工作,其中支持java.time而不去糖化。
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]]
发布于 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时跳过所有作用域的。
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();
}
}
}
来自另一个活动的用法示例-
NavType.fromArgType("java.time.LocalDate","java.time");
尽管您启用了desugaring
或D8编译的8+ API,但在更低的API上也会看到相同的错误。
发布于 2021-08-26 21:20:30
终于找到了问题跟踪器的链接。看起来这个插件有一些问题,目前唯一的方法是将LocalDate包装在一个非去糖化的对象中,或者使用LocalDate的不同表示(例如,long或string)
希望这个问题很快就能解决,我们都可以使用带有安全args插件的java.time.LocalDate。
发布于 2021-08-26 17:49:22
SafeArgs只能是可以放在捆绑包中的东西。这就是值类型(int、double、Boolean等)和Serializables和Parcelables。
因为LocalDate不是其中的任何一个,所以您必须找到一种方法来转换LocalDate,然后再将其转换回来。
我认为Date是可打包的,或者你可以使用字符串形式。
https://stackoverflow.com/questions/68941948
复制相似问题