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

目录:
前言
导入依赖和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
    我的Schema设置如下:
greendao{
    schemaVersion 1
    targetGenDir 'src/main/java'
}
一、数据库模型生成及读取操作:
 1、greenDao支持的数据模型生成方式包括以下两种:
  (1)编写greendaoGenerator的纯Java类库,以生成DaoMaster、DaoSession、bean和beanDao等;
       注:greenDao 3.0版本以下只能采用这种方式。在升级3.0以后,因支持了注释方式,该方式不再推荐。
  (2)利用注释的方式,生成 DaoMaster、DaoSession、bean和beanDao等;
    
 2、下文仅就注释方式生成流程进行简单的介绍,详细的讲解可参见 http://www.jianshu.com/p/4e6d72e7f57a
     首先,新建datamodel包,用以包含 DaoMaster、DaoSession、bean和beanDao等。
     然后新建Area实体类,代码如下:
@Entity
public class Area {
    @Id
    private String AreaCode;
    private String AreaName;
}
     最后,Build->Make Module 'app',即可自动生成 DaoMaster、DaoSession、Area和AreaDao。此时Area实体类的代码如下:
@Entity
public class Area {
    @Id
    private String AreaCode;
    private String AreaName;
    @Generated(hash = 262290694)
    public Area(String AreaCode, String AreaName) {
        this.AreaCode = AreaCode;
        this.AreaName = AreaName;
    }
    @Generated(hash = 179626505)
    public Area() {
    }
    public String getAreaCode() {
        return this.AreaCode;
    }
    public void setAreaCode(String AreaCode) {
        this.AreaCode = AreaCode;
    }
    public String getAreaName() {
        return this.AreaName;
    }
    public void setAreaName(String AreaName) {
        this.AreaName = AreaName;
    }
}
     添加其他实体类的方法与Area一样。需要注意的是,不要手动修改 DaoMaster、DaoSession、bean和beanDao的代码,因为每一次编译项目,都会重新生成一次DaoMaster、DaoSession、bean和beanDao。如果修改的话,就会被覆盖掉。
     为了便于数据的读取和添加,新建GreenDaoHelper辅助类,代码如下:
public class GreenDaoHelper extends Application {
    private GreenDaoHelper Instance;
    private static DaoMaster daoMaster;
    private static DaoSession daoSession;

    public GreenDaoHelper getInstance() {
        if (Instance == null) {
            Instance = this;
        }
        return Instance;
    }

    /**
    * 获取DaoMaster
    *
    * @param context
    * @return
    */
    public static DaoMaster getDaoMaster(Context context) {
        if (daoMaster == null) {
            try{
                DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(context,"test.db",null);
                daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return daoMaster;
    }

    /**
    * 获取DaoSession对象
    *
    * @param context
    * @return
    */
    public static DaoSession getDaoSession(Context context) {

        if (daoSession == null) {
            if (daoMaster == null) {
                getDaoMaster(context);
            }
            daoSession = daoMaster.newSession();
        }
        return daoSession;
    }
}
      在读写数据库之前,要添加读写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     在MainActivity.java中添加读写代码:
public class MainActivity extends AppCompatActivity {

    private TextView textview;
    private DaoSession session;

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

        textview=(TextView)findViewById(R.id.textview);

        session = GreenDaoHelper.getDaoSession(this);
        session.getAreaDao().deleteAll();//清空所有记录

        //添加记录
        Area area = new Area("01","北京");
        Area area1 = new Area("02","天津");
        session.getAreaDao().insert(area);
        session.getAreaDao().insert(area1);

        //查询记录
        StringBuilder stringBuilder = new StringBuilder();
        List<Area> areas = session.getAreaDao().loadAll();
        for (int i = 0,n = areas.size();i<n;++i){
            stringBuilder.append("地区编码:").append(areas.get(i).getAreaCode())
                    .append(",地区名称:").append(areas.get(i).getAreaName()).append("\n");
        }

        textview.setText(stringBuilder);
    }
}
     运行结果如下图所示:

     
二、修改数据库文件路径:
     默认情况下,新创建的数据存储在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 中的内容基本符合我们的需求。但是博主是在DaoMaster中重写方法的。通过上文我们知道,DaoMaster的代码是不能修改的。因此,我们可以将重写的方法放到GreenDaoHelper中去。 代码如下:
public class GreenDaoHelper extends Application {
    private GreenDaoHelper Instance;
    private static DaoMaster daoMaster;
    private static DaoSession daoSession;

    public GreenDaoHelper getInstance() {
        if (Instance == null) {
            Instance = this;
        }
        return Instance;
    }

