Android 数据存储

目录

  • 一、共享参数
    • 1.1 共享参数的用法
  • 二、数据库
    • 2.1 数据库管理器
    • 2.2 数据库帮助器
  • 三、存储卡的文件操作
    • 3.1 私有和公共存储空间
    • 3.2 读写文本文件
    • 3.3 读写图片文件
  • 四、应用组件Application
    • 4.1 Application的生命周期
    • 4.2 利用Application操作全局变量
    • 4.3 利用Room简化数据库操作

一、共享参数

SharedPreferences是Android的一个轻量级存储工具,它采用的存储结构是Key-Value的键值对方式,存储介质是XML文件,且以XML标记保存键值对。保存共享参数键值对信息的文件路为:/data/data/应用包名/shared_prefs/文件名.xml。
例如:


<map>
	<string name="name">Mr Leestring>
	<int nane="age" value="30"/>
	<boolean name="married" value="true" />
	<float name="weight" value="100.0"/>
map>

基于XML格式的特点,共享参数主要用于如下场合:
(1)简单且孤立的数据。若是复杂且相互关联的数据,则要保存于关系数据库。
(2)文本形式的数据。若是二进制数据,则要保存至文件。
(3)需要持久化存储的数据。App退出后再次启动时,之前保存的数据仍然有效。

1.1 共享参数的用法

共享参数类似map集合,也有get和put方法。
第一步:获取共享参数实例:

SharedPreferences preferences = getSharedPreferences("config", Context.MODE_PRIVATE);

上面的config就是文件名,如果没有会自动创建,MODE_PRIVATE是私有模式,只有当前app可以访问。
第二步:写入文件

//获取编辑器
SharedPreferences.Editor edit = preferences.edit();
edit.putString("name",name);
edit.putInt("age",Integer.parseInt(age));
edit.putFloat("height",Float.parseFloat(height));
edit.putFloat("weight",Float.parseFloat(weight));
edit.putBoolean("married",cb_married.isChecked());
//提交编辑器中的修改
edit.commit();

第三步:读取config文件

String name = preferences.getString("name", null);
int age = preferences.getInt("age", 0);
Float height = preferences.getFloat("height", 0f);
Float weight = preferences.getFloat("weight", 0f);
Boolean married = preferences.getBoolean("married", false);

二、数据库

2.1 数据库管理器

SQLiteDatabase是Android提供的SQLite数据库管理器,开发者可以在活动页面代码调用openOrCreateDatabase方法获取数据库实例,参考代码如下:

// 创建名为test.db的数据库。数据库如果不存在就创建它,如果存在就打开它
SQLiteDatabase db = openOrCreateDatabase(getFilesDir() + "/test.db",Context.MODE_PRIVATE, null);
String desc = String.format("数据库%s创建%s", db.getPath(), (db!=null)?"成功":"失败");
tv_database.setText(desc);
// deleteDatabase(getFilesDir() + "/test.db"); // 删除名为test.db数据库

获得数据库实例之后,就能对该数据库开展各项操作了。数据库管理器SQLiteDatabase提供了若干操作数据表的API,常用的方法有3类,列举如下:

1.管理类,用于数据库层面的操作

  • openDatabase:打开指定路径的数据库。
  • isOpen:判断数据库是否已打开。
  • close:关闭数据库。
  • getVersion:获取数据库的版本号。
  • setVersion:设置数据库的版本号。

2.事务类,用于事务层面的操作

  • beginTransaction:开始事务。
  • setTransactionSuccessful:设置事务的成功标志。
  • endTransaction:结束事务。执行本方法时,系统会判断之前是否调用了
  • setTransactionSuccessful方法,如果之前已调用该方法就提交事务,如果没有调用该方法就回滚
    事务。

3.数据处理类,用于数据表层面的操作

  • execSQL:执行拼接好的SQL控制语句。一般用于建表、删表、变更表结构。
  • delete:删除符合条件的记录。
  • update:更新符合条件的记录信息。
  • insert:插入一条记录。
  • query:执行查询操作,并返回结果集的游标。
  • rawQuery:执行拼接好的SQL查询语句,并返回结果集的游标。

2.2 数据库帮助器

