MCube动态化与原生工程结合最佳实践

跨端动态化开发方案重要性日益凸显,本文对我们团队MCube动态化实践做了总结,为大家提供经验和借鉴。

接入背景

随着我们工程的需求迭代,暴露出了业务需求量大,分端开发和发版更新成本高等痛点,使用H5页面来代替,在用户体验和性能相较原生有差异,所以我们团队开始了对动态化改造的研究。

在做过一些列动态化开发的尝试,并对多种方案进行调研后,我们选择了MCube的动态化方案。之所以选用MCube,是因为它有以下的优点:

1.方案成熟,对同为京东体系下的APP来说接入成本非常低,对照文档文档,整个接入的过程并不复杂。2.MCube有丰富原子组件,组件的灵活性非常高。

3.完善的组件市场可以很好的满足我们的需求。

4.一套XML模板适用多个平台,并且可以复用。

我们在接入的过程当中遇到兼容性问题,在沟通过程中,MCube的负责同学和开发人员积极性非常高,定制化开发的效率也很高,认真负责,有问必答,整个沟通过程非常愉快。

我们团队目前已经完成了从初步尝试,到一整个业务页面动态化改造的完整历程。期间遇到了许多问题,总结出了最佳化实践的经验,本篇文章做出总结,为其他已经接入和准备接入的同学提供经验。

单楼层动态化改造

虽然MCube的SDK提供了丰富的接口方法,但是为了方便使用和后续的全页面级别动态化控制,我们还是做了大量的封装和本地化开发。

首先,在数据层面:

根据MCube SDK的必传参数要求,我们封装了必要的module(页面名)和templateID(模版ID);在数据部分,模型封装了用于模版的数据源data,这样就可以让动态化视图(DYNContainerView)从数据源取数据进行展示,同时也方便外部下发;同时我们有大量原生楼层做动态化改造的场景,需要保留原数据进行交互和降级,所以模型预留了原生楼层使用的nativeData,方便在有以上需求的时候取用;最后,考虑到页面有将某些楼层按照section成组进行展示的场景,模型设计了用于组划分的groupID,相同groupID为一个section,按照页面具体的业务需求决定组的展示形式(卡片或者分割)。

在视图层面,以封装好的DynamicViewModel为基础,将动态化视图加载等操作进行了封装,形成了通用楼层CommonTableViewCell:

通用楼层的能力包括:

1.解析module和templateID

2.调用MCube SDK下载对应动态化模版,并在结束时给出回调

3.展示动态化视图DynView,使用数据进行刷新

4.支持设置降级视图,失败时进行降级

5.对于元素是原生视图的,通用楼层会用过layoutId将其找到,并通过Block回调出来,方便进行后续的操作,包括宽高调整,代理设置等等

复制代码
self.successBlock = ^(DYNContainerView * _Nonnull dynamicView) {
    UIView *nativeView = [dynamicView getRrenderViewWithLayoutId:@"100"];
    if ([nativeView isKindOfClass:[原生视图类名 class]]) {
       //原生事件处理   
    }
};

全页面动态化改造

有了通用楼层,我们就开始了下一步的改造,最终的目的是实现全页面动态化下发,这也是我们团队对于动态化改造的新理解:全页面的所有楼层均支持动态化下发,原生楼层与动态化楼层共存,由数据来驱动,灵活控制楼层的展示形式,另外还可以由配置平台设置开关,控制动态化楼层的降级。

理想情况下,业务页面的顺序和楼层数据都可以由后端下发的数据源来决定。但实际进行改造的时候,我们时常会面对的情况是:后端下发的result里混杂着所有楼层的数据字段,每个楼层在加载数据model的时候要到result里单独找到自己要用的字段进行解析,这样就存在着两个弊端:

1.楼层顺序无法由下发控制

2.无法做到分楼层动态化控制

所以我们改造的第一步是对下发的数据结构进行重新定义。

