HarmonyOS關于元數據綁定框架探索
前言
在上一篇HarmonyOS DataBinding 使用指南中,有人提到了元數據綁定框架并提出了疑問,元數據綁定框架跟DataBinding有什么區(qū)別?功能上似乎也是做數據綁定,我查閱了官方文檔,沒有太多的資料,只有Codelabs上有個Demo教程,帶著這種疑問,讓我們一起來探索一下。
概述
根據官方Demo介紹,元數據綁定框架是基于HarmonyOS SDK開發(fā)的一套提供UI和數據源綁定能力的框架。通過使用元數據綁定框架,HarmonyOS應用開發(fā)者無需開發(fā)繁瑣重復的代碼即可實現綁定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定義的一套詞匯和規(guī)則,我們用這套詞匯和規(guī)則用來定義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,并創(chuàng)建元數據實體,作用跟我們Databinding的
- <?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>
這時候
言歸正傳,在
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中
- public class TestData extends DataAbilityMetaData {
- public String toMessage(String message) {
- return message + "元數據綁定";
- }
- }
6.配置部分基本完成,接下來就是配置數據庫部分。數據庫部分內容也比較多,這里只做簡單的說明。關于數據庫后續(xù)會有專門的文章進行詳細講解,歡迎大家訂閱關注。
在我們上面XML布局中,
- 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";
- }
到目前位置我們整個元數據綁定的開發(fā)流程就完整了,下面是展示頁面:

Text顯示的內容就是我們TestOperation類,在數據庫添加的Message的數據( bucket.putString(COL_MSG, “元數據綁定” + idx++))。
UI容器組件綁定
接下來給大家說一下容器組件綁定,容器組件也就是我們的ListContainer,無處不列表,可以說是我們平時用的最多的組件,接下來給大家講一下ListContainer如何進行綁定。(大致配置與簡單UI差不多,下面只列出它們的區(qū)別之處)
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.跟正常使用一樣,我們需要創(chuàng)建繼承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>
這里需要注意的是,和普通布局區(qū)別在于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);
- }
- // 創(chuàng)建元數據請求對象
- 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進行綁定,如數據有發(fā)生變化,則onDataChange對列表進行更新,而在setSyncRequest傳參中我們改為false,表示為異步請求,因為IMetaDataObserver方法會異步執(zhí)行,如果傳Ture的話,會在onDataLoad方法執(zhí)行之后requestBinding方法才會返回,之后在請求綁定requestBinding方法中第三個參數,dataCallback傳入this進行監(jiān)聽。
6.最終實現效果

7.添加數據只需要調用我們之前的**TestOperation.insertAnAlarm(binding)**方法就可以進行數據添加:
元數據表達式
在xml文件中進行元數據綁定時 metaDataBinding會用到多種表達式,具體用法如下:
總結
元數據綁定的簡單使用就介紹到這里,這里只跟大家展示了我們最常用的兩種布局的綁定,我們還可以進行自定義UI的綁定、自定義數據源等等更多的用法等著大家一起來探索。
回到我們最初的問題,元數據綁定框架跟DataBinding有什么區(qū)別?我個人理解是,元數據綁定框架是基于元數據,而DataBinding則是綁定ActiveData(我們專欄有專門講解ActiveData的文章,歡迎大家前去查閱。),兩者的功能及數據源是不一樣的,可以針對自己的業(yè)務需求進行選擇。
但在Demo的編寫過程中,也發(fā)現了一個問題,同一個頁面普通UI組件和容器組件不能同事綁定,問題也時處在我們容器組件第5點所說的,實現了IMetaDataObserver接口進行異步請求,這點也希望跟大家一起繼續(xù)探索,歡迎在評論區(qū)共同探討。