Android GreenDao 使用总结(包括模型生成、增删改查、修改存储路径、数据库更新升级和加解密数据库)

本贴最后更新于 2685 天前,其中的信息可能已经时移俗易

目录:

前言

导入依赖和 Schema 设置

一、数据库模型生成及读取操作

二、修改数据库文件路径

三、获取加密的数据库

四、数据库升级又不删除数据

五、总结

前言:

在Android开发中,或多或少总要接触SQLite。然而在使用它时,我们往往需要做许多额外的工作,像编写 SQL 语句与解析查询结果等。所以,适用于 Android 的ORM 框架也就孕育而生了,现在市面上主流的框架有 OrmLite、SugarORM、Active Android、Realm 与 GreenDAO。而greenDAO号称是速度最快的ORM(见官网)。简单的讲,greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。

下面,我将详解地介绍如何在 Android Studio 上使用 greenDAO,并结合代码总结一些使用过程中的心得。

导入依赖和 Schema 设置:

导入依赖和Schema参数设置详细内容可参见官网和如下网址:[http://www.jianshu.com/p/4e6d72e7f57a](http://www.jianshu.com/p/4e6d72e7f57a)

** **我的 Schema 设置如下:

[plain] view plain copy

  1. greendao{
  2. schemaVersion 1
  3. targetGenDir 'src/main/java'
  4. }

一、数据库模型生成及读取操作:

1、greenDao 支持的数据模型生成方式包括以下两种:

(1)编写 greendaoGenerator 的纯 Java 类库,以生成 DaoMaster、DaoSession、bean 和 beanDao 等;

可参见:[http://www.open-open.com/lib/view/open1438065400878.html](http://www.open-open.com/lib/view/open1438065400878.html) 注:greenDao 3.0版本以下只能采用这种方式。在升级3.0以后,因支持了注释方式,该方式不再推荐。

(2)利用注释的方式,生成 DaoMaster、DaoSession、bean 和 beanDao 等;

2、下文仅就注释方式生成流程进行简单的介绍,详细的讲解可参见 http://www.jianshu.com/p/4e6d72e7f57a

首先,新建datamodel包,用以包含DaoMaster、DaoSession、bean和beanDao等。 然后新建Area实体类,代码如下:

[java] view plain copy

  1. @Entity

  2. public class Area {

  3. @Id
  4. private String AreaCode;
  5. private String AreaName;
  6. }

    最后,Build->Make Module 'app',即可自动生成 DaoMaster、DaoSession、Area 和 AreaDao。此时 Area 实体类的代码如下:

[java] view plain copy

  1. @Entity

  2. public class Area {

  3. @Id
  4. private String AreaCode;
  5. private String AreaName;
  6. @Generated(hash = 262290694)
  7. public Area(String AreaCode, String AreaName) {
  8. this.AreaCode = AreaCode;
  9. this.AreaName = AreaName;
  10. }
  11. @Generated(hash = 179626505)
  12. public Area() {
  13. }
  14. public String getAreaCode() {
  15. return this.AreaCode;
  16. }
  17. public void setAreaCode(String AreaCode) {
  18. this.AreaCode = AreaCode;
  19. }
  20. public String getAreaName() {
  21. return this.AreaName;
  22. }
  23. public void setAreaName(String AreaName) {
  24. this.AreaName = AreaName;
  25. }
  26. }

    添加其他实体类的方法与 Area 一样。需要注意的是,不要手动修改****DaoMaster、DaoSession、bean 和 beanDao 的代码,因为每一次编译项目,都会重新生成一次 DaoMaster、DaoSession、bean 和 beanDao。如果修改的话,就会被覆盖掉。

    为了便于数据的读取和添加,新建 GreenDaoHelper 辅助类,代码如下:

[java] view plain copy

  1. public class GreenDaoHelper extends Application {

  2. private GreenDaoHelper Instance;
  3. private static DaoMaster daoMaster;
  4. private static DaoSession daoSession;
  5. public GreenDaoHelper getInstance() {
  6. if (Instance == null) {
  7. Instance = this;
  8. }
  9. return Instance;
  10. }
  11. /**
  12. * 获取DaoMaster
  13. *
  14. * @param context
  15. * @return
  16. */
  17. public static DaoMaster getDaoMaster(Context context) {
  18. if (daoMaster == null) {
  19. try{
  20. DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(context,"test.db",null);
  21. daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
  22. }catch (Exception e){
  23. e.printStackTrace();
  24. }
  25. }
  26. return daoMaster;
  27. }
  28. /**
  29. * 获取DaoSession对象
  30. *
  31. * @param context
  32. * @return
  33. */
  34. public static DaoSession getDaoSession(Context context) {
  35. if (daoSession == null) {
  36. if (daoMaster == null) {
  37. getDaoMaster(context);
  38. }
  39. daoSession = daoMaster.newSession();
  40. }
  41. return daoSession;
  42. }
  43. }

    在读写数据库之前,要添加读写权限:

[html] view plain copy

  1. 在 MainActivity.java 中添加读写代码:

[java] view plain copy

  1. public class MainActivity extends AppCompatActivity {

  2. private TextView textview;
  3. private DaoSession session;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8. textview=(TextView)findViewById(R.id.textview);
  9. session = GreenDaoHelper.getDaoSession(this);
  10. session.getAreaDao().deleteAll();//清空所有记录
  11. //添加记录
  12. Area area = new Area("01","北京");
  13. Area area1 = new Area("02","天津");
  14. session.getAreaDao().insert(area);
  15. session.getAreaDao().insert(area1);
  16. //查询记录
  17. StringBuilder stringBuilder = new StringBuilder();
  18. List areas = session.getAreaDao().loadAll();
  19. for (int i = 0,n = areas.size();i
  20. stringBuilder.append("地区编码:").append(areas.get(i).getAreaCode())
  21. .append(",地区名称:").append(areas.get(i).getAreaName()).append("\n");
  22. }
  23. textview.setText(stringBuilder);
  24. }
  25. }

    运行结果如下图所示:

![](http://img.blog.csdn.net/20161230145836894)

二、修改数据库文件路径:

默认情况下,新创建的数据存储在data的包名目录下,设备如果不root的话,是无法查看SQLite数据库文件的。而实际应用中,我们往往需要copy数据库,或借用第三方工具查阅或编辑数据库内容。此时我们可以通过重写Context的getDatabasePath(String name)、openOrCreateDatabase(String name, **int** mode, CursorFactory factory)、openOrCreateDatabase(String name, **int** mode, CursorFactory factory, DatabaseErrorHandler errorHandler)等三个方法来修改SQLite文件的存储路径。 通过查询资料,发现[http://blog.csdn.net/chenzhenlindx/article/details/39183691](http://blog.csdn.net/chenzhenlindx/article/details/39183691)中的内容基本符合我们的需求。但是博主是在DaoMaster中重写方法的。通过上文我们知道,DaoMaster的代码是不能修改的。因此,我们可以将重写的方法放到GreenDaoHelper中去。代码如下:

[java] view plain copy

  1. public class GreenDaoHelper extends Application {
  2. private GreenDaoHelper Instance;
  3. private static DaoMaster daoMaster;
  4. private static DaoSession daoSession;
  5. public GreenDaoHelper getInstance() {
  6. if (Instance == null) {
  7. Instance = this;
  8. }
  9. return Instance;
  10. }
  11. /**
  12. * 获取DaoMaster
  13. *
  14. * @param context
  15. * @return
  16. */
  17. public static DaoMaster getDaoMaster(Context context) {
  18. if (daoMaster == null) {
  19. try{
  20. ContextWrapper wrapper = new ContextWrapper(context) {
  21. /**
  22. * 获得数据库路径,如果不存在,则创建对象对象
  23. *
  24. * @param name
  25. */
  26. @Override
  27. public File getDatabasePath(String name) {
  28. // 判断是否存在sd卡
  29. boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState());
  30. if (!sdExist) {// 如果不存在,
  31. Log.e("SD卡管理:", "SD卡不存在,请加载SD卡");
  32. return null;
  33. } else {// 如果存在
  34. // 获取sd卡路径
  35. String dbDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
  36. dbDir += "/Android";// 数据库所在目录
  37. String dbPath = dbDir + "/" + name;// 数据库路径
  38. // 判断目录是否存在,不存在则创建该目录
  39. File dirFile = new File(dbDir);
  40. if (!dirFile.exists())
  41. dirFile.mkdirs();
  42. // 数据库文件是否创建成功
  43. boolean isFileCreateSuccess = false;
  44. // 判断文件是否存在,不存在则创建该文件
  45. File dbFile = new File(dbPath);
  46. if (!dbFile.exists()) {
  47. try {
  48. isFileCreateSuccess = dbFile.createNewFile();// 创建文件
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. } else
  53. isFileCreateSuccess = true;
  54. // 返回数据库文件对象
  55. if (isFileCreateSuccess)
  56. return dbFile;
  57. else
  58. return super.getDatabasePath(name);
  59. }
  60. }
  61. /**
  62. * 重载这个方法,是用来打开SD卡上的数据库的,android 2.3及以下会调用这个方法。
  63. *
  64. * @param name
  65. * @param mode
  66. * @param factory
  67. */
  68. @Override
  69. public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {
  70. return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
  71. }
  72. /**
  73. * Android 4.0会调用此方法获取数据库。
  74. *
  75. * @see android.content.ContextWrapper#openOrCreateDatabase(java.lang.String,
  76. * int,
  77. * android.database.sqlite.SQLiteDatabase.CursorFactory,
  78. * android.database.DatabaseErrorHandler)
  79. * @param name
  80. * @param mode
  81. * @param factory
  82. * @param errorHandler
  83. */
  84. @Override
  85. public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
  86. return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
  87. }
  88. };
  89. DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);
  90. daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
  91. }catch (Exception e){
  92. e.printStackTrace();
  93. }
  94. }
  95. return daoMaster;
  96. }
  97. /**
  98. * 获取DaoSession对象
  99. *
  100. * @param context
  101. * @return
  102. */
  103. public static DaoSession getDaoSession(Context context) {
  104. if (daoSession == null) {
  105. if (daoMaster == null) {
  106. getDaoMaster(context);
  107. }
  108. daoSession = daoMaster.newSession();
  109. }
  110. return daoSession;
  111. }
  112. }
此时,再运行上述代码,就会在Android目录下发现我们的test.db文件。通过第三方工具,即可查看我们的数据库内容。下图是我用手机端的SqliteLookup工具查看到的数据库内容:

三、获取加密的数据库:

修改GreenDaoHelper.java,通过调用DaoMaster.OpenHelper类的getEncryptedWritableDb(password)或者getEncryptedReadableDb(password)方法,即可获取加密的数据库。

[java] view plain copy

  1. public static DaoMaster getDaoMaster(Context context) {

  2. if (daoMaster == null) {
  3. try{
  4. ContextWrapper wrapper = new ContextWrapper(context) {
  5. ...
  6. };
  7. DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);
  8. daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
  9. //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
  10. //daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
  11. }catch (Exception e){
  12. e.printStackTrace();
  13. }
  14. }
  15. return daoMaster;
  16. }

    此时,若要解密或重新加密数据库,可参考博客《利用 SQLCipher 加解密数据库(包括加解密已有的数据库)》