Android提供了数据库帮助器SQLiteOpenHelper,帮助开发者合理使用SQLite。
SQLiteOpenHelper的具体使用步骤如下:

步骤一,新建一个继承自SQLiteOpenHelper的数据库操作类,按提示重写onCreate和onUpgrade两个
方法。其中,onCreate方法只在第一次打开数据库时执行,在此可以创建表结构;而onUpgrade方法在数据库版本升高时执行,在此可以根据新旧版本号变更表结构。

步骤二,为保证数据库安全使用,需要封装几个必要方法,包括获取单例对象、打开数据库连接、关闭数据库连接,说明如下:

  • 获取单例对象:确保在App运行过程中数据库只会打开一次,避免重复打开引起错误。
  • 打开数据库连接:SQLite有锁机制,即读锁和写锁的处理;故而数据库连接也分两种,读连接可调用getReadableDatabase方法获得,写连接可调用getWritableDatabase获得。
  • 关闭数据库连接:数据库操作完毕,调用数据库实例的close方法关闭连接。

查询结果会返回一个游标类Cursor,Cursor的常用方法可分为3类,说明如下:

1.游标控制类方法,用于指定游标的状态

  • close:关闭游标。
  • isClosed:判断游标是否关闭。
  • isFirst:判断游标是否在开头。
  • isLast:判断游标是否在末尾。

2.游标移动类方法,把游标移动到指定位置

  • moveToFirst:移动游标到开头。
  • moveToLast:移动游标到末尾。
  • moveToNext:移动游标到下一条记录。
  • moveToPrevious:移动游标到上一条记录。
  • move:往后移动游标若干条记录。
  • moveToPosition:移动游标到指定位置的记录。

3.获取记录类方法,可获取记录的数量、类型以及取值

  • getCount:获取结果记录的数量。
  • getInt:获取指定字段的整型值。
  • getLong:获取指定字段的长整型值。
  • getFloat:获取指定字段的浮点数值。
  • getString:获取指定字段的字符串值。
  • getType:获取指定字段的字段类型。

SQLite帮助类

public class UserDBHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "user.db";
    private static final String TABLE_NAME = "user_info";
    private static final int DB_VERSION = 2;
    private static UserDBHelper mHelper = null;
    private static SQLiteDatabase mRDB = null;
    private static SQLiteDatabase mWDB = null;

    public UserDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }
    //单例模式
    public static UserDBHelper getInstance(Context context){
        if (mHelper == null) {
            mHelper = new UserDBHelper(context);
        }
        return mHelper;
    }
    //打开数据库的读连接
    public SQLiteDatabase openReadLink(){
        if (mRDB == null || !mRDB.isOpen()) {
            mRDB = mHelper.getReadableDatabase();
        }
        return mRDB;
    }
    //打开数据库的写连接
    public SQLiteDatabase openWriteLink(){
        if (mWDB == null || !mWDB.isOpen()) {
            mWDB = mHelper.getWritableDatabase();
        }
        return mWDB;
    }
    //关闭数据库连接
    public void closeLink(){
        if (mRDB!=null && mRDB.isOpen()){
            mRDB.close();
            mRDB = null;
        }
        if (mWDB!=null && mWDB.isOpen()){
            mWDB.close();
            mWDB = null;
        }
    }
    //创建数据库,执行建表语句
    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS "+TABLE_NAME+" (" +
                "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                "name VARCHAR NOT NULL ," +
                "age INTEGER NOT NULL," +
                "height LONG NOT NULL," +
                "weight FLOAT NOT NULL," +
                "married INTEGER NOT NULL);";
        db.execSQL(sql);
    }
    //数据库版本号DB_VERSION更新时执行
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql1 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN phone VARCHAR";
        db.execSQL(sql1);
        String sql2 = "ALTER TABLE "+TABLE_NAME+" ADD COLUMN password VARCHAR";
        db.execSQL(sql2);
    }
    //插入
    public long insert(User user){
        ContentValues values = new ContentValues();
        values.put("name",user.name);
        values.put("age",user.age);
        values.put("height",user.height);
        values.put("weight",user.weight);
        values.put("married",user.married);
        //执行插入记录动作,该语句返回插入记录的行号
        //如果第三个参数values为Nul1或者元素 个数为0,由 于insert()方法要求必须添加一条除 了主键之外其它字段为Null值的记录,
        //为了满足sQL语法的需要,insert 语句必须给定一个字段名 ,如: insert into person (name) values (NULL)
        // 倘若不给定字段名 ,insert语句就成 J这样: insert into person() values(), 显然这不满足标准SQL,
        //.如果第三个参数values不为Nu11并且元素的个数大于0,可以把第二个参数设置为null。
        try {
            //开始事务
            mWDB.beginTransaction();
            //int num = 10/ 0;
            mWDB.insert(TABLE_NAME,null,values);
            //标识成功,表示前面代码没有报错
            mWDB.setTransactionSuccessful();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //结束事务
            mWDB.endTransaction();
        }
        return 1;
    }
    //删除
    public long deleteByName(String name){
       return mWDB.delete(TABLE_NAME,"name = ?",new String[]{name});
    }
    //更新
    public long update(User user){
        ContentValues values = new ContentValues();
        values.put("name",user.name);
        values.put("age",user.age);
        values.put("height",user.height);
        values.put("weight",user.weight);
        values.put("married",user.married);
        return mWDB.update(TABLE_NAME,values,"name=?",new String[]{user.name});
    }
    //查询所有
    public List<User> queryAll(){
        List<User> list = new ArrayList<>();
        //执行记录查询动作,该语句返回结果集的游标
        Cursor cursor = mRDB.query(TABLE_NAME, null, null, null, null, null, null);
        //循环取出游标指向的每条记录
        while (cursor.moveToNext()){
            User user = new User();
            user.id = cursor.getInt(0);
            user.name = cursor.getString(1);
            user.age = cursor.getInt(2);
            user.height = cursor.getLong(3);
            user.weight = cursor.getFloat(4);
            //SQLite没有布尔型,用0表示false, 用1表示true
            user.married = cursor.getInt(5)==0?false:true;
            list.add(user);
        }
        return list;
    }
}

