元數(shù)據(jù)綁定系列(二):元數(shù)據(jù)綁定進階
先上源碼:
喵叔catuncle / TestMetaDataBinding(主程序)
喵叔catuncle / TestDataCenter(配合主程序演示跨進程、跨設備特性)
demo截圖
上一篇元數(shù)據(jù)綁定系列(一):元數(shù)據(jù)綁定的使用,算是元數(shù)據(jù)綁定入門。通過codelbs代碼的學習,這一篇我們更深入思考一下。不然,對于元數(shù)據(jù)綁定我們永遠只是停留在使用工具的重復勞動階段。
代碼項目結構

- entry:程序入口(主程序,自定義元數(shù)據(jù)類HelloUiMetaData也放在這個module)
- dataability:請忽略這個名字,最開始放的是AlarmClock的數(shù)據(jù)綁定。后來rdb作為數(shù)據(jù)源要和dataability對比,所以也放在這個module里。
- datacenter:數(shù)據(jù)源相關的類AlarmsDataAbility和AlarmRdbStoreMetaDataHelper以及數(shù)據(jù)庫都放在這個module。為了體現(xiàn)數(shù)據(jù)綁定的ui層和數(shù)據(jù)層分離的思想,所以我刻意把數(shù)據(jù)層放在獨立的Module中。
- mylibrary:放一些工具類
思考一:一個xml布局文件中可以使用多個元數(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:alignment="center"
- ohos:orientation="vertical">
- <request-meta-data
- name="data"
- schema="wang.unclecat.meta-data.hello"/>
- <request-meta-data
- name="helloUi"
- class="wang.unclecat.databinding.HelloUiMetaData"
- schema="wang.unclecat.meta-data.hello.ui"/>
- <Text
- ohos:id="$+id:text_helloworld"
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- metaDataBinding:text="@{data.msg}"
- ohos:text_size="40vp"
- />
- <Text
- ohos:id="$+id:text_helloworld2"
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:text_size="40vp"
- metaDataBinding:text="*{helloUi.toStr(@{helloUi.count})}"
- />
- </DirectionalLayout>
- MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
- .fromRequestItems(binding.getRequestMetaData())
- .setSyncRequest("data", true)
- .setSyncRequest("helloUi", true)
- .setCustomDao("data", simpleDao)
- .setCustomDao("helloUi", simpleDaoUi)
- .build();
demo中,我定義了兩個元數(shù)據(jù)data和helloUi,都可以成功綁定。
實際開發(fā)中,如果一個頁面中有多個業(yè)務上不同的區(qū)域,那么每個區(qū)域綁定的數(shù)據(jù)也必然是不同的。(如果強行定義在一個元數(shù)據(jù)實體中也可以,但是違背了“面向對象”的思想。)
詳見 wang.unclecat.databinding.slice.HelloSlice中示例
思考二:為什么Feature中使用 元數(shù)據(jù)綁定,Json Schema文件必須放在Entry的resource/rawfile.jsonschema路徑下?
否則報錯提示"沒有定義相應的schema"
- java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] ohos.mp.metadata.binding.schema.SchemaProto.getFields()' on a null object reference
反編譯:
- package ohos.mp.metadata.binding.schema;
- public class SchemaParseUtil {
- public static void loadAllMetaDataSchema(Context var0) {
- try {
- RawFileEntry var1 = var0.getResourceManager().getRawFileEntry("resources/rawfile/jsonschema/");//這個路徑有問題!??!
- Entry[] var2 = var1.getEntries();
- for(int var3 = 0; var3 < var2.length; ++var3) {
- String var4 = var2[var3].getPath();
- String var5 = "resources/rawfile/jsonschema/" + var4;
- loadSingleMetadataSchema(var0, var5);
- }
- } catch (IOException var6) {
- Log.error("Load All MetaData Schema Failed: IO exception");
- }
- }
- }
之前我總結過rawfile的使用,有這樣的結論:
就算在myfeature這個module里"resources/..."也是"entry/resources/..."的簡寫形式。
所以,Json Schema文件必須放在Entry中??墒?,這顯然不符合我們的開發(fā)習慣啊,我們應該把相關的資源放在同一個module里。
在本例中,dataability中使用的Json Schemaalarm_schema.json是放在dataability這個module中,我是怎么做到的呢?
其實很簡單
- package wang.unclecat.dataability;
- @MetaDataApplication(requireData = true, exportData = false)
- public class MyApplication extends AbilityPackage {
- @Override
- public void onInitialize() {
- super.onInitialize();
- //MetaDataFramework.init(this);//有bug
- MetaDataFrameworkExtra.init(this);//用它來代替
- }
- }
詳見wang.unclecat.mylibrary.MetaDataFrameworkExtra
- package wang.unclecat.mylibrary;
- public class MetaDataFrameworkExtra {
- public static void init(AbilityPackage var0) {
- MetaDataFramework.appContext = var0;
- loadAllMetaDataSchema(var0);//fix: SchemaParseUtil.loadAllMetaDataSchema(var0);
- UiOperatorManager.init();
- }
- //fix: SchemaParseUtil.loadAllMetaDataSchema(var0);
- public static void loadAllMetaDataSchema(Context var0) {
- String moduleName = var0.getHapModuleInfo().getModuleName();
- try {
- //這里是關鍵!把moduleName加上!
- RawFileEntry var1 = var0.getResourceManager().getRawFileEntry(moduleName+"/resources/rawfile/jsonschema/");
- Entry[] var2 = var1.getEntries();
- for(int var3 = 0; var3 < var2.length; ++var3) {
- String var4 = var2[var3].getPath();
- String var5 = moduleName+"/resources/rawfile/jsonschema/" + var4;
- loadSingleMetadataSchema(var0, var5);
- }
- } catch (IOException var6) {
- Log.error("Load All MetaData Schema Failed: IO exception");
- }
- }
- //call the method using reflection
- private static void loadSingleMetadataSchema(Context var0, String var1){
- try {
- Class<?> threadClazz = Class.forName("ohos.mp.metadata.binding.schema.SchemaParseUtil");
- Method method = threadClazz.getDeclaredMethod("loadSingleMetadataSchema", Context.class, String.class);
- method.setAccessible(true);
- System.out.println(method.invoke(null, var0, var1));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
思考三:除了DataAbility作為數(shù)據(jù)源,還可以有其它形式的數(shù)據(jù)源嗎?
codelabs中使用的元數(shù)據(jù)有如下三個:
- class ClockRowMetaData extends DataAbilityMetaData
- class CustomMetaData extends DataAbilityMetaData
- class NoteMetaData extends MetaData
MetaData是所有元數(shù)據(jù)類的最終基類。
其中ClockRowMetaData和CustomMetaData都繼承自DataAbilityMetaData,而DataAbilityMetaData的基類還是MetaData,DataAbilityMetaData以DataAbility為數(shù)據(jù)源。
NoteMetaData通過自定義的class MyDataHandler implements CustomDao.ICustomMetaDataHandler最終還是使用DataAbility作為數(shù)據(jù)源。
難道我們使用元數(shù)據(jù)綁定框架只能通過DataAbility來訪問數(shù)據(jù)?
當然不是。
com.huawei.middleplatform:ohos-metadata-binding:1.0.0.0中默認有三個MetaData的子類
- DataAbilityMetaData
- RdbStoreMetaData
- RemoteServiceMetaData
從名字上不難看出,它們本質(zhì)只是數(shù)據(jù)源不同,用法上應該大同小異。
在 喵叔catuncle / TestMetaDataBinding中
- 我也會演示RdbStoreMetaData的用法,它以Rdb(關系型數(shù)據(jù)庫)直接作為數(shù)據(jù)源,而不需要DataAbility中轉。
詳見:wang.unclecat.dataability.alarm.metadata.ClockRowMetaData2
- 也會直接繼承MetaData自定義我們的HelloUiMetaData元數(shù)據(jù)類,它的數(shù)據(jù)源很簡陋,只是內(nèi)存中的一個變量。
詳見:wang.unclecat.databinding.HelloUiMetaData
- 至于RemoteServiceMetaData后續(xù)我會慢慢加上,先把這篇文章寫完,不然放得時間太長了。
說了這么多,現(xiàn)在認真回答一下這個問題:除了DataAbility作為數(shù)據(jù)源,可以有Rdb、RemoteService、自定義數(shù)據(jù)源(這里可以根據(jù)自己的需求自由發(fā)揮)
思考四:關于RdbStoreMetaData的問題一:
設置"rdbstore:///wang.unclecat.datacenter.AlarmRdbStoreMetaDataHelper"這樣的uri會報錯
- ohos.mp.metadata.binding.databinding.DataSourceConnectionException:
- Make sure [wang.unclecat.datacenter.AlarmRdbStoreMetaDataHelper] is accessible
如果把元數(shù)據(jù)數(shù)據(jù)綁定的日志開關打開,我們可以看到這樣的error提示:
- "Illegal dataUri. supported type:rdbstore:///wang.unclecat.datacenter.AlarmRdbStoreMetaDataHelper
反編譯:
- package ohos.mp.metadata.binding.databinding;
- public class MetaDataRequestInfo {
- public static class Builder {
- public MetaDataRequestInfo.Builder setRequestSource(String var1, String var2) {
- if (!this.isIllegalDataUri(var2)) {
- String var4 = "|dataability|dataservice|datahandler|";
- Log.error("Illegal dataUri. supported type: " + var4);
- throw new DataSourceNotFoundException("Illegal dataUri. supported type: " + var4);
- } else {
- MetaDataRequestInfo.RequestItem var3 = this.getOrCreateRequest(var1);
- var3.dataUri = var2;
- return this;
- }
- }
- //這個方法有bug?。?!
- private boolean isIllegalDataUri(String var1) {
- String var2 = "^(dataability|dataservice|datahandler)\\:\\/.*?\\/.*?\\/.*";//這里有bug?。?!少了rdbstore
- return Pattern.matches(var2, var1);
- }
- public MetaDataRequestInfo build() {
- Iterator var1 = this.requestMap.keySet().iterator();
- while(var1.hasNext()) {
- String var2 = (String)var1.next();
- MetaDataRequestInfo.RequestItem var3 = (MetaDataRequestInfo.RequestItem)this.requestMap.get(var2);
- if (var3.predicates != null) {
- String var4 = var3.dataUri;
- String var5;
- if (var4.startsWith("dataability")) {
- if (var3.predicates.getType() != Type.DATA_ABILITY_PREDICARES) {
- var5 = "MetaDataRequestInfo-build: Invalid predicates type for request " + var3.name + ", should be DataAbilityPredicates object";
- Log.error(var5);
- throw new DataSourceNotFoundException(var5);
- }
- } else if (var4.startsWith("rdbstore")) {
- if (var3.predicates.getType() != Type.RDB_PREDICATES) {
- var5 = "MetaDataRequestInfo-build: Invalid predicates type for request " + var3.name + ", should be RdbPredicates object";
- Log.error(var5);
- throw new DataSourceNotFoundException(var5);
- }
- } else if (!var4.startsWith("dataservice") && var4.startsWith("datahandler") && var3.predicates.getType() != Type.PACMAP_PREDICATES) {
- var5 = "MetaDataRequestInfo-build: Invalid predicates type for request " + var3.name + ", should be PacMap object";
- Log.error(var5);
- throw new DataSourceNotFoundException(var5);
- }
- }
- }
- return new MetaDataRequestInfo(this);
- }
- }
- }
發(fā)現(xiàn)boolean isIllegalDataUri(String var1)有bug,解決方法就是跳過這個有bug的驗證方法:
- package wang.unclecat.mylibrary;
- public class MetaDataFrameworkExtra {
- public static void setRequestSource(MetaDataRequestInfo.Builder builder, String var1, String var2) {
- MetaDataRequestInfo.RequestItem requestItem = getOrCreateRequest(builder, var1);
- if (requestItem!=null) {
- requestItem.dataUri = var2;
- }
- }
- //call the method using reflection
- private static MetaDataRequestInfo.RequestItem getOrCreateRequest(MetaDataRequestInfo.Builder builder, String var1) {
- try {
- Class<?> clazz = Class.forName("ohos.mp.metadata.binding.databinding.MetaDataRequestInfo$Builder");
- Method method = clazz.getDeclaredMethod("getOrCreateRequest", String.class);
- method.setAccessible(true);
- return (MetaDataRequestInfo.RequestItem) method.invoke(builder, var1);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- }
思考五:關于RdbStoreMetaData的問題二
在使用RdbStoreMetaData的過程中,我發(fā)現(xiàn)對rdb的操作只有查詢是成功的,插入、刪除、修改都失敗。
反編譯:
- package ohos.mp.metadata.binding.dao.inner.rdbstore;
- public class RdbStoreDao extends MetaDataDao implements DataObserver {
- //代碼有省略。。。。
- public void notifyDataAdd(MetaData var1) {
- //空?所以ui層的改變,不會通過MetaData通知到數(shù)據(jù)層
- }
- public void notifyDataDelete(MetaData var1) {
- //空?所以ui層的改變,不會通過MetaData通知到數(shù)據(jù)層
- }
- public void notifyChange(MetaData var1, String var2, Object var3) {
- if (var1 instanceof RdbStoreMetaData) {
- RdbStoreMetaData var4 = (RdbStoreMetaData)var1;
- RdbStore var5 = this.helper.getRdbStore();
- if (var5.isOpen()) {
- var5.beginTransaction();
- RdbPredicates var6 = this.createIdentityPredicate(var4);
- try {
- var5.update(var4.createValuesBucketWithoutKeys(this.helper.getMetaDataIdentity()), var6);
- var5.markAsCommit();
- } catch (Exception var11) {
- Log.error("RdbStore Update Failed: Exception");
- } finally {
- var5.endTransaction();
- }
- }
- }
- }
- }
發(fā)現(xiàn)void notifyDataAdd(MetaData var1)和void notifyDataDelete(MetaData var1)是空實現(xiàn),所以ui上插入、刪除對MetaData的操作不會作用到數(shù)據(jù)層,也許是這個原因吧。當然,也有可能是我的用法不對。
ui上對MetaData的修改操作也同樣不生效,我懷疑RdbStoreDao類中對MetaData沒有調(diào)用addInnerObserver(),這只是和DataAbilityDao的實現(xiàn)對比后的猜測。
思考六:自定義MetaData都有哪幾種方法?
在demo中,目前我只是簡單通過setCustomDao()來設置自定義的SimpleDao并實現(xiàn)SimpleDao.ISimpleMetaDataHandler來完成對數(shù)據(jù)的綁定。
通過了解。自定義MetaData總結一下,有如下兩種方法:
設置SimpleDao并實現(xiàn)相應的SimpleDao.ISimpleMetaDataHandler
設置"datahandler:///com.huawei.metadatabindingdemo.custom_data_source.handler.MyDataHandler"這樣的uri,并實現(xiàn)相應的CustomDao.ICustomMetaDataHandler//codelabs中有演示, 本示例代碼中沒有實現(xiàn)
思考七:抽象類MetaDataDao的作用
此類封裝了對數(shù)據(jù)源的直接操作(連接、增刪改查等)。和常見的數(shù)據(jù)庫orm框架中Dao的概念一致。
默認有五個實現(xiàn)類,前三個和內(nèi)置的MetaData對應,后兩個用于我們自定義MetaData
- DataAbilityDao
- RdbStoreDao
- RemoteServiceDao
- SimpleDao
- CustomDao
順便看一下 MetaDataDaoHelper的反編譯結果:
- public class MetaDataDaoHelper {
- public MetaDataDaoHelper() {
- }
- public static MetaDataDao createDao(RequestItem var0, IMetaDataObserver var1) {
- String var2 = var0.dataUri;
- Object var3 = null;
- if (var0.customDao != null) {
- var3 = var0.customDao;
- } else if (var2.startsWith("dataability")) {
- var3 = new DataAbilityDao();
- } else if (var2.startsWith("rdbstore")) {
- var3 = new RdbStoreDao();
- } else if (var2.startsWith("dataservice")) {
- var3 = new RemoteServiceDao();
- } else if (var2.startsWith("datahandler")) {
- var3 = createFromUri(var2);
- }
- if (var3 == null) {
- throw new DataSourceNotFoundException("Request name:[" + var0.name + "],Request dataUri:[" + var2 + "]");
- } else {
- ((MetaDataDao)var3).setRequestItem(var0);
- ((MetaDataDao)var3).setMetaDataObserver(var1);
- Log.info("createDao: dao = " + var3.getClass().getName());
- return (MetaDataDao)var3;
- }
- }
- private static MetaDataDao createFromUri(String var0) {
- String var1 = var0.substring("datahandler:///".length());
- try {
- Class var2 = Class.forName(var1);
- Object var3 = var2.newInstance();
- if (var3 instanceof ICustomMetaDataHandler) {
- return CustomDao.create((ICustomMetaDataHandler)var3);
- } else if (var3 instanceof ISimpleMetaDataHandler) {
- return SimpleDao.create((ISimpleMetaDataHandler)var3);
- } else {
- throw new DataSourceNotFoundException("Class type of [" + var1 + "] doesn't match:" + ICustomMetaDataHandler.class.getTypeName() + "or" + ISimpleMetaDataHandler.class.getTypeName());
- }
- } catch (ClassNotFoundException var4) {
- throw new DataSourceNotFoundException("Create custom metadata dao fail missing class in " + var0, var4);
- } catch (InstantiationException | IllegalAccessException var5) {
- throw new DataSourceNotFoundException("Instance custom metadata dao fail with for " + var0, var5);
- }
- }
- }
可知MetaDataDao的實例化依賴uri的判斷結果
思考八:跨進程、跨設備訪問dataability類型的uri數(shù)據(jù)源
在同設備安裝 喵叔catuncle / TestDataCenter(配合主程序演示跨進程、跨設備特性),使用體驗上和本地訪問dataability完全一致,本示例代碼中已經(jīng)實現(xiàn)。
理論上講跨設備也應該是一樣的,不過跨設備訪問DataAbility會報如下的錯??墒俏乙呀?jīng)注冊了,并且本地(或本機跨進程)訪問都是好的。
- java.lang.IllegalArgumentException: DataAbility register failed, there is no corresponding dataAbility
- java.lang.IllegalStateException: No corresponding dataAbility, query failed
求大神指點跨設備訪問DataAbility的方法!!!
求大神指點跨設備訪問DataAbility的方法!!!
求大神指點跨設備訪問DataAbility的方法!!!
思考九: 如何開啟元數(shù)據(jù)綁定框架的日志
ohos.mp.metadata.binding.common.Log是框架的日志工具類,默認是關閉的,開發(fā)過程中,我們可以打開,方法如下:
- try {
- Field field = ohos.mp.metadata.binding.common.Log.class.getDeclaredField("enableDebug");
- field.setAccessible(true);
- field.set(null, true);
- } catch (Exception e) {
- e.printStackTrace();
- }
思考十:ui銷毀之后,綁定關系需要解綁嗎?
答案:需要,框架已自動完成,開發(fā)者無需干預。
還是反編譯:
- public abstract class MetaDataBinding implements IMetaDataObserver, LifecycleStateObserver {
- //子類的requestBinding()會調(diào)用此方法
- protected static <T extends MetaDataBinding> T createBinding(AbilitySlice var0, int layoutId, MetaDataRequestInfo var2, IMetaDataObserver var3) throws DataSourceConnectionException {
- if (var0 == null) {
- Log.warn("slice is null when creating binding");
- return null;
- } else {
- Lifecycle var4 = var0.getLifecycle();
- if (var4 == null) {
- Log.warn("lifecycle is null when creating binding");
- return null;
- } else {
- MetaDataBinding var5 = MetaDataFramework.getMetaDataBinding(layoutId);
- var5.createComponent(var0, var4);//inflat布局
- var5.requestBind(var2, var3);//綁定數(shù)據(jù)
- return var5;
- }
- }
- }
- protected Component createComponent(Context var1, Lifecycle var2) {
- this.context = var1;
- Component var3 = LayoutScatter.getInstance(var1).parse(this.getLayoutId(), (ComponentContainer)null, false);
- this.mComponent = new WeakReference(var3);
- this.lifecycle = var2;
- this.lifecycle.addObserver(this);//監(jiān)聽生命周期
- return var3;
- }
- //響應生命周期變化,自動解綁
- public void onStateChanged(Event var1, Intent var2) {
- if (var1 == Event.ON_STOP) {
- this.unbind();//解綁
- }
- }
由上可知,框架會監(jiān)聽AbilitySlice的生命周期變化,并在其銷毀時解綁
思考十一:元數(shù)據(jù)綁定的內(nèi)部實現(xiàn)
上面那么多次反編譯,我們也不難看出,元數(shù)據(jù)綁定基于APT(Annotation Processing Tool)即注解處理器。并且框架也由三部分組成(和ButterKinfe這類用到APT技術的框架一樣):
- ohos-metadata-annotation定義注解
- ohos-metadata-processor注解處理器
- ohos-metadata-binding工具類
生成類似如下的文件:
- wang.unclecat.dataability.metadatabinding.AlarmrowMetaDataBinding//主要完成數(shù)據(jù)的綁定、數(shù)據(jù)變化的監(jiān)聽
- wang.unclecat.dataability.metadatabinding.Alarm_rowLayoutMapper//完成Ui操作(get和set)
- //對ui的操作,詳細可見package ohos.mp.metadata.binding.uioperate
思考十二:每個頁面都對應一個DataAbility可以嗎?
我的回答:可以,但是不建議這樣做。
因為DataAbility類似Android的ContentProvider,會跟著應用一起啟動。如果有太多的DataAbility會占用太多內(nèi)存資源。那怎么使用元數(shù)據(jù)綁定的DataAbility呢?
我們看官方文檔是怎么介紹URI的:

- query:查詢參數(shù)。
- fragment:可以用于指示要訪問的子資源。
所以,我們只需要定義一個DataAbility,通過查詢參數(shù)決定要訪問的數(shù)據(jù)資源。