四、数据库升级又不删除数据:

在实际开发的过程中,数据库的结构可能会有所改变。而使用 DevOpenHelper 每次升级数据库时,表都会删除重建。因此,实际使用中需要建立类继承 DaoMaster.OpenHelper,实现 onUpgrade()方法。通过查询资料,**对未加密的数据库**,推荐使用升级辅助库 GreenDaoUpgradeHelper(可参见[https://github.com/yuweiguocn/GreenDaoUpgradeHelper/blob/master/README_CH.md](https://github.com/yuweiguocn/GreenDaoUpgradeHelper/blob/master/README_CH.md))。该库通过 MigrationHelper在删表重建的过程中,使用临时表保存数据并还原。 示例程序直接导入MigrationHelper.java源码。同时修改GreenDaoHelper.java文件,新建一个继承自DaoMaster.OpenHelper的内部类MySQLiteOpenHelper。具体代码如下:

[java] view plain copy

  1. public class GreenDaoHelper extends Application {

  2. private GreenDaoHelper Instance;
  3. private static DaoMaster daoMaster;
  4. private static DaoSession daoSession;
  5. public GreenDaoHelper getInstance() {
  6. if (Instance == null) {
  7. Instance = this;
  8. }
  9. return Instance;
  10. }
  11. /**
  12. * 获取DaoMaster
  13. *
  14. * @param context
  15. * @return
  16. */
  17. public static DaoMaster getDaoMaster(Context context) {
  18. if (daoMaster == null) {
  19. try{
  20. ContextWrapper wrapper = new ContextWrapper(context) {
  21. ...
  22. };
  23. DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);
  24. //daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
  25. //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
  26. daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
  27. }catch (Exception e){
  28. e.printStackTrace();
  29. }
  30. }
  31. return daoMaster;
  32. }
  33. /**
  34. * 获取DaoSession对象
  35. *
  36. * @param context
  37. * @return
  38. */
  39. public static DaoSession getDaoSession(Context context) {
  40. if (daoSession == null) {
  41. if (daoMaster == null) {
  42. getDaoMaster(context);
  43. }
  44. daoSession = daoMaster.newSession();
  45. }
  46. return daoSession;
  47. }
  48. private static class MySQLiteOpenHelper extends DaoMaster.OpenHelper {
  49. public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
  50. super(context, name, factory);
  51. }
  52. private static final String UPGRADE="upgrade";
  53. @Override
  54. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  55. MigrationHelper.migrate(db,AreaDao.class);
  56. Log.e(UPGRADE,"upgrade run success");
  57. }
  58. }
  59. }

    另外添加一个 People 实体类,并修改 schemaVersion 为更高的版本号,然后 Build->Make Module 'app',生成新的模型类。修改 OnUpgrade 方法如下:

[java] view plain copy

  1. @Override

  2. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

  3. MigrationHelper.migrate(db,AreaDao.class, PeopleDao.class);
  4. Log.e(UPGRADE,"upgrade run success");
  5. }

    此时,运行程序。虽然程序成功启动,但是报如下错误:

[java] view plain copy

  1. 12-30 10:02:12.503 6312-6312/com.wjk.greendaoexample E/SQLiteLog: (1) no such table: PEOPLE

  2. 12-30 10:02:12.508 6312-6312/com.wjk.greendaoexample E/MigrationHelper: 【Failed to generate temp table】PEOPLE_TEMP android.database.sqlite.SQLiteException: no such table: PEOPLE (code 1): , while compiling: CREATE TEMPORARY TABLE PEOPLE_TEMP AS SELECT * FROM PEOPLE;

    通过阅读源码发现,程序根据传入的 beanDao 对所有 bean 表都创建了临时表,并从 bean 表复制数据到 bean_temp 表中。而此时,People 实体是新创建的,数据库中并没有这个表,因此报上面的错误。此时,我们需对源码进行修改,仅对数据库中已有的表创建临时表并保存数据。此外,源码中是按字段恢复数据,为方便起见,本程序修改为全表查询恢复。代码如下:

[java] view plain copy

  1. public final class MigrationHelper {
  2. public static boolean DEBUG = false;
  3. private static String TAG = "MigrationHelper";
  4. private static List tablenames = new ArrayList<>();
  5. public static List getTables(SQLiteDatabase db){
  6. List tables = new ArrayList<>();
  7. Cursor cursor = db.rawQuery("select name from sqlite_master where type='table' order by name", null);
  8. while(cursor.moveToNext()){
  9. //遍历出表名
  10. tables.add(cursor.getString(0));
  11. }
  12. cursor.close();
  13. return tables;
  14. }
  15. public static void migrate(SQLiteDatabase db, Classextends AbstractDao>... daoClasses) {
  16. Database database = new StandardDatabase(db);
  17. if (DEBUG) {
  18. Log.d(TAG, "【Database Version】" + db.getVersion());
  19. Log.d(TAG, "【Generate temp table】start");
  20. }
  21. tablenames=getTables(db);
  22. generateTempTables(database, daoClasses);
  23. if (DEBUG) {
  24. Log.d(TAG, "【Generate temp table】complete");
  25. }
  26. dropAllTables(database, true, daoClasses);
  27. createAllTables(database, false, daoClasses);
  28. if (DEBUG) {
  29. Log.d(TAG, "【Restore data】start");
  30. }
  31. restoreData(database, daoClasses);
  32. if (DEBUG) {
  33. Log.d(TAG, "【Restore data】complete");
  34. }
  35. }
  36. private static void generateTempTables(Database db, Classextends AbstractDao>... daoClasses) {
  37. for (int i = 0; i < daoClasses.length; i++) {
  38. String tempTableName = null;
  39. try {
  40. DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
  41. if(!tablenames.contains(daoConfig.tablename)){//如果数据库中没有该表,则继续下次循环
  42. continue;
  43. }
  44. String tableName = daoConfig.tablename;
  45. tempTableName = daoConfig.tablename.concat("_TEMP");
  46. StringBuilder dropTableStringBuilder = new StringBuilder();
  47. dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
  48. db.execSQL(dropTableStringBuilder.toString());
  49. StringBuilder insertTableStringBuilder = new StringBuilder();
  50. insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
  51. insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
  52. db.execSQL(insertTableStringBuilder.toString());
  53. if (DEBUG) {
  54. Log.d(TAG, "【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
  55. Log.d(TAG, "【Generate temp table】" + tempTableName);
  56. }
  57. } catch (SQLException e) {
  58. Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
  59. }
  60. }
  61. }
  62. private static String getColumnsStr(DaoConfig daoConfig) {
  63. if (daoConfig == null) {
  64. return "no columns";
  65. }
  66. StringBuilder builder = new StringBuilder();
  67. for (int i = 0; i < daoConfig.allColumns.length; i++) {
  68. builder.append(daoConfig.allColumns[i]);
  69. builder.append(",");
  70. }
  71. if (builder.length() > 0) {
  72. builder.deleteCharAt(builder.length() - 1);
  73. }
  74. return builder.toString();
  75. }
  76. private static void dropAllTables(Database db, boolean ifExists, @NonNull Classextends AbstractDao>... daoClasses) {
  77. reflectMethod(db, "dropTable", ifExists, daoClasses);
  78. if (DEBUG) {
  79. Log.d(TAG, "【Drop all table】");
  80. }
  81. }
  82. private static void createAllTables(Database db, boolean ifNotExists, @NonNull Classextends AbstractDao>... daoClasses) {
  83. reflectMethod(db, "createTable", ifNotExists, daoClasses);
  84. if (DEBUG) {
  85. Log.d(TAG, "【Create all table】");
  86. }
  87. }
  88. /**
  89. * dao class already define the sql exec method, so just invoke it
  90. */
  91. private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Classextends AbstractDao>... daoClasses) {
  92. if (daoClasses.length < 1) {
  93. return;
  94. }
  95. try {
  96. for (Class cls : daoClasses) {
  97. Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
  98. method.invoke(null, db, isExists);
  99. }
  100. } catch (NoSuchMethodException e) {
  101. e.printStackTrace();
  102. } catch (InvocationTargetException e) {
  103. e.printStackTrace();
  104. } catch (IllegalAccessException e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. private static void restoreData(Database db, Classextends AbstractDao>... daoClasses) {
  109. for (int i = 0; i < daoClasses.length; i++) {
  110. String tempTableName = null;
  111. try {
  112. DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
  113. String tableName = daoConfig.tablename;
  114. if(!tablenames.contains(tableName)){
  115. continue;
  116. }
  117. tempTableName = daoConfig.tablename.concat("_TEMP");
  118. StringBuilder insertTableStringBuilder = new StringBuilder();
  119. insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" SELECT * FROM ").append(tempTableName).append(";");
  120. db.execSQL(insertTableStringBuilder.toString());
  121. if (DEBUG) {
  122. Log.d(TAG, "【Restore data】 to " + tableName);
  123. }
  124. StringBuilder dropTableStringBuilder = new StringBuilder();
  125. dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName);
  126. db.execSQL(dropTableStringBuilder.toString());
  127. if (DEBUG) {
  128. Log.d(TAG, "【Drop temp table】" + tempTableName);
  129. }
  130. } catch (SQLException e) {
  131. Log.e(TAG, "【Failed to restore data from temp table (probably new table)】" + tempTableName, e);
  132. }
  133. }
  134. }
  135. }
