数据持久化与分布式存储


1 数据持久化

需要被持久化的数据分为两种类型:

2 基于数据库的数据持久化

鸿蒙系统提供四种基于数据库的数据持久化方式:

数据库 结构化数据 非结构化数据
关系型数据库(RDB) ×
对象关系映射(ORM) ×
应用偏好数据库 ×
分布式数据库

2.1 关系型数据库(RDB)

2.1.1 连接数据库

graph LR rdbstore(RdbStore) databasehelper(DatabaseHelper) storeconfig(StoreConfig) rdbopencallback(RdbOpenCallback) rdb(rdb) helper(helper) getRdbStore(getRdbStore) config(config) 1(1) callback(callback) semicolon(";") rdb_desc(增删改查等操作) helper_desc(本地数据库) config_desc(数据库信息) 1_desc(数据库版本) callback_desc("创建、打开、升降级、损坏等回调") rdbstore-->rdb databasehelper-->helper storeconfig-->config rdbopencallback-->callback rdb---|=|helper---|.|getRdbStore---|"("|config---|,|1---|,|callback---|")"|semicolon rdb-->rdb_desc helper-->helper_desc config-->config_desc 1-->1_desc callback-->callback_desc
DatabaseHelper helper = new DatabaseHelper(this);

StoreConfig config = StoreConfig
    .newDefaultConfig("tarena.sqlite");

rdb = helper.getRdbStore(config, 1, new RdbOpenCallback() {
    @Override
    public void onCreate(RdbStore rdbStore) {
        rdbStore.executeSql(
            "CREATE TABLE IF NOT EXISTS t_student(" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "name TEXT NOT NULL, " +
                "age INTEGER, " +
                "sex TINYINT, " +
                "subject TEXT)");
    }

    @Override
    public void onUpgrade(RdbStore rdbStore, int i, int i1) {
    }
});

2.1.2 插入数据

ValuesBucket student = new ValuesBucket();
student.putString("name", "张飞");
student.putInteger("age", 22);
student.putInteger("sex", 1);
student.putString("subject", "C++");

long id = rdb.insert("t_student", student);
if (id == -1)
    showToast("插入数据失败");
else
    showToast("插入数据成功(" + id + ")");

2.1.3 批量插入数据

List<ValuesBucket> students = new ArrayList<>();

ValuesBucket student1 = new ValuesBucket();
student1.putString("name", "貂蝉");
student1.putInteger("age", 18);
student1.putInteger("sex", 0);
student1.putString("subject", "Java");
students.add(student1);

ValuesBucket student2 = new ValuesBucket();
student2.putString("name", "曹操");
student2.putInteger("age", 30);
student2.putInteger("sex", 1);
student2.putString("subject", "Java");
students.add(student2);

List<Long> ids = rdb.batchInsertOrThrowException(
    "t_student", students, ConflictResolution.ON_CONFLICT_ABORT);
for (long id : ids)
    if (id == -1)
        showToast("插入数据失败");
    else
        showToast("插入数据成功(" + id + ")");

2.1.4 查询数据

RdbPredicates predicates = new RdbPredicates(
    "t_student").orderByAsc("id");
String[] columns = new String[] {
    "id", "name", "age", "sex", "subject"};

ResultSet result = rdb.query(predicates, columns);
if (result == null)
    showToast("查询数据失败");
else {
    showToast("查询数据成功(" + result.getRowCount() + ")");

    while (result.goToNextRow()) {
        int id = result.getInt(0);
        String name = result.getString(1);
        int age = result.getInt(2);
        int sex = result.getInt(3);
        String subject = result.getString(4);

        HiLog.info(label, "%{public}d, %{public}s, " +
            "%{public}d, %{public}d, %{public}s",
            id, name, age, sex, subject);
    }
}
谓词(Predicates)方法 描述
and 逻辑与
or 逻辑或
distinct 每一记录均为唯一的
beginWrap 左小括号
endWrap 右小括号
equalTo 等于
notEqualTo 不等于
greaterThan 大于
greaterThanOrEqualTo 大于等于
lessThan 小于
lessThanOrEqualTo 小于等于
between 在某范围内
notBetween 在某范围外
contains 包含
beginsWith 匹配字符串开头的子字符串
endsWith 匹配字符串结尾的子字符串
in 在某离散值的范围内
notIn 在某离散值的范围外
like 模糊匹配
glob 文本通配符匹配
isNull 为空
isNotNull 非空
crossJoin 交叉连接
innerJoin 内连接
leftOuterJoin 左外连接
using 简化连接查询
on 连接条件
indexedBy 建立索引
groupBy 分组
orderByAsc 正序
orderByDesc 逆序
limit 限制查询结果数
offset 跳过指定数量的结果数

2.1.5 更新数据

ValuesBucket student = new ValuesBucket();
student.putString("subject", "HarmonyOS");
RdbPredicates predicates = new RdbPredicates(
    "t_student").equalTo("subject", "Java");

int nrows = rdb.update(student, predicates);
if (nrows == -1)
    showToast("更新数据失败");
else
    showToast("更新数据成功(" + nrows + ")");

2.1.6 删除数据

RdbPredicates predicates = new RdbPredicates(
    "t_student").equalTo("subject", "HarmonyOS");

int nrows = rdb.delete(predicates);
if (nrows == -1)
    showToast("删除数据失败");
else
    showToast("删除数据成功(" + nrows + ")");

2.1.7 断开数据库

rdb.close();

2.1.8 删除数据库

DatabaseHelper helper = new DatabaseHelper(this);

if (helper.deleteRdbStore("tarena.sqlite"))
    showToast("删数据库成功");
else
    showToast("删数据库失败");

通过getDatabaseDir()可以获得数据库文件的存储目录。

例程:RdbStorage

...\RdbStorage\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#00a2e8"/>
</shape>

...\RdbStorage\entry\src\main\resources\base\graphic\background_toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <solid ohos:color="#80000000"/>
    <corners ohos:radius="12vp"/>
</shape>

