YDL Android 组件化实践与拓展(2) - 组件场景差异化

为何差异化

因业务运营要求需要新包拓展市场关键字等原因,故很久之前从壹点灵主包拆分出心理咨询项目。之后迭代工作两端同时进行,因为很多代码都是公用的,如果有相同的迭代内容一般都是业务开发人将以开发好的代码拷贝一份至心理咨询项目内。

不过因为后期业务需求增长速度过快,双端同时维护成本变高,开发工作做起来越来越复杂。所以需要寻求一种解决方案,在尽可能使用通用代码的前提下,保证项目样式的差异化。

image

差异化实践

基础库统一

基础功能封装

应用配置

  • 创建基础层配置对象GlobalConfig,使用建造者模式传入配置属性

    class GlobalConfig private constructor(var builder: Builder) {
        class Builder() {
                  ....
            fun build(): GlobalConfig {
                return GlobalConfig(this)
            }
        companion object {
            fun builder(): Builder {
                return Builder()
            }
        }
    }
    
  • 创建AppDelegate Application 代理类,初始化应用配置信息

    class AppDelegate(context: Context) : IAppLifecycles,IApp{
    private var mGlobalConfig:GlobalConfig  ?=null
      
      init {
            //用反射, 将 AndroidManifest.xml 中带有 IConfigModule 标签的 class 转成对象集合(List<IConfigModule>)
            this.mModules = ManifestParser(context).parse()
            for (module in mModules!!) {
                //注入各 Module Application 的生命周期回调
                module.injectAppLifecycle(context, mAppLifecycles!!)
            }
        }
          
          override fun onCreate(application: Application) {
            this.mApplication = application
            mGlobalConfig = initModuleConfig(application, mModules!!)
        }
      
          override fun getGlobalConfig(): GlobalConfig {
            return mGlobalConfig!!
        }
    
    }
    
    

Base层封装

  • 创建ActivityDelegate,处理Rxlifecycle订阅取消操作

    class ActivityDelegate : Application.ActivityLifecycleCallbacks {
    .....
    }
    
  • 创建BaseActivity,获取RxlifecycleBehavior,处理沉浸式状态栏等通用方法`

    abstract class BaseActivity : AppCompatActivity(), IActivityLifecycleable {
    private val mLifecycleSubject = BehaviorSubject.create<ActivityEvent>()
        override fun provideLifecycleSubject(): Subject<ActivityEvent> {
            return mLifecycleSubject;
        }
        ...
    }
    

MVP封装

  • 创建MvpActivityDelegateImpl

    • 初始化Presenter,回调Presenter中方法
    •  实现Presenter缓存机制,防止横竖屏切换后Presenter多次重建
      
    class MvpActivityDelegateImpl<V : IView, P : IPresenter<V>>(
        protected var activity: Activity,
        private val delegateCallback: MvpDelegateCallback<V, P>, protected var keepPresenterInstance: Boolean
    ) {
      ...
      fun onCreate(bundle: Bundle?) {
            var presenter: P?
            if (bundle != null && keepPresenterInstance) {
                //如果是改变页面配置导致的页面重建,取缓存的Presenter
                mvpViewId = bundle.getString(KEY_Mvp_VIEW_ID)
            }
            ...
            presenter.attachView(mvpView)
        }
    
        private fun createViewIdAndCreatePresenter(): P {
            val presenter = delegateCallback.createPresenter()
            if (keepPresenterInstance) {
                mvpViewId = UUID.randomUUID().toString()
                PresenterManager.putPresenter(activity, mvpViewId!!, presenter)
            }
            return presenter
        }
    
        fun onDestroy() {
            //如果为横竖屏切换导致的生命周期变动,保留Presenter实例
            val retainPresenterInstance = retainPresenterInstance(keepPresenterInstance, activity)
            presenter.detachView()
           ...
        }
    
        ...
    }
    
  • 创建BaseMvpActivity,初始化MvpActivityDelegateImpl

  • 创建BasePresenter,绑定Activity生命周期

    abstract class BasePresenter< V : IView,M : IModel> : IPresenter<V>, LifecycleObserver {
        open lateinit var mModel: M
        open lateinit var mView: V
        abstract fun createModel(): M
    
        override fun attachView(view: V) {
            this.mView = view
            mModel = createModel()
            if (view is LifecycleOwner) {
                (view as LifecycleOwner).lifecycle.addObserver(this)
                if (mModel != null && mModel is LifecycleObserver) {
                    (view as LifecycleOwner).lifecycle.addObserver(mModel as LifecycleObserver)
                }
            }
        }
    
        override fun detachView() {
            //保证 Activity 结束时取消所有正在执行的订阅
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        fun onDestroy(owner: LifecycleOwner) {
            owner.lifecycle.removeObserver(this)
        }
    }
    
  • 创建BaseModel,绑定Activity生命周期

    abstract class BaseModel : IModel, LifecycleObserver {
    
        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        fun onDestroy(owner: LifecycleOwner) {
            owner.lifecycle.removeObserver(this)
        }
    }
    

功能组件拆分

  • 创建音乐播放器功能组件
  • 创建网络请求功能组件
  • 创建WebView功能组件

业务组件差异化

自动创建差异化开发环境

使用Gradle Plugin,读取业务组件插件配置信息,动态创建差异化代码编写环境

配置信息

modular {
    packageName "com.ydl.other"
    // 模块发布需要的参数
    publish {
        modules {
            xlzx {
               ...
            }

            ydl{
                            ...
                    }
        }
}

目录结构

image

原理

  • 读取modualr.gradle文件配置包名,在src目录下创建modules中定义的名称

     void initDirs(Project childProject, PublishOptions options) {
            if (options.isApi || options.name == "module") {
                //发布渠道名 为'module'时,不自动创建pins目录
                return
            }
            def pinName = options.name
            pinsManager.addPins(childProject, pinName)
    
            if (new File("$childProject.projectDir/src/${pinName}").exists()) {
                //目录已经存在
            } else {
                //创建目录
                String packageDir = options.packageName.replace(".", "/")
                // 创建java目录
                new File("$childProject.projectDir/src/${pinName}/java/" + packageDir).mkdirs()
                // 创建资源文件目录
                new File("$childProject.projectDir/src/${pinName}/res").mkdirs()
            }
        }
    
  • 添加modules下配置的名称为业务组件 productFlavors

  • 根据flavor创建业务组件aar上传任务


    image

使用

  • 开发过程在Build Variants中选择当前开发的项目类型


    image
  • 在通用代码目录下编写公共代码逻辑,在差异化代码目录编写不同的页面样式

  • 开发完成后,执行Task,上传指定私库

组件差异化应用架构

组件差异化架构

总结

因为合并后代码要求限制,无法在相同目录下创建多份样样式文件。故使用Android productFlavors属性,动态设置当前编译目录。在尽可能多共用相同逻辑代码的同时,保证了页面之间的差异化,减少了维护成本和开发人员工作量,维护了项目目录的整洁性。

致谢

感谢以下极客的无私分享

美团技术团队 - 美团外卖Android平台化架构演进实践

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容