此时,新建一个实体类Product,修改版本号,同时修改OnUpgrade方法 ,再次运行程序,则成功运行。 ** 注意:****MigrationHelper.migrate(),暂时只接收 SQLiteDatabase ,不接收 Database,且对****加密的****数据库是无效的**。而实际应用中,由于数据的重要性,数据库往往是必须要加密的。因此,**对于加密的****数据库该如何进行更新支持呢?** 具体思路如下,首先分析MigrationHelper.migrate()为什么不支持对加密的数据库的更新,然后再找出对应的解决方案。

(1)MigrationHelper.migrate()为什么不支持对加密的数据库的更新

通过加断点调试发现,数据库更新时并没有走MySQLiteOpenHelper的onUpgrade()方法,而是走的DatabaseOpenHelper抽象类里的EncryptedHelper内部类的onUpgrade()方法。如图所示:

EncryptedHelper内部类的onUpgrade()方法调用的是DatabaseOpenHelper抽象类本身的onUpgrade()方法,但DatabaseOpenHelper抽象类本身的onUpgrade()方法默认是啥也不执行的,如下图所示。因此,MigrationHelper.migrate()为什么不支持对加密的数据库的更新也就显而易见了。

(2)对加密的数据库的更新支持的解决方案

此时,可以通过修改greenDao的源码,在EncryptedHelper内部类的onUpgrade()方法中添加对MigrationHelper.migrate()的调用。 但,如果我不想修改源码,该怎么解决呢?默认情况下,获取和更新加密的数据库,调用的是DatabaseOpenHelper抽象类本身的getEncryptedWritableDb(String password)和onUpgrade()方法。此时,我们可以在GreenDaoHelper中定义一个新的类MyEncryptedSQLiteOpenHelper继承自DaoMaster.OpenHelper,并在这个类中对这两个方法进行重写,同时在内部自定义一个net.sqlcipher.database.SQLiteOpenHelper的继承类,以代替DatabaseOpenHelper抽象类里的EncryptedHelper内部类。 在添加代码之前,要先添加对sqlcipher的依赖,如下:

[html] view plain copy

  1. compile 'net.zetetic:android-database-sqlcipher:3.5.4@aar'

    具体代码如下:

[java] view plain copy

  1. public class GreenDaoHelper extends Application {

  2. ......
  3. public static DaoMaster getDaoMaster(Context context) {
  4. if (daoMaster == null) {
  5. try{
  6. ContextWrapper wrapper = new ContextWrapper(context) {
  7. ...
  8. };
  9. //DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);
  10. MyEncryptedSQLiteOpenHelper helper = new MyEncryptedSQLiteOpenHelper(wrapper,"test.db",null);
  11. daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
  12. //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
  13. //daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
  14. }catch (Exception e){
  15. e.printStackTrace();
  16. }
  17. }
  18. return daoMaster;
  19. }
  20. ......
  21. private static class MyEncryptedSQLiteOpenHelper extends DaoMaster.OpenHelper {
  22. private final Context context;
  23. private final String name;
  24. private final int version = DaoMaster.SCHEMA_VERSION;
  25. private boolean loadSQLCipherNativeLibs = true;
  26. public MyEncryptedSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
  27. super(context, name, factory);
  28. this.context=context;
  29. this.name=name;
  30. }
  31. private static final String UPGRADE="upgrade";
  32. @Override
  33. public void onUpgrade(Database db, int oldVersion, int newVersion) {
  34. EncryptedMigrationHelper.migrate((EncryptedDatabase) db,AreaDao.class, PeopleDao.class, ProductDao.class);
  35. Log.e(UPGRADE,"upgrade run success");
  36. }
  37. @Override
  38. public Database getEncryptedWritableDb(String password) {
  39. MyEncryptedHelper encryptedHelper = new MyEncryptedHelper(context,name,version,loadSQLCipherNativeLibs);
  40. return encryptedHelper.wrap(encryptedHelper.getReadableDatabase(password));
  41. }
  42. private class MyEncryptedHelper extends net.sqlcipher.database.SQLiteOpenHelper {
  43. public MyEncryptedHelper(Context context, String name, int version, boolean loadLibs) {
  44. super(context, name, null, version);
  45. if (loadLibs) {
  46. net.sqlcipher.database.SQLiteDatabase.loadLibs(context);
  47. }
  48. }
  49. @Override
  50. public void onCreate(net.sqlcipher.database.SQLiteDatabase db) {
  51. MyEncryptedSQLiteOpenHelper.this.onCreate(wrap(db));
  52. }
  53. @Override
  54. public void onUpgrade(net.sqlcipher.database.SQLiteDatabase db, int oldVersion, int newVersion) {
  55. MyEncryptedSQLiteOpenHelper.this.onUpgrade(wrap(db), oldVersion, newVersion);
  56. }
  57. @Override
  58. public void onOpen(net.sqlcipher.database.SQLiteDatabase db) {
  59. MyEncryptedSQLiteOpenHelper.this.onOpen(wrap(db));
  60. }
  61. protected Database wrap(net.sqlcipher.database.SQLiteDatabase sqLiteDatabase) {
  62. return new EncryptedDatabase(sqLiteDatabase);
  63. }
  64. }
  65. }
  66. }

    上述代码中引用了 EncryptedMigrationHelper.java 类。该类与 MigrationHelper.java 类似,只不过将 android.database.sqlite.SQLiteDatabase 替换为 net.sqlcipher.database.SQLiteDatabase,同时对代码做了微小的改动。EncryptedMigrationHelper.java 类的代码如下:

[java] view plain copy

  1. public class EncryptedMigrationHelper {
  2. public static boolean DEBUG = true;
  3. private static String TAG = "UpgradeHelper";
  4. private static List tablenames = new ArrayList<>();
  5. public static List getTables(SQLiteDatabase db){
  6. List tables = new ArrayList<>();
  7. Cursor cursor = db.rawQuery("select name from sqlite_master where type='table' order by name", null);
  8. while(cursor.moveToNext()){
  9. //遍历出表名
  10. tables.add(cursor.getString(0));
  11. }
  12. cursor.close();
  13. return tables;
  14. }
  15. public static void migrate(EncryptedDatabase db, Classextends AbstractDao>... daoClasses) {
  16. Database database = db;
  17. if (DEBUG) {
  18. Log.d(TAG, "【Database Version】" + db.getSQLiteDatabase().getVersion());
  19. Log.d(TAG, "【Generate temp table】start");
  20. }
  21. tablenames=getTables(db.getSQLiteDatabase());
  22. generateTempTables(database, daoClasses);
  23. if (DEBUG) {
  24. Log.d(TAG, "【Generate temp table】complete");
  25. }
  26. dropAllTables(database, true, daoClasses);
  27. createAllTables(database, false, daoClasses);
  28. if (DEBUG) {
  29. Log.d(TAG, "【Restore data】start");
  30. }
  31. restoreData(database, daoClasses);
  32. if (DEBUG) {
  33. Log.d(TAG, "【Restore data】complete");
  34. }
  35. }
  36. private static void generateTempTables(Database db, Classextends AbstractDao>... daoClasses) {
  37. for (int i = 0; i < daoClasses.length; i++) {
  38. String tempTableName = null;
  39. try {
  40. DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
  41. if(!tablenames.contains(daoConfig.tablename)){
  42. continue;
  43. }
  44. String tableName = daoConfig.tablename;
  45. tempTableName = daoConfig.tablename.concat("_TEMP");
  46. StringBuilder dropTableStringBuilder = new StringBuilder();
  47. dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
  48. db.execSQL(dropTableStringBuilder.toString());
  49. StringBuilder insertTableStringBuilder = new StringBuilder();
  50. insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
  51. insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
  52. db.execSQL(insertTableStringBuilder.toString());
  53. if (DEBUG) {
  54. Log.d(TAG, "【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
  55. Log.d(TAG, "【Generate temp table】" + tempTableName);
  56. }
  57. } catch (SQLException e) {
  58. Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
  59. }
  60. }
  61. }
  62. private static String getColumnsStr(DaoConfig daoConfig) {
  63. if (daoConfig == null) {
  64. return "no columns";
  65. }
  66. StringBuilder builder = new StringBuilder();
  67. for (int i = 0; i < daoConfig.allColumns.length; i++) {
  68. builder.append(daoConfig.allColumns[i]);
  69. builder.append(",");
  70. }
  71. if (builder.length() > 0) {
  72. builder.deleteCharAt(builder.length() - 1);
  73. }
  74. return builder.toString();
  75. }
  76. private static void dropAllTables(Database db, boolean ifExists, @NonNull Classextends AbstractDao>... daoClasses) {
  77. reflectMethod(db, "dropTable", ifExists, daoClasses);
  78. if (DEBUG) {
  79. Log.d(TAG, "【Drop all table】");
  80. }
  81. }
  82. private static void createAllTables(Database db, boolean ifNotExists, @NonNull Classextends AbstractDao>... daoClasses) {
  83. reflectMethod(db, "createTable", ifNotExists, daoClasses);
  84. if (DEBUG) {
  85. Log.d(TAG, "【Create all table】");
  86. }
  87. }
  88. /**
  89. * dao class already define the sql exec method, so just invoke it
  90. */
  91. private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Classextends AbstractDao>... daoClasses) {
  92. if (daoClasses.length < 1) {
  93. return;
  94. }
  95. try {
  96. for (Class cls : daoClasses) {
  97. Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
  98. method.invoke(null, db, isExists);
  99. }
  100. } catch (NoSuchMethodException e) {
  101. e.printStackTrace();
  102. } catch (InvocationTargetException e) {
  103. e.printStackTrace();
  104. } catch (IllegalAccessException e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. private static void restoreData(Database db, Classextends AbstractDao>... daoClasses) {
  109. for (int i = 0; i < daoClasses.length; i++) {
  110. String tempTableName = null;
  111. try {
  112. DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
  113. String tableName = daoConfig.tablename;
  114. if(!tablenames.contains(tableName)){
  115. continue;
  116. }
  117. tempTableName = daoConfig.tablename.concat("_TEMP");
  118. StringBuilder insertTableStringBuilder = new StringBuilder();
  119. insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" SELECT * FROM ").append(tempTableName).append(";");
  120. db.execSQL(insertTableStringBuilder.toString());
  121. if (DEBUG) {
  122. Log.d(TAG, "【Restore data】 to " + tableName);
  123. }
  124. StringBuilder dropTableStringBuilder = new StringBuilder();
  125. dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName);
  126. db.execSQL(dropTableStringBuilder.toString());
  127. if (DEBUG) {
  128. Log.d(TAG, "【Drop temp table】" + tempTableName);
  129. }
  130. } catch (SQLException e) {
  131. Log.e(TAG, "【Failed to restore data from temp table (probably new table)】" + tempTableName, e);
  132. }
  133. }
  134. }
  135. }