...\RdbStorage\entry\src\main\resources\base\layout\toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:left_padding="20vp"
    ohos:top_padding="5vp"
    ohos:right_padding="20vp"
    ohos:bottom_padding="5vp"
    ohos:background_element="$graphic:background_toast_dialog">

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\RdbStorage\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="关系型数据库"
        ohos:text_size="28fp"
        ohos:text_color="#00a2e8"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#00a2e8"
        />

    <Button
        ohos:id="$+id:btnInsert"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="插入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnBatch"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="批量插入"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnQuery"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="查询数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnUpdate"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="更新数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDelete"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删除数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDrop"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删数据库"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\RdbStorage\entry\src\main\java\com\minwei\rdbstorage\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    private RdbStore rdb;

    @Override
    public void onStart(Intent intent) {
        ...
        findComponentById(ResourceTable.Id_btnInsert)
            .setClickedListener(component -> onInsert());
        findComponentById(ResourceTable.Id_btnBatch)
            .setClickedListener(component -> onBatch());
        findComponentById(ResourceTable.Id_btnQuery)
            .setClickedListener(component -> onQuery());
        findComponentById(ResourceTable.Id_btnUpdate)
            .setClickedListener(component -> onUpdate());
        findComponentById(ResourceTable.Id_btnDelete)
            .setClickedListener(component -> onDelete());
        findComponentById(ResourceTable.Id_btnDrop)
            .setClickedListener(component -> onDrop());

        HiLog.info(label, "数据库存储目录:%{public}s", getDatabaseDir());
    }
    ...
    private void onInsert() {
        connectDatabase();

        ValuesBucket student = new ValuesBucket();
        student.putString("name", "张飞");
        student.putInteger("age", 22);
        student.putInteger("sex", 1);
        student.putString("subject", "C++");

        long id = rdb.insert("t_student", student);
        if (id == -1)
            showToast("插入数据失败");
        else
            showToast("插入数据成功(" + id + ")");

        disconnectDatabase();
    }

    private void onBatch() {
        connectDatabase();

        List<ValuesBucket> students = new ArrayList<>();

        ValuesBucket student1 = new ValuesBucket();
        student1.putString("name", "貂蝉");
        student1.putInteger("age", 18);
        student1.putInteger("sex", 0);
        student1.putString("subject", "Java");
        students.add(student1);

        ValuesBucket student2 = new ValuesBucket();
        student2.putString("name", "曹操");
        student2.putInteger("age", 30);
        student2.putInteger("sex", 1);
        student2.putString("subject", "Java");
        students.add(student2);

        List<Long> ids = rdb.batchInsertOrThrowException(
            "t_student", students, ConflictResolution.ON_CONFLICT_ABORT);
        for (long id : ids)
            if (id == -1)
                showToast("插入数据失败");
            else
                showToast("插入数据成功(" + id + ")");

        disconnectDatabase();
    }

    private void onQuery() {
        connectDatabase();

        RdbPredicates predicates = new RdbPredicates(
            "t_student").orderByAsc("id");
        String[] columns = new String[] {
            "id", "name", "age", "sex", "subject"};

        ResultSet result = rdb.query(predicates, columns);
        if (result == null)
            showToast("查询数据失败");
        else {
            showToast("查询数据成功(" + result.getRowCount() + ")");

            while (result.goToNextRow()) {
                int id = result.getInt(0);
                String name = result.getString(1);
                int age = result.getInt(2);
                int sex = result.getInt(3);
                String subject = result.getString(4);

                HiLog.info(label, "%{public}d, %{public}s, " +
                    "%{public}d, %{public}d, %{public}s",
                    id, name, age, sex, subject);
            }
        }

        disconnectDatabase();
    }

    private void onUpdate() {
        connectDatabase();

        ValuesBucket student = new ValuesBucket();
        student.putString("subject", "HarmonyOS");
        RdbPredicates predicates = new RdbPredicates(
            "t_student").equalTo("subject", "Java");

        int nrows = rdb.update(student, predicates);
        if (nrows == -1)
            showToast("更新数据失败");
        else
            showToast("更新数据成功(" + nrows + ")");

        disconnectDatabase();
    }

    private void onDelete() {
        connectDatabase();

        RdbPredicates predicates = new RdbPredicates(
            "t_student").equalTo("subject", "HarmonyOS");

        int nrows = rdb.delete(predicates);
        if (nrows == -1)
            showToast("删除数据失败");
        else
            showToast("删除数据成功(" + nrows + ")");

        disconnectDatabase();
    }

    private void onDrop() {
        DatabaseHelper helper = new DatabaseHelper(this);

        if (helper.deleteRdbStore("tarena.sqlite"))
            showToast("删数据库成功");
        else
            showToast("删数据库失败");
    }

    private void connectDatabase() {
        DatabaseHelper helper = new DatabaseHelper(this);

        StoreConfig config = StoreConfig
            .newDefaultConfig("tarena.sqlite");

        rdb = helper.getRdbStore(config, 1, new RdbOpenCallback() {
            @Override
            public void onCreate(RdbStore rdbStore) {
                rdbStore.executeSql(
                    "CREATE TABLE IF NOT EXISTS t_student(" +
                        "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                        "name TEXT NOT NULL, " +
                        "age INTEGER, " +
                        "sex TINYINT, " +
                        "subject TEXT)");
            }

            @Override
            public void onUpgrade(RdbStore rdbStore, int i, int i1) {
            }
        });
    }

    private void disconnectDatabase() {
        rdb.close();
    }

    private void showToast(String text) {
        Component component = LayoutScatter.getInstance(this).parse(
            ResourceTable.Layout_toast_dialog, null, false);
        ((Text)component.findComponentById(ResourceTable.Id_txt))
            .setText(text);
        new ToastDialog(this)
            .setContentCustomComponent(component)
            .setSize(LayoutConfig.MATCH_CONTENT,
                LayoutConfig.MATCH_CONTENT)
            .setDuration(5000)
            .setAlignment(LayoutAlignment.BOTTOM)
            .setOffset(0, AttrHelper.vp2px(60, this))
            .show();
    }
}

运行效果如下图所示:

2.2 对象关系映射(ORM)

以面向对象的思想操作关系型数据库。

graph LR subgraph 对象 property((属性)) javaclass((类)) object((对象)) end subgraph 关系 column((字段)) table((表结构)) row((记录)) end property---|映射|column javaclass---|映射|table object---|映射|row

2.2.1 启用注解

在...\OrmStorage\entry\build.gradle中添加编译选项:

compileOptions {
    annotationEnabled true
}

右上角同步。

2.2.2 实体类

添加orm包,添加Student类:

@Entity(tableName = "t_student")
public class Student extends OrmObject {
    @PrimaryKey(autoGenerate = true)
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;
    private String subject;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }
}

2.2.3 数据库类

在orm包中添加TarenaDatabase类:

@Database(entities = {Student.class}, version = 1)
public abstract class TarenaDatabase extends OrmDatabase {
}

2.2.4 连接数据库

DatabaseHelper helper = new DatabaseHelper(this);

orm = helper.getOrmContext("TarenaDB", "tarena.db",
    TarenaDatabase.class);

2.2.5 插入数据

Student[] students = new Student[3];

students[0] = new Student();
students[0].setName("张飞");
students[0].setAge(22);
students[0].setSex(1);
students[0].setSubject("C++");

students[1] = new Student();
students[1].setName("貂蝉");
students[1].setAge(18);
students[1].setSex(0);
students[1].setSubject("Java");

students[2] = new Student();
students[2].setName("曹操");
students[2].setAge(30);
students[2].setSex(1);
students[2].setSubject("Java");

for (Student student : students)
    if (orm.insert(student) && orm.flush())
        showToast("插入数据成功");
    else
        showToast("插入数据失败");

2.2.6 查询数据

OrmPredicates predicates = new OrmPredicates(
    Student.class).orderByAsc("id");

List<Student> students = orm.query(predicates);
if (students == null)
    showToast("查询数据失败");
else {
    showToast("查询数据成功(" + students.size() + ")");

    for (Student student : students)
        HiLog.info(label, "%{public}d, %{public}s, " +
            "%{public}d, %{public}d, %{public}s",
            student.getId(), student.getName(),
            student.getAge(), student.getSex(),
            student.getSubject());
}

2.2.7 更新数据

OrmPredicates predicates = new OrmPredicates(
    Student.class).equalTo("subject", "Java");
ValuesBucket student = new ValuesBucket();
student.putString("subject", "HarmonyOS");

int nrows = orm.update(predicates, student);
showToast("更新数据成功(" + nrows + ")");

2.2.8 删除数据

OrmPredicates predicates = new OrmPredicates(
    Student.class).equalTo("subject", "HarmonyOS");

List<Student> students = orm.query(predicates);

for (Student student : students)
    if (orm.delete(student) && orm.flush())
        showToast("删除数据成功");
    else
        showToast("删除数据失败");

2.2.9 断开数据库

orm.close();

通过getDatabaseDir()可以获得数据库文件的存储目录。

例程:OrmStorage

...\OrmStorage\entry\build.gradle

...
ohos {
    ...
    compileOptions {
        annotationEnabled true
    }
}
...

...\OrmStorage\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#22b14c"/>
</shape>

...\OrmStorage\entry\src\main\resources\base\graphic\background_toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <solid ohos:color="#80000000"/>
    <corners ohos:radius="12vp"/>
</shape>

...\OrmStorage\entry\src\main\resources\base\layout\toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:left_padding="20vp"
    ohos:top_padding="5vp"
    ohos:right_padding="20vp"
    ohos:bottom_padding="5vp"
    ohos:background_element="$graphic:background_toast_dialog">

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\OrmStorage\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="对象关系映射"
        ohos:text_size="28fp"
        ohos:text_color="#22b14c"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#22b14c"
        />

    <Button
        ohos:id="$+id:btnInsert"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="插入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnQuery"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="查询数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnUpdate"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="更新数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDelete"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删除数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\OrmStorage\entry\src\main\java\com\minwei\ormstorage\orm\Student.java

@Entity(tableName = "t_student")
public class Student extends OrmObject {
    @PrimaryKey(autoGenerate = true)
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;
    private String subject;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }
}

...\OrmStorage\entry\src\main\java\com\minwei\ormstorage\orm\TarenaDatabase.java

@Database(entities = {Student.class}, version = 1)
public abstract class TarenaDatabase extends OrmDatabase {
}