    /**
    * 获取DaoMaster
    *
    * @param context
    * @return
    */
    public static DaoMaster getDaoMaster(Context context) {

        if (daoMaster == null) {

            try{
                ContextWrapper wrapper = new ContextWrapper(context) {
                /**
                * 获得数据库路径,如果不存在,则创建对象对象
                *
                * @param name
                */
                @Override
                public File getDatabasePath(String name) {
                    // 判断是否存在sd卡
                    boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState());
                    if (!sdExist) {// 如果不存在,
                        Log.e("SD卡管理:", "SD卡不存在,请加载SD卡");
                        return null;
                    } else {// 如果存在
                        // 获取sd卡路径
                        String dbDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
                        dbDir += "/Android";// 数据库所在目录
                        String dbPath = dbDir + "/" + name;// 数据库路径
                        // 判断目录是否存在,不存在则创建该目录
                        File dirFile = new File(dbDir);
                        if (!dirFile.exists())
                            dirFile.mkdirs();

                        // 数据库文件是否创建成功
                        boolean isFileCreateSuccess = false;
                        // 判断文件是否存在,不存在则创建该文件
                        File dbFile = new File(dbPath);
                        if (!dbFile.exists()) {
                            try {
                                isFileCreateSuccess = dbFile.createNewFile();// 创建文件
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        } else
                            isFileCreateSuccess = true;
                        // 返回数据库文件对象
                        if (isFileCreateSuccess)
                            return dbFile;
                        else
                            return super.getDatabasePath(name);
                    }
                }

                /**
                * 重载这个方法,是用来打开SD卡上的数据库的,android 2.3及以下会调用这个方法。
                *
                * @param name
                * @param mode
                * @param factory
                */
                @Override
                public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {
                    return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
                }

                /**
                * Android 4.0会调用此方法获取数据库。
                *
                * @see android.content.ContextWrapper#openOrCreateDatabase(java.lang.String,
                *      int,
                *      android.database.sqlite.SQLiteDatabase.CursorFactory,
                *      android.database.DatabaseErrorHandler)
                * @param name
                * @param mode
                * @param factory
                * @param errorHandler
                */
                @Override
                public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
                    return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
                }
                };
                DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);
                daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return daoMaster;
    }

    /**
    * 获取DaoSession对象
    *
    * @param context
    * @return
    */
    public static DaoSession getDaoSession(Context context) {

        if (daoSession == null) {
            if (daoMaster == null) {
                getDaoMaster(context);
            }
            daoSession = daoMaster.newSession();
        }

        return daoSession;
    }
}
     此时,再运行上述代码,就会在Android目录下发现我们的test.db文件。通过第三方工具,即可查看我们的数据库内容。下图是我用手机端的SqliteLookup工具查看到的数据库内容:


三、获取加密的数据库:
     修改GreenDaoHelper.java,通过调用DaoMaster.OpenHelper类的getEncryptedWritableDb(password)或者 getEncryptedReadableDb(password)方法, 即可获取加密的数据库。
public static DaoMaster getDaoMaster(Context context) {

    if (daoMaster == null) {

        try{
            ContextWrapper wrapper = new ContextWrapper(context) {
                 ...
            };
            DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);
            daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
            //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
            //daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    return daoMaster;
}
     此时,若要解密或重新加密数据库,可参考 博客《利用SQLCipher加解密数据库(包括加解密已有的数据库)》

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

    public GreenDaoHelper getInstance() {
        if (Instance == null) {
            Instance = this;
        }
        return Instance;
    }

    /**
    * 获取DaoMaster
    *
    * @param context
    * @return
    */
    public static DaoMaster getDaoMaster(Context context) {

        if (daoMaster == null) {

            try{
                ContextWrapper wrapper = new ContextWrapper(context) {
                          ...
                };
                DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);
                //daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
                //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
                daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return daoMaster;
    }

    /**
    * 获取DaoSession对象
    *
    * @param context
    * @return
    */
    public static DaoSession getDaoSession(Context context) {

        if (daoSession == null) {
            if (daoMaster == null) {
                getDaoMaster(context);
            }
            daoSession = daoMaster.newSession();
        }

        return daoSession;
    }

    private static class MySQLiteOpenHelper extends DaoMaster.OpenHelper {

        public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
            super(context, name, factory);
        }

        private static final String UPGRADE="upgrade";

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            MigrationHelper.migrate(db,AreaDao.class);
            Log.e(UPGRADE,"upgrade run success");
        }
    }
}
     另外添加一个People实体类,并修改schemaVersion为更高的版本号,然后 Build->Make Module 'app',生成新的模型类。修改OnUpgrade方法如下:
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    MigrationHelper.migrate(db,AreaDao.class, PeopleDao.class);
    Log.e(UPGRADE,"upgrade run success");
}
     此时,运行程序。虽然程序成功启动,但是报如下错误:
