Android 架构之数据库框架搭建

 更新时间:2021年9月23日 14:26  点击:1291

前言:

你还在苦恼的写SQL么?你还在为数据库升级而烦恼么?你还在因查询数据而写繁琐不可用的代码么? 在这,这些都将不复存在!在本篇中,将会让你一点一滴从无到有创建一个不再为数据库而烦恼的框架。

在开始之前我们先欣赏一下本章实现的最终效果 效果展示 

 如图所示:

  • 对应的model,可直接成为表结构,不再写对应的 Create table xxx对应的SQL了
  • 对应model的Dao层,里面封装了数据表的基本操作(增删改查)
  • 对应的增删改查操作,再也不用SQL了,全用对象处理

接下来开始实战了

1、先创建对应相关操作的注解

1.1 bTable 标识表

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTable {
 //表名
    String value();
}

1.2 DbPrimaryKey 标识主键

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbPrimaryKey {
    
    //表列名
    String value();

    //是否为自动增长
    boolean isAuto() default false;
}

1.3 DbFiled 标识成员属性

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbFiled {
 
 //表列名
    String value();
    /*
 这里可以像主键一样,添加其他属性,比如是否唯一约束,是否非空等
 甚至可以将主键的约束放在这里来,只是表明可以这样做,具体怎样扩展,完全可以按你们想法来
 */
}

2、创建对应表操作类Dao层

2.1 建 待实现的基层 IBaseDao

public interface IBaseDao<T> {

    Long insert(T entity);

    int update(T entity, T where);

    /**
     * 删除数据
     *
     * @param where
     * @return
     */
    int delete(T where);


    /**
     * 查询数据
     */
    List<T> query(T where);

    List<T> query(T where, String groupBy, String orderBy, String having, Integer startIndex,
                  Integer limit);
}

代码分析:

这里创建了基类 IBaseDao ,拥有待实现的增删改查, T 代表对应的 数据表结构的 model

2.2 建已实现的基层 BaseDao

public class BaseDao<T> implements IBaseDao<T> {

    private static final String TAG = "hqk";

    /**
     * 持有数据库操作类的引用
     */
    private SQLiteDatabase database;
    /**
     * 持有操作数据库表所对应的java类型
     * User
     */
    private Class<T> entityClass;
    /**
     * 保证实例化一次
     */
    private boolean isInit = false;

    private String tableName;

    //    检查表
    private HashMap<String, Field> cacheMap;

    protected BaseDao() {
    }

    protected synchronized boolean init(Class<T> entity, SQLiteDatabase sqLiteDatabase) {
        if (!isInit) {
            //初始化完了  自动建表
            entityClass = entity;
            database = sqLiteDatabase;
            if (entity.getAnnotation(DbTable.class) == null) {
                tableName = entity.getClass().getSimpleName();
            } else {
                tableName = entity.getAnnotation(DbTable.class).value();
            }
            if (!database.isOpen()) {
                return false;
            }
            String sql = createTable();
            database.execSQL(sql);
            //建立好映射关系
            initCacheMap();
            isInit = true;
        }
        return true;
    }

    /**
     * 将真实表中的列名  + 成员变量进行 映射
     * 缓存对应的 表 Model里的属性名以及对应表列名
     */
    private void initCacheMap() {
        cacheMap = new HashMap<>();
        //这里没有必要查询 对应表中的任何数据,只想要对应表列名,所以 这 limit 0
        String sql = "select * from " + tableName + " limit 0";
        Cursor cursor = database.rawQuery(sql, null);
        String[] columnNames = cursor.getColumnNames();
        Field[] columnFields = entityClass.getDeclaredFields();
        //获取对应表中的列名数组,以及对应表Model里面的属性数组
        for (String columnName : columnNames) {
            Field resultField = null;
            for (Field field : columnFields) {
                //拿到对应属性的注解值
                String fieldAnnotationName = field.getAnnotation(DbFiled.class).value();
                //如果对应的属性注解值与数据库表列名相同,则拿到对应属性值
                if (columnName.equals(fieldAnnotationName)) {
                    resultField = field;
                    break;
                }
            }
            if (resultField != null) {
                cacheMap.put(columnName, resultField);
            }
        }

    }