...\OrmStorage\entry\src\main\java\com\minwei\ormstorage\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    private OrmContext orm;

    @Override
    public void onStart(Intent intent) {
        ...
        findComponentById(ResourceTable.Id_btnInsert)
            .setClickedListener(component -> onInsert());
        findComponentById(ResourceTable.Id_btnQuery)
            .setClickedListener(component -> onQuery());
        findComponentById(ResourceTable.Id_btnUpdate)
            .setClickedListener(component -> onUpdate());
        findComponentById(ResourceTable.Id_btnDelete)
            .setClickedListener(component -> onDelete());

        HiLog.info(label, "数据库存储目录:%{public}s", getDatabaseDir());
    }
    ...
    private void onInsert() {
        connectDatabase();

        Student[] students = new Student[3];

        students[0] = new Student();
        students[0].setName("张飞");
        students[0].setAge(22);
        students[0].setSex(1);
        students[0].setSubject("C++");

        students[1] = new Student();
        students[1].setName("貂蝉");
        students[1].setAge(18);
        students[1].setSex(0);
        students[1].setSubject("Java");

        students[2] = new Student();
        students[2].setName("曹操");
        students[2].setAge(30);
        students[2].setSex(1);
        students[2].setSubject("Java");

        for (Student student : students)
            if (orm.insert(student) && orm.flush())
                showToast("插入数据成功");
            else
                showToast("插入数据失败");

        disconnectDatabase();
    }

    private void onQuery() {
        connectDatabase();

        OrmPredicates predicates = new OrmPredicates(
            Student.class).orderByAsc("id");

        List<Student> students = orm.query(predicates);
        if (students == null)
            showToast("查询数据失败");
        else {
            showToast("查询数据成功(" + students.size() + ")");

            for (Student student : students)
                HiLog.info(label, "%{public}d, %{public}s, " +
                    "%{public}d, %{public}d, %{public}s",
                    student.getId(), student.getName(),
                    student.getAge(), student.getSex(),
                    student.getSubject());
        }

        disconnectDatabase();
    }

    private void onUpdate() {
        connectDatabase();

        OrmPredicates predicates = new OrmPredicates(
            Student.class).equalTo("subject", "Java");
        ValuesBucket student = new ValuesBucket();
        student.putString("subject", "HarmonyOS");

        int nrows = orm.update(predicates, student);
        showToast("更新数据成功(" + nrows + ")");

        disconnectDatabase();
    }

    private void onDelete() {
        connectDatabase();

        OrmPredicates predicates = new OrmPredicates(
            Student.class).equalTo("subject", "HarmonyOS");

        List<Student> students = orm.query(predicates);

        for (Student student : students)
            if (orm.delete(student) && orm.flush())
                showToast("删除数据成功");
            else
                showToast("删除数据失败");

        disconnectDatabase();
    }

    private void connectDatabase() {
        DatabaseHelper helper = new DatabaseHelper(this);

        orm = helper.getOrmContext("TarenaDB", "tarena.db",
            TarenaDatabase.class);
    }

    private void disconnectDatabase() {
        orm.close();
    }

    private void showToast(String text) {
        Component component = LayoutScatter.getInstance(this).parse(
            ResourceTable.Layout_toast_dialog, null, false);
        ((Text)component.findComponentById(ResourceTable.Id_txt))
            .setText(text);
        new ToastDialog(this)
            .setContentCustomComponent(component)
            .setSize(LayoutConfig.MATCH_CONTENT,
                LayoutConfig.MATCH_CONTENT)
            .setDuration(5000)
            .setAlignment(LayoutAlignment.BOTTOM)
            .setOffset(0, AttrHelper.vp2px(60, this))
            .show();
    }
}

运行效果如下图所示:

2.3 应用偏好数据库

应用偏好数据库采用键值对(而非二维表)的形式保存整型、浮点型、字符型等常用的数据类型。每个被存储在应用偏好数据库中的数据都需要为其设置一个键,通过键查找值,这就是应用偏好数据库的查询方式。

键值对形式的应用偏好数据库适用于存储应用程序的配置信息等非结构化数据。

2.3.1 连接数据库

DatabaseHelper helper = new DatabaseHelper(this);

pre = helper.getPreferences("config");

2.3.2 保存数据

pre.putBoolean("isAutoUpdate", true)
   .putInt("version", 1)
   .putString("currentUser", "minwei").flush();

2.3.3 查询数据

boolean isAutoUpdate = pre.getBoolean("isAutoUpdate", false);
int version = pre.getInt("version", 0);
String currentUser = pre.getString("currentUser", "");

2.3.4 删除数据

pre.delete("isAutoUpdate")
   .delete("version")
   .delete("currentUser");

2.3.5 观察数据

private PreferencesObserver observer = new PreferencesObserver() {
    @Override
    public void onChange(Preferences preferences, String s) {
        if (s.equals("counter"))
            ((Text)findComponentById(ResourceTable.Id_txt))
                .setText(String.valueOf(pre.getInt(s, 0)));
    }
};
pre.registerObserver(observer);
pre.unregisterObserver(observer);

应用偏好数据库其实就是一个XML文件,通过getPreferencesDir()可以获得该文件的存储目录。

例程:PreStorage

...\PreStorage\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#ff7f27"/>
</shape>

...\PreStorage\entry\src\main\resources\base\graphic\background_text.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <stroke ohos:width="2vp" ohos:color="#ff7f27"/>
</shape>

...\PreStorage\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="偏好数据库"
        ohos:text_size="28fp"
        ohos:text_color="#ff7f27"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#ff7f27"
        />

    <Button
        ohos:id="$+id:btnSave"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="保存数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnQuery"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="查询数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDelete"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删除数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnObserve"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="观察数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_text"
        ohos:text_size="24fp"
        ohos:text_color="#ff7f27"
        ohos:text_alignment="horizontal_center"
        />

</DirectionalLayout>

...\PreStorage\entry\src\main\java\com\minwei\prestorage\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    private Preferences pre;

    private PreferencesObserver observer = new PreferencesObserver() {
        @Override
        public void onChange(Preferences preferences, String s) {
            if (s.equals("counter"))
                ((Text)findComponentById(ResourceTable.Id_txt))
                    .setText(String.valueOf(pre.getInt(s, 0)));
        }
    };

    @Override
    public void onStart(Intent intent) {
        ...
        findComponentById(ResourceTable.Id_btnSave)
            .setClickedListener(component -> onSave());
        findComponentById(ResourceTable.Id_btnQuery)
            .setClickedListener(component -> onQuery());
        findComponentById(ResourceTable.Id_btnDelete)
            .setClickedListener(component -> onDelete());
        findComponentById(ResourceTable.Id_btnObserve)
            .setClickedListener(component -> onObserve());

        HiLog.info(label, "数据库存储目录:%{public}s",
            getPreferencesDir().toString());

        connectDatabase();

        ((Text)findComponentById(ResourceTable.Id_txt))
            .setText(String.valueOf(pre.getInt("counter", 0)));

        pre.registerObserver(observer);
    }
    ...
    @Override
    protected void onStop() {
        super.onStop();

        connectDatabase();

        pre.unregisterObserver(observer);
    }

    private void onSave() {
        connectDatabase();

        pre.putBoolean("isAutoUpdate", true)
           .putInt("version", 1)
           .putString("currentUser", "minwei").flush();
    }

    private void onQuery() {
        connectDatabase();

        boolean isAutoUpdate = pre.getBoolean("isAutoUpdate", false);
        int version = pre.getInt("version", 0);
        String currentUser = pre.getString("currentUser", "");

        HiLog.info(label, "[%{public}b][%{public}d][%{public}s]",
            isAutoUpdate, version, currentUser);
    }

    private void onDelete() {
        connectDatabase();

        pre.delete("isAutoUpdate")
           .delete("version")
           .delete("currentUser");
    }

    private void onObserve() {
        connectDatabase();

        pre.putInt("counter", pre.getInt("counter", 0) + 1).flush();
    }

    private void connectDatabase() {
        DatabaseHelper helper = new DatabaseHelper(this);

        pre = helper.getPreferences("config");
    }
}

运行效果如下图所示:

 

2.4 分布式数据库

2.4.1 什么是分布式数据库

分布式数据库属于NoSQL数据库,采用键值对的形式存储数据。因此分布式数据库不仅能象关系型数据库和对象关系映射那样存储结构化数据,也能象应用偏好数据库那样存储非结构化数据。

基于鸿蒙系统提供的分布式数据库API,可以借助分布式软总线,在多个组网设备之间进行无中心、点对点的同步。

