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);
}
}
}
}
这样就结束了。
API说明
- CursorAdapter.withCursor 方法
- 第一个Cursor参数:目标
Cursor
对象 - 第二个Class参数:解构的合约接口,需要是单方法的函数式接口,类名和方法名随意,该方法的参数需要使用
Key
注解 - 第三个T参数:合约接口的实现类,完成由所有字段到对象的转换,通常为全属性的构造方法的方法引用,比如
SystemMedia::new
- Key 注解
用于标注参数对应的字段名(Cursor列名)文章来源:https://www.toymoban.com/news/detail-814145.html
完整实现(差不多100行)
其中用到的Memorizer
工具见Java小技巧:创建带缓存的过程文章来源地址https://www.toymoban.com/news/detail-814145.html
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);
}
};
}
}
到了这里,关于Android小工具:利用解构来简化Cursor内容的读取的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!