12-30 10:02:12.503 6312-6312/com.wjk.greendaoexample E/SQLiteLog: (1) no such table: PEOPLE
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实体是新创建的,数据库中并没有这个表,因此报上面的错误。此时,我们需对源码进行修改,仅对数据库中已有的表创建临时表并保存数据。此外,源码中是按字段恢复数据,为方便起见,本程序修改为全表查询恢复。代码如下:
public final class MigrationHelper {
    public static boolean DEBUG = false;
    private static String TAG = "MigrationHelper";

    private static List<String> tablenames = new ArrayList<>();

    public static List<String> getTables(SQLiteDatabase db){
        List<String> tables = new ArrayList<>();

        Cursor cursor = db.rawQuery("select name from sqlite_master where type='table' order by name", null);
        while(cursor.moveToNext()){
            //遍历出表名
            tables.add(cursor.getString(0));
        }
        cursor.close();
        return tables;
    }

    public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        Database database = new StandardDatabase(db);
        if (DEBUG) {
            Log.d(TAG, "【Database Version】" + db.getVersion());
            Log.d(TAG, "【Generate temp table】start");
        }

        tablenames=getTables(db);

        generateTempTables(database, daoClasses);
        if (DEBUG) {
            Log.d(TAG, "【Generate temp table】complete");
        }
        dropAllTables(database, true, daoClasses);
        createAllTables(database, false, daoClasses);