人在哪儿数据在哪儿,而不是设备在哪儿数据在哪儿。

2.4.2 实现分布式存储的必要条件

能够实现分布式组网的设备必须满足以下三个条件:

  1. 均安装鸿蒙操作系统
  2. 在同一网络内且登录同一华为账号
             华为账号
   _____________|_____________
  /             |             \
手表 <-蓝牙-> 手机 <-WIFI-> 智慧屏
  \_____________|_____________/
                |
     基于分布式软总线多设备组网
  1. 打开多设备协同选项
    设置
      更多连接
        多设备协同
          多设备协同:On

为实现分布式数据库同步还需要满足以下两个条件:

  1. 相同的应用,即包名和签名必须相同
  2. 相同的StoreID

2.4.3 分布式数据库API

          KvManagerConfig  Options
                 |            |
                 v            v
KvManagerFactory--->KvManager--->KvStore

KvStore派生出两个子类:

 _____                   _____
|  A  |                 |  A  |
|     |                 |     |
| K:1 |       New       | K:1 |
|_____|                 |_____|
       \_______________/
 _____ / SingleKvStore \ _____
|  B  |                 |  B  |
|     |                 |     |
| K:2 |       Old       | K:1 |
|_____|                 |_____|

-------------------------------
 _____                   _____
|  A  |                 |  A  |
|     |                 |A_K:1|
| K:1 |                 |B_K:2|
|_____|                 |_____|
       \_______________/
 _____ / DeviceKvStore \ _____
|  B  |                 |  B  |
|     |                 |A_K:1|
| K:2 |                 |B_K:2|
|_____|                 |_____|

2.4.4 申请权限

config.json

"reqPermissions": [
  {
    "name": "ohos.permission.DISTRIBUTED_DATASYNC"
  }
]
List<String> reqPermissions = new ArrayList<>();

String[] permissions = {"ohos.permission.DISTRIBUTED_DATASYNC"};
for (String permission : permissions)
    if (verifySelfPermission(permission) != 0 &&
        canRequestPermission(permission))
        reqPermissions.add(permission);

requestPermissionsFromUser(
    reqPermissions.toArray(new String[0]), 0);

2.4.5 连接非结构化数据库

KvManagerConfig config = new KvManagerConfig(this);

KvManager manager = KvManagerFactory
    .getInstance().createKvManager(config);

Options options = new Options()
    .setCreateIfMissing(true)
    .setEncrypt(false)
    .setAutoSync(true)
    .setBackup(true)
    .setKvStoreType(KvStoreType.SINGLE_VERSION);

skv = manager.getKvStore(options, "config");

2.4.6 设置键值

