HarmonyOS關(guān)于元數(shù)據(jù)綁定框架探索
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
在上一篇HarmonyOS DataBinding 使用指南中,有人提到了元數(shù)據(jù)綁定框架并提出了疑問(wèn),元數(shù)據(jù)綁定框架跟DataBinding有什么區(qū)別?功能上似乎也是做數(shù)據(jù)綁定,我查閱了官方文檔,沒(méi)有太多的資料,只有Codelabs上有個(gè)Demo教程,帶著這種疑問(wèn),讓我們一起來(lái)探索一下。
概述
根據(jù)官方Demo介紹,元數(shù)據(jù)綁定框架是基于HarmonyOS SDK開(kāi)發(fā)的一套提供UI和數(shù)據(jù)源綁定能力的框架。通過(guò)使用元數(shù)據(jù)綁定框架,HarmonyOS應(yīng)用開(kāi)發(fā)者無(wú)需開(kāi)發(fā)繁瑣重復(fù)的代碼即可實(shí)現(xiàn)綁定UI和數(shù)據(jù)源。這跟Databinding功能類似,接下來(lái)讓我們?cè)賮?lái)看看它們有什么不同之處。
開(kāi)始使用
簡(jiǎn)單UI組件綁定
1.首先,我們?cè)谀K的build.gradle文件中的dependencies中添加對(duì)元數(shù)據(jù)綁定框架的引用,并開(kāi)啟注解處理器:
- 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.引用之后我們?cè)贛yApplication中對(duì)其進(jìn)行初始化并添加對(duì)應(yīng)注解,具體代碼如下:
- /**
- * requireData = true 表示該application需要獲取數(shù)據(jù)
- * exportData = false 表示該application不對(duì)外提供數(shù)據(jù)
- */
- @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是否需要獲取數(shù)據(jù),exportData表示該application是否外提供數(shù)據(jù),大家可根據(jù)自己的需求進(jìn)行配置。
3.接下來(lái)我們需要定義元數(shù)據(jù),數(shù)據(jù)是以Json的格式,而DataBinding則是采用ActiveData對(duì)象綁定數(shù)據(jù)。我們簡(jiǎn)單的定義兩個(gè)參數(shù)。Json數(shù)據(jù)采用得是Json Schema定義的一套詞匯和規(guī)則,我們用這套詞匯和規(guī)則用來(lái)定義Json元數(shù)據(jù)。最后我們需要將元數(shù)據(jù)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中加入元數(shù)據(jù)綁定的框架的命名空間:xmlns:metaDataBinding,并創(chuàng)建元數(shù)據(jù)實(shí)體,作用跟我們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>
這時(shí)候
言歸正傳,在
5.接下來(lái)需要在代碼中請(qǐng)求綁定,我們?cè)贏bilitySlice中的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 {
- // 請(qǐng)求綁定
- 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點(diǎn)說(shuō)到,TestData為對(duì)應(yīng)的XML中
- public class TestData extends DataAbilityMetaData {
- public String toMessage(String message) {
- return message + "元數(shù)據(jù)綁定";
- }
- }
6.配置部分基本完成,接下來(lái)就是配置數(shù)據(jù)庫(kù)部分。數(shù)據(jù)庫(kù)部分內(nèi)容也比較多,這里只做簡(jiǎn)單的說(shuō)明。關(guān)于數(shù)據(jù)庫(kù)后續(xù)會(huì)有專門的文章進(jìn)行詳細(xì)講解,歡迎大家訂閱關(guān)注。
在我們上面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類,是一個(gè)數(shù)據(jù)庫(kù)的操作類,負(fù)責(zé)數(shù)據(jù)庫(kù)的查詢或?qū)懭氲炔僮鳌?/p>
- 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, "元數(shù)據(jù)綁定" + 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類,就是我們對(duì)象關(guān)系映射數(shù)據(jù)庫(kù)的相關(guān)操作,具體可看官方文檔。
- @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";
- }
到目前位置我們整個(gè)元數(shù)據(jù)綁定的開(kāi)發(fā)流程就完整了,下面是展示頁(yè)面:

