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

-->
IT科技类资讯
上一篇:整合全栈服务能力 联想进入福布斯中国数字经济榜单前十
下一篇:分层级、筑底座、立标杆!新华三用数字化转型实践驱动行业变革