    /**
     * 组装 创建表的SQL语句
     *
     * @return
     */
    private String createTable() {
        StringBuffer stringBuffer = new StringBuffer();
        //开始组装 SQL语句
        stringBuffer.append("create table if not exists ");
        stringBuffer.append(tableName + " (");
        Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            Class type = field.getType();
            String primaryKey = null;
            try {
                primaryKey = field.getAnnotation(DbPrimaryKey.class).value();
            } catch (Exception e) {

            }
            Log.i(TAG, "createTable primaryKey " + primaryKey);
            Log.i(TAG, "createTable type " + type);
            if (type == String.class) {
                if (null == primaryKey) {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + " TEXT,");
                } else {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + " TEXT PRIMARY KEY,");
                }
            } else if (type == Double.class) {
                if (null == primaryKey) {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  DOUBLE,");
                } else {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  DOUBLE PRIMARY KEY,");
                }
            } else if (type == Integer.class) {
                if (null == primaryKey) {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  INTEGER,");
                } else {
                    boolean isAuto = field.getAnnotation(DbPrimaryKey.class).isAuto();
                    if (isAuto) {
                        stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  INTEGER PRIMARY KEY AUTOINCREMENT,");
                    } else {
                        stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  INTEGER PRIMARY KEY,");
                    }
                }
            } else if (type == Long.class) {
                if (null == primaryKey) {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  BIGINT,");
                } else {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  BIGINT PRIMARY KEY,");
                }
            } else if (type == byte[].class) {
                if (null == primaryKey) {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  BLOB,");
                } else {
                    stringBuffer.append(field.getAnnotation(DbFiled.class).value() + "  BLOB PRIMARY KEY,");
                }
            } else {
                  /*
                不支持的类型
                 */
                continue;
            }
        }
        //循环完成后,最后一项会有 逗号 ,如果最后一个是逗号,则删除最后一个字符
        if (stringBuffer.charAt(stringBuffer.length() - 1) == ',') {
            stringBuffer.deleteCharAt(stringBuffer.length() - 1);
        }
        //SQL 语句 收尾
        stringBuffer.append(")");
        Log.i(TAG, "createTable: " + stringBuffer.toString());
        return stringBuffer.toString();
    }

    @Override
    public Long insert(T entity) {
        Map<String, String> map = getValues(entity);
        ContentValues contentValues = getContentValues(map);
        return database.insert(tableName, null, contentValues);
    }

    /**
     * 获取对应 model 属性以及对应的注解值(表列名值)
     *
     * @param entity 对应 表结构的model
     * @return 返回 key= 列名,value=属性的值          map集合
     */
    private Map<String, String> getValues(T entity) {
        HashMap<String, String> map = new HashMap<>();
        //获取对应缓存 model 里面的属性键
        Iterator<Field> fieldIterator = cacheMap.values().iterator();
        while (fieldIterator.hasNext()) {
            Field field = fieldIterator.next();
            field.setAccessible(true);
            try {
                Object object = field.get(entity);
                if (object == null) {
                    continue;
                }
                String value = object.toString();
                String key = field.getAnnotation(DbFiled.class).value();
                //遍历 取出对应 属性的值 以及对应的 注解值,并添加至Map里
                if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
                    map.put(key, value);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }

    /**
     * 数据库数据结构的封装
     *
     * @param map 带有 以表列名为键,的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;
    }

    @Override
    public int update(T entity, T where) {
        Map values = getValues(entity);
        ContentValues contentValues = getContentValues(values);
        //条件
        Map whereMap = getValues(where);
        Condition condition = new Condition(whereMap);
        return database.update(tableName, contentValues, condition.whereClause, condition.whereArgs);
    }

    class Condition {
        String whereClause;
        String[] whereArgs;

        public Condition(Map<String, String> whereClause) {
            boolean flag = false;
            if (true && flag) {

            }
            ArrayList list = new ArrayList();
            StringBuilder stringBuilder = new StringBuilder();
            // 这里之所以先添加 1=1 这个条件 是因为
            // SQL  where  后面需要给条件判断,而下面 while 循环 直接添加了 and
            // SQL 语句就变成了 where and  这显然不符合SQL语句
            // 因此 加上 1=1 就变成了  where 1=1 and xx。起了一个呈上去下的作用

            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) {
                    stringBuilder.append(" and " + key + " =?");
                    list.add(value);
                }
            }
            this.whereClause = stringBuilder.toString();
            this.whereArgs = (String[]) list.toArray(new String[list.size()]);
        }
    }

    @Override
    public int delete(T where) {
        Map map = getValues(where);
        Condition condition = new Condition(map);
        return database.delete(tableName, condition.whereClause, condition.whereArgs);
    }

    @Override
    public List<T> query(T where) {
        return query(where, null, null, null, null, null
        );
    }
    //所有  条件
    @Override
    public List<T> query(T where, String groupBy, String orderBy, String having,Integer startIndex,
                         Integer limit) {
        String limitString=null;
        if(startIndex!=null&&limit!=null)
        {
            limitString=startIndex+" , "+limit;
        }

        Map map=getValues(where);
        Condition condition=new Condition(map);
        Cursor cursor=  database.query(tableName, null, condition.whereClause,
                condition.whereArgs,
                groupBy, having,
                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 {
//                cachmap        ---对象中的成员变量    Filed    annotion-- tb_name
//cacheMap    name  ---Filed       1
//            tb_name       ---Filed  2
                item=where.getClass().newInstance();
                Iterator iterator=cacheMap.entrySet().iterator();
                while (iterator.hasNext())
                {
                    Map.Entry entry= (Map.Entry) iterator.next();
                    //tb_name
                    /**
                     * 得到列名
                     */
                    String colomunName= (String) entry.getKey();
//                    通过列名查找到游标的索性
                    Integer colmunIndex= cursor.getColumnIndex(colomunName);
//                    Filed
//反射的成员 cursor
                    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==byte[].class)
                        {
                            field.set(item,cursor.getBlob(colmunIndex));
                            /*
                            不支持的类型
                             */
                        }else {
                            continue;
                        }

                    }

                }
                list.add(item);
            } catch ( Exception e) {
                e.printStackTrace();
            }


        }

        return list;
    }
}

