room

title: 数据存储

共享参数SharedPreferences

实际开发中,共享参数经常存储的数据包括:App的个性化配置信息、用户使用App的行为信息、临时需要保存的片段信息等。(如记住密码)

本节介绍Android的键值对存储方式——共享参数SharedPreferences的使用方法,包括:如何将数据保

存到共享参数,如何从共享参数读取数据,如何使用共享参数实现登录页面的记住密码功能,如何利用

设备浏览器找到共享参数文件。

SharedPreferences是Android的一个轻量级存储工具,它采用的存储结构是Key-Value的键值对方式,

类似于Java的Properties,二者都是把Key-Value的键值对保存在配置文件中。

不同的是,Properties的文件内容形如Key=Value,而SharedPreferences的存储介质是XML文件,且以XML标记保存键值对。保存共享参数键值对信息的文件路径为:/data/data/应用包名/shared_prefs/文件名.xml。下面是一个共享参数的XML文件例子

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="name">Mr Lee</string>
    <int nane="age" value="30"/>
    <boolean name="married" value="true" />
    <float name="weight" value="100.0"/>
</map>

基于XML格式的特点,共享参数主要用于如下场合:

(1)简单且孤立的数据。若是复杂且相互关联的数据,则要保存于关系数据库。

(2)文本形式的数据。若是二进制数据,则要保存至文件。

(3)需要持久化存储的数据。App退出后再次启动时,之前保存的数据仍然有效。

共享参数对数据的存储和读取操作类似于Map,也有存储数据的put方法,以及读取数据的get方法。

调用getSharedPreferences方法可以获得共享参数实例,获取代码示例如下:

// 从share.xml获取共享参数实例
SharedPreferences shared = getSharedPreferences("share", MODE_PRIVATE);

由以上代码可知,getSharedPreferences方法的第一个参数是文件名,填share表示共享参数的文件名

是share.xml;第二个参数是操作模式,填MODE_PRIVATE表示私有模式。

往共享参数存储数据要借助于Editor类,保存数据的代码示例如下:

SharedPreferences.Editor editor = shared.edit(); // 获得编辑器的对象
editor.putString("name", "Mr Lee"); // 添加一个名为name的字符串参数
editor.putInt("age", 30); // 添加一个名为age的整型参数
editor.putBoolean("married", true); // 添加一个名为married的布尔型参数
editor.putFloat("weight", 100f); // 添加一个名为weight的浮点数参数
editor.commit(); // 提交编辑器中的修改

从共享参数读取数据相对简单,直接调用共享参数实例的get * * * 方法即可读取键值,注意 get***方法

的第二个参数表示默认值,读取数据的代码示例如下:

String name = shared.getString ( "name.","");//从共享参数获取名为name的字符串
int age = shared.getInt ("age",0);// 从共享参数获取名为age 的整型数
boolean married = shared.getBoolean ( "married", false);//从共享参数获取名为married
的布尔数
float weight = shared.getFloat ( "weight",0);//从共享参数获取名为weight的浮点数

实现记住密码功能

(1)声明一个共享参数对象,并在onCreate中调用getSharedPreferences方法获取共享参数的实例。

(2)登录成功时,如果用户勾选了“记住密码”,就使用共享参数保存手机号码与密码。也就是在

loginSuccess方法中增加以下代码:

// 如果勾选了“记住密码”,就把手机号码和密码都保存到共享参数中
if (isRemember) {
SharedPreferences.Editor editor = mShared.edit(); // 获得编辑器的对象
editor.putString("phone", et_phone.getText().toString()); // 添加名叫phone的手
机号码
editor.putString("password", et_password.getText().toString()); // 添加名叫
password的密码
editor.commit(); // 提交编辑器中的修改
}

(3)再次打开登录页面时,App从共享参数读取手机号码与密码,并自动填入编辑框。也就是在

onCreate方法中增加以下代码:

// 从share_login.xml获取共享参数对象
mShared = getSharedPreferences("share_login", MODE_PRIVATE);
// 获取共享参数保存的手机号码
String phone = mShared.getString("phone", "");
// 获取共享参数保存的密码
String password = mShared.getString("password", "");
et_phone.setText(phone); // 往手机号码编辑框填写上次保存的手机号
et_password.setText(password); // 往密码编辑框填写上次保存的密码

Application

本节介绍Android重要组件Application的基本概念和常见用法。首先说明Application的生命周期贯穿了

App的整个运行过程,接着利用Application实现App全局变量的读写,然后阐述了如何借助App实例来

操作Room数据库框架。

生命周期

Application是Android的一大组件,在App运行过程中有且仅有一个Application对象贯穿应用的整个生

命周期。打开AndroidManifest.xml,发现activity节点的上级正是application节点,不过该节点并未指

定name属性,此时App采用默认的Application实例。

注意到每个activity节点都指定了name属性,譬如常见的name属性值为.MainActivity,让人知晓该

activity的入口代码是MainActivity.java。现在尝试给application节点加上name属性,看看其庐山真面

目,具体步骤说明如下:

(1)打开AndroidManifest.xml,给application节点加上name属性,表示application的入口代码是

MainApplication.java。修改后的application节点示例如下:

<application
android:name=".MainApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">

(2)在Java代码的包名目录下创建MainApplication.java,要求该类继承Application,继承之后可供重

写的方法主要有以下3个。

  • onCreate:在App启动时调用。

  • onTerminate:在App终止时调用(按字面意思)。

  • onConfigurationChanged:在配置改变时调用,例如从竖屏变为横屏。

需要注意的是onTerminate永远不会执行,不能用它回收资源

Application读写全局变量

