HarmonyOS 关于元数据绑定框架探索

Posted HarmonyOS技术社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HarmonyOS 关于元数据绑定框架探索相关的知识,希望对你有一定的参考价值。

前言

在上一篇HarmonyOS DataBinding 使用指南中,有人提到了元数据绑定框架并提出了疑问,元数据绑定框架跟DataBinding有什么区别?功能上似乎也是做数据绑定,我查阅了官方文档,没有太多的资料,只有Codelabs上有个Demo教程,带着这种疑问,让我们一起来探索一下。

概述

根据官方Demo介绍,元数据绑定框架是基于HarmonyOS SDK开发的一套提供UI和数据源绑定能力的框架。通过使用元数据绑定框架,HarmonyOS应用开发者无需开发繁琐重复的代码即可实现绑定UI和数据源。这跟Databinding功能类似,接下来让我们再来看看它们有什么不同之处。

开始使用

简单UI组件绑定

1.首先,我们在模块的build.gradle文件中的dependencies中添加对元数据绑定框架的引用,并开启注解处理器:

implementation \'com.huawei.middleplatform:ohos-metadata-annotation:1.0.0.0\'
implementation \'com.huawei.middleplatform:ohos-metadata-binding:1.0.0.0\'
annotationProcessor \'com.huawei.middleplatform:ohos-metadata-processor:1.0.0.0\'
ohos {
    compileOptions {
        annotationEnabled true
    }
}

2.引用之后我们在MyApplication中对其进行初始化并添加对应注解,具体代码如下:

/**
 * requireData = true 表示该application需要获取数据
 * exportData = false 表示该application不对外提供数据
 */
@MetaDataApplication(requireData = true, exportData = false)
public class MyApplication extends AbilityPackage {
    private static Context context;

    @Override
    public void onInitialize() {
        super.onInitialize();
        mContext = this.getContext();
        //初始化MetaDataFramework
        MetaDataFramework.init(this);
    }
      public static Context getApplication() {
        return context;
    }
}

其中注解中的requireData表示该application是否需要获取数据,exportData表示该application是否外提供数据,大家可根据自己的需求进行配置。

3.接下来我们需要定义元数据,数据是以Json的格式,而DataBinding则是采用ActiveData对象绑定数据。我们简单的定义两个参数。Json数据采用得是Json Schema定义的一套词汇和规则,我们用这套词汇和规则用来定义Json元数据。最后我们需要将元数据Json文件放在resource/rawfile.jsonschema路径下。

{
  "id": "com.example.meta-data.time",
  "title": "test",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "test description",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer"
    },
    "message": {
      "type": "string"
    }
  }
}

4.在我们XML布局文件中,最外层的Layout中加入元数据绑定的框架的命名空间:xmlns:metaDataBinding,并创建元数据实体,作用跟我们Databinding的<binddata>标签类似,之后再我们组件中进行数据绑定,注意!在Databinding中用的是ohos的命名空间,而使用元数据绑定的时候,需要用metaDataBinding命名空间,具体代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
    ohos:
    ohos:
    ohos:orientation="vertical">
    <request-meta-data
        name="TestData"
        schema="com.example.meta-data.time"
        uri="dataability:///com.example.time.db.TestDataAbility"/>
     <Text
        ohos:id="$+id:title_text"
        ohos:
        ohos:
        metaDataBinding:text="@{TestData.message}"
        ohos:text_alignment="center"
        ohos:text_color="#FF555555"
        ohos:text_size="50"/>
</DirectionalLayout>

这时候<request-meta-data>这个标签就会报错:request-meta-data is not allowed here,具体原因还不清楚,怀疑是编译器的原因,但没关系,这并不影响我们的运行。

言归正传,在<request-meta-data>标签中,name为元数据名称,之后我们进行绑定的时候根据这个名称来引用,schema需要与刚定义的Json数据中id一致,uri则是我们使用元数据绑定的DataAbility路径。