复制代码
"result":{
    "dynamicFloors":
        [
            {
                "type":"楼层类型",
                "module":"页面名",
                "templateID":"模版ID",
                "groupID":"part1"
                "data":{
                }
            }
        ]
}

我们设定了一个动态化楼层数组dynamicFloors,里面的每个元素代表了某一楼层的数据。type用于确定楼层类型,对于动态化楼层,会额外下发module和templateID,以此来区分动态化和原生楼层。如果发现下发了module和templateID,那么自动用DynamicViewModel来解析,将data设置进数据源,然后用CommonTableViewCell来加载和展示楼层。流程图如下:

后续可以推动服务端按照这个结构进行下发,当然,在推动其他团队同学改在之前,前端也可以先行对下发的result进行改造,但千万要保证数据安全并做好容错处理和降级开关。

改造成果:

踩坑Q&A

接入JDBDynamicModule后安装/编译报错

我们在接入的过程当中遇到了诸如类名和方法名冲突的问题,需要与MCube的相关负责人沟通,单独为工程进行SDK的定制化开发,解除依赖。

哪些楼层适合做成动态化楼层

就我们的经验来讲,适合做成动态化楼层需要满足以下一个或多个条件:

1.楼层的展示样式存在灵活变化的需求。

2.楼层的交互形式简单且单一。动态化着重于UI展示,相对来说模版交互事件开发较为繁琐。

3.安卓与iOS差异不大。这里的差异主要说的是模开发的差异,由于MCube本身的机制,在进行模版开发时经常会有安卓和iOS模版字段不通用的情况,具体见下面"安卓与iOS模版开发时的差异"部分。差异小可以保证安卓和iOS能采用同一套模版,减少开发工作量。

通过loadWithCallback加载模版失败

首先检查DYNContainerConfig对象的module和templateID是否设置正确。

其次检查是否实现了网络加载桥接类以及 协议。MCube的所有网络请求都要经过桥接类来进行处理,走我们本地的网络请求并把结果返回给MCube。首先根据自己的命名需要创建一个类,并实现 协议,在+load里对创建的类进行注册,主要是告诉MCube你的网络请求后续都由我这个类来处理

复制代码
+ (void)load {
   [DYNMapperManager registeProtocolName:DYNProtocolConfig.networkProtocolName handlerClassName:NSStringFromClass(self)];
}

桥文件里的主要工作是实现协议里定义的相关方法,网络请求的DYNNetworkProtocol中主要需要实现的有:

getServerURL

根据自己的网络环境配置,返回MCube接口请求的URL头部,有两套:

1.正式环境:api.m.jd.com/XXX?functionId=

2.测试环境:beta-api.m.jd.com/XXX?functionId=

requestWithSetupModel: finish: cancel:

在这个方法里,需要创建API请求,将setupModel里的functionId和params,以及appType放进入参,发起请求,并把请求结果通过finish/cancel的Block回调返回给MCube进行处理。我们也可以在这里断点或者进行自己的处理,最主要的接口是获取模版列表,可以查看下发的模版列表文件是否正常。

getNetworkType

根据当前的网络环境返回网络类型,比如Wi-Fi,5G和4G等等。

模版中的ImageView图片无法加载

图片加载相关的请求也需要创建对应的桥接类,实现 协议的相关方法。同时还需要给UIImageView创建一个category分类UIImageView+DYNBImageViewHandler,在分类里覆写dyn_setImageWithURL: placeholderImage:和dyn_setImageWithURL: placeholderImage: completed:方法,在它们里面利用sdWebImage等其他工具进行图片下载。

如何在模版中使用原生视图组件

MCube中可以将原生视图组件做为元素写到模版里,有以下步骤

1.原生视图组件实现 协议里的两个方法:

复制代码
#pragma mark - DYNCustomViewProtocol
- (id)getCustomView {
    return self;
}

- (void)setDataWithValue:(id)value object:(id)object sender:(DYNViewLayout *)sender {
    if ([value isKindOfClass:[原生模型 class]]) {
        //使用模型刷新原生视图组件
    }
}

