这章主要是来学习SOLite数据库

对于,数据保存来说,有的需要持久化保存,那么此时临时性的savedInstanceState显然不能完成任务,那么此时还好安卓有提供一个沙盒目录,就是data/data/应用包名,把文件放在沙盒里面可以阻止其他应用对他的访问,但是保存大量数据的时候,不会使用txt这种格式的,原因是因为要度txt的话需要读取花费太多的时间,此时就需要用到SQLite数据库了

一.首先需要先定义schema(数据字段)

在创建数据库之前我们要先清楚知道我们要存储什么样的数据,创建schema的方式有很多,但是我们应该不要去重复造轮子,应该避免在应用中重复使用遵守的编程规则

这边先来简单的在Java代码中定义data schema(描述表名和数据字段)

首先先创建定义schema的Java类,将其命名为CrimeDbSchema,然后再新建类的对话框中输入包名 database.CrimeDbSchema。这样就可以把这份文件放入专用的包名中去,实现数据库的相关代码的统一组织和归类

 

(1)在CrimeDbSchema中定义一个数据表的内部类

public class CrimeDbSchema {
public static final class CrimeTable{
public static final String NAME = "crimes";
}
}

内部类的作用是定义数据表元素的String常量,

然后的内部类里面再定义一个类来存储数据表元素

public static final class Cols {
public static final String UUID = "uuid";
public static final String TITLE = "title";
public static final String DATE = "date";
public static final String SOLVED = "'solved";
}

有了这些代码就可以在Java代码中安全引用了

二.对于数据库来说,它有需要步骤需要判断,但是安卓已经为我们提供了一个SQLiteOpenHelper类来解决许多伪问题,我们应该新建一个类去继承这个类,然后覆盖父类的方法,如下面代码:

public class CrimeBaseHelper extends SQLiteOpenHelper {
private static final int VERSION = 1;
private static final String DATABASE_NAME = "crimeBase.db";
public CrimeBaseHelper(Context context){
super(context,DATABASE_NAME,null,VERSION);
}
public void onCreate(SQLiteDatabase db){
}
public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion){
}
(2)然后我们就应该在之前的模型类中去创建和使用数据库
public class CrimeLab {
private Context mContext;
private SQLiteDatebase mDatabase;
private CrimeLab(Context context){
mContext = context.getApplicationContext();
mDatabase = new CrimeBaseHelper(mContext).getWritableDatabase();
mCrimes = new ArrayList();
 
}
}

使用getWritableDatabase()方法时,它会去完成创建数据库的各种逻辑,onCreate()时来创建数据库的,而onUpdate()方法是用来升级数据库的

下面得代码是用来创建SQL的,放在onCreate()代码中:

db.execSQL("create table" + CrimeDbSchema.CrimeTable.NAME);

接着在里面去创建数据表:

db.execSQL("create table" + CrimeDbSchema.CrimeTable.NAME + "(" + " _id integer primary key autoincrement," + CrimeTable.Cols.UUID + " ," + CrimeTable.Cols.TITLE + " ," + CrimeTable.Cols.DATE + " ," + CrimeTable.Cols.SOLVED + ")");

在创建数据表的时候记得需要把每一个元素之间添加逗号,不然创建数据表会失败,在表名的后面添加"(",然后再添加数据表元素

 

三.处理数据库的相关问题,当要升级数据库时,我们可以选择记录版本,然后再onUpgrade()中去升级,但是最好的方法就是直接删除数据库文件,然后再重新开始,这是在开发时的。

四.最后如果我们就要改用mDatabase来存储数据了,在原来的代码上删除与mCrime的代码,然后改用mDatabase来存储数据

而如果想要对数据库写入数据,那么就需要使用ContentValue类了,这个类就是用来用来写入和更新数据的,它是一个键值存储类,它只存储SQLite数据,

要将Crime记录转换成ContentValues,那么就是需要在CrimeLab中创建ContentValve实例,然后创建如下面的私有方法用来村粗数据,

private static ContentValues getContentValues(Crime crime) {
ContentValues values = new ContentValues();
values.put(CrimeTable.Cols.UUID,crime.getId().toString());
values.put(CrimeTable.Cols.TITLE,crime.getTitle());
values.put.(CrimeTable.Cols.DATE,crime.getDate());
values.put(CrimeTable.Cols.SOLVED,crime.isSolved()? 1 : 0);
return valves;
}

ContentValues的键创建的键就是数据表字段,不然会导致数据插入和更新失败,除了id由数据库自动创建外,其他的都需要代码来创建。

此时的添加一条数据就是等于,对数据库插入了一条数据,可以使用下面的代码:

public void addCrime(Crime c){
ContentValues values = getContentValues(c);
mDatabase.insert(CrimeTable.NAME,null,values);
}

第一个参数就是数据表名,第三个就是要写入的数据,第二个参数我们经常传入null

 

而如果想要用来更新列表,那么我们就是用下面的代码来

public void updateCrime(Crime crime){
String uuidString = crime.getId().toString();
ContentValues values = getContentValues(crime);
mDatebase.update(CrimeTable.NAME,values,CrimeTable.Cols.UUID + " = ?",new String[]{ uuidString});
}

 

对于这个方法来说,我们需要确认知道我们需要传入需要更新的记录,做法就是创建where子句,然后指定where子句的参数值,

最后当我们创建好数据表后,我们就需要在onPause()方法中去调用刷新CrimeLab中的Crime数据,如下面的代码

public void onPause(){
super.onPause();
CrimeLab.get(getActivity()).updateCrime(mCrime);
}