Text顯示的內(nèi)容就是我們TestOperation類,在數(shù)據(jù)庫(kù)添加的Message的數(shù)據(jù)( bucket.putString(COL_MSG, “元數(shù)據(jù)綁定” + idx++))。
UI容器組件綁定
接下來(lái)給大家說(shuō)一下容器組件綁定,容器組件也就是我們的ListContainer,無(wú)處不列表,可以說(shuō)是我們平時(shí)用的最多的組件,接下來(lái)給大家講一下ListContainer如何進(jìn)行綁定。(大致配置與簡(jiǎn)單UI差不多,下面只列出它們的區(qū)別之處)
1.首先我們需要在XML中添加ListContainer組件,我們直接沿用剛才的數(shù)據(jù):
- <?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.跟正常使用一樣,我們需要?jiǎng)?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表示列表的條目,它持有一個(gè)元數(shù)據(jù)對(duì)象,我們對(duì)每個(gè)item進(jìn)行數(shù)據(jù)綁定,獲取UI組件及響應(yīng)點(diǎn)擊事件。
- 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是我們定義布局后自動(dòng)生成的MetaDataBinding類,通過(guò)createBinding方法將布局與數(shù)據(jù)進(jìn)行綁定。
4.接下來(lái)看一下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的元數(shù)據(jù)實(shí)體為
5.接下來(lái)就是在AbilitySlice中進(jìn)行請(qǐng)求綁定:
- 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)建元數(shù)據(jù)請(qǐng)求對(duì)象
- MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
- .setMetaDataClass("TestData", TestData.class)
- .setSyncRequest("TestData", false)
- .build();
- MetaDataBinding binding;
- Component mainComponent;
- try {
- // 請(qǐng)求綁定
- 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));
- }
- }
容器組件綁定的話,我們實(shí)現(xiàn)了IMetaDataObserver接口,主要用于數(shù)據(jù)的加載及數(shù)據(jù)更新,在onDataLoad將Provider跟ListContainer進(jìn)行綁定,如數(shù)據(jù)有發(fā)生變化,則onDataChange對(duì)列表進(jìn)行更新,而在setSyncRequest傳參中我們改為false,表示為異步請(qǐng)求,因?yàn)镮MetaDataObserver方法會(huì)異步執(zhí)行,如果傳Ture的話,會(huì)在onDataLoad方法執(zhí)行之后requestBinding方法才會(huì)返回,之后在請(qǐng)求綁定requestBinding方法中第三個(gè)參數(shù),dataCallback傳入this進(jìn)行監(jiān)聽(tīng)。
6.最終實(shí)現(xiàn)效果

7.添加數(shù)據(jù)只需要調(diào)用我們之前的**TestOperation.insertAnAlarm(binding)**方法就可以進(jìn)行數(shù)據(jù)添加:
元數(shù)據(jù)表達(dá)式
在xml文件中進(jìn)行元數(shù)據(jù)綁定時(shí) metaDataBinding會(huì)用到多種表達(dá)式,具體用法如下:
總結(jié)
元數(shù)據(jù)綁定的簡(jiǎn)單使用就介紹到這里,這里只跟大家展示了我們最常用的兩種布局的綁定,我們還可以進(jìn)行自定義UI的綁定、自定義數(shù)據(jù)源等等更多的用法等著大家一起來(lái)探索。
回到我們最初的問(wèn)題,元數(shù)據(jù)綁定框架跟DataBinding有什么區(qū)別?我個(gè)人理解是,元數(shù)據(jù)綁定框架是基于元數(shù)據(jù),而DataBinding則是綁定ActiveData(我們專欄有專門講解ActiveData的文章,歡迎大家前去查閱。),兩者的功能及數(shù)據(jù)源是不一樣的,可以針對(duì)自己的業(yè)務(wù)需求進(jìn)行選擇。
但在Demo的編寫過(guò)程中,也發(fā)現(xiàn)了一個(gè)問(wèn)題,同一個(gè)頁(yè)面普通UI組件和容器組件不能同事綁定,問(wèn)題也時(shí)處在我們?nèi)萜鹘M件第5點(diǎn)所說(shuō)的,實(shí)現(xiàn)了IMetaDataObserver接口進(jìn)行異步請(qǐng)求,這點(diǎn)也希望跟大家一起繼續(xù)探索,歡迎在評(píng)論區(qū)共同探討。
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)