activeandroid是一个开源的数据库框架,使我们操作数据库更方便,简单。
1:添加依赖:
a:在项目的build.gradle文件添加:
allprojects { repositories { google() jcenter() mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } }
b:在app的build.gradle文件添加依赖:
implementation 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
2:配置数据库的名字和版本号。
a:让你自己的Application对象继 承自com.activeandroid.app.Application而不是android.app.Application。如果你需要继承其他库 的Application,则需要在Application中初始化和处理ActiveAndroid。
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
ActiveAndroid.initialize(this);
}
@Override
public void onTerminate() {
super.onTerminate();
ActiveAndroid.dispose();
}
}
b:在AndroidManifest.xml
文件中配置数据库名称和数据库版本号。
<meta-data <manifest ...> <application android:name=".MyApp" ...> ...
<meta-data android:name="AA_DB_NAME" android:value="test-aa.db" /> <meta-data android:name="AA_DB_VERSION" android:value="1" />
</application> </manifest>
3:创建自己的表,也就是我们的实体类。你的实体类必须继承自Model,这样你的类名就是你的表名。如果不想使用类名做表名,则可以使用@Table定义表名。@Column用于定义列名。
@Table(name = "MyPerson",id = "_id") public class Person extends Model { @Column(name = "person_id") private int personId; //private int id; @Column(name = "person_name") private String name; @Column(name = "person_describe") private String describe; public int getPersonId() { return personId; } public void setPersonId(int personId) { this.personId = personId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescribe() { return describe; } public void setDescribe(String describe) { this.describe = describe; } @Override public String toString() { return "<PersonId: " + personId + ", Name: '" + name + '\'' + ", Describe: '" + describe + '\'' + '>'; } }
MyPerson就是表名,activeAndroid 会为每一个表分配一个使用自增长的ID作为主键。这个自增长的列表默认叫Id,我们可以为这个列起名字。
@Table(name = "MyPerson",id = "_id")
当然我们也可以指定我们的parson_id为主键。(因为如果我们用框架分配的Id为主键的话,我们会发现,同一个personId的人会被多次插入表中,造成冗余的数据。)
@Column(name = "person_id",unique = true) private int personId;
指定某个列可以被索引,在其注解中添加:index = true。
@Column(name = "person_id",unique = true,index = true) private int personId;
其他属性可以查询依赖包中com.activeandroid.annotation.Column。
另外model里面有个方法叫getId,所以我们的实体类里面的属性不能有个属性叫id,因为如果有个属性叫id,在重写get,set方法的时候,会和父类的方法有冲突。那么如果原始的json里面就是id,例如;{"id":"1","name":"zmm","describe":"zmm是个好人"}
我们在使用Gson解析的时候,定义实体类Person的时候,就必须写id了,而这个框架又不能用id,肿么办,莫慌,其实Gson给我们提供了一个注解:@SerializedName("id"),即把json里面的id转成我们实体类的personId;
@SerializedName("id") @Column(name = "person_id") private int personId;
这样就可以正常解析了。
4:在 这一步是优化ActiveAndroid的启动速度(可有可无)
AndroidManifest.xml文件里,添加标签:
<meta-data
android:name="AA_MODELS" android:value="com.example.zongm.testapplication.bean.Person" />
至此你就可以对你的数据进行增删改查了。
5:数据处理
A:增删改查
你可以新建一个Manager类,对数据的增删改查都放该类里面。
保存:这个save方法有一个返回值,这个返回值表示当前插入的数据的id。
public boolean add(Person person) { if (person == null) return false; return person.save() > 0; }
批量保存:
public boolean add(List<Person> items) { Log.d(TAG, "Add Items : " + (items == null ? "null" : items.size())); if (items == null || items.size() == 0) return false; boolean success = true; ActiveAndroid.beginTransaction(); try { for (Person item : items) { success &= add(item); } ActiveAndroid.setTransactionSuccessful(); success &= true; } catch (Exception e) { e.printStackTrace(); success &= false; Log.e(TAG, "addAll : error : " + e.getClass()); } finally { ActiveAndroid.endTransaction(); } return success; }
当然也可以循环,单个保存,但是那样更慢,开个事务进行保存,1可以加快速度,2:万一有一个保存失败了,可以回滚。当然其他的操作,例如删除,更新,只要是批量的操作,都可以开启事务来执行。
删除:
// 第一种删除 public void delete(Person item) { Log.d(TAG, "Delete : " + item); if (item == null) { return; } item.delete(); }
//第二种删除
public void delete(int personId){ new Delete().from(Person.class).where("person_id=?",personId).execute(); }
修改:(同理也有两种方法)
public void update(String personName, String newPersonName) { new Update(Person.class).set("person_name = ?", newPersonName).where("person_name = ?", personName); }
查询:
//1:把person里面的所有数据都查出来。
@NonNull public List<Person> getAll() { return getAll(Integer.MAX_VALUE); } private List<Person> getAll(int count) { return new Select().from(Person.class).limit(count).execute(); }
//2:条件查询。
//a:根据id查一个person
public Person getPersonById(int id) { return new Select().from(Person.class).where("person_id=?", id).executeSingle(); }
//b;根据一组id拼成的ids,查出一组person。注意,这里ids格式必须是:1,2,3,4,
public List<Person> getPersonListByIds(String ids) { if (TextUtils.isEmpty(ids)) return null; List<Person> items = new Select().from(Person.class).where(" person_id in (" + ids + ")").execute(); return items; }
其实这个框架简化了不少我们的工作,查询做的非常像SQLite的原生查询语句,几乎涵盖了所有的指令,基本所有的条件查询也都支持,只要你的sql语句记得牢,where里面可以放任何你可以写出来的sql语句,当然也支持关联查询,具体的使用方法可以参照github上的测试用例去了解。
B:类型序列
ActiveAndroid默认处理多种类型。如果需要处理自定义数据类型,继承TypeSerializer,重写其方法。比如我们要将Date类型转换为long类型保存,读取的时候又直接得到Date类型:
1:
/** * @author zongm on 2019/8/15 */ public class DateUtilSerializer extends TypeSerializer { /** *返回序列的类 */ @Override public Class<?> getDeserializedType() { return Date.class; } /** *返回存储到数据库中的类型 */ @Override public Class<?> getSerializedType() { return long.class; } /** *将我们所使用的数据类型转换为ActiveAndroid存储的数据类型 */ @Override public Object serialize(Object data) { if (data == null) { return null; } return ((Date) data).getTime(); } /** *使存储的数据转换成使用的数据类型 */ @Override public Object deserialize(Object data) { if (data == null) { return null; } return new Date((Long) data); } }
2:然后注册自定义的序列化类型,在AndroidManifest.xml声明它们:
<meta-data android:name="AA_SERIALIZERS" android:value="com.example.zongm.testapplication.DateUtilSerializer"/>
其次ActiveAndroid自带的TypeSerializer:
C:对数据库升级(适用于:A:在原来数据表的基础上增加字段,而又顾忌用户原来保存的数据,不想删除原来的数据表再重新建表,B:在原来的数据库上,增加了一个新的表)
A:使用A情况
1、首先,你需要更改AndroidManifest.xml中数据库版本号AA_DB_VERSION(必须比上一版本号大的正整数)
2、其次,你需要在assest目录里面创建sql文件,目录结构(app/main/assets/migrations/升级后的版本号.sql),文件里面你需要写上你变动数据库的sql语句(一行一句sql语句),比如我们想在MyPerson表里增加age字段,我们需要这样写:
ALTER TABLE MyPerson ADD COLUMN person_age INTEGER;
3、最后,你需要在Person对象里面添加的对应字段(如果有增加字段的话,添加规则参考上面创建数据库模型
@Column(name = "person_age") private int age;
B:使用B情况。
1、同A情况的第一步
2、只需要在AndroidManifest.xml里面的 AA_MODELS标签下增加你增加的表名即可。
<meta-data android:name="AA_MODELS" android:value="com.example.zongm.testapplication.bean.Person, com.example.zongm.testapplication.bean.Person2" />
最后说下使用过程中遇到的问题
1:在save的时候报错:
Caused by: java.lang.SecurityException: Failed to find provider null for user 0; expected to find a valid ContentProvider for this authority
原因:
当我们在 8.0 或 8.1 系统上使用 26 或以上的版本的 SDK 时,调用 ContentResolver 的 notifyChange 方法通知数据更新,或者调用 ContentResolver 的 registerContentObserver 方法监听数据变化时,会出现异常:java.lang.SecurityException: Failed to find provider < authority > for user 0,而在 SDK 26 之前的版本上这两个方法都是可以正常运行的。
具体原因请移步这位大哥的博客:https://blog.csdn.net/weixin_37077539/article/details/80067073
解决办法:找到AndroidManifest.xml文件,在application结点下面添加以下代码:
<provider android:name="com.activeandroid.content.ContentProvider" android:authorities="${applicationId}" android:enabled="true" android:exported="false"></provider>
2:当你的实体类有一个带参的构造方法时,你再调用save的时候,报错:
Caused by: java.lang.RuntimeException: Your model com.example.zongm.testapplication.bean.Person does not define a default constructor. The default constructor is required for now in ActiveAndroid models, as the process to populate the ORM model is : 1. instantiate default model 2. populate fields
原因:因为ActiveAndroid 的Model的有一个无参的构造函数,我们的Person是Model的子类,我们写了一个带参的构造方法,则默认的无参构造方法就不存在了,我们看下ActiveAndroid 的源码,SQLiteUtils的processCursor():
public static <T extends Model> List<T> processCursor(Class<? extends Model> type, Cursor cursor) { TableInfo tableInfo = Cache.getTableInfo(type); String idName = tableInfo.getIdName(); final List<T> entities = new ArrayList<T>(); try { Constructor<?> entityConstructor = type.getConstructor(); if (cursor.moveToFirst()) { /** * Obtain the columns ordered to fix issue #106 (https://github.com/pardom/ActiveAndroid/issues/106) * when the cursor have multiple columns with same name obtained from join tables. */ List<String> columnsOrdered = new ArrayList<String>(Arrays.asList(cursor.getColumnNames())); do { Model entity = Cache.getEntity(type, cursor.getLong(columnsOrdered.indexOf(idName))); if (entity == null) { entity = (T) entityConstructor.newInstance(); } entity.loadFromCursor(cursor); entities.add((T) entity); } while (cursor.moveToNext()); } } catch (NoSuchMethodException e) { throw new RuntimeException( "Your model " + type.getName() + " does not define a default " + "constructor. The default constructor is required for " + "now in ActiveAndroid models, as the process to " + "populate the ORM model is : " + "1. instantiate default model " + "2. populate fields" ); } catch (Exception e) { Log.e("Failed to process cursor.", e); } return entities; }
首先 判断LruCache缓存中是否存在Model类.如果不存在,就去反射创造一个实体出来,
entityConstructor.newInstance();点进去也就是在Class.class。的newInstance()方法是个native方法,具体怎么写的我们看不到,但是看注释,解释说的大概就是根据一个无参的构造方法,获得一个对象。
故:我们需要通过一个无参的构造方法来反射得到一个对象。因为我们写了一个带参的构造方法,默认的无参的构造方法就不存在了,所以需要我们手动写上。
解决办法:
在我们的Person实体类里面显示的定义一个无参的构造函数。
即:
public Person() { } public Person(int personId) { this.personId = personId; }
3:数据总是重复保存到数据库。
这个的原因在上面已经讲过了,可能因为你没有定义主键。把你的实体类里面具有唯一性的属性设置为主键,一般都是实体类的id。
4:因为数据重复保存,所有有的小阔爱就在保存之前那,先去查询有没有,如果有的话,先删除,再保存,导致报错:
注意:不要将Delete配合executeSingle使用,因为这样使用不可避免的会碰到两个坑:
- SQLite默认不支持DELETE和LIMIT并存的操作.而executeSingle会使用Limt 1语句.
- 使用Delete和executeSingle配合,其实是先执行SELETE操作,然后再执行Model的delte操作.但是ActiveAndroid源码中没有判空,会导致空指针
详细请移步:http://ju.outofmemory.cn/entry/341803
5:这是gson解析的问题。Gson解析json数据时,如果属性值为null时报异常错误
解决办法就是:用GsonBuilder创建Gson实例,而不是new Gson();
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC, Modifier.FINAL).create();
还有很多GsonBuilder的使用方法,请自行搜索。
每日语录:一切有为法,如梦幻泡影,如露亦如电,应作如是观。
单曲循环《只爱西经》