5.接下来需要在代码中请求绑定,我们在AbilitySlice中的onStart方法中添加如下代码:

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
      Test alarm = TestOperation.queryFirst(this);
        if (alarm == null) {
            TestOperation.insert(this);
        }
    MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
            .setMetaDataClass("TestData", TestData.class)
            .setSyncRequest("TestData", true)
            .build();
    MetaDataBinding binding;
    Component mainComponent;
    try {
        // 请求绑定
        binding = AbilityindexpageMetaDataBinding.requestBinding(this, request, null);
        // 获得绑定的界面组件
        mainComponent = binding.getLayoutComponent();
    } catch (DataSourceConnectionException e) {
        mainComponent = LayoutScatter.getInstance(this)
                .parse(ResourceTable.Layout_error_layout, null, false);
    }
    setUIContent((ComponentContainer) mainComponent);
}

刚才第4点说到,TestData为对应的XML中<request-meta-data>标签中的nameTestData类是继承自DataAbilityMetaData的类,我们可以在里面根据业务需求对数据进行处理,作用有点类似DataBindingModel。配置完布局文件之后会自动生成XXXMetaDataBinding文件,然后通过调用requestBinding方法进行绑定,如果绑定异常的话我们就返回一个错误页面。

public class TestData  extends DataAbilityMetaData {
    public String toMessage(String message) {
        return message + "元数据绑定";
    }
}

6.配置部分基本完成,接下来就是配置数据库部分。数据库部分内容也比较多,这里只做简单的说明。关于数据库后续会有专门的文章进行详细讲解,欢迎大家订阅关注。

在我们上面XML布局中,<request-meta-data>标签uri属性指向的就是我们的DataAbility,这边主要做的就是数据访问更新等等。

public class TestDataAbility extends Ability {
    public static final Uri CLOCK_URI = Uri.parse(
            "dataability:///com.example.time.db.TestDataAbility");

    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
    private OrmContext ormContext = null;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        HiLog.info(LABEL_LOG, "TestDataAbility onStart");
        DatabaseHelper manager = new DatabaseHelper(this);
        ormContext = manager.getOrmContext(
                TestOrmDatabase.DATABASE_NAME_ALIAS,
                TestOrmDatabase.DATABASE_NAME,
                TestOrmDatabase.class);
    }

    @Override
    public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
        if (uri.equals(CLOCK_URI)) {
            OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, Test.class);
            return ormContext.query(ormPredicates, columns);
        }
        return null;
    }

    @Override
    public int insert(Uri uri, ValuesBucket value) {
        Test alarm = new Test();
        if (ormContext.insert(alarm.fromValues(value))) {
            ormContext.flush();
            DataAbilityHelper.creator(this, uri).notifyChange(uri);
            return (int) alarm.getRowId();
        }
        return -1;
    }

    @Override
    public int delete(Uri uri, DataAbilityPredicates predicates) {
        return 0;
    }

    @Override
    public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
        OrmPredicates ormPredicates;
        if (predicates == null) {
            Integer id = value.getInteger("id");
            if (id == null) {
                return -1;
            }
            value.delete("id");
            ormPredicates = new OrmPredicates(Test.class).equalTo("id", id);
        } else {
            ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, Test.class);
        }
        int rst = ormContext.update(ormPredicates, value);
        DataAbilityHelper.creator(getContext(), uri).notifyChange(uri);
        return rst;
    }

    @Override
    public FileDescriptor openFile(Uri uri, String mode) {
        return null;
    }

    @Override
    public String[] getFileTypes(Uri uri, String mimeTypeFilter) {
        return new String[0];
    }

    @Override
    public PacMap call(String method, String arg, PacMap extras) {
        return null;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (ormContext != null) {
            ormContext.close();
        }
    }
}

TestOperation类,是一个数据库的操作类,负责数据库的查询或写入等操作。

public class TestOperation {
    private static final String COL_MSG = "message";
    private static int idx = 0;
    private static int count = 0;

