OEM打包方案探索

需求

个别厂商需求话定制,要求提供不同包名、logo、图片资源、代码逻辑的apk

实现方式

1、拉新分支管理

从主线分支拉不同OEM版本的分支,在新分支上改动

分支图示:

 

冲突

  • 单次提供需求可以满足

  • 频繁OEM需求场景下,主线代码已更改:

    1、从主线分支拉取,从新修改,工作量重复

    2、维护OEM分支代码,跟随主线分支代码合并改动,工作量大、重复

2、Flavor渠道方式管理

原生gradle配置文件下新建Flavor渠道,按渠道打包,原生代码目录下新建对应渠道名资源文件夹,替换资源。Flutter通过Channel方式读取原生Target,代码中判断Target执行代码逻辑

示例代码:

  if (GetIt.I<AppConfigUseCase>().isSmartHome){
    /// do something
  } else if (GetIt.I<AppConfigUseCase>().isBotslab){
    /// do otherthing
  }

 

冲突

  • 不新增更多Target情况下,可以满足

  • 多Target场景下,代码逻辑复杂,岔口太多

优化

Flutter中读yaml配置文件方式

yaml配置文件示例:

name: botslab
description: target config file.
​
appTarget: botslab
​
##设备背景占位图
device_image_placeholder: botslab
​
#首页背景图
home_header_bg_screen:
  bg: botslab
​
#首页入口
main_entry_model:
  #是否含有商城入口
  hasMall: false
​
#首页相关
home_screen:
  getHeaderInfo: false
​
#背景样式
# type1: lottie
# type2: image
home_screen_template: image
​
#用户中心
user_profile_screen:
  #使用问题
  user_profile_item_problem: false
  headBg: botslab
​
DeviceUseCase:
  hasVirtualDevices: false
​
#设置页开关
settings_screen:
  #区域与语言
  user_setting_item_group_region_language: true
  #网络检测
  user_setting_item_net_detect: true
  #地区
  user_setting_item_region: true

代码逻辑岔路合并

      if (GetIt.I<AppConfigUseCase>().targetConfigBean["main_entry_model"]["hasMall"] == true)

根据运行时传入的target解析对应的yaml配置文件

  var configFileName = 'packages/service_common/assets/target_config_';
​
  @override
  Future<void> initTargetConfig() async {
    final target = _appConfigData["target"] as String;
    final targetFileName = configFileName + target + ".yaml";
    final data = await rootBundle.loadString(targetFileName);
    _targetConfigBean = loadYaml(data) as Map;
  }

按功能需求配置yaml配置文件,放在指定目录

 

难点

1、yaml配置文件仅方便配置开关类功能需求,难以描述更加定制化需求,仍无法和target彻底解耦

比如代码中不同逻辑需求是这样的:

if (GetIt.I<AppConfigUseCase>().targetConfigBean["home_header_bg_screen"]["bg"] == "smarthome") {
      return Transform(
        alignment: Alignment.topCenter,
        transform: Matrix4.translationValues(0, offsetY, 0)..scale(scaleY),
        child: LottieBuilder.asset(
          "assets/lottie/background.json",
          package: "feature_main",
          width: double.infinity,
          height: botslabHeaderHeight,
          fit: BoxFit.cover,
        ),
      );
    } else {
      return Transform(
        alignment: Alignment.topCenter,
        transform: Matrix4.translationValues(0, offsetY, 0)..scale(scaleY),
        child: Image(
          image: ImageAssets.botslabHomeBg,
          height: botslabHeaderHeight,
          width: double.infinity,
          fit: BoxFit.cover,
        ),
      );
    }

配置文件是这样的

​
##设备背景占位图
device_image_placeholder: botslab
​
#首页背景图
home_header_bg_screen:
  bg: botslab

逐渐有点坏代码的味道

配置文件梳理

1、梳理分类变体配置类型

按需求功能点类型区分,配置文件中配置类型目前可分为三种类型

a、功能开关是否打开

要求不同target设置项列表展示差异,部分功能入口显示隐藏控制

if(isOpen)
  BuildItem(),

b、图片资源显示差异

