android - Android 使用安全参数对 java.time.LocalDate 进行脱糖
问题描述
尝试从 threetenabp 迁移到在 Android 项目中使用脱糖。https://developer.android.com/studio/write/java8-support#library-desugaring
我遇到的问题是安全参数和导航库以及片段参数。
例如
<argument
android:name="lastTime"
android:defaultValue="@null"
app:argType="java.time.LocalDate"
app:nullable="true"
/>
不幸的是,这会在较低 API 级别上启动应用程序时导致崩溃。在 API 21 和 API 25 之间,但适用于 API 26 及更高版本,其中支持 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]]
解决方案
我认为这不是safe args plugin
问题。
NavType 表示可以在 NavArgument 中使用的类型。
基本类型(例如 int、long、boolean、float 和字符串)、parcelable 和可序列化类(包括 Enums)以及每种受支持类型的数组都有内置的 NavTypes。
在这种情况下,您使用的是全名类的参数类型,java.time.LocalDate
它是可序列化的类。fromArgType
并且通过API 26 或更高版本的方法返回其 Object 成功分类和初始化。我们知道java.time.LocalDate
在 API 26 中添加了。
但是在通过较低的 API 将参数类型解析为 NavType 时desugaring
无法解决此类问题,我不知道为什么。但是您可以在旧设备上以不同的方式使用 API。认为ClassNotFoundException
fromArgType
textView.setText(LocalDate.now().getMonth().toString());
您可以检查NavType
class 以查看它在解析 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 上看到相同的错误,
推荐阅读
- python - 在每个 epoch 之后重置度量的局部变量
- mysql - 选择没有对应支付订单的重复支付挂单
- java - java中Object[]与特定对象数组的区别
- mongodb - Symfony 3.4 中 mongodb 中的全文搜索
- reporting-services - 当有些人使用 ZIP+4 而其他人不使用时,正在寻找一种通过邮政编码拉订单的方法
- c# - C# 应用程序使用 OAuth 验证 EWS 引发错误 AADSTS65005:资源无效
- assembly - 如何在 ARMv7 Thumb-2 程序集中读取条件标志?
- r - R ggsave 输出不同的 pdf 和 png
- android - 如何从应用程序模块上的库模块正确调用 mainactivity
- gnuplot - 如何解决热图中的 gnuplot 工件