    public TestOperation() {
    }
    public static void insert(Context context) {
        try {
            int time = Math.abs((int) System.currentTimeMillis());
            ValuesBucket bucket = new ValuesBucket();
            bucket.putString(COL_MSG, "元数据绑定" + idx++);
            DataAbilityHelper.creator(context).insert(TestDataAbility.CLOCK_URI, bucket);
        } catch (DataAbilityRemoteException ex) {
        }
    }
    public static void insertAnAlarm(MetaDataBinding binding) {
        MetaDataRequestInfo.RequestItem requestItem = binding.getRequestInfo().getRequestItem("TestData");
        MetaData metaData = AbilityindexpageMetaDataBinding.createMetaData(requestItem);
        metaData.put(COL_MSG, "count" + count);
        binding.addMetaData(metaData, requestItem);
        count++;
    }
    public static Test queryFirst(Context context) {
        DataAbilityHelper helper = DataAbilityHelper.creator(context);
        ResultSet resultSet = null;
        try {
            resultSet = helper.query(
                    TestDataAbility.CLOCK_URI,
                    new String[]{COL_MSG},
                    null);
        } catch (DataAbilityRemoteException e) {
        }
        Test test = null;
        if (resultSet != null) {
            boolean hasData = resultSet.goToFirstRow();
            if (!hasData) {
                return null;
            }
            test = getQueryResults(resultSet);
        }
        return test;
    }

    private static Test getQueryResults(ResultSet resultSet) {
        Test alarm = new Test();
        for (String column : resultSet.getAllColumnNames()) {
            int index = resultSet.getColumnIndexForName(column);
            alarm.setMessage(getFromColumn(resultSet, index).toString());
        }
        return alarm;
    }

    private static Object getFromColumn(ResultSet resultSet, int index) {
        ResultSet.ColumnType type = resultSet.getColumnTypeForIndex(index);
        switch (type) {
            case TYPE_INTEGER:
                return resultSet.getInt(index);
            case TYPE_FLOAT:
                return resultSet.getDouble(index);
            case TYPE_STRING:
                return resultSet.getString(index);
            case TYPE_BLOB:
            case TYPE_NULL:
            default:
                return null;
        }
    }
}

TestOrmDatabase类,就是我们对象关系映射数据库的相关操作,具体可看官方文档。

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

    public static final String DATABASE_NAME = "TestOrmDatabase.db";

    public static final String DATABASE_NAME_ALIAS = "TestOrmDatabase";
}

到目前位置我们整个元数据绑定的开发流程就完整了,下面是展示页面:

Text显示的内容就是我们TestOperation类,在数据库添加的Message的数据( bucket.putString(COL_MSG, "元数据绑定" + idx++))。

UI容器组件绑定

接下来给大家说一下容器组件绑定,容器组件也就是我们的ListContainer,无处不列表,可以说是我们平时用的最多的组件,接下来给大家讲一下ListContainer如何进行绑定。(大致配置与简单UI差不多,下面只列出它们的区别之处)

1.首先我们需要在XML中添加ListContainer组件,我们直接沿用刚才的数据:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
    ohos:
    ohos:
    ohos:orientation="vertical">
    <request-meta-data
        name="TestData"
        schema="com.example.meta-data.time"
        uri="dataability:///com.example.time.db.TestDataAbility"/>
    <Text
        ohos:id="$+id:title_text"
        ohos:
        ohos:
        ohos:text="容器组件绑定"
        ohos:text_alignment="center"
        ohos:text_color="#FF555555"
        ohos:text_size="50"/>

    <ListContainer
        ohos:id="$+id:list_view"
        ohos:top_margin="10vp"
        ohos:
        ohos:
        />
</DirectionalLayout>

2.跟正常使用一样,我们需要创建继承BaseItemProviderProvider类:

public class TestListProvider extends BaseItemProvider {

    private final Context mContext;
    private List<TestRow> mData;

    public TestListProvider(Context mContext) {
        this.mContext = mContext;
    }