C/C++有全局变量的概念,因为全局变量保存在内存中,所以操作全局变量就是操作内存,显然内存的读写速度远比读写数据库或读写文件快得多。所谓全局,指的是其他代码都可以引用该变量,因此全局变量是共享数据和消息传递的好帮手。

不过Java没有全局变量的概念,与之比较接近的是类里面的静态成员变量,该变量不但能被外部直接引用,而且它在不同地方引用的值是一样的(前提是在引用期间不能改动变量值),所以借助静态成员变量也能实现类似全局变量的功能。

Application的生命周期覆盖了App运行的全过程。不像短暂的Activity生命周期,一旦退出该页面,Activity实例就被销毁。因此,利用Application的全生命特性,能够在Application实例中保存全局变量。适合在Application中保存的全局变量主要有下面3类数据:

(1)会频繁读取的信息,例如用户名、手机号码等。

(2)不方便由意图传递的数据,例如位图对象、非字符串类型的集合对象等。

(3)容易因频繁分配内存而导致内存泄漏的对象,例如Handler处理器实例等。比如room

要想通过Application实现全局内存的读写,得完成以下3项工作:

(1)编写一个继承自Application的新类MyApplication。该类采用单例模式,内部先声明自身类的一

个静态成员对象,在创建App时把自身赋值给这个静态对象,然后提供该对象的获取方法getInstance。

public class MyApplication extends Application {
    private final static String TAG = "MainApplication";
    private static MainApplication mApp; // 声明一个当前应用的静态实例
    // 声明一个公共的信息映射对象,可当作全局变量使用
    public HashMap<String, String> infoMap = new HashMap<String, String>();
    // 利用单例模式获取当前应用的唯一实例
    public static MainApplication getInstance() {return mApp;}
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        mApp = this; // 在打开应用时对静态的应用实例赋值
    }
}

(2)在活动页面代码中调用MyApplication的getInstance方法,获得它的一个静态对象,再通过该对

象访问MyApplication的公共变量和公共方法。

(3)不要忘了在AndroidManifest.xml中注册新定义的Application类名,也就是给application节点增

加android:name属性,其值为.MainApplication。

接下来演示如何读写内存中的全局变量,首先分别创建写内存页面和读内存页面,其中写内存页面把用户的注册信息保存到全局变量infoMap,而读内存页面从全局变量infoMap读取用户的注册信息

public class AppWriteActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name;
    private EditText et_age;
    private EditText et_height;
    private EditText et_weight;
    private CheckBox ck_married;
    private MyApplication app;

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

        et_name = findViewById(R.id.et_name);
        et_age = findViewById(R.id.et_age);
        et_height = findViewById(R.id.et_height);
        et_weight = findViewById(R.id.et_weight);
        ck_married = findViewById(R.id.ck_married);
 //      此处注意执行顺序,先进行了保存
        findViewById(R.id.btn_save).setOnClickListener(this);

        app = MyApplication.getInstance();
        reload();
    }

    private void reload() {
        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)) {
            ck_married.setChecked(true);
        } else {
            ck_married.setChecked(false);
        }
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        String age = et_age.getText().toString();
        String height = et_height.getText().toString();
        String weight = et_weight.getText().toString();

        app.infoMap.put("name", name);
        app.infoMap.put("age", age);
        app.infoMap.put("height", height);
        app.infoMap.put("weight", weight);
        app.infoMap.put("married", ck_married.isChecked() ? "是" : "否");
    }

Room

由于Room并未集成到SDK中,而是作为第三方框架提供,因此要修改模块的build.gradle文件,往dependencies节点添加下面两行配置,表示导入指定版本的Room库:

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

导入Room库之后,还要编写若干对应的代码文件。以录入图书信息为例,此时要对图书信息表进行增

删改查,则具体的编码过程分为下列5个步骤:

1.编写图书信息表对应的实体类

假设图书信息类名为BookInfo,且它的各属性与图书信息表的各字段一一对应,那么要给该类添加

“@Entity”注解,表示该类是Room专用的数据类型,对应的表名称也叫BookInfo。如果BookInfo表的

name字段是该表的主键,则需给BookInfo类的name属性添加“@PrimaryKey”与“@NonNull”两个注

解,表示该字段是个非空的主键。下面是BookInfo类的定义代码例子:

//书籍信息
@Entity
@Data //生成get set
public class BookInfo {
@PrimaryKey // 该字段是主键,不能重复
@NonNull // 主键必须是非空字段
private String name; // 书籍名称
private String author; // 作者
private String press; // 出版社
private double price; // 价格
}

2.编写图书信息表对应的持久化类

所谓持久化,指的是将数据保存到磁盘而非内存,其实等同于增删改等SQL语句。假设图书信息表的持

久化类名叫作BookDao,那么该类必须添加“@Dao”注解,内部的记录查询方法必须添加“@Query”注

解,记录插入方法必须添加“@Insert”注解,记录更新方法必须添加“@Update”注解,记录删除方法必须

添加“@Delete”注解(带条件的删除方法除外)。对于记录查询方法,允许在@Query之后补充具体的查询语句以及查询条件;对于记录插入方法与记录更新方法,需明确出现重复记录时要采取哪种处理策略。下面是BookDao类的定义代码例子:

@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”注解。下面是数据库类BookDatabase的定义代码例子:

//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运行之时有且仅有一个应用实例。下面是自定义Application类的代码例子:

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();

完成以上5个编码步骤之后,接着调用持久化对象的queryXXX、insertXXX、updateXXX、deleteXXX等

方法,就能实现图书信息的增删改查操作了。例程的图书信息演示页面有两个,分别是记录保存页面和

记录读取页面,其中记录保存页面通过insertOneBook方法向数据库添加图书信息。

posted @ 2023-04-22 18:07  ZZX11  阅读(35)  评论(0编辑  收藏  举报