图片资源路径不同,例如placeHolder,botslab 加载"assets/images/smarthome_img_place_holder.png"目录下图片,smarthome加载"assets/images/botslab_img_place_holder.png"目录下图片

if(target1){
  AssetImage("assets/images/smarthome_img_place_holder.png",package:"res_common");
} else if(target2){
  AssetImage("assets/images/botslab_img_place_holder.png",package:"res_common");
}

c、代码逻辑执行差异

对于某target要单独调用一只接口或执行某处理逻辑

if(target1){
  getHeaderInfo();
}

d、控件展示差异

不同target返回不同widget组件

if(target1){
  return Widget1()
} else if(target2){
  return Widget2()
}

结论

配置文件方便做功能开关、密码、域名、资源路径类静态替换设置控制a、b、c,不适合做代码逻辑类控制如d这种代码和target版本强相关类型代码,无法彻底解耦。

试想一下,如果将类似代码逻辑过多塞进配置文件,那它和真正的代码文件又有什么分别。

yaml:我只是一个小小的配置文件,为什么对我的要求要这么多?why

所以,采用所有类似入口均设置一项配置name: targetName代码中沿用之前判断方式

参考springboot项目中配置文件

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: root
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  redis:
    database: 0
    host: localhost
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
    timeout: 0

 

2、确定配置对象结构和访问方式

按配置文件声明的控制项设计配置对象结构,按键值对映射,全部按一级展开成实例对象,加注释和回车方式归并。后续有分类需求可按二级开关string: Map<string,dynamic>控制。

配置对象

class TargetConfigBean{
  String appTarget;
  bool hasMainEntryMall;
​
  bool hasHomeScreenGetHeaderInfo;
​
  bool hasUserProfileItemProblem;
​
  bool hasVirtualDevices;
​
  bool hasSettingsItemGroupRegionLanguage;
  bool hasSettingsItemNetDetect;
  bool hasSettingsItemRegion;
  
  factory TargetConfigBean.fromJson(Map<dynamic,dynamic> json) =>
      _targetConfigBeanFromJson(json);
}

yaml文件解析

TargetConfigBean _targetConfigBeanFromJson(Map<dynamic, dynamic> json) {
  final bean = TargetConfigBean();
  bean.appTarget = json["appTarget"] as String;
  bean.hasMainEntryMall = json["main_entry_model"]["hasMall"] as bool;
  bean.hasHomeScreenGetHeaderInfo = json["home_screen"]["getHeaderInfo"] as bool;
  bean.hasUserProfileItemProblem = json["user_profile_screen"]["user_profile_item_problem"] as bool;
  bean.hasVirtualDevices = json["DeviceUseCase"]["hasVirtualDevices"] as bool;
  bean.hasSettingsItemGroupRegionLanguage = json["settings_screen"]["user_setting_item_group_region_language"] as bool;
  bean.hasSettingsItemNetDetect = json["settings_screen"]["user_setting_item_net_detect"] as bool;
  bean.hasSettingsItemRegion = json["settings_screen"]["user_setting_item_region"] as bool;
  return bean;
}

访问方式

/// a、b、c类接入
if (GetIt.I<AppConfigUseCase>().targetConfigBean.hasMainEntryMall){
  /// open
}
  
/// d类接入
if (GetIt.I<AppConfigUseCase>().targetConfigBean.appTarget == AppTarget.smarthome.targetName) {
  /// do something
} else if (GetIt.I<AppConfigUseCase>().targetConfigBean.appTarget == AppTarget.botslab.targetName) {
  /// do otherthing
}
​
​

调整后yaml配置

name: botslab
description: target config file.
​
appTarget: botslab
​
#首页入口
main_entry_model:
  #是否含有商城入口
  hasMall: false
​
#首页相关
home_screen:
  getHeaderInfo: false
​
#用户中心
user_profile_screen:
  #使用问题
  user_profile_item_problem: false
​
DeviceUseCase:
  hasVirtualDevices: false
​
#设置页开关
settings_screen:
  #区域与语言
  user_setting_item_group_region_language: true
  #网络检测
  user_setting_item_net_detect: true
  #地区
  user_setting_item_region: true

展望

探索编译期读配置文件方案

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值