    public void initData(List<TestRow> testList) {
        this.mData = testList;
    }
    public void addItems(List<TestRow> alarmList) {
        this.mData.addAll(alarmList);
        mContext.getUITaskDispatcher().asyncDispatch(this::notifyDataChanged);
    }
    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int i) {
        return mData.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
        TestRow testRow = mData.get(i);
        if (component == null) {
            Component newComponent = testRow.createComponent();
            testRow.bindComponent(newComponent);
            return newComponent;
        } else {
            testRow.bindComponent(component);
            return component;
        }
    }
}

3.TestRow表示列表的条目,它持有一个元数据对象,我们对每个item进行数据绑定,获取UI组件及响应点击事件。

public class TestRow {
    private final AbilitySlice context;
    private final TestData clockMeta;

    public TestRow(AbilitySlice context, MetaData clockMeta) {
        this.context = context;
        this.clockMeta = (TestData) clockMeta;
    }

    public Component createComponent() {
       TestlistitemlayoutMetaDataBinding metaBinding = TestlistitemlayoutMetaDataBinding.createBinding(context, clockMeta);
        Component comp = metaBinding.getLayoutComponent();
        comp.setTag(metaBinding);
        return comp;
    }

    public void bindComponent(Component component) {
        TestlistitemlayoutMetaDataBinding metaBinding = (TestlistitemlayoutMetaDataBinding) component.getTag();
        metaBinding.reBinding(component, clockMeta);
    }
//      public void onClick() { 
//       context.present(new XXXSlice(clockMeta), new Intent()); 
//   } 
}

TestlistitemlayoutMetaDataBinding是我们定义布局后自动生成的MetaDataBinding类,通过createBinding方法将布局与数据进行绑定。

4.接下来看一下item的布局:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
    ohos:
    ohos:
    ohos:background_element="#000000"
    ohos:orientation="vertical">
    <using-meta-data
        class="com.example.time.bean.TestData"
        name="TestData"
        schema="com.example.meta-data.time"/>
    <DirectionalLayout
        ohos:
        ohos:
        ohos:background_element="#3c3c3c"
        ohos:bottom_padding="15vp"
        ohos:end_padding="15vp"
        ohos:orientation="vertical"
        ohos:start_padding="15vp"
        ohos:top_padding="15vp">

        <Text
            ohos:id="$+id:title_tv"
            ohos:
            ohos:
            metaDataBinding:text="@={TestData.message}"
            ohos:text_size="16fp"
            ohos:text_color="#ffffff"
            />
        <Text
            ohos:id="$+id:desc_tv"
            ohos:
            ohos:
            ohos:top_margin="10vp"
            metaDataBinding:text="@={TestData.message}"
            ohos:text_size="12fp"
            ohos:text_color="#727272"
            />
    </DirectionalLayout>

</DirectionalLayout>

这里需要注意的是,和普通布局区别在于item的元数据实体为<using-meta-data>。

5.接下来就是在AbilitySlice中进行请求绑定:

public class IndexPageAbilitySlice extends AbilitySlice implements IMetaDataObserver {
    private ListContainer mListContainer;
    private TestListProvider mTestListProvider;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        initView();
    }

    private void initView() {
        Test alarm = TestOperation.queryFirst(this);
        if (alarm == null) {
            TestOperation.insert(this);
        }
        // 创建元数据请求对象
        MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
                .setMetaDataClass("TestData", TestData.class)
                .setSyncRequest("TestData", false)
                .build();
        MetaDataBinding binding;
        Component mainComponent;
        try {
            // 请求绑定
            binding = AbilityindexpageMetaDataBinding.requestBinding(this, request, this);
            // 获得绑定的界面组件
            mainComponent = binding.getLayoutComponent();
        } catch (DataSourceConnectionException e) {
            mainComponent = LayoutScatter.getInstance(this)
                    .parse(ResourceTable.Layout_error_layout, null, false);
        }
        setUIContent((ComponentContainer) mainComponent);
        mListContainer = (ListContainer) findComponentById(ResourceTable.Id_list_view);
        mTestListProvider = new TestListProvider(this);

    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

    @Override
    public void onDataLoad(List<MetaData> list, MetaDataRequestInfo.RequestItem requestItem) {
        if (list == null || requestItem == null) {
            return;
        }
        if (mListContainer != null) {
            mTestListProvider.initData(createAlarms(this, list));
            mListContainer.setItemProvider(mTestListProvider);
        }
    }
    private List<TestRow> createAlarms(AbilitySlice context, List<MetaData> dataList) {
        List<TestRow> list = new ArrayList<>();
        for (MetaData metaData : dataList) {
            TestRow item = new TestRow(context, metaData);
            list.add(item);
        }
        return list;
    }
    @Override
    public void onDataChange(List<MetaData> list, List<MetaData> list1, List<MetaData> list2, MetaDataRequestInfo.RequestItem requestItem) {
        if (list == null) {
            return;
        }
        mTestListProvider.addItems(createAlarms(this, list));
    }
}

