美文网首页Android高级技术Android开发Android开发经验谈
Android 架构师之路18 面向对象数据库架构设计

Android 架构师之路18 面向对象数据库架构设计

作者: 香沙小熊 | 来源:发表于2018-02-06 18:11 被阅读295次

    Android 架构师之路 目录

    前言

    原来在项目中经常使用SqliteopenHelper这个类来实现数据库的增删改查,但是使用它非常的繁琐,需要写很多啰嗦的代码。所以面向对象数据库框架的设计是解决上述问题的办法。

    1.OOP数据库设计UML类图

    角色:
    • BaseDaoFactory: 用来创建数据库和初始化数据库
    • IBaseDao: 增删改查方法接口
    • BaseDao: 实现增删改查方法的抽象模板类(子类方法UserDao增删改查方法在该类实现)
    • Bean: 使用注解方式来定义表中相关字段名和类型,如UserBean
    • ConcreteDao: 创建表,定相关义字段及其长度,(UserDao)和UserBean中的字段对应
    • Client: 调用类,如Activity

    2.各角色实现

    BaseDaoFactory:
    public class BaseDaoFactory {
    
        private static BaseDaoFactory instance = new BaseDaoFactory();
    
        private String sqliteDatabasePath;
    
        private SQLiteDatabase sqLiteDatabase;
    
        public BaseDaoFactory() {
            sqliteDatabasePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/teacher.db";
            openDatabase();
        }
    
        public static BaseDaoFactory getInstance() {
            return instance;
        }
    
        public synchronized <T extends BaseDao<M>, M> T getDataHelper(Class<T> clazz, Class<M> entityClass) {
            BaseDao baseDao = null;
            try {
                baseDao = clazz.newInstance();
                baseDao.init(entityClass, sqLiteDatabase);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return (T) baseDao;
        }
        /**
         *  targetSdkVersion  大于22时 要申请存储读写权限
         */
        //打开数据库操作
        private void openDatabase() {
    
            this.sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(sqliteDatabasePath, null);
        }
    }
    
    IBaseDao:
    public interface IBaseDao<T>{
        /**
         * @param entity
         * @return
         */
        Long insert(T entity);
    
        /**
         * 更新数据
         * @param entity
         * @param where
         * @return
         */
        int update(T entity,T where);
    
        /**
         * 删除数据
         * @param where
         * @return
         */
        int delete(T where);
    
        List<T> query(T where);
    
        List<T> query(T where,String orderBy,Integer startIndex,Integer limit);
    }
    
    
    BaseDao:
    /**
     * Created by Xionghu on 2018/2/2.
     * Desc: 真正和底层打交道
     *
     * @param <T>
     */
    
    public abstract class BaseDao<T> implements IBaseDao<T> {
    
        /**
         * 持有数据库操作类的引用
         */
        private SQLiteDatabase database;
    
        /**
         * 保证实例化一次
         */
        private boolean isInit = false;
    
        /**
         * 持有操作数据表所对应的Java类型
         * User
         */
        private Class<T> entityClass;
    
        /**
         * 维护这表名与成员变量名的映射关系
         * key ---->表名
         * value ---->Field
         */
        private HashMap<String, Field> cacheMap;
    
        private String tableName;
    
        /**
         * @param entity
         * @param sqLiteDatabase
         * @return 实例化一次
         */
        protected synchronized boolean init(Class<T> entity, SQLiteDatabase sqLiteDatabase) {
            if (!isInit) {
                entityClass = entity;
                Log.d("sqlite", entityClass.getSimpleName());
                database = sqLiteDatabase;
                if (entity.getAnnotation(DbTable.class) == null) {
                    tableName = entity.getClass().getSimpleName();
                } else {
                    tableName = entity.getAnnotation(DbTable.class).value();
                }
                if (!database.isOpen()) {
                    return false;
                }
                if (!TextUtils.isEmpty(createTable())) {
                    database.execSQL(createTable());
    
                }
                cacheMap = new HashMap<>();
                initCacheMap();
                isInit = true;
            }
            return isInit;
        }
    
        /**
         * 维护映射关系
         */
        private void initCacheMap() {
            String sql = "select * from " + this.tableName + " limit 1 , 0 ";
            Cursor cursor = null;
            try {
    
                cursor = database.rawQuery(sql, null);
                /**
                 * 表的列名数组
                 */
                String[] columnNames = cursor.getColumnNames();
                /**
                 * 拿到Field数组
                 */
                Field[] colmunFields = entityClass.getFields();
                for (Field field : colmunFields) {
                    field.setAccessible(true);
                }
                /**
                 * 开始找对应关系
                 */
                for (String colmunName : columnNames) {
                    /**
                     * 如果找到对应的Filed就赋值给他
                     * User
                     */
                    Field colmunFiled = null;
                    for (Field field : colmunFields) {
                        String fileName = null;
                        if (field.getAnnotation(DbFiled.class) != null) {
                            fileName = field.getAnnotation(DbFiled.class).value();
                        } else {
                            fileName = field.getName();
                        }
                        /**
                         * 如果表的名字 等于 成员变量的注解名字
                         */
                        if (colmunName.equals(fileName)) {
                            colmunFiled = field;
                            break;
                        }
                    }
                    //找到了对应关系
                    if (colmunFiled != null) {
                        cacheMap.put(colmunName, colmunFiled);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                cursor.close();
            }
        }
    
        @Override
        public Long insert(T entity) {
            Map<String, String> map = getValues(entity);
            ContentValues values = getContentValues(map);
            Long result = database.insert(tableName, null, values);
            return result;
        }
    
        /**
         * @param entity
         * @param where
         * @return
         */
        @Override
        public int update(T entity, T where) {
            int result = -1;
            Map values = getValues(entity);
    
            /**
             *讲条件对象转换map
             */
            Map whereClause = getValues(where);
            Condition condition = new Condition(whereClause);
            ContentValues contentValues = getContentValues(values);
            result = database.update(tableName, contentValues, condition.getWhereClause(), condition.getWhereArgs());
    
            return result;
        }
    
        @Override
        public int delete(T where) {
            Map map = getValues(where);
            Condition condition = new Condition(map);
    
            /**
             * id=1 数据
             * id=? new String[]{ String.value(1)}
             */
            int result = database.delete(tableName, condition.getWhereClause(), condition.getWhereArgs());
            return result;
        }
    
        //查询所有数据
        @Override
        public List<T> query(T where) {
            return query(where, null, null, null);
        }
    
        @Override
        public List<T> query(T where, String orderBy, Integer startIndex, Integer limit) {
            Map map = getValues(where);
            String limitString = null;
            if (startIndex != null && limit != null) {
                limitString = startIndex + " , " + limit;
            }
            Condition condition = new Condition(map);
            Cursor cursor = database.query(tableName, null, condition.getWhereClause(), condition.getWhereArgs(),
                    null, null, orderBy, limitString);
            List<T> result = getResult(cursor, where);
            cursor.close();
            return result;
        }
    
    
        private List<T> getResult(Cursor cursor, T where) {
            ArrayList list = new ArrayList();
            Object item;
            while (cursor.moveToNext()) {
                try {
                    item = where.getClass().newInstance();
                    /**
                     * 列名 name
                     * 成员变量名 Filed
                     */
                    Iterator iterator = cacheMap.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry entry = (Map.Entry) iterator.next();
                        /**
                         * 得到列名
                         */
                        String colomunName = (String) entry.getKey();
                        /**
                         * 然后以列名 拿到列名在游标的位置
                         */
                        Integer colmunIndex = cursor.getColumnIndex(colomunName);
    
                        Field field = (Field) entry.getValue();
                        Class type = field.getType();
                        if (colmunIndex != -1) {
                            if (type == String.class) {
                                //反射方式赋值
                                field.set(item, cursor.getString(colmunIndex));
                            } else if (type == Double.class) {
                                field.set(item, cursor.getDouble(colmunIndex));
                            } else if (type == Integer.class) {
                                field.set(item, cursor.getInt(colmunIndex));
                            } else if (type == Long.class) {
                                field.set(item, cursor.getLong(colmunIndex));
                            } else if (type == Float.class) {
                                field.set(item, cursor.getFloat(colmunIndex));
                            } else if (type == byte[].class) {
                                field.set(item, cursor.getBlob(colmunIndex));
                            } else {
                                continue;
                            }
    
                        }
    
    
                    }
                    list.add(item);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            return list;
        }
    
        /**
         * 转换成ContentValues
         *
         * @param map
         * @return
         */
        private ContentValues getContentValues(Map<String, String> map) {
            ContentValues contentValues = new ContentValues();
            Set keys = map.keySet();
            Iterator<String> iterator = keys.iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                String value = map.get(key);
                if (value != null) {
                    contentValues.put(key, value);
                }
            }
            return contentValues;
        }
    
    
        private Map<String, String> getValues(T entity) {
            HashMap<String, String> result = new HashMap<>();
            Iterator<Field> fieldsIterator = cacheMap.values().iterator();
            /**
             * 循环遍历 映射map的 Field
             */
            while (fieldsIterator.hasNext()) {
                Field columnToField = fieldsIterator.next();
                String cacheKey = null;
                String cacheValue = null;
                if (columnToField.getAnnotation(DbFiled.class) != null) {
                    cacheKey = columnToField.getAnnotation(DbFiled.class).value();
                } else {
                    cacheKey = columnToField.getName();
                }
                try {
                    if (null == columnToField.get(entity)) {
                        continue;
                    }
                    cacheValue = columnToField.get(entity).toString();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                result.put(cacheKey, cacheValue);
            }
            return result;
        }
    
    
        /**
         * 创建表
         *
         * @return
         */
        protected abstract String createTable();
    
        /**
         * 封装修改语句
         */
        class Condition {
    
    
            /**
             * 查询条件
             * name= ?&& password = ?
             */
            private String whereClause;
    
            private String[] whereArgs;
    
    
            public Condition(Map<String, String> whereClause) {
                ArrayList list = new ArrayList();
                StringBuilder stringBuilder = new StringBuilder();
    
                stringBuilder.append(" 1=1 ");
    
                Set keys = whereClause.keySet();
                Iterator iterator = keys.iterator();
                while (iterator.hasNext()) {
                    String key = (String) iterator.next();
                    String value = whereClause.get(key);
    
                    if (value != null) {
                        /**
                         * 拼接条件查询语句
                         * 1=1 and name =? and password = ?
                         */
                        stringBuilder.append(" and " + key + " =?");
                        list.add(value);
                    }
                }
                this.whereClause = stringBuilder.toString();
                this.whereArgs = (String[]) list.toArray(new String[list.size()]);
            }
    
            public String getWhereClause() {
                return whereClause;
            }
    
            public void setWhereClause(String whereClause) {
                this.whereClause = whereClause;
            }
    
            public String[] getWhereArgs() {
                return whereArgs;
            }
    
            public void setWhereArgs(String[] whereArgs) {
                this.whereArgs = whereArgs;
            }
        }
    }
    
    
    Bean:
    @DbTable("tb_user")
    public class User {
    
        /**
         * 数据字段支持注解和非注解
         * 非注解:
         *     public Integer userId;
         * 数据保存的字段名为 userId
         * 注解:
         *     @DbFiled("teacher_id")
         *     public Integer userId;
         *  数据保存的字段名为 teacher_id
         *
         *  要用Integer型 不要用int型,负责查询不到
         */
        public Integer userId;
    
        @DbFiled("name")
        public String name;
        @DbFiled("password")
        public String password;
    
        public User(Integer userId, String name, String password) {
            this.userId = userId;
            this.name = name;
            this.password = password;
        }
    
        public User() {
        }
    
    
        public Integer getUserId() {
            return userId;
        }
    
        public void setUserId(Integer userId) {
            this.userId = userId;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "userId=" + userId +
                    ", name='" + name + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    
    @Target(ElementType.FIELD) //字段、枚举的常量
    @Retention(RetentionPolicy.RUNTIME) //注解会在class字节码文件中存在,在运行时可以痛过反射获取到
    public @interface DbFiled {
        String value();
    }
    
    @Target(ElementType.TYPE) //运用在接口、类枚举、注解
    @Retention(RetentionPolicy.RUNTIME) //注解会在class字节码文件中存在,在运行时可以痛过反射获取到
    public @interface DbTable {
        String value();
    }
    
    ConcreteDao:
    public class UserDao extends BaseDao {
    
        @Override
        protected String createTable() {
            return "create table if not exists tb_user(userId int,name varchar(20),password varchar(20))";
        }
    }
    
    public class FileDao extends BaseDao {
        @Override
        protected String createTable() {
            return "create table if not exists tb_file(time varchar(20),path varchar(20),description varchar(20))";
        }
    }
    
    Client:
    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "Main";
        IBaseDao<User> baseDao;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            baseDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
    
        }
    
    
    }
    

    3.增删改查测试

    Step1:增
        public void save(View view) {
    
            for (int i = 0; i < 20; i++) {
                User user = new User(i, "teacher", "123456");
                baseDao.insert(user);
            }
    
    /**
     *写入文件数据
     */
    //        BaseDao<FileBean> fileBeanBaseDao = BaseDaoFactory.getInstance().getDataHelper(FileDao.class, FileBean.class);
    //        fileBeanBaseDao.insert(new FileBean("2019-12-13", Environment.getExternalStorageDirectory() + "/kpioneer", "asdfg"));
    
        }
    

    执行queryAll 打印数据

    02-06 17:51:06.151 4858-4858/com.haocai.haocaisqlite I/Main: 查询到 20 条数据
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=0, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=1, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=2, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=3, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=4, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=5, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=6, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=7, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=8, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=9, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=10, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=11, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=12, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=13, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=14, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=15, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=16, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=17, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=18, name='teacher', password='123456'}
    02-06 17:51:06.152 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=19, name='teacher', password='123456'}
    
    Step2:改
        public void update(View view) {
            for (int i = 10; i < 20; i++) {
                User where = new User();
                where.setUserId(i);
                User user = new User(i, "kpioneer", "8888");
    
                //更新原name = teacher的数据
                baseDao.update(user, where);
            }
    
        }
    

    执行queryAll 打印数据

    02-06 17:54:18.650 4858-4858/com.haocai.haocaisqlite I/Main: 查询到 20 条数据
    02-06 17:54:18.650 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=0, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=1, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=2, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=3, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=4, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=5, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=6, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=7, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=8, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=9, name='teacher', password='123456'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=10, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=11, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=12, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=13, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=14, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=15, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=16, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=17, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=18, name='kpioneer', password='8888'}
    02-06 17:54:18.651 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=19, name='kpioneer', password='8888'}
    
    Step3:查
        public void query(View view) {
            User where = new User();
            where.setName("teacher");
            List<User> list = baseDao.query(where);
    
            Log.i(TAG, "查询到 " + list.size() + " 条数据");
            for (User user : list) {
                Log.i(TAG, user.toString());
            }
    
            System.out.println("--------查询某条数据-------");
            User where2 = new User();
            where2.setName("teacher");
            where2.setUserId(5);
            List<User> list2 = baseDao.query(where2);
            Log.i(TAG, "查询到 " + list2.size() + " 条数据");
        }
    
    

    打印数据

    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: 查询到 10 条数据
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=0, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=1, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=2, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=3, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=4, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=5, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=6, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=7, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=8, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=9, name='teacher', password='123456'}
    02-06 17:58:49.530 4858-4858/com.haocai.haocaisqlite I/System.out: --------查询某条数据-------
    02-06 17:58:49.533 4858-4858/com.haocai.haocaisqlite I/Main: 查询到 1 条数据
    
    Step4:删
        public void delete(View view) {
            User user = new User();
            user.setName("teacher");
            baseDao.delete(user);
        }
    

    执行queryAll 打印数据

    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: 查询到 10 条数据
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=10, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=11, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=12, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=13, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=14, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=15, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=16, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=17, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=18, name='kpioneer', password='8888'}
    02-06 18:00:29.196 4858-4858/com.haocai.haocaisqlite I/Main: User{userId=19, name='kpioneer', password='8888'}
    
    至此,面向对象数据库架构设计完成。
    源码下载
    Github:https://github.com/kpioneer123/OOPSqliteDemo

    相关文章

      网友评论

        本文标题:Android 架构师之路18 面向对象数据库架构设计

        本文链接:https://www.haomeiwen.com/subject/afigzxtx.html