        if (DEBUG) {
            Log.d(TAG, "【Restore data】start");
        }
        restoreData(database, daoClasses);
        if (DEBUG) {
            Log.d(TAG, "【Restore data】complete");
        }
    }

    private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for (int i = 0; i < daoClasses.length; i++) {
            String tempTableName = null;

            try {
                DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
                if(!tablenames.contains(daoConfig.tablename)){//如果数据库中没有该表,则继续下次循环
                    continue;
                }
                String tableName = daoConfig.tablename;
                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());
                if (DEBUG) {
                    Log.d(TAG, "【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
                    Log.d(TAG, "【Generate temp table】" + tempTableName);
                }
            } catch (SQLException e) {
                Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
            }
        }
    }

    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);
        if (DEBUG) {
            Log.d(TAG, "【Drop all table】");
        }
    }

    private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
        reflectMethod(db, "createTable", ifNotExists, daoClasses);
        if (DEBUG) {
            Log.d(TAG, "【Create all table】");
        }
    }

    /**
    * 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++) {
            String tempTableName = null;

            try {
                DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
                String tableName = daoConfig.tablename;

                if(!tablenames.contains(tableName)){
                    continue;
                }

                tempTableName = daoConfig.tablename.concat("_TEMP");
                StringBuilder insertTableStringBuilder = new StringBuilder();
                insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" SELECT * FROM ").append(tempTableName).append(";");
                db.execSQL(insertTableStringBuilder.toString());
                if (DEBUG) {
                    Log.d(TAG, "【Restore data】 to " + tableName);
                }

                StringBuilder dropTableStringBuilder = new StringBuilder();
                dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName);
                db.execSQL(dropTableStringBuilder.toString());
                if (DEBUG) {
                    Log.d(TAG, "【Drop temp table】" + tempTableName);
                }
            } catch (SQLException e) {
                Log.e(TAG, "【Failed to restore data from temp table (probably new table)】" + tempTableName, e);
            }
        }
    }
}
     此时,新建一个实体类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的依赖,如下:
compile 'net.zetetic:android-database-sqlcipher:3.5.4@aar'
     具体代码如下:
public class GreenDaoHelper extends Application {

    ......

    public static DaoMaster getDaoMaster(Context context) {

        if (daoMaster == null) {

            try{
                ContextWrapper wrapper = new ContextWrapper(context) {
                          ...
                };
                //DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);
                MyEncryptedSQLiteOpenHelper helper = new MyEncryptedSQLiteOpenHelper(wrapper,"test.db",null);
                daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
                //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
                //daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return daoMaster;
    }

    ......

    private static class MyEncryptedSQLiteOpenHelper extends DaoMaster.OpenHelper {

        private final Context context;
        private final String name;
        private final int version = DaoMaster.SCHEMA_VERSION;

        private boolean loadSQLCipherNativeLibs = true;

        public MyEncryptedSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
            super(context, name, factory);
            this.context=context;
            this.name=name;
        }

        private static final String UPGRADE="upgrade";

        @Override
        public void onUpgrade(Database db, int oldVersion, int newVersion) {

            EncryptedMigrationHelper.migrate((EncryptedDatabase) db,AreaDao.class, PeopleDao.class, ProductDao.class);
            Log.e(UPGRADE,"upgrade run success");
        }

        @Override
        public Database getEncryptedWritableDb(String password) {
            MyEncryptedHelper encryptedHelper = new MyEncryptedHelper(context,name,version,loadSQLCipherNativeLibs);
            return encryptedHelper.wrap(encryptedHelper.getReadableDatabase(password));
        }

        private class MyEncryptedHelper extends net.sqlcipher.database.SQLiteOpenHelper {
            public MyEncryptedHelper(Context context, String name, int version, boolean loadLibs) {
                super(context, name, null, version);
                if (loadLibs) {
                    net.sqlcipher.database.SQLiteDatabase.loadLibs(context);
                }
            }

            @Override
            public void onCreate(net.sqlcipher.database.SQLiteDatabase db) {
                MyEncryptedSQLiteOpenHelper.this.onCreate(wrap(db));
            }

            @Override
            public void onUpgrade(net.sqlcipher.database.SQLiteDatabase db, int oldVersion, int newVersion) {
                MyEncryptedSQLiteOpenHelper.this.onUpgrade(wrap(db), oldVersion, newVersion);
            }

            @Override
            public void onOpen(net.sqlcipher.database.SQLiteDatabase db) {
                MyEncryptedSQLiteOpenHelper.this.onOpen(wrap(db));
            }

            protected Database wrap(net.sqlcipher.database.SQLiteDatabase sqLiteDatabase) {
                return new EncryptedDatabase(sqLiteDatabase);
            }
        }
    }
}
     上述代码中引用了EncryptedMigrationHelper.java类。该类与 MigrationHelper.java类似,只不过将android.database.sqlite.SQLiteDatabase替换为net.sqlcipher.database.SQLiteDatabase,同时对代码做了微小的改动。 EncryptedMigrationHelper.java类的代码如下:
public class EncryptedMigrationHelper {
    public static boolean DEBUG = true;
    private static String TAG = "UpgradeHelper";

    private static List<String> tablenames = new ArrayList<>();

    public static List<String> getTables(SQLiteDatabase db){
        List<String> tables = new ArrayList<>();

        Cursor cursor = db.rawQuery("select name from sqlite_master where type='table' order by name", null);
        while(cursor.moveToNext()){
            //遍历出表名
            tables.add(cursor.getString(0));
        }
        cursor.close();
        return tables;
    }

    public static void migrate(EncryptedDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        Database database = db;
        if (DEBUG) {
            Log.d(TAG, "【Database Version】" + db.getSQLiteDatabase().getVersion());
            Log.d(TAG, "【Generate temp table】start");
        }

        tablenames=getTables(db.getSQLiteDatabase());

        generateTempTables(database, daoClasses);
        if (DEBUG) {
            Log.d(TAG, "【Generate temp table】complete");
        }
        dropAllTables(database, true, daoClasses);
        createAllTables(database, false, daoClasses);

        if (DEBUG) {
            Log.d(TAG, "【Restore data】start");
        }
        restoreData(database, daoClasses);
        if (DEBUG) {
            Log.d(TAG, "【Restore data】complete");
        }
    }

    private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for (int i = 0; i < daoClasses.length; i++) {
            String tempTableName = null;

            try {
                DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
                if(!tablenames.contains(daoConfig.tablename)){
                    continue;
                }
                String tableName = daoConfig.tablename;
                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());
                if (DEBUG) {
                    Log.d(TAG, "【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
                    Log.d(TAG, "【Generate temp table】" + tempTableName);
                }
            } catch (SQLException e) {
                Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
            }
        }
    }

    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);
        if (DEBUG) {
            Log.d(TAG, "【Drop all table】");
        }
    }

    private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
        reflectMethod(db, "createTable", ifNotExists, daoClasses);
        if (DEBUG) {
            Log.d(TAG, "【Create all table】");
        }
    }

    /**
    * 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++) {
            String tempTableName = null;

            try {
                DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
                String tableName = daoConfig.tablename;

                if(!tablenames.contains(tableName)){
                    continue;
                }

                tempTableName = daoConfig.tablename.concat("_TEMP");
                StringBuilder insertTableStringBuilder = new StringBuilder();
                insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" SELECT * FROM ").append(tempTableName).append(";");
                db.execSQL(insertTableStringBuilder.toString());
                if (DEBUG) {
                    Log.d(TAG, "【Restore data】 to " + tableName);
                }

                StringBuilder dropTableStringBuilder = new StringBuilder();
                dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName);
                db.execSQL(dropTableStringBuilder.toString());
                if (DEBUG) {
                    Log.d(TAG, "【Drop temp table】" + tempTableName);
                }
            } catch (SQLException e) {
                Log.e(TAG, "【Failed to restore data from temp table (probably new table)】" + tempTableName, e);
            }
        }
    }
}
     通过加断点调试发现,数据库更新时调用了MyEncryptedSQLiteOpenHelper的onUpgrade()方法 。如图所示:




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

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

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



我的github地址:https://github.com/WJKCharlie/GreenDaoExample
  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值