游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

Android组件化场景下多module依赖优雅实践方案

  作者:leobertlan
  如果没有记错,15年那会Android项目逐步转向使用Gradle构建,时至今日,组件化已经不再是一个新颖的话题。
  虽然我将这篇文章放在了Gradle分类中,但是我们知道,使用gradle构建的后端项目,热点聚焦在:实现微服务化,项目是拆开的,决定了依赖库已经是静态jar包,和我们要讨论的场景是不一致的。所以我们还是在Android领域中讨论这个问题。
  在各种方案的组件化实施中,一定会将部分功能模块拆分,进行library下沉。于是,就有了处理依赖的场景。相信大家思考过这样一个问题:如果下沉的library也提前编译好静态aar包,我们的项目编译时间会缩短。
  毋庸置疑,这样做会直接从源头解决编译时间长的问题,就是减少编译内容。但是,项目合并在一起,难免就想在开发下层library时,直接用上层业务集成进行冒烟。ps:这个做法并不好,应当为library配置好冒烟测试环境,虽然会耗费掉一定的时间。
  理想归理想,最终还是会败给现实,这个问题就变成了鱼和熊掌想要兼得的问题。
  为了让阅读的目标更加明确,我们先思考一个问题:
  这样一个项目依赖关系,如果做到改动B的内容,却不需要重新编译A,运行APP,验证B的修改我们下面会进行一定地展开,来体悟这个问题。为什么使用远程仓库中的依赖包比使用本地静态aar要方便
  我们知道,对于一个module,我们对其进行编译生成静态aar包,只会处理它自身的内容。那么他的依赖是如何传递的?
  通过pom文件
  举个例子:
  我们新建一个module,看一下依赖:dependencies{implementationorg。jetbrains。kotlin:kotlinstdlib:kotlinversionimplementationandroidx。core:corektx:1。3。2implementationandroidx。appcompat:appcompat:1。2。0implementationcom。google。android。material:material:1。2。1testImplementationjunit:junit:4。androidTestImplementationandroidx。test。ext:junit:1。1。2androidTestImplementationandroidx。test。espresso:espressocore:3。3。0}
  利用mavenplugin进行发布,会有任务生成pom文件,如下:lt;?xmlversion1。0encodingUTF8?projectxsi:schemaLocationhttp:maven。apache。orgPOM4。0。0http:maven。apache。orgxsdmaven4。0。0。xsdxmlnshttp:maven。apache。orgPOM4。0。0xmlns:xsihttp:www。w3。org2001XMLSchemainstancemodelVersion4。0。0modelVersiongroupIdleobertgroupIdBartifactIdversion1。0。0versionpackagingaarpackagingdependenciesdependencygroupIdorg。jetbrains。kotlingroupIdkotlinstdlibartifactIdversion1。4。21versionscopecompilescopedependencydependencygroupIdandroidx。coregroupIdcorektxartifactIdversion1。3。2versionscopecompilescopedependencydependencygroupIdandroidx。appcompatgroupIdappcompatartifactIdversion1。2。0versionscopecompilescopedependencydependencygroupIdcom。google。android。materialgroupIdmaterialartifactIdversion1。2。1versionscopecompilescopedependencydependenciesproject
  我们发现,关于测试相关的依赖并没有被收录到pom文件中。这很合理,测试代码是针对该module的,并不需要提供给使用方,其依赖自然也不需要传递。我们知道,AGP中现在有4种声明依赖的方式(除去testXXX这种变种)apiimplementationcompileOnlyruntimeOnly
  runtimeOnly对应以前的apk方式声明依赖,我们直接忽略掉,测试一下生成的pom文件。dependencies{apiorg。jetbrains。kotlin:kotlinstdlib:kotlinversionimplementationandroidx。core:corektx:1。3。2compileOnlyandroidx。appcompat:appcompat:1。2。0compileOnlycom。google。android。material:material:1。2。1testImplementationjunit:junit:4。androidTestImplementationandroidx。test。ext:junit:1。1。2androidTestImplementationandroidx。test。espresso:espressocore:3。3。0}lt;?xmlversion1。0encodingUTF8?projectxsi:schemaLocationhttp:maven。apache。orgPOM4。0。0http:maven。apache。orgxsdmaven4。0。0。xsdxmlnshttp:maven。apache。orgPOM4。0。0xmlns:xsihttp:www。w3。org2001XMLSchemainstancemodelVersion4。0。0modelVersiongroupIdleobertgroupIdBartifactIdversion1。0。0versionpackagingaarpackagingdependenciesdependencygroupIdorg。jetbrains。kotlingroupIdkotlinstdlibartifactIdversion1。4。21versionscopecompilescopedependencydependencygroupIdandroidx。coregroupIdcorektxartifactIdversion1。3。2versionscopecompilescopedependencydependenciesproject
  使用compileOnly方式的并没有被收录到pom文件中,而api和implementation方式,在pom文件中,都表现为采用compile的方案应用依赖。
  ps:api和implementation在编码期的不同,不是我们讨论的重点,略。
  回到我们开始的问题,将library发布时,按照约定,会将library本身的依赖收录到pom文件中。相应的,使用方使用仓库中的依赖项时,gradle会拉取其对应的pom文件,并添加依赖。
  所以,如果我们直接使用一个编译好的静态包,而丢弃了他对应的pom文件时,可能会丢失依赖,出现打包失败或者运行异常。这意味着我们需要人为维护依赖传递
  我们记住这些内容,并先放到一边。下沉后,library会有多个层级
  例如图中:APPAB,即APP依赖A,A依赖B,而A和B都是library
  我们知道,对于B,并不会有什么说法,只会出现在A和APP
  如果不使用静态包,那么A会声明:apiproject(:B)或者implementationproject(:B)
  我们先看一下,这样生成的libraryA的pom文件lt;?xmlversion1。0encodingUTF8?projectxsi:schemaLocationhttp:maven。apache。orgPOM4。0。0http:maven。apache。orgxsdmaven4。0。0。xsdxmlnshttp:maven。apache。orgPOM4。0。0xmlns:xsihttp:www。w3。org2001XMLSchemainstancemodelVersion4。0。0modelVersiongroupIdleobertgroupIdAartifactIdversion1。0。0versionpackagingaarpackagingdependenciesdependencygroupIdDemogroupIdBartifactIdversionunspecifiedversionscopecompilescopedependencydependenciesproject
  会得到groupID是项目名,artifactId是module名,version是未知的一个依赖项。假如我将A编译为静态包并发布到仓库,并运用了pom中的依赖描述,一定会得到无法找到:DemoBunspecified。pom的问题。
  当然,这个问题可以通过在APP中重新声明B的依赖来解决。
  这意味着,我们需要时刻保持警惕,维护各个module的依赖。否则,我们无法同时享受:静态包减少编译随心的修改局部并集成测试
  这显然是一件不人道主义的事情。
  反思一下,对于A而言,它需要B,但仅在两个时机需要:编译时受检,完成编译运行时
  作为一个library,它本身并不对应运行时,所以,compileOnly是其声明对B的依赖的最佳方式。这意味着,最终对应运行时的内容,即APP,需要在编译时加入对B的依赖。在原先A使用Api方式声明对B的依赖时,是通过gradle分析pom文件实现的依赖加入。而现在,需要人为维护,只需要实现人道主义,就可以鱼和熊掌兼得。反思依赖传递的本质
  一般我们会像下面的演示代码一样声明依赖:APP:implementationproject(A)implementationproject(Foo)A:implementationproject(B)implementationproject(Bar)
  因为依赖传递性,APP其实依赖了A,Foo,B,Bar。其实就是一颗树中,除去根节点的节点集合。而对于一个非根节点,它被依赖的形式只有两种:静态包,不需要重新编译,节约编译时间module,需要再次编译,可以运用最新改动
  我们可以定义这样一个键值对信息:project。ext。depRules〔B:p,A:a〕
  p代表使用project,a代表使用静态包。
  并将这颗树的内容表达出来:我们先忽略掉Foo和Barproject。ext。deps〔A:〔B:〔p:project(:B),a:leobert:B:1。0。0〕〕,APP:〔A:〔p:project(:A),a:leobert:A:1。0。0〕〕〕。with(true){A。each{eAPP。put(e。key,e。value)}}
  以A为例,我们可以通过代码实现动态添加依赖:project。afterEvaluate{pprintln(handledepsfor:p)deps。A。each{edefruledepRules。get(e。key)println(finddepsofA:ruleisrule,depis:e。value。get(rule)。toString())project。dependencies。add(compileOnly,e。value。get(rule))}}
  同理,对于APP:project。afterEvaluate{pprintln(handledepsfor:p)deps。APP。each{edefruledepRules。get(e。key)println(finddepsofApp:ruleisrule,depis:e。value。get(rule)。toString())project。dependencies。add(implementation,e。value。get(rule))}}
  查看输出:Configureproject:Ahandledepsfor:project:AfinddepsofA:ruleisp,depis:project:BConfigureproject:apphandledepsfor:project:appfinddepsofApp:ruleisa,depis:leobert:A:1。0。0finddepsofApp:ruleisp,depis:project:B
  这样,我们就可以通过修改对应节点的依赖方式配置而实现鱼和熊掌兼得。不再受pom文件的约束。当时,我们回到上面说的不人道主义之处,我们通过了with函数,将A自身的依赖信息,注入到APP中。
  但是当树的规模变大时,人为维护就很累了。这是必须要解决的,当然,这很容易解决。我们直接使用递归处理即可
  贴近人的直观感受才优雅,逐步实现人道主义我们添加一个全局闭包:ext。utils〔applyDependency:{project,edefruledepRules。get(e。key)println(finddepsofApp:ruleisrule,depis:e。value。get(rule)。toString())project。dependencies。add(implementation,e。value。get(rule))try{println(trytoaddsubdepsof:e。key)defsubdeps。get(e。key)if(sub!nullsub。get(isEnd)!true){sub。each{seext。utils。applyDependency(project,se)}}}catch(Exceptionignore){}}〕
  注意,因为我们定义的依赖信息是:moduleName(moduleName(scopeNamedepInfo))的方式。
  这导致我们判断末端节点有一定的困难,即递归的尾部判断存在困难,我们需要人为标记一下末端节点这时,我们只需描述一下树即可:同样忽略Foo,Barproject。ext。deps〔A:〔B:〔isEnd:true,p:project(:B),a:leobert:B:1。0。0〕〕,APP:〔A:〔p:project(:A),a:leobert:A:1。0。0〕〕〕
  问题基本得到解决了,但是并不优雅。优雅,优雅,优雅
  我们不妨再修改一下对依赖树的描述方式,将节点信息和树结构分开,重新改进:
  更人道主义的依赖描述project。ext。deps〔A:〔B〕,app:〔A〕〕project。ext。modules〔A:〔p:project(:A),a:leobert:A:1。0。0〕,B:〔p:project(:B),a:leobert:B:1。0。0〕〕project。ext。depRules〔B:p,A:a〕
  抽象添加依赖的过程,递归处理每一个节点的依赖收集,并向宿主module添加,当某个节点在ext。deps中没有任何依赖时,归:ext。utils〔applyDependency:{project,scope,edefruledepRules。get(e)defeInfoext。modules。get(e)println(finddepsofproject:ruleisrule,depis:eInfo。get(rule)。toString())project。dependencies。add(scope,eInfo。get(rule))defsubdeps。get(e)listdepsofeprintln(trytoaddsubdepsof:esub)if(sub!null!sub。isEmpty()){sub。each{dOfEext。utils。applyDependency(project,scope,dOfE)}}}〕
  每个module只需要指定自己的scope::appproject。afterEvaluate{pprintln(handledepsfor:p)deps。get(p。name)。each{erootProject。ext。utils。applyDependency(p,implementation,e)}}:Aproject。afterEvaluate{pprintln(handledepsfor:p。name)deps。get(p。name)。each{erootProject。ext。utils。applyDependency(p,compileOnly,e)}}
  只要不是独立运行的module,就是compileOnly,否则就是implementation。输出也容易拍错:Configureproject:Ahandledepsfor:Afinddepsofproject:A:ruleisp,depis:project:Btrytoaddsubdepsof:BnullConfigureproject:apphandledepsfor:project:appfinddepsofproject:app:ruleisa,depis:leobert:A:1。0。0trytoaddsubdepsof:A〔B〕finddepsofproject:app:ruleisp,depis:project:Btrytoaddsubdepsof:Bnull
  测试一个复杂场景我们再上图的基础上,让B和Foo依赖Baseproject。ext。deps〔app:〔A,Foo〕,A:〔B,Bar〕,Foo:〔Base〕,B:〔Base〕,〕project。ext。modules〔A:〔p:project(:A),a:leobert:A:1。0。0〕,B:〔p:project(:B),a:leobert:B:1。0。0〕,Foo:〔p:project(:Foo),〕,Bar:〔p:project(:Bar),〕,Base:〔p:project(:Base),〕〕project。ext。depRules〔B:p,A:a,Foo:p,Bar:p,Base:p〕Configureproject:Ahandledepsfor:Afinddepsofproject:A:ruleisp,depis:project:Btrytoaddsubdepsof:B〔Base〕finddepsofproject:A:ruleisp,depis:project:Basetrytoaddsubdepsof:Basenullfinddepsofproject:A:ruleisp,depis:project:Bartrytoaddsubdepsof:BarnullConfigureproject:apphandledepsfor:project:appfinddepsofproject:app:ruleisa,depis:leobert:A:1。0。0trytoaddsubdepsof:A〔B,Bar〕finddepsofproject:app:ruleisp,depis:project:Btrytoaddsubdepsof:B〔Base〕finddepsofproject:app:ruleisp,depis:project:Basetrytoaddsubdepsof:Basenullfinddepsofproject:app:ruleisp,depis:project:Bartrytoaddsubdepsof:Barnullfinddepsofproject:app:ruleisp,depis:project:Footrytoaddsubdepsof:Foo〔Base〕finddepsofproject:app:ruleisp,depis:project:Basetrytoaddsubdepsof:BasenullConfigureproject:Barhandledepsfor:BarConfigureproject:Basehandledepsfor:BaseConfigureproject:Foohandledepsfor:Foofinddepsofproject:Foo:ruleisp,depis:project:Basetrytoaddsubdepsof:Basenull
  随着,树规模的增大,阅读依赖关系还算明显,但是阅读日志,又不太优雅了。总结和展望
  我们通过探寻,发现了一种可以鱼和熊掌兼得地依赖处理方式,让我们在Android领域组件化场景下(单项目,多module),能够灵活地切换:静态包依赖,缩短编译时间项目依赖,快速部署变更进行集成测试
  对了,上面我们没有重点提到如何切换,其实非常地简单:
  只需要修改project。ext。depRules中对应的配置项即可。
  如果后面还有闲情逸致的话,可以再写一个studio的插件,获取dependency。gradle的信息,输出可视化的依赖树;rule配置,直接做成多个开关,优雅,永不过时。最后
  在这里就再分享一份由大佬亲自收录整理的Android学习PDF架构视频面试文档源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
  这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
  当然,你也可以拿去查漏补缺,提升自身的竞争力。
  真心希望可以帮助到大家,Android路漫漫,共勉!
  如果你有需要的话,只需私信我【进阶】即可获取