try {
    skv.putBoolean("isAutoUpdate", true);

    List<Entry> items = new ArrayList<>();

    Entry item1 = new Entry("version", Value.get(1));
    items.add(item1);

    Entry item2 = new Entry("currentUser", Value.get("minwei"));
    items.add(item2);

    skv.putBatch(items);
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

2.4.7 获取键值

try {
    boolean isAutoUpdate = skv.getBoolean("isAutoUpdate");
    int version = skv.getInt("version");
    String currentUser = skv.getString("currentUser");

    HiLog.info(label, "[%{public}b][%{public}d][%{public}s]",
        isAutoUpdate, version, currentUser);
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

2.4.8 删除键值

try {
    skv.delete("isAutoUpdate");
    skv.delete("version");
    skv.delete("currentUser");
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

2.4.9 连接结构化数据库

KvManagerConfig config = new KvManagerConfig(this);

KvManager manager = KvManagerFactory
    .getInstance().createKvManager(config);

Options options = new Options()
    .setCreateIfMissing(true)
    .setEncrypt(false)
    .setAutoSync(true)
    .setBackup(true)
    .setKvStoreType(KvStoreType.SINGLE_VERSION)
    .setSchema(createStudentSchema());

skv = manager.getKvStore(options, "t_student");
private Schema createStudentSchema() {
    Schema studentSchema = new Schema();
    studentSchema.setSchemaMode(SchemaMode.COMPATIBLE);

    FieldNode idNode = new FieldNode("id");
    idNode.setType(FieldValueType.INTEGER);
    studentSchema.getRootFieldNode().appendChild(idNode);

    FieldNode nameNode = new FieldNode("name");
    nameNode.setType(FieldValueType.STRING);
    studentSchema.getRootFieldNode().appendChild(nameNode);

    FieldNode ageNode = new FieldNode("age");
    ageNode.setType(FieldValueType.INTEGER);
    studentSchema.getRootFieldNode().appendChild(ageNode);

    FieldNode sexNode = new FieldNode("sex");
    sexNode.setType(FieldValueType.INTEGER);
    studentSchema.getRootFieldNode().appendChild(sexNode);

    FieldNode subjectNode = new FieldNode("subject");
    subjectNode.setType(FieldValueType.STRING);
    studentSchema.getRootFieldNode().appendChild(subjectNode);

    return studentSchema;
}

2.4.10 插入数据

try {
    connectStructuredDatabase();

    ZSONObject[] students = new ZSONObject[3];

    students[0] = new ZSONObject();
    students[0].put("id", 1);
    students[0].put("name", "张飞");
    students[0].put("age", 22);
    students[0].put("sex", 1);
    students[0].put("subject", "C++");

    students[1] = new ZSONObject();
    students[1].put("id", 2);
    students[1].put("name", "貂蝉");
    students[1].put("age", 18);
    students[1].put("sex", 0);
    students[1].put("subject", "Java");

    students[2] = new ZSONObject();
    students[2].put("id", 3);
    students[2].put("name", "曹操");
    students[2].put("age", 30);
    students[2].put("sex", 1);
    students[2].put("subject", "Java");

    for (int i = 0; i < students.length; ++i)
        skv.putString(String.valueOf(i),
            students[i].toString());
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

2.4.11 查询数据

try {
    connectStructuredDatabase();

    Query query = Query.select().orderByAsc("$.id");

    List<Entry> entries = skv.getEntries(query);
    for (Entry entry : entries) {
        String key = entry.getKey();
        ZSONObject student = ZSONObject.stringToZSON(
            entry.getValue().getString());

        HiLog.info(label, "%{public}s -> %{public}d, " +
            "%{public}s, %{public}d, %{public}d, %{public}s",
            key, student.getInteger("id"),
            student.getString("name"),
            student.getInteger("age"),
            student.getInteger("sex"),
            student.getString("subject"));
    }
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

2.4.12 更新数据

try {
    connectStructuredDatabase();

    Query query = Query.select().orderByAsc("$.id");

    List<Entry> entries = skv.getEntries(query);
    for (Entry entry : entries) {
        String key = entry.getKey();
        ZSONObject student = ZSONObject.stringToZSON(
            entry.getValue().getString());

        if (student.getString("subject").equals("Java")) {
            student.put("subject", "HarmonyOS");
            skv.putString(key, student.toString());
        }
    }
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

2.4.13 删除数据

try {
    connectStructuredDatabase();

    Query query = Query.select().orderByAsc("$.id");

    List<Entry> entries = skv.getEntries(query);
    for (Entry entry : entries) {
        String key = entry.getKey();
        ZSONObject student = ZSONObject.stringToZSON(
            entry.getValue().getString());

        if (student.getString("subject").equals("HarmonyOS"))
            skv.delete(key);
    }
}
catch (KvStoreException exception) {
    HiLog.info(label, exception.toString());
}

例程:DisStorage

...\DisStorage\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "reqPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      }
    ]
  }
}

...\DisStorage\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#ee1c24"/>
</shape>

...\DisStorage\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="分布式数据库"
        ohos:text_size="28fp"
        ohos:text_color="#ee1c24"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#ee1c24"
        />

    <Button
        ohos:id="$+id:btnSet"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="设置键值"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnGet"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="获取键值"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDel"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删除键值"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Text
        ohos:height="1vp"
        ohos:width="match_parent"
        ohos:top_margin="15vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#ee1c24"
        />

    <Button
        ohos:id="$+id:btnInsert"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="插入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnQuery"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="查询数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnUpdate"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="更新数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDelete"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删除数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\DisStorage\entry\src\main\java\com\minwei\disstorage\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    private SingleKvStore skv;

    @Override
    public void onStart(Intent intent) {
        ...
        requestPermissions();

        findComponentById(ResourceTable.Id_btnSet)
            .setClickedListener(component -> onSet());
        findComponentById(ResourceTable.Id_btnGet)
            .setClickedListener(component -> onGet());
        findComponentById(ResourceTable.Id_btnDel)
            .setClickedListener(component -> onDel());
        findComponentById(ResourceTable.Id_btnInsert)
            .setClickedListener(component -> onInsert());
        findComponentById(ResourceTable.Id_btnQuery)
            .setClickedListener(component -> onQuery());
        findComponentById(ResourceTable.Id_btnUpdate)
            .setClickedListener(component -> onUpdate());
        findComponentById(ResourceTable.Id_btnDelete)
            .setClickedListener(component -> onDelete());
    }
    ...
    private void onSet() {
        try {
            connectUnstructuredDatabase();

            skv.putBoolean("isAutoUpdate", true);

            List<Entry> entries = new ArrayList<>();

            Entry entry1 = new Entry("version", Value.get(1));
            entries.add(entry1);

            Entry entry2 = new Entry("currentUser", Value.get("minwei"));
            entries.add(entry2);

            skv.putBatch(entries);
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void onGet() {
        try {
            connectUnstructuredDatabase();

            boolean isAutoUpdate = skv.getBoolean("isAutoUpdate");
            int version = skv.getInt("version");
            String currentUser = skv.getString("currentUser");

            HiLog.info(label, "[%{public}b][%{public}d][%{public}s]",
                isAutoUpdate, version, currentUser);
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void onDel() {
        try {
            connectUnstructuredDatabase();

            skv.delete("isAutoUpdate");
            skv.delete("version");
            skv.delete("currentUser");
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void onInsert() {
        try {
            connectStructuredDatabase();

            ZSONObject[] students = new ZSONObject[3];

            students[0] = new ZSONObject();
            students[0].put("id", 1);
            students[0].put("name", "张飞");
            students[0].put("age", 22);
            students[0].put("sex", 1);
            students[0].put("subject", "C++");

            students[1] = new ZSONObject();
            students[1].put("id", 2);
            students[1].put("name", "貂蝉");
            students[1].put("age", 18);
            students[1].put("sex", 0);
            students[1].put("subject", "Java");

            students[2] = new ZSONObject();
            students[2].put("id", 3);
            students[2].put("name", "曹操");
            students[2].put("age", 30);
            students[2].put("sex", 1);
            students[2].put("subject", "Java");

            for (int i = 0; i < students.length; ++i)
                skv.putString(String.valueOf(i),
                    students[i].toString());
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void onQuery() {
        try {
            connectStructuredDatabase();

            Query query = Query.select().orderByAsc("$.id");

            List<Entry> entries = skv.getEntries(query);
            for (Entry entry : entries) {
                String key = entry.getKey();
                ZSONObject student = ZSONObject.stringToZSON(
                    entry.getValue().getString());

                HiLog.info(label, "%{public}s -> %{public}d, " +
                    "%{public}s, %{public}d, %{public}d, %{public}s",
                    key, student.getInteger("id"),
                    student.getString("name"),
                    student.getInteger("age"),
                    student.getInteger("sex"),
                    student.getString("subject"));
            }
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void onUpdate() {
        try {
            connectStructuredDatabase();

            Query query = Query.select().orderByAsc("$.id");

            List<Entry> entries = skv.getEntries(query);
            for (Entry entry : entries) {
                String key = entry.getKey();
                ZSONObject student = ZSONObject.stringToZSON(
                    entry.getValue().getString());

                if (student.getString("subject").equals("Java")) {
                    student.put("subject", "HarmonyOS");
                    skv.putString(key, student.toString());
                }
            }
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void onDelete() {
        try {
            connectStructuredDatabase();

            Query query = Query.select().orderByAsc("$.id");

            List<Entry> entries = skv.getEntries(query);
            for (Entry entry : entries) {
                String key = entry.getKey();
                ZSONObject student = ZSONObject.stringToZSON(
                    entry.getValue().getString());

                if (student.getString("subject").equals("HarmonyOS"))
                    skv.delete(key);
            }
        }
        catch (KvStoreException exception) {
            HiLog.info(label, exception.toString());
        }
    }

    private void requestPermissions() {
        List<String> reqPermissions = new ArrayList<>();

        String[] permissions = {"ohos.permission.DISTRIBUTED_DATASYNC"};
        for (String permission : permissions)
            if (verifySelfPermission(permission) != 0 &&
                canRequestPermission(permission))
                reqPermissions.add(permission);

        requestPermissionsFromUser(
            reqPermissions.toArray(new String[0]), 0);
    }

    private void connectUnstructuredDatabase() {
        KvManagerConfig config = new KvManagerConfig(this);

        KvManager manager = KvManagerFactory
            .getInstance().createKvManager(config);

        Options options = new Options()
            .setCreateIfMissing(true)
            .setEncrypt(false)
            .setAutoSync(true)
            .setBackup(true)
            .setKvStoreType(KvStoreType.SINGLE_VERSION);

        skv = manager.getKvStore(options, "config");
    }

    private void connectStructuredDatabase() {
        KvManagerConfig config = new KvManagerConfig(this);

        KvManager manager = KvManagerFactory
            .getInstance().createKvManager(config);

        Options options = new Options()
            .setCreateIfMissing(true)
            .setEncrypt(false)
            .setAutoSync(true)
            .setBackup(true)
            .setKvStoreType(KvStoreType.SINGLE_VERSION)
            .setSchema(createStudentSchema());

        skv = manager.getKvStore(options, "t_student");
    }

    private Schema createStudentSchema() {
        Schema studentSchema = new Schema();
        studentSchema.setSchemaMode(SchemaMode.COMPATIBLE);

        FieldNode idNode = new FieldNode("id");
        idNode.setType(FieldValueType.INTEGER);
        studentSchema.getRootFieldNode().appendChild(idNode);

        FieldNode nameNode = new FieldNode("name");
        nameNode.setType(FieldValueType.STRING);
        studentSchema.getRootFieldNode().appendChild(nameNode);

        FieldNode ageNode = new FieldNode("age");
        ageNode.setType(FieldValueType.INTEGER);
        studentSchema.getRootFieldNode().appendChild(ageNode);

        FieldNode sexNode = new FieldNode("sex");
        sexNode.setType(FieldValueType.INTEGER);
        studentSchema.getRootFieldNode().appendChild(sexNode);

        FieldNode subjectNode = new FieldNode("subject");
        subjectNode.setType(FieldValueType.STRING);
        studentSchema.getRootFieldNode().appendChild(subjectNode);

        return studentSchema;
    }
}

3 基于文件的数据持久化

类似图片、音频、视频、文档等数据量较大的二进制信息,不便于通过数据库实现持久化,一般直接以文件形式保存在文件系统中,分为以下两种形式:

3.1 本地文件系统

3.1.1 本地目录

在鸿蒙设备中用于应用程序存储文件的位置有两个:

/data/user/0/<bundle_name>/ - 沙盒目录,getDataDir()
|
|_ cache/                   - 缓存目录,getCacheDir()
|_ code_cache/              - 代码缓存,getCodeCacheDir()
|_ files/                   - 文件目录,getFilesDir()
/storage/emulated/0/         \
或                           | - 外部存储
/storage/emulated/sdcard/    /
|
|_ Android/data/<bundle_name>/ - 私有目录,用户能访问,其它应用不能访问
|  |
|  |_ cache/                   - 缓存目录,getExternalCacheDir()
|  |_ <name1>/                 - 自建目录,getExternalFilesDir(<name1>)
|  |_ <name2>/                 - 自建目录,getExternalFilesDir(<name2>)
|  |_ ...                      - ...
|
|_ ...                         - 公有区域,用户和其它应用都能访问

应用程序访问沙盒目录和外部存储私有目录中的文件,不需要任何特殊权限,直接使用Java语言提供的文件操作接口即可。但如果需要访问外部存储公有区域中的文件,则必须借助于DataAbility。

沙盒和外存中的缓存目录一般用于存放临时文件。需要持久化保存的文件通常放在files或自建目录下。

包含账号、密码、聊天记录等私密信息的文件最好放在沙盒目录中,而象电子地图、照片等文件,为了便于用户增删备份,存放在外部存储中更为合理。

3.1.2 写入数据

try {
    FileWriter fw = new FileWriter(
        getExternalFilesDir("docs") + "/note.txt");
    BufferedWriter bw = new BufferedWriter(fw);

    bw.write("文件中的数据");
    bw.newLine();

    bw.close();
}
catch (IOException exception) {
    HiLog.info(label, exception.getLocalizedMessage());
}

3.1.3 读取数据

try {
    FileReader fr = new FileReader(
        getExternalFilesDir("docs") + "/note.txt");
    BufferedReader br = new BufferedReader(fr);

    String line;
    while ((line = br.readLine()) != null)
        HiLog.info(label, line);

    br.close();
}
catch (IOException exception) {
    HiLog.info(label, exception.getLocalizedMessage());
}

例程:LocFile

...\LocFile\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#254061"/>
</shape>

...\LocFile\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="本地文件"
        ohos:text_size="28fp"
        ohos:text_color="#254061"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#254061"
        />

    <Button
        ohos:id="$+id:btnDir"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="本地目录"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnWrite"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="写入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnRead"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="读取数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\LocFile\entry\src\main\java\com\minwei\locfile\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    @Override
    public void onStart(Intent intent) {
        ...
        findComponentById(ResourceTable.Id_btnDir)
            .setClickedListener(component -> onDir());
        findComponentById(ResourceTable.Id_btnWrite)
            .setClickedListener(component -> onWrite());
        findComponentById(ResourceTable.Id_btnRead)
            .setClickedListener(component -> onRead());
    }
    ...
    private void onDir() {
        HiLog.info(label, "沙盒目录:" + getDataDir().toString());
        HiLog.info(label, "沙盒缓存:" + getCacheDir().toString());
        HiLog.info(label, "代码缓存:" + getCodeCacheDir().toString());
        HiLog.info(label, "沙盒文件:" + getFilesDir().toString());

        HiLog.info(label, "外存缓存:" + getExternalCacheDir().toString());
        HiLog.info(label, "外存自建:" + getExternalFilesDir("docs").toString());
    }

    private void onWrite() {
        try {
            FileWriter fw = new FileWriter(
                getExternalFilesDir("docs") + "/note.txt");
            BufferedWriter bw = new BufferedWriter(fw);

            bw.write("文件中的数据");
            bw.newLine();

            bw.close();
        }
        catch (IOException exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void onRead() {
        try {
            FileReader fr = new FileReader(
                getExternalFilesDir("docs") + "/note.txt");
            BufferedReader br = new BufferedReader(fr);

            String line;
            while ((line = br.readLine()) != null)
                HiLog.info(label, line);

            br.close();
        }
        catch (IOException exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }
}

运行效果如下图所示:

3.2 分布式文件系统

3.2.1 分布式文件系统

分布式文件系统可以将文件在设备之间共享。与分布式数据库类似,分布式文件系统的设计目的就是能够让文件在不同设备间进行共享。让文件跟着人走,而不是跟着设备走。在分布式文件系统中实现文件共享需要满足以下条件:

不过与分布式数据库不同,分布式文件系统中的文件并不会在设备间同步,而仅仅是提供了共享访问接口(文件的元数据在设备间同步)。在多个设备间,同一个文件仅存在一份副本。

3.2.2 申请权限

config.json

"reqPermissions": [
  {
    "name": "ohos.permission.DISTRIBUTED_DATASYNC"
  }
]
List<String> reqPermissions = new ArrayList<>();

String[] permissions = {"ohos.permission.DISTRIBUTED_DATASYNC"};
for (String permission : permissions)
    if (verifySelfPermission(permission) != 0 &&
        canRequestPermission(permission))
        reqPermissions.add(permission);

requestPermissionsFromUser(
    reqPermissions.toArray(new String[0]), 0);

3.2.3 分布式目录

getDistributedDir()
/mnt/mdfs/16806570328871500801/merge_view/data/com.minwei.disfile/MainAbility

3.2.4 写入数据

try {
    FileWriter fw = new FileWriter(
        getDistributedDir() + "/note.txt");
    BufferedWriter bw = new BufferedWriter(fw);

    bw.write("文件中的数据");
    bw.newLine();

    bw.close();
}
catch (IOException exception) {
    HiLog.info(label, exception.getLocalizedMessage());
}

3.2.5 读取数据

try {
    FileReader fr = new FileReader(
        getDistributedDir() + "/note.txt");
    BufferedReader br = new BufferedReader(fr);

    String line;
    while ((line = br.readLine()) != null)
        HiLog.info(label, line);

    br.close();
}
catch (IOException exception) {
    HiLog.info(label, exception.getLocalizedMessage());
}

3.2.6 注意事项

使用分布式文件存储需要注意以下几点:

例程:DisFile

...\DisFile\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "reqPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      }
    ]
  }
}

...\DisFile\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#385723"/>
</shape>

...\DisFile\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="分布式文件"
        ohos:text_size="28fp"
        ohos:text_color="#385723"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#385723"
        />

    <Button
        ohos:id="$+id:btnDir"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="分布式目录"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnWrite"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="写入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnRead"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="读取数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\DisFile\entry\src\main\java\com\minwei\disfile\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    @Override
    public void onStart(Intent intent) {
        ...
        requestPermissions();

        findComponentById(ResourceTable.Id_btnDir)
            .setClickedListener(component -> onDir());
        findComponentById(ResourceTable.Id_btnWrite)
            .setClickedListener(component -> onWrite());
        findComponentById(ResourceTable.Id_btnRead)
            .setClickedListener(component -> onRead());
    }
    ...
    private void onDir() {
        HiLog.info(label, "分布式目录:" + getDistributedDir().toString());
    }

    private void onWrite() {
        try {
            FileWriter fw = new FileWriter(
                getDistributedDir() + "/note.txt");
            BufferedWriter bw = new BufferedWriter(fw);

            bw.write("文件中的数据");
            bw.newLine();

            bw.close();
        }
        catch (IOException exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void onRead() {
        try {
            FileReader fr = new FileReader(
                getDistributedDir() + "/note.txt");
            BufferedReader br = new BufferedReader(fr);

            String line;
            while ((line = br.readLine()) != null)
                HiLog.info(label, line);

            br.close();
        }
        catch (IOException exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void requestPermissions() {
        List<String> reqPermissions = new ArrayList<>();

        String[] permissions = {"ohos.permission.DISTRIBUTED_DATASYNC"};
        for (String permission : permissions)
            if (verifySelfPermission(permission) != 0 &&
                canRequestPermission(permission))
                reqPermissions.add(permission);

        requestPermissionsFromUser(
            reqPermissions.toArray(new String[0]), 0);
    }
}

运行效果如下图所示:

4 DataAbility

4.1 DataAbility的基本概念

DataAbility提供了对文件和数据库的统一访问接口。借助DataAbility,一个应用产生的数据既可以被自己访问,也能够被其它应用访问。允许其它应用访问自己的数据通常被称为“跨应用的数据访问”,这正是开发DataAbility的主要目的。

与分布式数据库和分布式文件系统一样,DataAbility也支持在分布式组网内的不同设备间同步和共享数据,但跨应用的数据访问显然是DataAbility的专有特征。

4.2 DataAbility的配置

在config.json文件中,DataAbility的type属性被定义为data。另外两种Ability包括PageAbility和ServiceAbility,其type属性分别被定义为page和service。

4.3 DataAbility类

与其它类型的Ability一样,DataAbility也是Ability基类的子类,但其生命周期方法中通常只会用到onStart()和onStop(),分别用于连接(打开)和断开(关闭)数据库(文件)。

作为数据需求者的应用,通常会用到如下方法:

4.4 DataAbilityHelper类

DataAbilityHelper是DataAbility的远程调用工具,其中的方法与后者一一对应。

应用A -> DataAbilityHelper    DataAbility -> 应用B
         |_openFile        -> |_openFile
         |_getFileTypes    -> |_getFileTypes
         |_insert          -> |_insert
         |_query           -> |_query
         |_update          -> |_update
         |_delete          -> |_delete
         |_...             -> |_...

4.5 DataAbility的URI

dataability://<设备ID>/<路径>[?<参数>][#<片段标识符>]

同一设备不同应用间的数据访问,“<设备ID>”可以为空,但其后的“/”不能省略:

dataability:///<路径>[?<参数>][#<片段标识符>]

4.6 跨应用访问数据库

4.6.1 数据提供者

添加DataAbility类的子类,其在config.json中的配置如下:

{
  "name": "com.minwei.dataprovider.StudentDataAbility",
  "icon": "$media:icon",
  "description": "$string:studentdataability_description",
  "type": "data",
  "uri": "dataability://com.minwei.dataprovider.StudentDataAbility",
  "permissions": [
    "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER"
  ],
  "visible": true
}

该类的实现:

public class StudentDataAbility extends Ability {
    private RdbStore rdb;

    public void onStart(Intent intent) {
        DatabaseHelper helper = new DatabaseHelper(this);

        StoreConfig config = StoreConfig
            .newDefaultConfig("tarena.sqlite");

        rdb = helper.getRdbStore(config, 1, new RdbOpenCallback() {
            public void onCreate(RdbStore rdbStore) {
                rdbStore.executeSql(
                    "CREATE TABLE IF NOT EXISTS t_student(" +
                        "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                        "name TEXT NOT NULL, " +
                        "age INTEGER, " +
                        "sex TINYINT, " +
                        "subject TEXT)");
            }
            ...
        });
    }

    protected void onStop() {
        rdb.close();
    }

    public ResultSet query(Uri uri, String[] columns,
                           DataAbilityPredicates predicates) {
        return rdb.query(DataAbilityUtils.createRdbPredicates(
            predicates, "t_student"), columns);
    }

    public int insert(Uri uri, ValuesBucket value) {
        return (int)rdb.insert("t_student", value);
    }

    public int delete(Uri uri, DataAbilityPredicates predicates) {
        return rdb.delete(DataAbilityUtils.createRdbPredicates(
            predicates, "t_student"));
    }

    public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
        return rdb.update(value, DataAbilityUtils.createRdbPredicates(
            predicates, "t_student"));
    }
    ...
}

例程:DataProvider

...\DataProvider\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "abilities": [
      ...
      {
        "name": "com.minwei.dataprovider.StudentDataAbility",
        "icon": "$media:icon",
        "description": "$string:studentdataability_description",
        "type": "data",
        "uri": "dataability://com.minwei.dataprovider.StudentDataAbility",
        "permissions": [
          "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER"
        ],
        "visible": true
      }
    ],
    "defPermissions": [
      {
        "name": "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER"
      }
    ]
  }
}

...\DataProvider\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical"
    ohos:background_element="#804080">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="数据提供者"
        ohos:text_size="36fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\DataProvider\entry\src\main\java\com\minwei\dataprovider\StudentDataAbility.java

public class StudentDataAbility extends Ability {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        StudentDataAbility.class.getCanonicalName());

    private RdbStore rdb;

    @Override
    public void onStart(Intent intent) {
        ...
        DatabaseHelper helper = new DatabaseHelper(this);

        StoreConfig config = StoreConfig
            .newDefaultConfig("tarena.sqlite");

        rdb = helper.getRdbStore(config, 1, new RdbOpenCallback() {
            @Override
            public void onCreate(RdbStore rdbStore) {
                rdbStore.executeSql(
                    "CREATE TABLE IF NOT EXISTS t_student(" +
                        "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                        "name TEXT NOT NULL, " +
                        "age INTEGER, " +
                        "sex TINYINT, " +
                        "subject TEXT)");
            }

            @Override
            public void onUpgrade(RdbStore rdbStore, int i, int i1) {
            }
        });
    }

    @Override
    protected void onStop() {
        ...
        rdb.close();
    }

    @Override
    public ResultSet query(Uri uri, String[] columns,
                           DataAbilityPredicates predicates) {
        return rdb.query(DataAbilityUtils.createRdbPredicates(
            predicates, "t_student"), columns);
    }

    @Override
    public int insert(Uri uri, ValuesBucket value) {
        ...
        return (int)rdb.insert("t_student", value);
    }

    @Override
    public int delete(Uri uri, DataAbilityPredicates predicates) {
        return rdb.delete(DataAbilityUtils.createRdbPredicates(
            predicates, "t_student"));
    }

    @Override
    public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
        return rdb.update(value, DataAbilityUtils.createRdbPredicates(
            predicates, "t_student"));
    }
    ...
}

4.6.2 数据需求者

在config.json中添加权限:

"defPermissions": [
  {
    "name": "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER",
    "grantMode": "system_grant"
  }
],
"reqPermissions": [
  {
    "name": "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER"
  }
]

通过URI指定目标DataAbility,借助DataAbilityHelper访问其它应用的数据库:

public class MainAbilitySlice extends AbilitySlice {
    private DataAbilityHelper helper;

    private final Uri uri = Uri.parse(
        "dataability:///com.minwei.dataprovider.StudentDataAbility");

    public void onStart(Intent intent) {
        helper = DataAbilityHelper.creator(this);
        ...
    }

    private void onInsert() {
        ...
        long id = helper.insert(uri, student);
        ...
    }

    private void onQuery() {
        ...
        ResultSet result = helper.query(uri, columns, predicates);
        ...
    }

    private void onUpdate() {
        ...
        int nrows = helper.update(uri, student, predicates);
        ...
    }

    private void onDelete() {
        ...
        int nrows = helper.delete(uri, predicates);
        ...
    }
    ...
}

例程:DataDemander

...\DataDemander\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "defPermissions": [
      {
        "name": "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER",
        "grantMode": "system_grant"
      }
    ],
    "reqPermissions": [
      {
        "name": "com.minwei.dataprovider.DataAbilityShellProvider.PROVIDER"
      }
    ]
  }
}

...\DataDemander\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#804080"/>
</shape>

...\DataDemander\entry\src\main\resources\base\graphic\background_toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <solid ohos:color="#80000000"/>
    <corners ohos:radius="12vp"/>
</shape>

...\DataDemander\entry\src\main\resources\base\layout\toast_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:left_padding="20vp"
    ohos:top_padding="5vp"
    ohos:right_padding="20vp"
    ohos:bottom_padding="5vp"
    ohos:background_element="$graphic:background_toast_dialog">

    <Text
        ohos:id="$+id:txt"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\DataDemander\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="数据需求者"
        ohos:text_size="28fp"
        ohos:text_color="#804080"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#804080"
        />

    <Button
        ohos:id="$+id:btnInsert"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="插入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnQuery"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="查询数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnUpdate"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="更新数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnDelete"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="删除数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\DataDemander\entry\src\main\java\com\minwei\datademander\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    private DataAbilityHelper helper;

    private final Uri uri = Uri.parse(
        "dataability:///com.minwei.dataprovider.StudentDataAbility");

    @Override
    public void onStart(Intent intent) {
        ...
        helper = DataAbilityHelper.creator(this);

        findComponentById(ResourceTable.Id_btnInsert)
            .setClickedListener(component -> onInsert());
        findComponentById(ResourceTable.Id_btnQuery)
            .setClickedListener(component -> onQuery());
        findComponentById(ResourceTable.Id_btnUpdate)
            .setClickedListener(component -> onUpdate());
        findComponentById(ResourceTable.Id_btnDelete)
            .setClickedListener(component -> onDelete());
    }
    ...
    private void onInsert() {
        ValuesBucket[] students = new ValuesBucket[3];

        students[0] = new ValuesBucket();
        students[0].putString("name", "张飞");
        students[0].putInteger("age", 22);
        students[0].putInteger("sex", 1);
        students[0].putString("subject", "C++");

        students[1] = new ValuesBucket();
        students[1].putString("name", "貂蝉");
        students[1].putInteger("age", 18);
        students[1].putInteger("sex", 0);
        students[1].putString("subject", "Java");

        students[2] = new ValuesBucket();
        students[2].putString("name", "曹操");
        students[2].putInteger("age", 30);
        students[2].putInteger("sex", 1);
        students[2].putString("subject", "Java");

        for (ValuesBucket student : students)
            try {
                long id = helper.insert(uri, student);
                if (id == -1)
                    showToast("插入数据失败");
                else
                    showToast("插入数据成功(" + id + ")");
            }
            catch (Exception exception) {
                HiLog.info(label, exception.getLocalizedMessage());
            }
    }

    private void onQuery() {
        String[] columns = new String[] {
            "id", "name", "age", "sex", "subject"};
        DataAbilityPredicates predicates =
            new DataAbilityPredicates().orderByAsc("id");

        try {
            ResultSet result = helper.query(uri, columns, predicates);

            showToast("查询数据成功(" + result.getRowCount() + ")");

            while (result.goToNextRow()) {
                int id = result.getInt(0);
                String name = result.getString(1);
                int age = result.getInt(2);
                int sex = result.getInt(3);
                String subject = result.getString(4);

                HiLog.info(label, "%{public}d, %{public}s, " +
                        "%{public}d, %{public}d, %{public}s",
                    id, name, age, sex, subject);
            }

            result.close();
        }
        catch (Exception exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void onUpdate() {
        ValuesBucket student = new ValuesBucket();
        student.putString("subject", "HarmonyOS");
        DataAbilityPredicates predicates =
            new DataAbilityPredicates().equalTo("subject", "Java");

        try {
            int nrows = helper.update(uri, student, predicates);
            if (nrows == -1)
                showToast("更新数据失败");
            else
                showToast("更新数据成功(" + nrows + ")");
        }
        catch (Exception exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void onDelete() {
        DataAbilityPredicates predicates =
            new DataAbilityPredicates().equalTo("subject", "HarmonyOS");

        try {
            int nrows = helper.delete(uri, predicates);
            if (nrows == -1)
                showToast("删除数据失败");
            else
                showToast("删除数据成功(" + nrows + ")");
        }
        catch (Exception exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void showToast(String text) {
        Component component = LayoutScatter.getInstance(this).parse(
            ResourceTable.Layout_toast_dialog, null, false);
        ((Text)component.findComponentById(ResourceTable.Id_txt))
            .setText(text);
        new ToastDialog(this)
            .setContentCustomComponent(component)
            .setSize(LayoutConfig.MATCH_CONTENT,
                LayoutConfig.MATCH_CONTENT)
            .setDuration(5000)
            .setAlignment(LayoutAlignment.BOTTOM)
            .setOffset(0, AttrHelper.vp2px(60, this))
            .show();
    }
}

运行效果如下图所示:

 

4.7 跨应用访问文件

4.7.1 数据提供者

添加DataAbility类的子类,其在config.json中的配置如下:

{
  "name": "com.minwei.fileprovider.FileDataAbility",
  "icon": "$media:icon",
  "description": "$string:filedataability_description",
  "type": "data",
  "uri": "dataability://com.minwei.fileprovider.FileDataAbility",
  "permissions": [
    "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER"
  ],
  "visible": true
}

该类的实现:

public class FileDataAbility extends Ability {
    ...
    public FileDescriptor openFile(Uri uri, String mode) {
        String filename = uri.getDecodedPathList().get(1);
        String pathname = getDataDir() + File.separator + filename;
        File file = new File(pathname);

        try {
            if (mode.equals("w"))
                return MessageParcel.dupFileDescriptor(
                    new FileOutputStream(file).getFD());
            else if (mode.equals("r"))
                return MessageParcel.dupFileDescriptor(
                    new FileInputStream(file).getFD());
        }
        catch (IOException exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }

        return null;
    }
    ...
}

例程:FileProvider

...\FileProvider\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "abilities": [
      ...
      {
        "name": "com.minwei.fileprovider.FileDataAbility",
        "icon": "$media:icon",
        "description": "$string:filedataability_description",
        "type": "data",
        "uri": "dataability://com.minwei.fileprovider.FileDataAbility",
        "permissions": [
          "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER"
        ],
        "visible": true
      }
    ],
    "defPermissions": [
      {
        "name": "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER"
      }
    ]
  }
}

...\FileProvider\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical"
    ohos:background_element="#804000">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="文件提供者"
        ohos:text_size="36fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\FileProvider\entry\src\main\java\com\minwei\fileprovider\FileDataAbility.java

public class FileDataAbility extends Ability {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        FileDataAbility.class.getCanonicalName());
    ...
    @Override
    public FileDescriptor openFile(Uri uri, String mode) {
        String filename = uri.getDecodedPathList().get(1);
        String pathname = getDataDir() + File.separator + filename;
        File file = new File(pathname);

        try {
            if (mode.equals("w"))
                return MessageParcel.dupFileDescriptor(
                    new FileOutputStream(file).getFD());
            else if (mode.equals("r"))
                return MessageParcel.dupFileDescriptor(
                    new FileInputStream(file).getFD());
        }
        catch (IOException exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }

        return null;
    }
    ...
}

4.7.2 数据需求者

在config.json中添加权限:

"defPermissions": [
  {
    "name": "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER",
    "grantMode": "system_grant"
  }
],
"reqPermissions": [
  {
    "name": "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER"
  },
  {
    "name": "ohos.permission.WRITE_USER_STORAGE"
  },
  {
    "name": "ohos.permission.READ_USER_STORAGE"
  }
]

通过URI指定目标DataAbility,借助DataAbilityHelper访问其它应用的文件:

public class MainAbilitySlice extends AbilitySlice {
    private DataAbilityHelper helper;

    private final Uri uri = Uri.parse(
        "dataability:///com.minwei.fileprovider.FileDataAbility/note.txt");

    public void onStart(Intent intent) {
        helper = DataAbilityHelper.creator(this);
        ...
    }

    private void onWrite() {
        ...
        FileDescriptor fd = helper.openFile(uri, "w");
        ...
    }

    private void onRead() {
        ...
        FileDescriptor fd = helper.openFile(uri, "r");
        ...
    }
    ...
}

例程:FileDemander

...\FileDemander\entry\src\main\config.json

{
  ...
  "module": {
    ...
    "defPermissions": [
      {
        "name": "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER",
        "grantMode": "system_grant"
      }
    ],
    "reqPermissions": [
      {
        "name": "com.minwei.fileprovider.DataAbilityShellProvider.PROVIDER"
      },
      {
        "name": "ohos.permission.WRITE_USER_STORAGE"
      },
      {
        "name": "ohos.permission.READ_USER_STORAGE"
      }
    ]
  }
}

...\FileDemander\entry\src\main\resources\base\graphic\background_button.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners ohos:radius="100"/>
    <solid ohos:color="#804000"/>
</shape>

...\FileDemander\entry\src\main\resources\base\layout\ability_main.xml

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:left_padding="80vp"
    ohos:right_padding="80vp"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="文件需求者"
        ohos:text_size="28fp"
        ohos:text_color="#804000"
        />

    <Text
        ohos:height="2vp"
        ohos:width="match_parent"
        ohos:top_margin="10vp"
        ohos:bottom_margin="5vp"
        ohos:background_element="#804000"
        />

    <Button
        ohos:id="$+id:btnWrite"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="写入数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

    <Button
        ohos:id="$+id:btnRead"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:padding="8vp"
        ohos:top_margin="10vp"
        ohos:background_element="$graphic:background_button"
        ohos:text="读取数据"
        ohos:text_size="24fp"
        ohos:text_color="#ffffff"
        />

</DirectionalLayout>

...\FileDemander\entry\src\main\java\com\minwei\filedemander\slice\MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel label = new HiLogLabel(
        HiLog.LOG_APP, 0x00101,
        MainAbilitySlice.class.getCanonicalName());

    private DataAbilityHelper helper;

    private final Uri uri = Uri.parse(
        "dataability:///com.minwei.fileprovider.FileDataAbility/note.txt");

    @Override
    public void onStart(Intent intent) {
        ...
        helper = DataAbilityHelper.creator(this);

        findComponentById(ResourceTable.Id_btnWrite)
            .setClickedListener(component -> onWrite());
        findComponentById(ResourceTable.Id_btnRead)
            .setClickedListener(component -> onRead());
    }
    ...
    private void onWrite() {
        try {
            FileDescriptor fd = helper.openFile(uri, "w");

            FileWriter fw = new FileWriter(fd);
            BufferedWriter bw = new BufferedWriter(fw);

            bw.write("文件中的数据");
            bw.newLine();

            bw.close();
        }
        catch (Exception exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }

    private void onRead() {
        try {
            FileDescriptor fd = helper.openFile(uri, "r");

            FileReader fr = new FileReader(fd);
            BufferedReader br = new BufferedReader(fr);

            String line;
            while ((line = br.readLine()) != null)
                HiLog.info(label, line);

            br.close();
        }
        catch (Exception exception) {
            HiLog.info(label, exception.getLocalizedMessage());
        }
    }
}

运行效果如下图所示:

 

更多精彩,敬请期待……


达内集团C++教学部 2021年10月4日