离线开发
开发工具: androidStudio ,SQLite3
使用开源库:GreenDao 3.2.2
离线已实现:
1. 增、删、改、查
2. 新增表,字段,更改字段,升级数据迁移
3. 从服务器下载数据,并更新到本地数据表,避免重复添加
4. 代码封装
集成:
1. 项目根目录添加环境
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // 添加 GreenDao 插件
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' } // 添加
}
}
2. app 下添加依赖
// 与 application 同级
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao'
// android 节点中,rootProject.ext.publish.versionCode 文尾介绍
greendao {
// 数据库升级视为 App 重大升级,跟随 versionCode ,方便记忆
schemaVersion rootProject.ext.publish.versionCode
}
// dependencies 节点中
implementation 'org.greenrobot:greendao:3.2.2' // 添加
3. 对象,也就是表
@Entity(nameInDb = "Users")
public class UserBean {
@Id(autoincrement = true)
@Index(unique = true)//设置唯一性
@Property(nameInDb = "F_id")
private Long F_id;
@Property(nameInDb = "F_byyx")
private String F_byyx;
@Property(nameInDb = "F_csrq")
private String F_csrq;
...
}
4. 普通的增删改查
UserBeanDao userBeanDao = DaoManager.getInstance().getDaoSession().getUserBeanDao();
// 增
userBeanDao.insert(new UserBean(100L, "王大锤", "女"));
// 删
userBeanDao.deleteByKey(100L);
// 改
UserBean bean = mUserBeanDao.load(32L);
bean.setF_csrq("改改");
userBeanDao.update(bean);
// 查
StringBuilder sb = new StringBuilder();
List<UserBean> userBeans = mUserBeanDao.loadAll();
for (UserBean userBean : userBeans) {
sb.append("id:").append(userBean.getId())
.append("f_id:").append(userBean.getF_id())
.append("F_byyx:").append(userBean.getF_byyx())
.append("F_csrq:").append(userBean.getF_csrq())
.append("\n");
}
Log.i(TAG, "showDataList: "+sb.toString();
5. 新增表,字段,更改字段,升级时数据迁移,最后全部代码介绍
// 这里升级最好是将所有的表都写上,工具类会自动进行数据迁移
MigrationHelper.migrate(
db,
StuffBeanDao.class,
DepartmentBeanDao.class
);
6. 从服务器下载数据,并更新到本地数据表,业务上会一次下载多张表,所以我统一放到了一个子线程中处理(真正实现过程使用的 IntentService ,其内部就是子线程,用完后,会自动销毁)
// 模拟网络数据
List<UserBean> list = new ArrayList<>();
list.add(new UserBean( 111L, "王大宝", "男"));
list.add(new UserBean( 112L, "王大宝", "男"));
list.add(new UserBean( 113L, "王大宝", "男"));
list.add(new UserBean( 114L, "王大宝", "男"));
list.add(new UserBean( 115L, "王大宝", "男"));
list.add(new UserBean( 116L, "王大宝", "男"));
list.add(new UserBean( 117L, "王大宝", "男"));
// 更新或插入操作
for (int i = 0; i < list.size(); i++) {
UserBean bean = list.get(i);
// dao.hasKey(bean) 这句话只是说这个对象中是否存在主键,因为我设置了主键所以肯定存在
// mUserBeanDao.load(bean.getF_id()) != null ,查询看看是否存在,存在则更新,不存在则插入
if (mUserBeanDao.load(bean.getF_id()) != null) {
mUserBeanDao.update(bean);
} else {
mUserBeanDao.insert(bean);
}
}
全部源码
// 常量
public interface DbConstants {
String DB_NAME = "test.db";// 数据库名称
}
// 应用启动类
public class GreenDaoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initGreenDao();
}
/**
* 初始化 GreenDao 数据库
*/
private void initGreenDao() {
DaoManager.getInstance().init(this);
DaoManager.getInstance().getDaoMaster();
}
}
// 数据迁移类
public final class MigrationHelper {
private static final String TAG = "MigrationHelper";
private static final String SQLITE_MASTER = "sqlite_master";
private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";
public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
LogUtil.d(TAG,"【The Old Database Version】" + db.getVersion());
Database database = new StandardDatabase(db);
migrate(database, daoClasses);
}
public static void migrate(Database database, Class<? extends AbstractDao<?, ?>>... daoClasses) {
LogUtil.d(TAG,"【Generate temp table】start");
generateTempTables(database, daoClasses);
LogUtil.d(TAG,"【Generate temp table】complete");
LogUtil.d(TAG,"【Drop all table and recreate all table】");
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(database, daoClasses[i]);
dropTable(database, true, daoConfig);
createTable(database, false, daoConfig);
}
LogUtil.d(TAG,"【Restore data】start");
restoreData(database, daoClasses);
LogUtil.d(TAG,"【Restore data】complete");
}
private static void dropTable(Database database, boolean ifExists, DaoConfig daoConfig) {
String sql = String.format("DROP TABLE %s\"%s\"", ifExists ? "IF EXISTS " : "", daoConfig.tablename);
database.execSQL(sql);
}
private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
String tempTableName = null;
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
if (!isTableExists(db, false, tableName)) {
LogUtil.d(TAG,"【New Table】" + tableName);
continue;
}
try {
tempTableName = daoConfig.tablename.concat("_TEMP");
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
LogUtil.d(TAG,"【Generate temp table】 dropTableStringBuilder:" + dropTableStringBuilder);
db.execSQL(dropTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
LogUtil.d(TAG,"【Generate temp table】 insertTableStringBuilder:" + insertTableStringBuilder);
db.execSQL(insertTableStringBuilder.toString());
LogUtil.d(TAG,"【Table】" + tableName +"\n ---Columns-->" + getColumnsStr(daoConfig));
LogUtil.d(TAG,"【Generate temp table】" + tempTableName);
} catch (SQLException e) {
Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
}
}
}
private static boolean isTableExists(Database db, boolean isTemp, String tableName) {
if (db == null || TextUtils.isEmpty(tableName)) {
return false;
}
String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;
String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?";
Cursor cursor=null;
int count = 0;
try {
cursor = db.rawQuery(sql, new String[]{"table", tableName});
if (cursor == null || !cursor.moveToFirst()) {
return false;
}
count = cursor.getInt(0);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return count > 0;
}
private static String getColumnsStr(DaoConfig daoConfig) {
if (daoConfig == null) {
return "no columns";
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < daoConfig.allColumns.length; i++) {
builder.append(daoConfig.allColumns[i]);
builder.append(",");
}
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
}
private static void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
if (!isTableExists(db, true, tempTableName)) {
continue;
}
try {
// get all columns from tempTable, take careful to use the columns list
List<String> columns = getColumns(db, tempTableName);
ArrayList<String> properties = new ArrayList<>(columns.size());
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (columns.contains(columnName)) {
properties.add(columnName);
}
}
if (properties.size() > 0) {
final String columnSQL = TextUtils.join(",", properties);
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
LogUtil.d(TAG,"【Restore data】 db sql: " + insertTableStringBuilder);
db.execSQL(insertTableStringBuilder.toString());
LogUtil.d(TAG,"【Restore data】 to " + tableName);
}
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(dropTableStringBuilder.toString());
LogUtil.d(TAG,"【Drop temp table】" + tempTableName);
} catch (SQLException e) {
Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e);
}
}
}
private static List<String> getColumns(Database db, String tableName) {
List<String> columns = null;
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null);
if (null != cursor && cursor.getColumnCount() > 0) {
columns = Arrays.asList(cursor.getColumnNames());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
if (null == columns)
columns = new ArrayList<>();
}
return columns;
}
public static void createTable(Database db, boolean ifNotExists, DaoConfig daoConfig) {
String tableName = daoConfig.tablename;
StringBuilder builder = new StringBuilder();
builder.append("CREATE TABLE ");
builder.append(ifNotExists ? "IF NOT EXISTS ": "");
builder.append(tableName);
builder.append(getColumnsSql(daoConfig));
LogUtil.d(TAG,"【createTable】 sql:" + builder.toString());
db.execSQL(builder.toString()); // 6: Description
}
private static String getColumnsSql(DaoConfig daoConfig) {
if (daoConfig == null) {
return "";
}
StringBuilder builder = new StringBuilder(" (");
for (int i = 0; i < daoConfig.properties.length; i++) {
builder.append(String.format("\"%s\" %s,", daoConfig.properties[i].columnName,
getPropertyType(daoConfig.properties[i].type)));
}
if (daoConfig.properties.length > 0 && builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
builder.append("); ");
return builder.toString();
}
/**
* 根据字段类型返回对应的数据库字段语句
* @param type
* @return
*/
private static String getPropertyType(Class<?> type) {
if (type.equals(byte[].class)) {
return "BLOB";
} else if (type.equals(String.class)) {
return "TEXT DEFAULT ''";
} else if (type.equals(boolean.class) || type.equals(Boolean.class)
|| type.equals(int.class) || type.equals(Integer.class)
|| type.equals(long.class) || type.equals(Long.class)
|| type.equals(Date.class) || type.equals(Byte.class)) {
return "INTEGER DEFAULT (0)";
} else if (type.equals(float.class) || type.equals(Float.class)
|| type.equals(double.class) || type.equals(Double.class)){
return "REAL DEFAULT (0)";
}
return "TEXT DEFAULT ''";
}
}
// 数据库管理类,封装
public class DaoManager {
private Context mContext;
//创建数据库的名字
private static final String DB_NAME = DbConstants.DB_NAME;
//多线程中要被共享的使用volatile关键字修饰 GreenDao管理类
private volatile static DaoManager mInstance;
//它里边实际上是保存数据库的对象
private static DaoMaster mDaoMaster;
//创建数据库的工具
private static DbOpenHelper mHelper;
//管理gen里生成的所有的Dao对象里边带有基本的增删改查的方法
private static DaoSession mDaoSession;
private DaoManager() {
}
/**
* 单例模式获得操作数据库对象
*
* @return
*/
public static DaoManager getInstance() {
if (mInstance == null) {
synchronized (DaoManager.class) {
if (mInstance == null) {
mInstance = new DaoManager();
}
}
}
return mInstance;
}
/**
* 初始化上下文创建数据库的时候使用
*/
public void init(Context context) {
this.mContext = context;
}
/**
* 判断是否有存在数据库,如果没有则创建
*
* @return
*/
public DaoMaster getDaoMaster() {
if (mDaoMaster == null) {
mHelper = new DbOpenHelper(mContext,DB_NAME);
mDaoMaster = new DaoMaster(mHelper.getWritableDatabase());
}
return mDaoMaster;
}
/**
* 完成对数据库的添加、删除、修改、查询操作,
*
* @return
*/
public DaoSession getDaoSession() {
if (mDaoSession == null) {
if (mDaoMaster == null) {
mDaoMaster = getDaoMaster();
}
mDaoSession = mDaoMaster.newSession(IdentityScopeType.None);
}
return mDaoSession;
}
/**
* 关闭所有的操作,数据库开启后,使用完毕要关闭
*/
public void closeConnection() {
closeHelper();
closeDaoSession();
}
public void closeHelper() {
if (mHelper != null) {
mHelper.close();
mHelper = null;
}
}
public void closeDaoSession() {
if (mDaoSession != null) {
mDaoSession.clear();
mDaoSession = null;
}
}
}
// 升级时需要的类
public class DbOpenHelper extends DaoMaster.OpenHelper {
public DbOpenHelper(Context context, String name) {
super(context, name);
}
@Override
public void onCreate(Database db) {
super.onCreate(db);
startMigrate(db);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
super.onUpgrade(db, oldVersion, newVersion);
startMigrate(db);
}
private void startMigrate(Database db) {
// 这里升级最好是将所有的表都写上,关键是数据库一定要更新,版本 和 app 同步,
MigrationHelper.migrate(
db,
StuffBeanDao.class,
DepartmentBeanDao.class
);
}
}
文章结尾:
解释:schemaVersion rootProject.ext.publish.versionCode ,就是全局配置,如果依赖库多了方便管理
步骤1:在项目根目录新建 config.gradle 文件
ext {
android = [
compileSdkVersion: 29,
buildToolsVersion: "28.0.0",
minSdkVersion : 19,
targetSdkVersion : 28,
supportLibVersion: "28.0.0"
]
publish = [
applicationId: "com.test.test",
versionCode : 1,
versionName : "1.0"
]
}
步骤2:在项目根目录 build.gradle 文件顶部引入
apply from: "config.gradle"
步骤3:在 App build.gradle 文件中使用
greendao {
// 跟随 versionCode todo 数据库升级视为 App 重大升级,直接升级 VersionCode & VersionName
schemaVersion rootProject.ext.publish.versionCode
}