通过加断点调试发现,数据库更新时调用了MyEncryptedSQLiteOpenHelper的onUpgrade()方法。如图所示:

至此,我们已能够对加密的数据库进行更新支持。

**
**

五、总结:

该博客对greenDao模型生成、增删改查、存储路径修改、加解密及更新升级等都有了较为详细的描述,在实际开发中也基本够用。不过,需要注意的是,本文引用的EncryptedMigrationHelper.java类和MigrationHelper.java类均只支持对数据库对象的新增和删除,对于对象字段有修改的情况没做考虑。如果读者有需要的话,可另行开发。

参考文献如下:

官网:[http://greenrobot.org/greendao/](http://greenrobot.org/greendao/) github网站:[https://github.com/greenrobot/greenDAO](https://github.com/greenrobot/greenDAO) greenDao Generator方式介绍:[http://www.open-open.com/lib/view/open1438065400878.html](http://www.open-open.com/lib/view/open1438065400878.html) 注释方式及增删改查详细介绍:[http://www.jianshu.com/p/4e6d72e7f57a](http://www.jianshu.com/p/4e6d72e7f57a) greenDao数据库存储路径修改:[http://blog.csdn.net/chenzhenlindx/article/details/39183691](http://blog.csdn.net/chenzhenlindx/article/details/39183691) greenDao数据库升级:[https://github.com/yuweiguocn/GreenDaoUpgradeHelper](https://github.com/yuweiguocn/GreenDaoUpgradeHelper)

源码下载

**我的 github 地址:**https://github.com/WJKCharlie/GreenDaoExample

  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    335 引用 • 324 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 630 关注
  • 脑图

    脑图又叫思维导图,是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一种实用性的思维工具。

    31 引用 • 97 回帖
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 665 关注
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    24904 引用 • 102551 回帖
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    31 引用 • 108 回帖
  • 叶归
    5 引用 • 16 回帖 • 11 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    498 引用 • 1395 回帖 • 256 关注
  • Word
    13 引用 • 40 回帖
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 73 关注
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 83 关注
  • 印象笔记
    3 引用 • 16 回帖
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖 • 1 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 107 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    211 引用 • 358 回帖 • 1 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 612 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 643 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 47 关注
  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 446 关注
  • Office

    Office 现已更名为 Microsoft 365. Microsoft 365 将高级 Office 应用(如 Word、Excel 和 PowerPoint)与 1 TB 的 OneDrive 云存储空间、高级安全性等结合在一起,可帮助你在任何设备上完成操作。

    5 引用 • 34 回帖 • 2 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    297 引用 • 755 回帖
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 1 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 161 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖 • 1 关注
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 6 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 86 关注