Android小工具:利用解构来简化Cursor内容的读取

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说明

  1. CursorAdapter.withCursor 方法
  • 第一个Cursor参数:目标Cursor对象
  • 第二个Class参数:解构的合约接口,需要是单方法的函数式接口,类名和方法名随意,该方法的参数需要使用Key注解
  • 第三个T参数:合约接口的实现类,完成由所有字段到对象的转换,通常为全属性的构造方法的方法引用,比如SystemMedia::new
  1. Key 注解

用于标注参数对应的字段名(Cursor列名)

完整实现(差不多100行)

其中用到的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);
            }
        };
    }
}

你可能感兴趣的:(Java,android,Cursor,destructure,java,adapter)