代码分析:

在这个BaseDao 里面,几乎分担了数据表大部分的脏活累活,根据model结构自动生成对应SQL并创建对应表,以及基础的增删改查操作。

2.3 建对应model 的Dao层

1.UserDao

public class UserDao<User> extends BaseDao<User> {

    @Override
    public Long insert(User entity) {
        return super.insert(entity);
    }

    @Override
    public List<User> query(User where) {
        return super.query(where);
    }

    @Override
    public int delete(User where) {
        return super.delete(where);
    }

    @Override
    public int update(User entity, User where) {
        return super.update(entity, where);
    }

    @Override
    public List<User> query(User where, String groupBy, String orderBy, String having, Integer startIndex, Integer limit) {
        return super.query(where, groupBy, orderBy, having, startIndex, limit);
    }
}

2.PhotoDao

public class PhotoDao<Photo> extends BaseDao<Photo> {

    @Override
    public Long insert(Photo entity) {
        return super.insert(entity);
    }

    @Override
    public int update(Photo entity, Photo where) {
        return super.update(entity, where);
    }

    @Override
    public List<Photo> query(Photo where) {
        return super.query(where);
    }

    @Override
    public int delete(Photo where) {
        return super.delete(where);
    }
}

代码分析:

虽然 BaseDao 已经完成了几乎所有的操作,但是一旦遇到多表查询的时候,光是一个BaseDao远远不够。所以这里还是选择创建不同modelDao层,并继承与BaseDao。也就是说,有多少表,最好就创建对应多少个Dao层。

3、创建数据库工厂

public class BaseDaoFactory {

    private final String TAG = "hqk";
    private SQLiteDatabase sqLiteDatabase;

    private String sqliteDatabasePath;

    private static BaseDaoFactory instance = new BaseDaoFactory();

    //饿汉单例模式
    public static BaseDaoFactory getInstance() {
        return instance;
    }

    public BaseDaoFactory() {
        //读者可随意更改路径以及对应数据库名,这里演示暂时放在根目录
        sqliteDatabasePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/hqk.db";
        sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(sqliteDatabasePath, null);
        Log.i(TAG, "sqliteDatabasePath : " + sqliteDatabasePath);
        Log.i(TAG, "sqLiteDatabase : " + sqLiteDatabase.getPath());
    }


    /**
     * @param clazz
     * @param entityClass
     * @param <R>         我们在这可以把它看成某一个对象,它继承与 BaseDao<T> ,而里面的T 就是下面的那个空对象
     * @param <T>         我们在这可以吧它看成某一个空对象 T
     * @return
     */
    public synchronized <R extends BaseDao<T>, T> R createBaseDao(Class<R> clazz, Class<T> entityClass) {
        BaseDao baseDao = null;
        try {
            baseDao = clazz.newInstance();
            baseDao.init(entityClass, sqLiteDatabase);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        return (R) baseDao;
    }
}

代码分析:

这里也没啥好分析的,就一个数据库创建,以及对应model的初始化。唯一值得注意的就是初始化的时候用了俩个泛型,具体什么意思,可按照代码注释理解。

4、创建对应model

1.User

@DbTable("tb_user")
public class User {
    
    @DbPrimaryKey(value = "tb_id", isAuto = true)
    @DbFiled("tb_id")
    public Integer id;
    @DbFiled("tb_name")
    public String name;//
    
    @DbFiled("tb_age")
    public Integer age;

    public User(String name, Integer age) {

        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public User() {
    }


}

2.Photo

@DbTable("tb_photo")
public class Photo {
    @DbFiled("time")
    private  String time;
    @DbFiled("id")
    private  Long id;
    @DbFiled("path")
    private  String path;

    public Photo( ) {
    }