三、存储卡的文件操作

3.1 私有和公共存储空间

系统给每个App都分配了默认的私有存储空间。App在私有空间上读写文件无须任何授权,但是若想在公共空间读写文件,则要在AndroidManifest.xml里面添加下述的权限配置。

    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

上面虽然设置了读写权限,但是app的存储权限设置中仍然是禁止的,要去手动打开。


既然存储卡分为公共空间和私有空间两部分,它们的空间路径获取也就有所不同。
若想获取公共空间的存储路径,调用的是Environment.getExternalStoragePublicDirectory方法;
若想获取应用私有空间的存储路径,调用的是getExternalFilesDir方法。
下面是分别获取两个空间路径的代码例子:

// 获取系统的公共存储路径
String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
// 获取当前App的私有存储路径
String privatePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();

boolean isLegacy = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
	// Android10的存储空间默认采取分区方式,此处判断是传统方式还是分区方式
	isLegacy = Environment.isExternalStorageLegacy();
}
String desc = "系统的公共存储路径位于" + publicPath +
"\n\n当前App的私有存储路径位于" + privatePath +
"\n\nAndroid7.0之后默认禁止访问公共存储目录" +
"\n\n当前App的存储空间采取" + (isLegacy?"传统方式":"分区方式");
tv_path.setText(desc);

3.2 读写文本文件

首先创建一个写文件的工具类,进行封装:

