我想所有的Android开发者都接触过类似下面这样的代码:
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
int type = bundle.getInt(KEY_TYPE, 0);
String id = bundle.getString(KEY_ID);
Serializable data = bundle.getSerializable(KEY_DATA);
这是典型的利用Bundle
传参的示例,这里只有读取参数,自然有对应的写入参数,因为代码差不多,就省略了。
这段代码几乎就是纯手工代码,每个词法单元都需要手敲1-3个字符,才能敲回车来补全。所以就想尝试利用类似解构的方式来简化这个读写过程。
先放一段目前常写的代码做对比:
//定义key的名称
private static final String KEY_TYPE = "type";
private static final String KEY_ID = "id";
private static final String KEY_DATA = "data";
//写入Bundle
bundle.putInt(KEY_TYPE, 1);
bundle.putString(KEY_ID, "u_1234");
bundle.putSerializable(KEY_DATA, new Data());
//读取Bundle
int type = bundle.getInt(KEY_TYPE, 0);
String id = bundle.getString(KEY_ID);
Serializable data = bundle.getSerializable(KEY_DATA);
修改后:
//定义bundle的传参规范
interface TestContract {
void consume(@Key("id") String id, @Key("type") int type, @Key("data") Serializable data);
}
//写入Bundle
Bundle bundle = BundleAdapter.build(TestContract.class, test -> test.consume("u_1234", 1, new Data()));
//读取Bundle
BundleAdapter.withBundle(bundle, TestContract.class, (id, type, data) -> {/* 处理读到的参数 */});
呃,好像也没有省略多少代码,就是多加了一些参数类型约束。
里面有用到Memorizer
工具见Java小技巧:创建带缓存的过程
public abstract class BundleAdapter<T> {
public static <T> Bundle build(Class<T> clazz, Consumer<T> consumer) {
Bundle bundle = new Bundle();
T proxyInstance = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
List<Pair<String, BundleAdapter<?>>> bundleAdapters = BundleAdapter.adapterExtractor.apply(clazz);
if (bundleAdapters.size() == args.length) {
IntStream.range(0, args.length).forEach(i -> {
Pair<String, BundleAdapter<?>> pair = bundleAdapters.get(i);
BundleAdapter adapter = pair.second;
adapter.write(bundle, pair.first, args[i]);
});
return null;
} else {
throw new IllegalStateException("adapter length is not equal to args length:" + bundleAdapters.size() + " - " + args.length);
}
});
consumer.accept(proxyInstance);
return bundle;
}
public static <T> void withBundle(Bundle bundle, Class<T> clazz, T t) {
List<Pair<String, BundleAdapter<?>>> list = BundleAdapter.adapterExtractor.apply(clazz);
Method[] methods = clazz.getMethods();
if (methods.length == 1) {
Object[] args = list.stream().map(pair -> pair.second.read(bundle, pair.first)).toArray();
try {
methods[0].invoke(t, args);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} else {
throw new IllegalStateException("methods length is not 1, current is " + methods.length);
}
}
static final Function<Class<?>, List<Pair<String, BundleAdapter<?>>>> adapterExtractor = Memorizer.memorize(clazz -> {
Method[] methods = clazz.getMethods();
if (methods.length == 1) {
Method desMethod = methods[0];
Class<?> returnType = desMethod.getReturnType();
if (returnType == void.class) {
Annotation[][] parameterAnnotations = desMethod.getParameterAnnotations();
Type[] parameterTypes = desMethod.getGenericParameterTypes();
if (parameterTypes.length == parameterAnnotations.length) {
List<Pair<String, BundleAdapter<?>>> adapterList = new LinkedList<>();
for (int i = 0; i < parameterTypes.length; i++) {
Type parameterType = parameterTypes[i];
Optional<Pair<String, BundleAdapter<?>>> pairOptional = Arrays.stream(parameterAnnotations[i])
.filter(annotation -> annotation instanceof Key)
.map(annotation -> (Key) annotation)
.findFirst()
.map(key -> new Pair<>(key.value(), BundleAdapter.adapterBuilder.apply(parameterType)));
if (pairOptional.isPresent()) {
adapterList.add(pairOptional.get());
} else {
throw new IllegalStateException("every parameter must contains a Key annotation");
}
}
return adapterList;
} else {
throw new IllegalStateException("parameters length is not equal to annotations length");
}
} else {
throw new IllegalStateException("return type must be Void, current is " + returnType);
}
} else {
throw new IllegalArgumentException("methods size must be 1, current is " + methods.length);
}
});
private static final Function<Type, BundleAdapter<?>> adapterBuilder = Memorizer.memorize(type -> {
if (Integer.class.equals(type) || int.class.equals(type)) {
return create(Bundle::getInt, bundle -> bundle::putInt);
} else if (Float.class.equals(type) || float.class.equals(type)) {
return create(Bundle::getFloat, bundle -> bundle::putFloat);
} else if (Long.class.equals(type) || long.class.equals(type)) {
return create(Bundle::getLong, bundle -> bundle::putLong);
} else if (Boolean.class.equals(type) || boolean.class.equals(type)) {
return create(Bundle::getBoolean, bundle -> bundle::putBoolean);
} else if (Byte.class.equals(type) || byte.class.equals(type)) {
return create(Bundle::getByte, bundle -> bundle::putByte);
} else if (String.class.equals(type)) {
return create(Bundle::getString, bundle -> bundle::putString);
} else if (Short.class.equals(type) || short.class.equals(type)) {
return create(Bundle::getShort, bundle -> bundle::putShort);
} else if (Character.class.equals(type) || char.class.equals(type)) {
return create(Bundle::getChar, bundle -> bundle::putChar);
} else if (CharSequence.class.equals(type)) {
return create(Bundle::getCharSequence, bundle -> bundle::putCharSequence);
} else if (Parcelable.class.equals(type)) {
return create((bundle, key) -> (Parcelable) bundle.getParcelable(key), bundle -> bundle::putParcelable);
} else if (Serializable.class.equals(type)) {
return create(Bundle::getSerializable, bundle -> bundle::putSerializable);
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
if (int.class.equals(componentType)) {
return create(Bundle::getIntArray, bundle -> bundle::putIntArray);
} else if (float.class.equals(componentType)) {
return create(Bundle::getFloatArray, bundle -> bundle::putFloatArray);
} else if (long.class.equals(componentType)) {
return create(Bundle::getLongArray, bundle -> bundle::putLongArray);
} else if (byte.class.equals(componentType)) {
return create(Bundle::getByteArray, bundle -> bundle::putByteArray);
} else if (short.class.equals(componentType)) {
return create(Bundle::getShortArray, bundle -> bundle::putShortArray);
} else if (char.class.equals(componentType)) {
return create(Bundle::getCharArray, bundle -> bundle::putCharArray);
} else if (CharSequence.class.equals(componentType)) {
return create(Bundle::getCharSequenceArray, bundle -> bundle::putCharSequenceArray);
} else if (String.class.equals(componentType)) {
return create(Bundle::getStringArray, bundle -> bundle::putStringArray);
} else if (Parcelable.class.equals(componentType)) {
return create(Bundle::getParcelableArray, bundle -> bundle::putParcelableArray);
} else if (boolean.class.equals(componentType)) {
return create(Bundle::getBooleanArray, bundle -> bundle::putBooleanArray);
} else {
throw new IllegalArgumentException("unsupported array type:" + componentType);
}
} else if (type instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) type).getRawType();
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
if (ArrayList.class.equals(rawType)) {
if (typeArguments.length == 1) {
Type typeArgument = typeArguments[0];
if (String.class.equals(typeArgument)) {
return create(Bundle::getStringArrayList, bundle -> bundle::putStringArrayList);
} else if (Integer.class.equals(typeArgument)) {
return create(Bundle::getIntegerArrayList, bundle -> bundle::putIntegerArrayList);
} else if (CharSequence.class.equals(typeArgument)) {
return create(Bundle::getCharSequenceArrayList, bundle -> bundle::putCharSequenceArrayList);
} else if (Parcelable.class.equals(typeArgument)) {
return create(Bundle::getParcelableArrayList, bundle -> bundle::putParcelableArrayList);
} else {
throw new IllegalStateException("unsupported typeArgument:" + typeArgument);
}
} else {
throw new IllegalStateException("typeArguments length must be 1, current is " + typeArguments.length);
}
} else {
throw new IllegalStateException("ParameterizedType must be ArrayList");
}
} else {
throw new IllegalArgumentException("unsupported type:" + type);
}
});
private static <T> BundleAdapter<T> create(BiFunction<Bundle, String, T> reader, Function<Bundle, BiConsumer<String, T>> writer) {
return new BundleAdapter<T>() {
@Override
T read(Bundle bundle, String key) {
return reader.apply(bundle, key);
}
@Override
void write(Bundle bundle, String key, T value) {
writer.apply(bundle).accept(key, value);
}
};
}
abstract T read(Bundle bundle, String key);
abstract void write(Bundle bundle, String key, T value);
}