    public Photo(String time, Long id, String path) {
        this.time = time;
        this.id = id;
        this.path = path;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

代码分析:

这俩类就是对应表结构model 类,用到了对应注解,相信通过注解能够清楚知道对应表结构是怎样的。

5、最终使用

ainActivity

public class MainActivity extends AppCompatActivity {

    UserDao<User> userDao;

    PhotoDao<Photo> photoDao;

    private ArrayList<User> listUser = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        requestPermission(this);
    }


    public void save(View view) {
        User user = new User("hqk", 18);
        long size = userDao.insert(user);
        Photo photo = new Photo("time", System.currentTimeMillis(), "path");
        long photoSize = photoDao.insert(photo);
        Toast.makeText(this, "save line :   " + size, Toast.LENGTH_LONG).show();
    }


    public void update(View view) {
        User where = new User();
        where.setAge(18);
        int size = userDao.update(new User("TOM", 99), where);
        Toast.makeText(this, "update Size :   " + size, Toast.LENGTH_LONG).show();
    }

    public void delete(View view) {
        User where = new User();
        where.setAge(18);
        int size = userDao.delete(where);
        Toast.makeText(this, "delete Size :   " + size, Toast.LENGTH_LONG).show();
    }

    public void queryList(View view) {
        listUser.clear();
        listUser.addAll(userDao.query(new User()));
        Toast.makeText(this, "查询条数为:" + listUser.size(), Toast.LENGTH_LONG).show();
    }


    public void requestPermission(
            Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(activity,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);
            return;
        }
        createTable();


    }

    private void createTable() {
        userDao = BaseDaoFactory.getInstance().createBaseDao(UserDao.class, User.class);
        photoDao = BaseDaoFactory.getInstance().createBaseDao(PhotoDao.class, Photo.class);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        createTable();
    }
}

到此这篇关于Android 架构之数据库框架搭建的文章就介绍到这了,更多相关Android 架构数据库框架搭建内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • PHP 数据库缓存Memcache操作类

    操作类就是把一些常用的一系列的数据库或相关操作写在一个类中,这样调用时我们只要调用类文件,如果要执行相关操作就直接调用类文件中的方法函数就可以实现了,下面整理了...2016-11-25
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Intellij IDEA连接Navicat数据库的方法

    这篇文章主要介绍了Intellij IDEA连接Navicat数据库的方法,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借价值,需要的朋友可以参考下...2021-03-25
  • 在数据库里将毫秒转换成date格式的方法

    在开发过程中,我们经常会将日期时间的毫秒数存放到数据库,但是它对应的时间看起来就十分不方便,我们可以使用一些函数将毫秒转换成date格式。 一、 在MySQL中,有内置的函数from_unixtime()来做相应的转换,使用如下: 复制...2014-05-31
  • C#操作本地文件及保存文件到数据库的基本方法总结

    C#使用System.IO中的文件操作方法在Windows系统中处理本地文件相当顺手,这里我们还总结了在Oracle中保存文件的方法,嗯,接下来就来看看整理的C#操作本地文件及保存文件到数据库的基本方法总结...2020-06-25
  • 基于BootStrap Metronic开发框架经验小结【八】框架功能总体界面介绍

    这篇文章主要介绍了基于BootStrap Metronic开发框架经验小结【八】框架功能总体界面介绍 的相关资料,需要的朋友可以参考下...2016-05-14
  • 如何解决局域网内mysql数据库连接慢

    通过内网连另外一台机器的mysql服务, 确发现速度N慢! 等了大约几十秒才等到提示输入密码。 但是ping mysql所在服务器却很快! 想到很久之前有过类似的经验, telnet等一些服务在连接请求的时候,会做一些反向域名解析(如果...2015-10-21
  • MySQL快速复制数据库数据表的方法

    某些时候,例如为了搭建一个测试环境,或者克隆一个网站,需要复制一个已存在的mysql数据库。使用以下方法,可以非常简单地实现。假设已经存在的数据库名字叫db1,想要复制一份,命名为newdb。步骤如下:1. 首先创建新的数据库newd...2015-10-21
  • mysqldump命令导入导出数据库方法与实例汇总

    mysqldump命令的用法1、导出所有库系统命令行mysqldump -uusername -ppassword --all-databases > all.sql 2、导入所有库mysql命令行mysql>source all.sql; 3、导出某些库系统命令行mysqldump -uusername -ppassword...2015-10-21
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • Mysql数据库错误代码中文详细说明

    1005:创建表失败1006:创建数据库失败1007:数据库已存在,创建数据库失败1008:数据库不存在,删除数据库失败1009:不能删除数据库文件导致删除数据库失败1010:不能删除数据目录导致删除数据库失败1011:删除数据库...2013-09-23
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • c#异步读取数据库与异步更新ui的代码实现

    这篇文章主要介绍了c#从数据库里取得数据并异步更新ui的方法,大家参考使用吧...2020-06-25