public class FileUtil {
    //把字符串保存到指定路径的文本文件.
    public static void saveText(String path,String txt){
        BufferedWriter bw = null;
        try{
            bw = new BufferedWriter(new FileWriter(path));
            bw.write(txt);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (bw != null) {
                try{
                    bw.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    //从指定路径的文本文件中读取内容字符串
    public static String openText(String path){
        BufferedReader br = null;
        StringBuilder builder = new StringBuilder();
        try{
            br = new BufferedReader(new FileReader(path));
            String line = null;
            while ((line = br.readLine())!=null){
                builder.append(line);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (br != null) {
                try{
                    br.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return builder.toString();
    }
}

app的主要java代码:

//外部存储的公共空间
directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
//内部存储的私有空间
path = directory + File.separatorChar + fileName;
Log.d("path",path);
FileUtil.saveText(path,sb.toString());
ToastUtil.show(this,"保存成功");

3.3 读写图片文件

文本文件读写可以转换为对字符串的读写,而图片文件保存的是图像数据,需要专门的位图工Bitmap处理。位图对象依据来源不同又分成3种获取方式,分别对应位图工厂BitmapFactory的下列3种方法:

  • decodeResource:从指定的资源文件中获取位图数据。例如下面代码表示从资源文件huawei.png
    获取位图对象:
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.huawei);
    
  • decodeFile:从指定路径的文件中获取位图数据。注意从Android 10开始,该方法只适用于私有目录下的图片,不适用公共空间下的图片。
  • decodeStream:从指定的输入流中获取位图数据。比如使用IO流打开图片文件,此时文件输入流
    对象即可作为decodeStream方法的入参,相应的图片读取代码如下:
// 从指定路径的图片文件中读取位图数据
public static Bitmap openImage(String path) {
	Bitmap bitmap = null; // 声明一个位图对象
	// 根据指定的文件路径构建文件输入流对象
	try (FileInputStream fis = new FileInputStream(path)) {
		bitmap = BitmapFactory.decodeStream(fis); // 从文件输入流中解码位图数据
	} catch (Exception e) {
		e.printStackTrace();
	}
	return bitmap; // 返回图片文件中的位图数据
}

得到位图对象之后,就能在图像视图上显示位图。图像视图ImageView提供了下列方法显示各种来源的图片:

  • setImageResource:设置图像视图的图片资源,该方法的入参为资源图片的编号,形如"R.drawable.去掉扩展名的图片名称”。
  • setImageBitmap:设置图像视图的位图对象,该方法的入参为Bitmap类型。
  • setImageURI:设置图像视图的路径对象,该方法的入参为Uri类型。字符串格式的文件路径可通过代码“Uri.parse(file_path)”转换成路径对象。

读文件的操作很多,但写入文件的方式只有通过位图对象的compress方法将位图数据压缩到文件输出流:

 	//把位图数据保存到指定路径的图片文件
    public static void saveImage(String path, Bitmap bitmap) {
        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream(path);
            //把位图数据压缩到文件输出流中
            bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (fos != null) {
                try{
                    fos.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

读取图片:

	//从指定路径的图片文件中读取位图数据
    public static Bitmap readImage(String path) {
        Bitmap bitmap = null;
        FileInputStream fis = null;
        try{
            fis = new FileInputStream(path);
            bitmap = BitmapFactory.decodeStream(fis);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (fis != null) {
                try{
                    fis.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }

app代码:

public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_save:
                String fileName = System.currentTimeMillis() + ".jpeg";
                //获取当前App的私有下载目录
                path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + File.pathSeparator + fileName;
                Log.d("path",path);
                //从指定的资源文件中获取位图对象
                Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
                //把位图对象保存为图片文件
                FileUtil.saveImage(path,b1);
                ToastUtil.show(this,"保存成功");
                break;
            case R.id.btn_read:
                //第一种方式读取
                Bitmap b2 = FileUtil.readImage(path);
                iv_image.setImageBitmap(b2);
                //第二种方式读取
                //Bitmap b3 = BitmapFactory.decodeFile(path);
                //iv_image.setImageBitmap(b3);
                //第三种方式:直接调用setImageURI方法,设置图像视图的路径对象
                //iv_image.setImageURI(Uri.parse(path));
                break;
        }
    }

四、应用组件Application

4.1 Application的生命周期

Application是Android的一大组件,在App运行过程中有且仅有一个Application对象贯穿应用的整个生
命周期。打开AndroidManifest.xml,发现activity节点的上级正是application节点,不过该节点并未指
定name属性,此时App采用默认的Application实例。

自定义一个类继承Application类:

public class MyApplication extends Application {
    //单例对象
    private static MyApplication mApp;
    public static MyApplication getInstance(){
        return mApp;
    }
	//在App启动时调用
    public void onCreate() {
        super.onCreate();
        mApp = this;
    }
	//在App终止时调用,只用于虚拟环境,在开发设备中默认会删除这个方法,不被回调
    public void onTerminate() {
        super.onTerminate();
        Log.d("MyApplication","onTerminate");
    }
    //在配置改变时调用,例如从竖屏变为横屏。
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d("MyApplication","onConfigurationChanged");
    }

}

继承Application,继承之后可供重写的方法主要有以下3个:

  • onCreate:在App启动时调用。
  • onTerminate:在App终止时调用(按字面意思)。
  • onConfigurationChanged:在配置改变时调用,例如从竖屏变为横屏。

修改AndroidManifest.xml,设置application节点的name属性为MyApplication,此时app采用的是我们自己定义的application

<application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestMyApplicaion">
</application>

4.2 利用Application操作全局变量

适合在Application中保存的全局变量主要有下面3类数据:
(1)会频繁读取的信息,例如用户名、手机号码等。
(2)不方便由意图传递的数据,例如位图对象、非字符串类型的集合对象等。
(3)容易因频繁分配内存而导致内存泄漏的对象,例如Handler处理器实例等。

由于全局变量操作的是内存,所以速度比较快
下面演示如何使用全局变量去存储一个人的信息:
先在MyApplication中定义一个map集合用于存储数据

public class MyApplication extends Application {
    //单例对象
    private static MyApplication mApp;
    //声明一个公共的信息映射对象,可当作全局变量使用
    public HashMap<String,String> infoMap = new HashMap<>();
    public static MyApplication getInstance(){
        return mApp;
    }
	//在App启动时调用
    public void onCreate() {
        super.onCreate();
        mApp = this;
    }
	//在App终止时调用,只用于虚拟环境,在开发设备中默认会删除这个方法,不被回调
    public void onTerminate() {
        super.onTerminate();
        Log.d("MyApplication","onTerminate");
    }
    //在配置改变时调用,例如从竖屏变为横屏。
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d("MyApplication","onConfigurationChanged");
    }

}

读写操作使用:

//获取MyApplication实例
MyApplication app = MyApplication.getInstance();
//写入数据
String name = et_name.getText().toString();
String age = et_age.getText().toString();
String height = et_height.getText().toString();
String weight = et_weight.getText().toString();
//将数据保存到application中
app.infoMap.put("name",name);
app.infoMap.put("age",age);
app.infoMap.put("height",height);
app.infoMap.put("weight",weight);
app.infoMap.put("married",cb_married.isChecked()?"是":"否");


//从application中获取数据
String name = app.infoMap.get("name");
if (name == null){
    return;
}
String age = app.infoMap.get("age");
String height = app.infoMap.get("height");
String weight = app.infoMap.get("weight");
String married = app.infoMap.get("married");

et_name.setText(name);
et_age.setText(age);
et_height.setText(height);
et_weight.setText(weight);
if ("是".equals(married)){
    cb_married.setChecked(true);
}else{
    cb_married.setChecked(false);
}

4.3 利用Room简化数据库操作

虽然Android提供了数据库帮助器,但是开发者在进行数据库编程时仍有诸多不便,比如每次增加一张
新表,开发者都得手工实现代码逻辑。

数据库框架—Room,该框架同样基于SQLite,但它通过注解技术极大地简化了数据库操作,减少了原来相当一部分编码工作量。因为是第三方框架所以要在build.gradle中引入依赖:
dependencies节点添加下面两行配置,表示导入指定版本的Room库

implementation 'androidx.room:room-runtime:2.5.2'
annotationProcessor 'androidx.room:room-compiler:2.5.2'

导入Room库之后,还要编写若干对应的代码文件。以录入图书信息为例,此时要对图书信息表进行增
删改查,则具体的编码过程分为下列5个步骤:

1.编写图书信息表对应的实体类
假设图书信息类名为BookInfo,且它的各属性与图书信息表的各字段一一对应,那么要给该类添加
“@Entity”注解,表示该类是Room专用的数据类型,对应的表名称也叫BookInfo。如果BookInfo表的
name字段是该表的主键,则需给BookInfo类的name属性添加“@PrimaryKey”与“@NonNull”两个注
解,表示该字段是个非空的主键。

//书籍信息
@Entity
public class BookInfo {
	@PrimaryKey // 该字段是主键,不能重复
	@NonNull // 主键必须是非空字段
	private String name; // 书籍名称
	private String author; // 作者
	private String press; // 出版社
	private double price; // 价格
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	public String getAuthor() {
		return this.author;
	}
	public void setPress(String press) {
		this.press = press;
	}
	public String getPress() {
		return this.press;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public double getPrice() {
		return this.price;
	}
}

2.编写图书信息表对应的持久化类
所谓持久化,指的是将数据保存到磁盘而非内存,其实等同于增删改等SQL语句。要在接口上添加@Dao注解

@Dao
public interface BookDao {
	@Query("SELECT * FROM BookInfo") // 设置查询语句
	List<BookInfo> queryAllBook(); // 加载所有书籍信息
	@Query("SELECT * FROM BookInfo WHERE name = :name") // 设置带条件的查询语句
	BookInfo queryBookByName(String name); // 根据名字加载书籍
	@Insert(onConflict = OnConflictStrategy.REPLACE) // 记录重复时替换原记录
	void insertOneBook(BookInfo book); // 插入一条书籍信息
	@Insert
	void insertBookList(List<BookInfo> bookList); // 插入多条书籍信息
	@Update(onConflict = OnConflictStrategy.REPLACE)// 出现重复记录时替换原记录
	int updateBook(BookInfo book); // 更新书籍信息
	@Delete
	void deleteBook(BookInfo book); // 删除书籍信息
	@Query("DELETE FROM BookInfo WHERE 1=1") // 设置删除语句
	void deleteAllBook(); // 删除所有书籍信息
}

3.编写图书信息表对应的数据库类
因为先有数据库然后才有表,所以图书信息表还得放到某个数据库里,这个默认的图书数据库要从
RoomDatabase派生而来,并添加“@Database”注解。

//entities表示该数据库有哪些表,version表示数据库的版本号
//exportSchema表示是否导出数据库信息的json串,建议设为false,若设为true还需指定json文件的保存路径
@Database(entities = {BookInfo.class},version = 1, exportSchema = false)
public abstract class BookDatabase extends RoomDatabase {
	// 获取该数据库中某张表的持久化对象
	public abstract BookDao bookDao();
}

4.在自定义的Application类中声明图书数据库的唯一实例
为了避免重复打开数据库造成的内存泄漏问题,每个数据库在App运行过程中理应只有一个实例,此时要求开发者自定义新的Application类,在该类中声明并获取图书数据库的实例,并将自定义的
Application类设为单例模式,保证App运行之时有且仅有一个应用实例。

public class MainApplication extends Application {
	private final static String TAG = "MainApplication";
	private static MainApplication mApp; // 声明一个当前应用的静态实例
	// 声明一个公共的信息映射对象,可当作全局变量使用
	public HashMap<String, String> infoMap = new HashMap<String, String>();
	private BookDatabase bookDatabase; // 声明一个书籍数据库对象
	// 利用单例模式获取当前应用的唯一实例
	public static MainApplication getInstance() {
		return mApp;
	}
	@Override
	public void onCreate() {
		super.onCreate();
		Log.d(TAG, "onCreate");
		mApp = this; // 在打开应用时对静态的应用实例赋值
		// 构建书籍数据库的实例
		bookDatabase = Room.databaseBuilder(mApp, BookDatabase.class,"BookInfo")
						.addMigrations() // 允许迁移数据库(发生数据库变更时,Room默认删除原数据库再创建新数据库。如此一来原来的记录会丢失,故而要改为迁移方式以便保存原有记录)
						.allowMainThreadQueries() // 允许在主线程中操作数据库(Room默认不能在主
						线程中操作数据库)
						.build();
	}
	// 获取书籍数据库的实例
	public BookDatabase getBookDB(){
		return bookDatabase;
	}
}

5.在操作图书信息表的地方获取数据表的持久化对象

// 从App实例中获取唯一的图书持久化对象
BookDao bookDao = MainApplication.getInstance().getBookDB().bookDao();

你可能感兴趣的:(Android,android)