(3)而对于需要读取数据的时候,我们就需要使用到SQLiteDatabase.query()方法了query()方法由许多个,我们应该使用下面的方法。

public Cursor query (String table,String[] columns,String where,String[] whereArgs,String groupBy,String having,String orderBy,String limit)

我们应该在模型类中去调用这个方法来查询CrimeTable的记录,在这个模型类中新建一个私有方法

private Cursor queryCrimes(String whereClause,String[] whereArgs) {
Cursor cursor = mDatabase.query(
CrimeTable.NAME,null,whereClause,whereArgs,null,null,null
);
return cursor;
}

六.而对于从cursor获取数据来说,都需要有下面的代码的

String uuidString = cursor.getString(cursor.getColumnIndex(CrimeTable.Cols.UUID));这样的代码,每获取一条记录都需要有这样重复都太过重复了,那么我们应该创建专用的Cursor的子类,创建Cursor的子类的最简单的方法就是使用CursorWraapper

在数据库包中新建立CrimeCursorWrapper类

public class CrimeCursorWrapper extends CursorWrapper {
public CrimeCursorWrapper(Cursor cursor){
super(cursor);
}
}

上面创建了一个Cursor封装类,该类拥有Cursor的全部方法,这样封装的目的就是用来新创建新的方法,以方便操作内部的cursor

在该封装类中去增加一个getCrime()的方法

public Crime getCrime(){
String uuidString = getString(getColumnIndex(CrimeTable.Cols.UUID));
String title = getString(getColumnIndex(CrimeTable.Cols.TITLE));
return null;
}

(4)我们这边也需要返回具有UUID的Crime,所以我们需要在Crime.java中去添加一个有此用途的构造方法,

public Crime (UUID id){
mId = id;
mDate = new Date();
}

然后我们就去完成上面的在CrimeCursorWrapper的getCrime()

public Crime getCrime(){
String uuidString = getString(getColumnIndex(CrimeTable.Cols.UUID));
String title = getString(getColumnIndex(CrimeTable.Cols.UUID));
Crime crime = new Crime(UUID.fromString(uuidString));
crime.setTitle(title);
return crime;
 
}

七.而当使用CrimeCursorWrapper类后,我们就可以直接在CrimeLab中去取得List<Crime>,思路如下

我们将查询到的cursor封装到CrimeCursorWrapper类中,然后调用封装类的getCrime()方法遍历取出Crime

在CrimeLab中修改下面的方法

private CrimeCursorWrapper queryCrimes(String whereClause,String whereArgs) {
Cursor cursor = mDatabase.query(CrimeTable.NAME,null,whereClause,whereArgs,null,null,null);
return new CrimeCursorWrapper(cursor);
}

然后在CrimeLab类中去返回列表

public List<Crime> getCrimes(){
List<Crime> crimes = new ArrayList<>();
CrimeCursorWrapper  cursor = queryCrimes(null,null);
try{
cursor.moveToFirst();
while(!cursor.isAfterLast()){
crimes.add(cursor.getCrime());
cursor.moveToNext();
}
}finally{
cursor.close();
}
return crimes;
}

对于cursor来说,最后需要调用cursor的close()方法来关闭它

而对于CrimeLab来说,也可以通过getCrime(UUID id)方法来获得某条特定的书据的第一条,如下面的代码

public Crime getCrime(UUID id) {
CrimeCursorWrapper cursor = queryCrimes(CrimeTable.Cols.UUID + " = ?",new String[] { id.toString()});
try{
if(cursor.getCount() == 0){
return null;
}
cursor.moveToFirst();
return cursor.getCrime();
} finally {
cursor.close();
}
}

八.但此时模型层还需要刷新才会显示出来数据,这是因为虽然Crime记录已经存入数据库中,但是数据读取还没有完善,例如我们编辑好新的crime后,点击后退键,此时我们就会发现CrimeListActivity并没有更新刷新,原因在于之前只有一个List<Crime>,而且每一个Crime在List<Crime>只存在一个对象,要获取哪一个crime只能去寻找crimes,但现在crimes已经废弃不用了,而getCrimes()方法中的List<Crime>是Crime对象的快照,要刷新CrimeListActivity界面,首先就需要更新这个快照

 

要刷新crime显示,首先需要在CrimeListFragment类中添加下面的代码

public void setCrimes(List<Crime> crimes){
mCrimes = crimes;
}

然后在updateUI方法中去调用setCrimes()方法,如下面的代码

private void updateUIU(){
CrimeLab crimeLab = CrimeLab.get(getActivity());
List<Crime> crimes = crimeLab.getCrimes();
if(mAdapter == null){
mAdapter = new CrimeAdapter(crimes);
mCrimeRecyclerView.setAdapter(mAdapter);
} else{
mAdapter.setCrimes(crimes);
mAdapter.notifyDataSetChanged();
}
updateSubtitle();
}

这样后,当我们增加新的一条记录,然后后退键,就会发现在CrimeListActivity中已经出现了刚才增加的记录。

十.应用上下文作用,在前面,我们已经发现了在CrimeLab的构造方法中去使用了应用上下文,private CrimeLab(Context context) {

mContext = context.getApplicationContext();
}

应用上下文的话,只要有activity存在,那么Android就会创建了Application对象,用户在不同的activity之间导航时,activity有时消失了,但是application不会受到伤害的,CrimeLab是一个单例,这表明一旦创建了它就会一直存在,直到整个进程都消失,这边也可以看出CrimeLab引用着mContext对象,为了避免资源浪费,我们使用了应用上下文,