容器组件绑定的话,我们实现了IMetaDataObserver接口,主要用于数据的加载及数据更新,在onDataLoadProviderListContainer进行绑定,如数据有发生变化,则onDataChange对列表进行更新,而在setSyncRequest传参中我们改为false,表示为异步请求,因为IMetaDataObserver方法会异步执行,如果传Ture的话,会在onDataLoad方法执行之后requestBinding方法才会返回,之后在请求绑定requestBinding方法中第三个参数,dataCallback传入this进行监听。

6.最终实现效果

7.添加数据只需要调用我们之前的TestOperation.insertAnAlarm(binding)方法就可以进行数据添加:

元数据表达式

xml文件中进行元数据绑定时 metaDataBinding会用到多种表达式,具体用法如下:

标识符 意义 示例
@ 取得原数据属性的值赋值到UI属性的单项绑定 metaDataBinding:text="@{ClockMetaData.message}"
@= 元数据属性的值和UI属性双向绑定 metaDataBinding:text="@={ClockMetaData.message}"
* 绑定自定义函数 metaDataBinding:text="*{ClockMetaData.getTimeZone(@{ClockMetaData.hour})}"
$ 绑定资源文件 metaDataBinding:image_src="@{ClockMetaData.enabled} == 1 ? ${Media_icon_switch_enabled} : ${Media_icon_switch_disabled}"
# 点击事件触发给元数据赋值 metaDataBinding:onClick="#{ClockMetaData.enabled = (@{ClockMetaData.enabled} == 1 ? 0 : 1)}"

总结

元数据绑定的简单使用就介绍到这里,这里只跟大家展示了我们最常用的两种布局的绑定,我们还可以进行自定义UI的绑定、自定义数据源等等更多的用法等着大家一起来探索。

回到我们最初的问题,元数据绑定框架跟DataBinding有什么区别?我个人理解是,元数据绑定框架是基于元数据,而DataBinding则是绑定ActiveData我们专栏有专门讲解ActiveData的文章,欢迎大家前去查阅。),两者的功能及数据源是不一样的,可以针对自己的业务需求进行选择。

但在Demo的编写过程中,也发现了一个问题,同一个页面普通UI组件和容器组件不能同事绑定,问题也时处在我们容器组件第5点所说的,实现了IMetaDataObserver接口进行异步请求,这点也希望跟大家一起继续探索,欢迎在评论区共同探讨。

作者:曾瑞绅
更多原创内容请关注:中软国际 HarmonyOS 技术学院

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

想了解更多关于鸿蒙的内容,请访问:

51CTO和华为官方战略合作共建的鸿蒙技术社区

https://harmonyos.51cto.com/#bkwz

以上是关于HarmonyOS 关于元数据绑定框架探索的主要内容,如果未能解决你的问题,请参考以下文章

HarmonyOS之深入解析媒体数据的管理与操作

HarmonyOS实战—探索鸿蒙OS

HarmonyOS实战—探索鸿蒙OS

元数据管理框架如何制定,方法都在这!

实用代码片段将json数据绑定到html元素 (转)

数据绑定库和MVVM