五十多岁了想买一部手机,2000元以内,有哪些推荐?感谢您的阅读!五十多岁了想买一部手机,2000元以内,有哪些推荐?其实,对于50多岁的用户来说,他们所用的手机并非是所谓的功能机,我觉得对于我们来说能够使用的手机会非常的……浙江的民营企业家有多优秀?来讨论一下和数据比对一下中国最新93家千亿市值的公司,有26家是浙江人创立的,你知道么?浙商确实牛掰啊,长三角三省一市市值最高企业都是浙商创立的。上海拼多多,江苏药明生物,安徽阳光电源,浙……微信更新新增视频号和直播推送开关iPad支持同屏多开近日,微信正式向用户推送新版本,更新描述还是熟悉的配方熟悉的味道,只注明解决了些问题,更新了若干功能。但网友们是精明的,毕竟是一款国民应用,哪怕是一些小功能加入也会影响很……国内高端苹果无人能敌,vivo追平华为,小米排名第四相信大家都看到了Counterpoint特意针对国内7月份发布了一份报告,昨天晚上这份报告成为众人热议的焦点所在。因为这份报告的内容让很多人直呼想不到,想不到。下面就给大家详细……机动车是城市氨气早高峰重要来源大气中氨气浓度日变化明显,其峰值浓度一般出现在早晨。近日,中科院大气物理研究所研究员潘月鹏团队基于氮同位素溯源技术追踪了北京大气氨的来源,发现机动车对氨气早高峰的贡献高达40。……清华教授用18小时讲完的Python,整整428集,拿走不谢兄弟!毫无套路!!!福利分享:1、本套视频一共428集,本套视频共分4季第一季Python基础。第二季。Python深入和扩展第三季网络编程、多线程、扩……专精特新企业天空卫士获科技创新奖2021年是我国网络与数据安全产业具有里程碑意义的一年,对天空卫士来说,也是收获颇丰的一年。源于高速的成长能力、出色的创新能力、专业的技术服务能力,北京经济技术开发区为天……腾讯又发股票了!2。29万员工人均可获得16。6万港元21世纪经济报道记者白杨北京报道1月21日晚,腾讯发布公告称,董事会已决议向不少于22900位奖励人士授予合共8004807股奖励股份。公告称,发行新股份的目的旨在……扫雷都玩过,但是你知道扫雷的世界纪录有多快吗?每个人一定都接触过一种叫做扫雷的游戏,这是由微软设计师罗伯特德姆和卡特约翰逊于1981年设计的。许多人可能在几次尝试后就放弃了。毕竟,如果他们不理解游戏规则,即使这是最简单的难……遭华为公开嘲讽,三星回怼我们才是第一(观察者网讯文一鸣)在3月26日的华为发布会上,余承东公布了P30Pro的DxOMark成绩。成绩显示,华为P30Pro后置相机总分为112分,成为迄今为止拍照成绩最好的智能手……评论以用户满意度倒逼快递业健康发展送货上门变成自提、乡村取件加收快递费,提起如今的快递行业,人们普遍觉得末端配送服务水平已远不及当初。国家邮政局官网目前正在就《快递市场管理办法(修订草案)》征求各方意见。修订草……贝莱德(BLK。US)推出首只区块链ETF智通财经APP获悉,贝莱德(BLK。US)推出了首只专注于区块链的ETFTheiSharesBlockchainandTechETF(IBLC),旨在为围绕区块链技术不断增长的……
为什么在日本是实体店干掉电商,在中国却是电商干掉实体?之所以会出现这种现象,原因大概有一下几点吧:1、在我国电商起步的时候,平台因为有资本操作,基本上是赔本引流,再加上征税不规范,电商的成本比实体店要低很多。而在日本,是不一……苹果ARVR头显将通过Memojis和SharePlay支持2月14日消息,据国外媒体报道,据知名苹果分析师MarkGurman介绍,苹果ARVR头显中FaceTime的体验可能基于Memojis和SharePlay构建而成。据报……买车不纠结,一个计算题告诉你电动汽车和燃油汽车的好和坏对于现在大多数人而言,买车已经成为了最重要的一件事情。因为没有汽车就等于是鸟,没有了翅膀是一样的道理,就没有办法正常的行驶。特别是现在新能源汽车的兴起,让很多人在买车的时候非常……海棠经雨胭脂透开播邓伦李一桐化身美妆博主不走寻常路星关系10月25日讯由广厦传媒有限公司出品,导演何澍培执导,邓伦、李一桐、应昊茗、张雅卓、方中信、李若彤等主演的民国言情剧《海棠经雨胭脂透》已于昨晚20:00在芒果TV独播。该……脑梗神剧来到你的世界收官在即6。8亿人都在看的网剧!星关系讯:由蓝港影业、腾讯视频联合出品,王奕丁执导,林柏宏、李浩菲、黄俊捷、黄一琳等主演的爱情奇幻剧《来到你的世界》即将收官。该剧讲述的是漫画家何解(林柏宏饰)阴差阳错穿越到了……电视剧来到你的世界今播主角逆天改命打破次元墙《来到你的世界》今播主角逆天改命打破次元墙奇幻浪漫爱情剧《来到你的世界》将于今晚正式上线。该剧由邓科监制,王奕丁执导,南镇编剧,林柏宏、李浩菲、黄俊捷、黄一琳联合出演。因……来到你的世界曝光终极预告,破次元情侣遇跨世界难题由蓝港影业出品,今晚20:00将在腾讯视频独家播出的奇幻爱情网剧《来到你的世界》今日发布终极预告,错综复杂的人物关系、反转不停的剧情发展全面曝光。异次元情侣上演反转戏码遭……梗玩年!来到你的世界反套路颠覆想象梗玩年!《来到你的世界》反套路颠覆想象奇幻爱情网剧《来到你的世界》将于6月13日登陆腾讯视频播出。该剧由邓科监制,王奕丁执导,南镇编剧,林柏宏、李浩菲、黄俊捷、黄一琳联合……电视剧走火聚焦留守儿童正能量传递引发网友热议电视剧《走火》聚焦留守儿童正能量传递引发网友热议由李小平、李小亭导演联合执导,李松编剧,李晓重原著,于毅、周放、高旻睿、王阳、张皓伦、白那日苏等领衔主演的四十集大型电视连……27地又加入,跨境电商综试区有多重要?日前,国务院批复同意在鄂尔多斯等27个城市和地区设立跨境电子商务综合试验区。这是该试验区近7年时间里的第6次扩围,范围已扩至全国132个城市和地区。从沿海到内陆,超百个城市和地……电视剧你迟到的许多年亮相国剧盛典黄晓明为角色减重十斤《你迟到的许多年》亮相国剧盛典黄晓明为角色减重十斤12日晚,电视剧《你迟到的许多年》总出品人兼总制片人刘毛毛、导演林柯携主演黄晓明、殷桃出席了纪念中国电视剧诞生60周年盛……电视剧来自海洋的你开播周雨彤演绎跨物种人鱼恋《来自海洋的你》开播周雨彤演绎跨物种人鱼恋由红圈影业出品,青年导演吴强执导,姚瑶担任总编剧,周雨彤、李宏毅领衔主演的奇幻爱情剧《来自海洋的你》正式开播。剧中,周雨彤挑战经……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网