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

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

目录:

前言

导入依赖和 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 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    337 引用 • 324 回帖 • 2 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • RemNote
    2 引用 • 16 回帖 • 25 关注
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 512 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    56 引用 • 85 回帖 • 3 关注
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖
  • OkHttp

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

    16 引用 • 6 回帖 • 92 关注
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    92 引用 • 752 回帖
  • Excel
    31 引用 • 28 回帖
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    85 引用 • 324 回帖 • 1 关注
  • 小说

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

    32 引用 • 108 回帖
  • GraphQL

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

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

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • 外包

    有空闲时间是接外包好呢还是学习好呢?

    26 引用 • 234 回帖 • 1 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    29 引用 • 202 回帖 • 37 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    268 引用 • 666 回帖 • 1 关注
  • OnlyOffice
    4 引用 • 18 关注
  • Typecho

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

    12 引用 • 67 回帖 • 447 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 618 关注
  • WiFiDog

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

    1 引用 • 7 回帖 • 611 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 259 关注
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    35 引用 • 468 回帖 • 758 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    108 引用 • 295 回帖
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    20 引用 • 245 回帖 • 239 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 563 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 113 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖
  • SQLite

    SQLite 是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是全世界使用最为广泛的数据库引擎。

    4 引用 • 7 回帖
  • 微软

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

    8 引用 • 44 回帖 • 2 关注