HarmonyOS关于元数据绑定框架探索
想了解更多内容,于元请访问:
和华为官方合作共建的数据鸿蒙技术社区
https://harmonyos.51cto.com
前言
在上一篇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的标签类似,之后再我们组件中进行数据绑定,注意!在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:height="match_parent" ohos:width="match_parent" 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:height="300" ohos:width="match_parent" metaDataBinding:text="@{ TestData.message}" ohos:text_alignment="center" ohos:text_color="#FF555555" ohos:text_size="50"/> </DirectionalLayout>这时候这个标签就会报错:request-meta-data is not allowed here,具体原因还不清楚,怀疑是亿华云编译器的原因,但没关系,这并不影响我们的运行。
言归正传,在标签中,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中标签中的name,TestData类是继承自DataAbilityMetaData的类,我们可以在里面根据业务需求对数据进行处理,作用有点类似DataBinding的Model。配置完布局文件之后会自动生成XXXMetaDataBinding文件,然后通过调用requestBinding方法进行绑定,如果绑定异常的话我们就返回一个错误页面。
public class TestData extends DataAbilityMetaData { public String toMessage(String message) { return message + "元数据绑定"; } }6.配置部分基本完成,站群服务器接下来就是配置数据库部分。数据库部分内容也比较多,这里只做简单的说明。关于数据库后续会有专门的文章进行详细讲解,欢迎大家订阅关注。
在我们上面XML布局中,标签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:height="match_parent" ohos:width="match_parent" 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:height="300" ohos:width="match_parent" 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:height="match_parent" ohos:width="match_parent" /> </DirectionalLayout>2.跟正常使用一样,我们需要创建继承BaseItemProvider的Provider类:
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:height="match_content" ohos:width="match_parent" 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:height="match_content" ohos:width="match_parent" 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:height="match_content" ohos:width="match_parent" metaDataBinding:text="@={ TestData.message}" ohos:text_size="16fp" ohos:text_color="#ffffff" /> <Text ohos:id="$+id:desc_tv" ohos:height="match_content" ohos:width="match_parent" ohos:top_margin="10vp" metaDataBinding:text="@={ TestData.message}" ohos:text_size="12fp" ohos:text_color="#727272" /> </DirectionalLayout> </DirectionalLayout>这里需要注意的是,和普通布局区别在于item的元数据实体为。
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接口,主要用于数据的加载及数据更新,在onDataLoad将Provider跟ListContainer进行绑定,如数据有发生变化,则onDataChange对列表进行更新,而在setSyncRequest传参中我们改为false,表示为异步请求,因为IMetaDataObserver方法会异步执行,如果传Ture的话,会在onDataLoad方法执行之后requestBinding方法才会返回,之后在请求绑定requestBinding方法中第三个参数,dataCallback传入this进行监听。
6.最终实现效果

7.添加数据只需要调用我们之前的**TestOperation.insertAnAlarm(binding)**方法就可以进行数据添加:
元数据表达式
在xml文件中进行元数据绑定时 metaDataBinding会用到多种表达式,具体用法如下:
总结
元数据绑定的简单使用就介绍到这里,这里只跟大家展示了我们最常用的两种布局的绑定,我们还可以进行自定义UI的绑定、自定义数据源等等更多的用法等着大家一起来探索。
回到我们最初的问题,元数据绑定框架跟DataBinding有什么区别?我个人理解是,元数据绑定框架是基于元数据,而DataBinding则是绑定ActiveData(我们专栏有专门讲解ActiveData的文章,欢迎大家前去查阅。),两者的功能及数据源是不一样的,可以针对自己的业务需求进行选择。
但在Demo的编写过程中,也发现了一个问题,同一个页面普通UI组件和容器组件不能同事绑定,问题也时处在我们容器组件第5点所说的,实现了IMetaDataObserver接口进行异步请求,这点也希望跟大家一起继续探索,欢迎在评论区共同探讨。
想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com