2.在+load里声明模版元素与原生视图类的对应关系:

复制代码
+ (void)load {
    [DYNMapperManager registeProtocolName:@"模版元素名" module:@"模块名" handlerClassName:@"原生视图组件类名"];
}

3.模版中用对应的元素名进行编写

复制代码
<FlexboxLayout layoutId="999" width="match_parent" height="wrap_content" flexDirection="column" justifyContent="flex_start" bgColor="#FFFFFF">
    <模版元素名 layoutId="100" width="100%" height="100%" data="${原生模型对应key}" bgColor="#FFFFFF">
    </模版元素名>
</FlexboxLayout>

安卓与iOS模版开发时的差异

上文提到:由于MCube本身的机制,在进行模版开发时经常会有安卓和iOS模版字段不通用的情况,例如:

在我们的开发过程中,遇到的安卓和iOS不兼容的问题有:

1.安卓的嵌套滑动组件接入工程后,会报与本地的库有冲突。

2.FlexboxLayout 设置100%安卓可以滑动,ios滑动不了。

3.ImageView scaleType="stretch" 安卓会展示比例有问题,需要加useAspectRatio,ios没问题。

4.安卓在设置渐变色的时候语法不生效:

模版中如何做复杂的逻辑

1.数值结算

复制代码
<ImageView height="$calc(44*SCREEN_WIDTH/375)" width="$calc(SCREEN_WIDTH)" />

2.条件判断

复制代码
<ImageView src="@{${data.img1}?${data.img1}:${data.img2}}"/>
<View visibility="@{@{${data.show}!=null}?1:2}"/>

3.渐变色(拼接语法)

复制代码
<FlexboxLayout bgColorList="$joint(${startColored},$unescape(comma),${endColored})">
<TextView text="$joint((${data.text1}  ${data.text2}))"/>

上传和发布模版

模版要上传到配置平台(具体网址请咨询相关负责人,并开通权限),在配置平台创建APP,模块(页面)和具体楼层,楼层可以发布不同版本,上传缩略图和模版文件,最后还可以选择白名单发布或者灰度发布。

如何做降级方案?

降级的形式我们在不同维度设立了三套方案:

视图层面

如果MCube的SDK加载动态化视图失败,会给我们一个失败回调Block,那么我们的处理是可以利用存在DynamicViewModel里的nativeData去加载原生楼层作为降级。

下发数据层面

可以通过控制是否下发module和templateID,来控制该楼层走原生展示还是动态化展示。

业务楼层层面

我们在配置平台设置了白名单(dynamicWhiteList)进行控制:按模块名(module)-模版名(templateID)来定位具体楼层,配置true(降级)/false(动态化)决定是否降级。

数据格式:

复制代码
"dynamicWhiteList":{
    "页面名module":{
        "模版templateID 1":true,//降级
        "模版templateID 2":false//动态化
    }
}

作者:京东零售 姜海

来源:京东云开发者社区 转载请注明来源

相关推荐
Gizzap_Tech3 分钟前
国内官网增加英文版:URL、语言切换与 hreflang 怎么配置?
前端
剪刀石头布啊11 分钟前
泛洪DFS、BFS
前端
qetfw11 分钟前
Windows Server AD CS:根 CA、Web 证书模板与域内自动注册
前端·windows·windows-server
剪刀石头布啊12 分钟前
为什么React组件很少前缀,而vue的不少组件都有前缀
前端
剪刀石头布啊17 分钟前
gird网格布局
前端
啃火龙果的兔子19 分钟前
Google Chrome(谷歌浏览器)常用快捷键
前端·chrome
剪刀石头布啊21 分钟前
js真的是单线程实现异步并发么?
前端
剪刀石头布啊26 分钟前
js中改变this指向的操作有哪些
前端
剪刀石头布啊26 分钟前
vue、react 列表中使用 index 作为 key 的话,修改某一个元素后会发生什么
前端
方方洛28 分钟前
glowglow:语言无关的语法高亮器是怎么实现的
前端·前端框架