为何差异化
因业务运营要求需要新包拓展市场关键字等原因,故很久之前从壹点灵主包拆分出心理咨询项目。之后迭代工作两端同时进行,因为很多代码都是公用的,如果有相同的迭代内容一般都是业务开发人将以开发好的代码拷贝一份至心理咨询项目内。
不过因为后期业务需求增长速度过快,双端同时维护成本变高,开发工作做起来越来越复杂。所以需要寻求一种解决方案,在尽可能使用通用代码的前提下,保证项目样式的差异化。
差异化实践
基础库统一
基础功能封装
应用配置
-
创建基础层配置对象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{
...
}
}
}
目录结构
原理
-
读取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上传任务
使用
-
开发过程在Build Variants中选择当前开发的项目类型
在通用代码目录下编写公共代码逻辑,在差异化代码目录编写不同的页面样式
开发完成后,执行Task,上传指定私库
组件差异化应用架构
总结
因为合并后代码要求限制,无法在相同目录下创建多份样样式文件。故使用Android productFlavors属性,动态设置当前编译目录。在尽可能多共用相同逻辑代码的同时,保证了页面之间的差异化,减少了维护成本和开发人员工作量,维护了项目目录的整洁性。
致谢
感谢以下极客的无私分享