Cursor
这个类是Android开发者难以避免的,比如数据库、ContentResolver内容的读取,但通过这个类读取内容非常的繁琐,针对要读取的每一个字段都会有这样一段代码:
int idIndex = cursor.getColumnIndex("id"); //获取字段对应的列index(列index通常并不需要每次都获取)
if(idIndex >= 0){ //判断列index的合法性
String id = cursor.getString(idIndex); //获取对应列的内容
}
这种代码基本没法复用,而且还都是纯手工代码,自动生成比较麻烦,我希望可以像用json映射那样,每个字段/列一行代码就完成这个任务,所以本文就仿照以前解构Bundle
一样,来解构Cursor
(完整实现差不多100行)。
以MediaStore
读取照片为例,先编写内容要映射到的Java数据类(重点在于其中的CursorContract
):
public class SystemMedia implements Serializable {
private long id;
private String data;
private long size;
private String displayName;
private String mimeType;
private long dateAdded;
private long dateModified;
private long bucketId;
private String bucketDisplayName;
private String album;
private int height;
private int width;
private int orientation;
public interface CursorContract { //重点:这个类声明映射的合约,需要提供一个同样参数的构造方法以方便使用
SystemMedia consume(@Key(MediaStore.MediaColumns._ID) long id,
@Key(MediaStore.MediaColumns.DATA) String data,
@Key(MediaStore.MediaColumns.SIZE) long size,
@Key(MediaStore.MediaColumns.DISPLAY_NAME) String displayName,
@Key(MediaStore.MediaColumns.MIME_TYPE) String mimeType,
@Key(MediaStore.MediaColumns.DATE_ADDED) long dateAdded,
@Key(MediaStore.MediaColumns.DATE_MODIFIED) long dateModified,
@Key(MediaStore.MediaColumns.BUCKET_ID) long bucketId,
@Key(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME) String bucketDisplayName,
@Key(MediaStore.MediaColumns.HEIGHT) int height,
@Key(MediaStore.MediaColumns.WIDTH) int width,
@Key(MediaStore.MediaColumns.ALBUM) String album,
@Key(MediaStore.MediaColumns.ORIENTATION) int orientation);
}
public SystemMedia(long id, String data, long size, String displayName, String mimeType, long dateAdded, long dateModified, long bucketId, String bucketDisplayName, int height, int width, String album, int orientation) {
this.id = id;
this.data = data;
this.size = size;
this.displayName = displayName;
this.mimeType = mimeType;
this.dateAdded = dateAdded;
this.dateModified = dateModified;
this.bucketId = bucketId;
this.bucketDisplayName = bucketDisplayName;
this.height = height;
this.width = width;
this.album = album;
this.orientation = orientation;
}
public SystemMedia() {
}
//省略 getter 和 setter
}
然后我们的查询代码就变成了:
public void query(Context context) {
try (Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null,
null, null, null)) {
if (cursor != null) {
List<SystemMedia> result = new ArrayList<>();
while (cursor.moveToNext()) {
SystemMedia media = (SystemMedia) CursorAdapter.withCursor(cursor, SystemMedia.CursorContract.class, SystemMedia::new);
result.add(media);
}
}
}
}
这样就结束了。
Cursor
对象Key
注解SystemMedia::new
用于标注参数对应的字段名(Cursor列名)
其中用到的Memorizer
工具见Java小技巧:创建带缓存的过程
public abstract class CursorAdapter<T> {
abstract T read(Cursor cursor, int index);
public static <T> Object withCursor(Cursor cursor, Class<T> clazz, T t) {
List<Pair<Integer, CursorAdapter<?>>> list = CursorAdapter.adapterExtractor.apply(cursor).apply(clazz);
Method[] methods = clazz.getMethods();
if (methods.length == 1) {
Object[] args = list.stream().map(pair -> pair.first >= 0 ? pair.second.read(cursor, pair.first) : null).toArray();
try {
return 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<Cursor, Function<Class<?>, List<Pair<Integer, CursorAdapter<?>>>>> adapterExtractor = Memorizer.weakMemorize(cursor -> Memorizer.memorize(clazz -> {
Method[] methods = clazz.getMethods();
if (methods.length == 1) {
Method desMethod = methods[0];
Annotation[][] parameterAnnotations = desMethod.getParameterAnnotations();
Type[] parameterTypes = desMethod.getGenericParameterTypes();
if (parameterTypes.length == parameterAnnotations.length) {
List<Pair<Integer, CursorAdapter<?>>> adapterList = new LinkedList<>();
for (int i = 0; i < parameterTypes.length; i++) {
Type parameterType = parameterTypes[i];
Optional<Pair<Integer, CursorAdapter<?>>> pairOptional = Arrays.stream(parameterAnnotations[i])
.filter(annotation -> annotation instanceof Key)
.map(annotation -> (Key) annotation)
.findFirst()
.map(key -> new Pair<>(cursor.getColumnIndex(key.value()), CursorAdapter.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 IllegalArgumentException("methods size must be 1, current is " + methods.length);
}
}));
private static final Function<Type, CursorAdapter<?>> adapterBuilder = Memorizer.memorize(type -> {
if (int.class.equals(type) || Integer.class.equals(type)) {
return create(Cursor::getInt);
} else if (float.class.equals(type) || Float.class.equals(type)) {
return create(Cursor::getFloat);
} else if (long.class.equals(type) || Long.class.equals(type)) {
return create(Cursor::getLong);
} else if (short.class.equals(type) || Short.class.equals(type)) {
return create(Cursor::getShort);
} else if (String.class.equals(type)) {
return create(Cursor::getString);
} else if (double.class.equals(type) || Double.class.equals(type)) {
return create(Cursor::getDouble);
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
if (byte.class.equals(componentType)) {
return create(Cursor::getBlob);
} else {
throw new IllegalStateException("unsupported componentType:" + componentType);
}
} else {
throw new IllegalArgumentException("unsupported type : " + type);
}
});
private static <T> CursorAdapter<T> create(BiFunction<Cursor, Integer, T> reader) {
return new CursorAdapter<T>() {
@Override
T read(Cursor cursor, int index) {
return reader.apply(cursor, index);
}
};
}
}