apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao'
android {
...
greendao{
schemaVersion 1 //数据库版本号,升级数据库需要修改版本号
daoPackage'com.xiaopao.greendao.gen' //一般为app包名+生成文件的文件夹名
targetGenDir 'src/main/java' //自动生成的greendao代码存放路径
}
}
dependencies {
...
compile 'org.greenrobot:greendao:3.2.2' // add library
compile 'org.greenrobot:greendao-generator:3.2.2'
}
②在项目的build.gradle文件内添加内容进行配置
buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.0' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin }
public class DbManager {
public static final boolean ENCRYPTED = true; // 是否加密
private static final String DB_NAME = "Wallet.db"; //数据库名
private static DbManager mDbManager;
private static DaoMaster.DevOpenHelper mDevOpenHelper; // 需要升级时用MySQLiteOpenHelper替换DaoMaster.DevOpenHelper,保障更新版本时保存老数据
private static DaoMaster mDaoMaster;
private static DaoSession mDaoSession;
private Context mContext;
private DbManager(Context context) {
this.mContext = context; // 初始化数据库信息
mDevOpenHelper = new DaoMaster.DevOpenHelper(context, DB_NAME);
getDaoMaster(context);
getDaoSession(context);
}
public static DbManager getInstance(Context context) {
if (null == mDbManager) {
synchronized (DbManager.class) {
if (null == mDbManager) {
mDbManager = new DbManager(context); } } }
return mDbManager;
}
/** * 获取可读数据库 *
* @param context
* @return */
public static SQLiteDatabase getReadableDatabase(Context context) {
if (null == mDevOpenHelper) {
getInstance(context); }
return mDevOpenHelper.getReadableDatabase();
}
/** * 获取可写数据库 *
* @param context
* @return */
public static SQLiteDatabase getWritableDatabase(Context context) {
if (null == mDevOpenHelper) { getInstance(context); }
return mDevOpenHelper.getWritableDatabase();
}
/** * 获取DaoMaster *
* 判断是否存在数据库,如果没有则创建数据库
* @param context
* @return */
public static DaoMaster getDaoMaster(Context context) {
if (null == mDaoMaster) { synchronized (DbManager.class) {
if (null == mDaoMaster) {
//升级对应的代码,请关注下面的一个类
MyOpernHelper helper = new MyOpernHelper(context,DB_NAME,null);
mDaoMaster = new DaoMaster(helper.getWritableDatabase()); } } }
return mDaoMaster;
}
/** * 获取DaoSession *
* @param context
* @return */
public static DaoSession getDaoSession(Context context) {
if (null == mDaoSession) {
synchronized (DbManager.class) {
mDaoSession = getDaoMaster(context).newSession(); } }
return mDaoSession; }
}
如:调用方式可在继承Application的类中初始化时调用:
DbManager.getInstance(this).getDaoMaster(this);
//定义好变量和注解后直接在android studio 中Build Make project就能自动生成get 和set方法以及如下图gen文件夹的内容
@Entity
public class WalletInfo {
@Id
private Long id;
private String name;
}
//调用 DbManager.getDaoSession().getWalletInfoDao() //返回对象的Dao类进行增删改查
private void insertData(WalletInfo walletInfo) {DbManager.getDaoSession(context).getWalletInfoDao().insert(walletInfo);}
private void deleteAll(){ DbManager.getDaoSession(context).getWalletInfoDao().deleteAll();}
//这里有个坑得注意:需保证自增长id不为空
private void updateData(WalletInfo walletInfo){DbManager.getDaoSession(context).getWalletInfoDao().update(walletInfo);}
private List selectData(){return DbManager.getDaoSession(context).getWalletInfoDao().loadAll();}
// 自定义openHelper类,继承DaoMaster.DevOpenHelper,重写onUpgrade(Database db, int oldVersion, int newVersion)方法,在该方法中使用MigrationHelper进行数据库升级以及数据迁移。
public class MySQLiteOpenHelper extends DaoMaster.OpenHelper{
public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
//把需要管理的数据库表DAO作为最后一个参数传入到方法中(只传更改过的表,新增的不用)
MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
@Override
public void onCreateAllTables(Database db, boolean ifNotExists) {
DaoMaster.createAllTables(db, ifNotExists);
}
@Override
public void onDropAllTables(Database db, boolean ifExists) {
DaoMaster.dropAllTables(db, ifExists);
}
}, WalletInfoDao.class);//可添加多个改动过的表对象Dao
}
}
// 网上有不少MigrationHelper的源码,这里采用的是https://github.com/yuweiguocn/GreenDaoUpgradeHelper中的MigrationHelper
/**
* Created by xiaopao on 2018/10/25.
* 解决greenDao的数据库更新升级(不删除原数据)
*/
public class MigrationHelper {
public static boolean DEBUG = false;
private static String TAG = "MigrationHelper";
private static final String SQLITE_MASTER = "sqlite_master";
private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";
private static WeakReference weakListener;
public interface ReCreateAllTableListener{
void onCreateAllTables(Database db, boolean ifNotExists);
void onDropAllTables(Database db, boolean ifExists);
}
public static void migrate(SQLiteDatabase db, Class extends AbstractDao, ?>>... daoClasses) {
printLog("【The Old Database Version】" + db.getVersion());
Database database = new StandardDatabase(db);
migrate(database, daoClasses);
}
public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class extends AbstractDao, ?>>... daoClasses) {
weakListener = new WeakReference<>(listener);
migrate(db, daoClasses);
}
public static void migrate(Database database, ReCreateAllTableListener listener, Class extends AbstractDao, ?>>... daoClasses) {
weakListener = new WeakReference<>(listener);
migrate(database, daoClasses);
}
public static void migrate(Database database, Class extends AbstractDao, ?>>... daoClasses) {
printLog("【Generate temp table】start");
generateTempTables(database, daoClasses);
printLog("【Generate temp table】complete");
ReCreateAllTableListener listener = null;
if (weakListener != null) {
listener = weakListener.get();
}
if (listener != null) {
listener.onDropAllTables(database, true);
printLog("【Drop all table by listener】");
listener.onCreateAllTables(database, false);
printLog("【Create all table by listener】");
} else {
dropAllTables(database, true, daoClasses);
createAllTables(database, false, daoClasses);
}
printLog("【Restore data】start");
restoreData(database, daoClasses);
printLog("【Restore data】complete");
}
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)) {
printLog("【New Table】" + tableName);
continue;
}
try {
tempTableName = daoConfig.tablename.concat("_TEMP");
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
db.execSQL(dropTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
printLog("【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 dropAllTables(Database db, boolean ifExists, @NonNull Class extends AbstractDao, ?>>... daoClasses) {
reflectMethod(db, "dropTable", ifExists, daoClasses);
printLog("【Drop all table by reflect】");
}
private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class extends AbstractDao, ?>>... daoClasses) {
reflectMethod(db, "createTable", ifNotExists, daoClasses);
printLog("【Create all table by reflect】");
}
/**
* dao class already define the sql exec method, so just invoke it
*/
private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Class extends AbstractDao, ?>>... daoClasses) {
if (daoClasses.length < 1) {
return;
}
try {
for (Class cls : daoClasses) {
Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
method.invoke(null, db, isExists);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
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 newTableInfos = TableInfo.getTableInfo(db, tableName);
List tempTableInfos = TableInfo.getTableInfo(db, tempTableName);
ArrayList selectColumns = new ArrayList<>(newTableInfos.size());
ArrayList intoColumns = new ArrayList<>(newTableInfos.size());
for (TableInfo tableInfo : tempTableInfos) {
if (newTableInfos.contains(tableInfo)) {
String column = '`' + tableInfo.name + '`';
intoColumns.add(column);
selectColumns.add(column);
}
}
// NOT NULL columns list
for (TableInfo tableInfo : newTableInfos) {
if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) {
String column = '`' + tableInfo.name + '`';
intoColumns.add(column);
String value;
if (tableInfo.dfltValue != null) {
value = "'" + tableInfo.dfltValue + "' AS ";
} else {
value = "'' AS ";
}
selectColumns.add(value + column);
}
}
if (intoColumns.size() != 0) {
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("REPLACE INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", intoColumns));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", selectColumns));
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
printLog("【Restore data】 to " + tableName);
}
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(dropTableStringBuilder.toString());
printLog("【Drop temp table】" + tempTableName);
} catch (SQLException e) {
Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e);
}
}
}
private static List getColumns(Database db, String tableName) {
List 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;
}
private static void printLog(String info){
if(DEBUG){
Log.d(TAG, info);
}
}
private static class TableInfo {
int cid;
String name;
String type;
boolean notnull;
String dfltValue;
boolean pk;
@Override
public boolean equals(Object o) {
return this == o
|| o != null
&& getClass() == o.getClass()
&& name.equals(((TableInfo) o).name);
}
@Override
public String toString() {
return "TableInfo{" +
"cid=" + cid +
", name='" + name + '\'' +
", type='" + type + '\'' +
", notnull=" + notnull +
", dfltValue='" + dfltValue + '\'' +
", pk=" + pk +
'}';
}
private static List getTableInfo(Database db, String tableName) {
String sql = "PRAGMA table_info(" + tableName + ")";
printLog(sql);
Cursor cursor = db.rawQuery(sql, null);
if (cursor == null)
return new ArrayList<>();
TableInfo tableInfo;
List tableInfos = new ArrayList<>();
while (cursor.moveToNext()) {
tableInfo = new TableInfo();
tableInfo.cid = cursor.getInt(0);
tableInfo.name = cursor.getString(1);
tableInfo.type = cursor.getString(2);
tableInfo.notnull = cursor.getInt(3) == 1;
tableInfo.dfltValue = cursor.getString(4);
tableInfo.pk = cursor.getInt(5) == 1;
tableInfos.add(tableInfo);
// printLog(tableName + ":" + tableInfo);
}
cursor.close();
return tableInfos;
}
}
}
6、加密
注意: 加密需要添加sqlcipher库,而该库体积庞大,使用后apk大小会增加大概5M,所以如果你的应用对安全性要求不高,不建议使用。
加密数据库的步骤:
第一步:
Module下的build.gradle中添加一个库依赖,用于数据库加密。
compile 'net.zetetic:android-database-sqlcipher:3.5.7'//使用加密数据库时需要添加
第二步:
获取DaoSession的过程中,使用getEncryptedWritableDb(“你的密码”)来获取操作的数据库,而不是getWritableDatabase()。
mSQLiteOpenHelper = new MySQLiteOpenHelper(MyApplication.getInstance(), DB_NAME, null);//建库
mDaoMaster = new DaoMaster(mSQLiteOpenHelper.getEncryptedWritableDb("你的密码"));//加密
//mDaoMaster = new DaoMaster(mSQLiteOpenHelper.getWritableDatabase());
mDaoSession = mDaoMaster.newSession();
第三步:
使用上面步骤得到的DaoSession进行具体的数据表操作。
如果运行后报无法加载有关so库的异常,请对项目进行clean和rebuild。
7、混淆
在proguard-rules.pro文件中添加以下内容进行混淆配置
# greenDAO开始
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use RxJava:
-dontwarn rx.**
# greenDAO结束
# 如果按照上面介绍的加入了数据库加密功能,则需添加一下配置
#sqlcipher数据库加密开始
-keep class net.sqlcipher.** {*;}
-keep class net.sqlcipher.database.** {*;}
#sqlcipher数据库加密结束
8、在线查看本地数据库(注:仅适合调试时用)
在android stuido 的build.gradle的dependencies 中添加:debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
然后在浏览器地址栏输入:你调试的手机IP:8080,即可查看了!
感谢如下参考链接:
https://blog.csdn.net/qq_35956194/article/details/79167897#commentBox
https://www.jianshu.com/p/628ecca143e8