Realm环境配置
在工程根目录下的build.gradle添加如下配置:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.1' classpath "io.realm:realm-gradle-plugin:3.5.0" } }
在模块目录下的build.gradle添加如下插件:
apply plugin: 'realm-android'
使用Realm之前,先传递context对象完成Realm的初始化,可在Application的onCreate完成初始化操作,如下:
public class MainApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); } }
Realm的配置分为两种:默认配置和用户自定义配置,先看如下代码:
private static final String REALM_PATH =Environment.getExternalStorageDirectory().getAbsolutePath() + "/realm_dir/"; @Override public void onCreate() { super.onCreate(); Realm.init(this); // 默认配置获取的Realm实例 Realm.getDefaultInstance(); RealmConfiguration.Builder builder = new RealmConfiguration.Builder() .name("custom.realm") .directory(new File(REALM_PATH)) .schemaVersion(1) .modules(Realm.getDefaultModule()) .deleteRealmIfMigrationNeeded() .encryptionKey(getKey()); // Realm.setDefaultConfiguration(builder.build()); // 用户配置获取的Realm实例 Realm.getInstance(builder.build()); }
从上述代码可知,Realm是以builder来构建RealmConfiguration。RealmConfiguration主要包括数据库名字(name)、数据库地址(directory)、版本号(schemaVersion)、数据库加密密匙(encryptionKey)等,且Realm已内置了一套默认配置的实现,可通过setDefaultConfiguration来修改默认配置。总之,Realm的数据库配置还是相对灵活的,且能满足一般的用户数据。
Realm支持数据库加密,只需配置密匙即可加密数据库。关键在于密匙的管理,一般做法如下:
- 先生成RSA密匙对,对于sdk23及以上可使用Android KeyStore,否则自己维护RSA密匙对;
- 生成AES密匙;
- 用RSA的公匙加密AES密匙;
- 存储加密后的AES密匙,如使用SharedPreferences;
- 获取被存储的AES密匙,并用RSA私匙解密AES密匙,配置RealmConfiguration即可打开加密数据库。
总结:使用Android KeyStore减轻了对RSA密匙对的维护工作,只需专注于AES密匙的存储。值得注意的是此处使用RSA的签名验证功能,即用RSA公钥加密AES,RSA私钥解密AES。因为Android KeyStore不对外公开RSA的密匙对,所以不但能加密数据而且还能确保AES密匙的唯一性,意味着解密出来的AES密匙一定能打开已加密的Realm数据库文件。
Realm Models
因Realm数据库的底层设计,所有的Model必需继承于RealmObject。但Model写法与JavaBean写法无甚差异,比如下实例:
public class User extends RealmObject { private String name; private int age; @Ignore private int sessionId; // Standard getters & setters generated by your IDE… public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSessionId() { return sessionId; } public void setSessionId(int sessionId) { this.sessionId = sessionId; } }
RealmObject支持存储的属性常见类型有:boolean, byte, short, int, long, float, double, String, Date and byte[]。另外还支持RealmObject的子类或者RealmList的集合类型。如下例子:
public class UserInfo extends RealmObject { @PrimaryKey private String userId; private Job job; private RealmList<Colleague> colleagues; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Job getJob() { return job; } public void setJob(Job job) { this.job = job; } public RealmList<Colleague> getColleagues() { return colleagues; } public void setColleagues(RealmList<Colleague> colleagues) { this.colleagues = colleagues; } } public class Job extends RealmObject { private String jobTitle; private String companyName; public String getJobTitle() { return jobTitle; } public void setJobTitle(String jobTitle) { this.jobTitle = jobTitle; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } } public class Colleague extends RealmObject{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
在Model中声明的属性都将被存储,可以通过加Ignore注解来忽略存储。对于static或volatile的属性,会默认忽略,不需加Ignore注解。
RealmObject Model的最大特点是自动更新,是一种内存数据与文件数据的同步策略,可减轻查询数据的消耗。如下例子:
public void test() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Dog myDog = realm.createObject(Dog.class); myDog.setName("Fido"); myDog.setAge(1); } }); Dog myDog = realm.where(Dog.class).equalTo("age", 1).findFirst(); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Dog myPuppy = realm.where(Dog.class).equalTo("age", 1).findFirst(); myPuppy.setAge(2); } }); myDog.getAge(); // => 2 }
5.简单理解,RealmObject Model对应于Realm数据库中的一张表,而一张表有且仅有一个主键。主键属性的类型只能是String或integer类型。
- 用法在前面的UserInfo对象中,只需对主键属性添加@PrimaryKey注解即可。
- 主键的好处是达到复用RealmObjec的目的,原理是在创建RealmObject对象时借助主键去查询是否存在该对象,存在该对象即可返回,否则创建新对象。
创建RealmObject对象的三种方式:createObject、copyToRealmOrUpdate与copyToRealm用法如下:
//第一种:创建新实例 //final MyObject obj = Realm.createObject(MyObject.class); final MyObject obj = new MyObject(); obj.setId(42); obj.setName("Fish"); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // 第二种:依据主键判断,不存在创建新实例,否则抛异常 // realm.copyToRealm(obj); // 第三种:依据主键判断,不存在创建新实例,否则更新并返回实例 realm.copyToRealmOrUpdate(obj); } });
- 主键的副作用是有可能带来性能问题,就来创建RealmObject对象来说,使用copyToRealmOrUpdate虽能复用对象,但是其性能瓶颈在于查找与更新实例。
Realm 调试
- Stetho是用于调试Realm的便利工具,同时还需要chrome浏览器的支持。
先在工程根目录的gradle文件添加路径,如下
allprojects { repositories { jcenter() maven { url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo' } } }
在模块目录的gradle文件添加依赖
dependencies { compile 'com.facebook.stetho:stetho:1.5.0' compile 'com.uphyca:stetho_realm:2.1.0' }
打开chrome浏览器,输入chrome://inspect,即可开始realm的调试,如下图
注意在发布正式版本前,需要关闭Stetho,否则容易泄露数据。
Realm Writes
- Realm Writes分为插入、更新和删除三种操作,实现的方式是Transaction。
Transaction提交与取消,模板如下
// Obtain a Realm instance Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); //... add or update objects here ... realm.commitTransaction();
realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); // ... realm.cancelTransaction();
一个完整事务是以beginTransaction开始,以提交或取消二选一的方式来结束一个事务。
Realm事务的执行是同步进行,因此在UI线程执行事务有可能造成ANR,解决方法是使用Realm.executeTransactionAsync异步执行,如下代码
public void executeTransactionAsync() { realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm bgRealm) { Dog myDog = realm.createObject(Dog.class); myDog.setName("Mike"); myDog.setAge(4); } }, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { // Transaction was a success. } }, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { // Transaction failed and was automatically canceled. } }); }
Realm推荐使用executeTransaction方法来执行事务,其实就是对前面模板代码的封装,如下代码:
public void executeTransaction() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Dog myDog = realm.createObject(Dog.class); myDog.setName("Jack"); myDog.setAge(12); } }); }
Realm Queries
Realm使用RealmQuery来封装SQL语句,体现如下:
public void query() { // Build the query looking at all users: RealmQuery<User> query = realm.where(User.class); // Add query conditions: query.equalTo("name", "John"); query.or().equalTo("name", "Peter"); // Execute the query: RealmResults<User> result1 = query.findAll(); }
上述代码可简写如下:
public void query() { RealmResults<User> result2 = realm.where(User.class) .equalTo("name", "John") .or() .equalTo("name", "Peter") .findAll(); }
不难发现,where用于指定数据库表名,equalTo等则是匹配条件,find则是执行查询操作。
查询的结果封装在RealmResults,这是Realm实现的一个集合。
删除查询结果,如下实例:
public void delete() { // obtain the results of a query final RealmResults<Dog> results = realm.where(Dog.class).findAll(); // All changes to data must happen in a transaction realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // remove single match results.deleteFirstFromRealm(); results.deleteLastFromRealm(); // remove a single object Dog dog = results.get(5); dog.deleteFromRealm(); // Delete all matches results.deleteAllFromRealm(); } }); }
Realm 总结
Realm的核心主要包括环境配置、models、write、query几大块:通过RealmConfiguration实现个性化配置;继承RealmObject完成数据持久化;借助Transaction完成数据的插入、更新和删除操作;使用RealmQuery和RealmResults分别封装SQL查询语句与查询的结果。