范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

细究Android开发代码中心化所带来的问题

  作者:汐颜染瞳べ代码中心化问题
  将一个大型的项目拆分成多个Module或者新开的组件化项目,想要的预期是这些module之间是平级的关系。这样一来就可以使得业务相对集中,每个人都可以专注在一件事上。
  同时,代码的耦合度也会随之降低,达到高度解耦状态,因为同级的module不存在依赖关系,在编译上就是隔离的,这会让组件间的依赖非常清楚,同时也具有更高的重用性,组件强调复用,模块强调职责划分。他们没有非常严格的划分。
  达到可复用要求的模块,那么这个模块就是组件。每个组件的可替代性、热插拔、独立编译都将可行。
  代码中心化在Android组件化中的问题体现
  貌似Android的组件化是非常简单且可行的,AS提供的module创建方式加gradle.properies 自定义属性可读,或者ext全局可配置的project属性亦或kotlin dsl 中kotlin的语法糖都为我们提供了application和library的切换。
  然后将代码放在不同的仓库位置最好是单独git仓库级别的管理隔离,就能达到我们想要解决的一系列问题。
  然而事情并不是想象的那么简单...
  一些列的问题接踵而至,于我而言影响最深的就是应用设计时使用映射型数据库,导致集成模式和组件模式中复用出现问题;最终使用注解配合Java特性生成代码,虽然不完美但是依然解决了此问题。正当我为了胜利欢呼的时刻,随即闪现出了一个重要且紧急的问题, 代码中心化的问题。
  这个问题是怎么出现的呢?在微信Android模块化架构重构实践中是这样描述的。
  然而随着代码继续膨胀,一些问题开始突显出来。首先出问题的是基础工程libnetscene和libplugin。基础工程一直处于不断膨胀的状态,同时主工程也在不断变大。同时基础工程存在中心化问题,许多业务Storage类被附着在一个核心类上面,久而久之这个类已经没法看了。此外当初为了平滑切换到gradle避免结构变化太大以及太多module,我们将所有工程都对接到一个module上。缺少了编译上的隔离,模块间的代码边界出现一些劣化。虽然紧接着开发了工具来限制模块间的错误依赖,但这段时间里的影响已经产生。在上面各种问题之下,许多模块已经称不上"独立"了。所以当我们重新审视代码架构时,以前良好模块化的架构设计已经逐渐变了样。
  再看他们分析问题的原因:  翻开基础工程的代码,我们看到除了符合设计初衷的存储、网络等支持组件外,还有相当多的业务相关代码。这些代码是膨胀的来源。但代码怎么来的,非要放这?一切不合理皆有背后的逻辑。
  在之前的架构中,我们大量适用Event事件总线作为模块间通信的方式,也基本是唯一的方式。使用Event作为通信的媒介,自然要有定义它的地方,好让模块之间都能知道Event结构是怎样的。这时候基础工程好像就成了存放Event的唯一选择——Event定义被放在基础工程中;接着,遇到某个模块A想使用模块B的数据结构类,怎么办?
  把类下沉到基础工程;遇到模块A想用模块B的某个接口返回个数据,Event好像不太适合?那就把代码下沉到基础工程吧……
  就这样越来越多的代码很"自然的"被下沉到基础工程中。
  我们再看看主工程,它膨胀的原因不一样。分析一下基本能确定的是,首先作为主干业务一直还有需求在开发,膨胀在所难免,缺少适当的内部重构但暂时不是问题的核心。另一部分原因,则是因为模块的生命周期设计好像已经不满足使用需要。之前的模块生命周期是从"Account初始化"到"Account已注销",所以可以看出在这时机之外肯定还有逻辑。
  放在以前这不是个大问题,刚启动还不等"Account初始化"就要执行的逻辑哪有那么多。而现在不一样,再简单的逻辑堆积起来也会变复杂。此时,在模块生命周期外的逻辑基本上只能放主工程。
  此外的问题,模块边界破坏、基础工程中心化,都是代码持续劣化的帮凶...
  看完之后就陷入了沉思,这个问题不就是我们面临的问题吗?不仅是在组件化中,在很多形成依赖关系的场景中都有此类问题。
  假设有user组件和分享组件,分享组件需要user组件提供数据。
  具体是怎么体现的呢,我们来看一组图:
  解决方式为分享组件依赖user组件,能解决问题,假设,有一个组件A,需要引用分享组件,就必须依赖分享组件和user组件,这就一举打破了组件编译隔离的远景,组件化将失去香味儿。
  将user组件中的公共数据部分下沉到base组件,分享组件依赖base组件即可实现数据提供,然而当非常多的组件需要互相提供数据时,将出现中心化问题,只需要分享组件的B组件不得不依赖base组件,引入其他数据。也就造成了代码中心化下沉失去组件化的意义。  怎么解决代码中心化问题
  微信面对这个痛心疾首的问题时发出了"君有疾在腠理,不治将恐深" 的感慨,但也出具了非常厉害的操作-.api化。
  这个操作非常高级,做法非常腾讯,但是此文档中只提到了精髓,没有具体的操作步骤,对我们来讲依然存在挑战。
  什么是代码中心化问题的.api方案
  先看一下具体的操作过程是什么样的。上图3中,我们使用某种技术将user组件中需要共享数据的部分抽象成接口,利用AS对文件类型的配置将(kotlin)后拽修改为.api ,然后再创建一个同包名的module-api 组件用来让其他组件依赖,分享组件和其他组件以及自身组件在module模式下均依赖该组件,这样就能完美的将需要共享的数据单独出去使用了。
  SPI 方式实现
  这个有点类似SPI(Service Provider Interface)机制:
  大概就是说我们可以将要共享的数据先抽象到接口中形成标准服务接口,然后在具体的实现中,然后在对应某块中实现该接口,当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名;然后利用 ServiceLoader 来加载配置文件中指定的实现,此时我们在不同组件之间通过ServiceLoader加载需要的文件了。
  利用ARouter
  利用ARouter在组件间传递数据的方式+gralde自动生成module-api组件,形成中心化问题的.api化。假设我们满足上述的所有关系,并且构建正确,那我们怎么处理组件间的通信?
  Arouter 阿里通信路由  @Route(path = "/test/activity") public class YourActivity extend Activity {     ... }  跳转:  ARouter.getInstance().build("/test/activity").withLong("key1", 666L).navigation()// 声明接口,其他组件通过接口来调用服务 public interface HelloService extends IProvider {    String sayHello(String name); } // 实现接口 @Route(path = "/yourservicegroupname/hello", name = "测试服务") public class HelloServiceImpl implements HelloService {     @Override     public String sayHello(String name) {         return "hello, " + name;     }     @Override     public void init(Context context) {     } }  //测试 public class Test {     @Autowired     HelloService helloService;     @Autowired(name = "/yourservicegroupname/hello")     HelloService helloService2;     HelloService helloService3;     HelloService helloService4;     public Test() {         ARouter.getInstance().inject(this);     }     public void testService() {     // 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取     // Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)     helloService.sayHello("Vergil");     helloService2.sayHello("Vergil");     // 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType     helloService3 = ARouter.getInstance().navigation(HelloService.class);     helloService4 = (HelloService)ARouter.getInstance().build("/yourservicegroupname/hello").navigation();     helloService3.sayHello("Vergil");     helloService4.sayHello("Vergil");     } }
  假如user组件的用户信息需要给支付组件使用,那我们怎么处理?
  ARouter可以通过上面的IProvider注入服务的方式通信,或者使用EventBus这种方式。  data class UserInfo(val uid: Int, val name: String) /** *@author kpa *@date 2021/7/21 2:15 下午 *@email billkp@yeah.net *@description 用户登录、获取信息等 */ interface IAccountService : IProvider {     //获取账号信息 提供信息*     fun getUserEntity(): UserInfo? }  //注入服务 @Route(path = "/user/user-service") class UserServiceImpl : IAccountService {     //... }
  在支付组件中
  问题就暴露在了我们眼前,支付组件中的IAccountService和UserInfo从哪里来?
  这也就是module-api 需要解决的问题,在原理方面:
  将需要共享的数据和初始化数据的类文件设计为.api文件
  打开AS-> Prefernces -> File Types找到kotlin(Java)选中在File name patterns 里面添加".api"(注意这个后缀随意开心的话都可以设置成.kpa)
  举例:  data class UserInfo(val userName: String, val uid: Int)interface UserService {     fun getUserInfo(): UserInfo }
  生成包含共享的数据和初始化数据的类文件的module-api组件
  这步操作有以下实现方式。  自己手动创建一个module-api 组件 显然这是不可取但是可行的  使用脚本语言shell 、python 等扫描指定路径生成对应module-api  利用Android 编译环境及语言groovy,编写gradle脚本,优势在于不用考虑何时编译,不打破编译环境,书写也简单  module-api 脚本
  找到这些问题出现的原理及怎么去实现之后,从github上找到了优秀的人提供的脚本,完全符合我们的使用预期。  def includeWithApi(String moduleName) {  def packageName = "com/xxx/xxx"      //先正常加载这个模块     include(moduleName)      //找到这个模块的路径     String originDir = project(moduleName).projectDir     //这个是新的路径     String targetDir = "${originDir}-api"     //原模块的名字     String originName = project(moduleName).name     //新模块的名字     def sdkName = "${originName}-api"     //这个是公共模块的位置,我预先放了一个 新建的api.gradle 文件进去     String apiGradle = project(":apilibrary").projectDir     // 每次编译删除之前的文件     deleteDir(targetDir)      //复制.api文件到新的路径     copy() {         from originDir         into targetDir         exclude "**/build/"         exclude "**/res/"         include "**/*.api"     }     //直接复制公共模块的AndroidManifest文件到新的路径,作为该模块的文件     copy() {         from "${apiGradle}/src/main/AndroidManifest.xml"         into "${targetDir}/src/main/"     }     //复制 gradle文件到新的路径,作为该模块的gradle     copy() {         from "${apiGradle}/api.gradle"         into "${targetDir}/"     }      //删除空文件夹     deleteEmptyDir(*new* File(targetDir))     //todo 替换成自己的包名     //为AndroidManifest新建路径,路径就是在原来的包下面新建一个api包,作为AndroidManifest里面的包名     String packagePath = "${targetDir}/src/main/java/" + packageName + "${originName}/api"     //todo 替换成自己的包名,这里是apilibrary模块拷贝的AndroidManifest,替换里面的包名     //修改AndroidManifest文件包路径     fileReader("${targetDir}/src/main/AndroidManifest.xml", "commonlibrary", "${originName}.api")      new File(packagePath).mkdirs()     //重命名一下gradle     def build = new* File(targetDir + "/api.gradle")      if(build.exists()) {         build.renameTo(new File(targetDir + "/build.gradle"))     }     // 重命名.api文件,生成正常的.java文件     renameApiFiles(targetDir, ".api", ".java")     //正常加载新的模块     include ":$sdkName"   }  private void deleteEmptyDir(File dir) {      if(dir.isDirectory()) {         File[] fs = dir.listFiles()         if(fs != null && fs.length > 0) {             for (int i = 0; i < fs.length; i++) {                 File tmpFile = fs[i]                 if (tmpFile.isDirectory() {                     deleteEmptyDir(tmpFile)                 }                 if (tmpFile.isDirectory() && tmpFile.listFiles().length <= 0){                     tmpFile.delete()                 }           }        }    if (dir.isDirectory() && dir.listFiles().length == 0) {         dir.delete()    }  }  private void deleteDir(String targetDir) {      FileTree targetFiles = fileTree(targetDir)     targetFiles.exclude "*.iml"     targetFiles.each { File file ->         file.delete()     }  }  /** * rename api files(java, kotlin...) **/  private def renameApiFiles(root_dir, String suffix, String replace) {      FileTree* files = fileTree(root_dir).include("**/*$suffix")     files.each {         File file ->         file.renameTo(*new* File(file.absolutePath.replace(suffix, replace)))     }  }  //替换AndroidManifest里面的字段*  def fileReader(path, name, sdkName) {      def readerString = ""     def hasReplace = false     file(path).withReader("UTF-8") { reader ->         reader.eachLine {             if (it.find(name)) {                 it = it.replace(name, sdkName)                 hasReplace = true             }             readerString <<= it             readerString << " "          }         if (hasReplace) {             file(path).withWriter("UTF-8") {                 within ->                 within.append(readerString)             }         }     return readerString     } }
  使用  includeWithApi ":user"
  Democomponent-api地址为:
  https://github.com/kongxiaoan/component-api最后
  在这里就还分享一份由大佬亲自收录整理的Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
  这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
  当然,你也可以拿去查漏补缺,提升自身的竞争力。
  真心希望可以帮助到大家,Android路漫漫,共勉!
  如果你有需要的话,只需私信我【进阶】即可获取

AI智能人像后期插件有多强大?ON1PortraitAI202115。5最新版本AI智能人像后期插件有多强大?ON1PortraitAI202115。5最新版本ON1系列AI插件全面升级到15。5版本!ON12021系列的AI后期插件已经全面升级到了15。5版小鹏汽车回应车主刹车失灵维权已妥善解决!网友小鹏是特斯拉派过来的卧底,开始车传车了?新浪科技讯5月8日下午消息,有网友发布的一张照片显示,一小鹏汽车疑似出现刹车故障问题,车主将涉事车辆开到小鹏汽车门店前拉横幅维权。照片中,一辆小鹏汽车上挂着写有刹车故障险丧命一车毛鸿蒙OS成三大手机操作系统之一?传闻多家厂商已经在接触华为之前有提到过,最新的鸿蒙OS系统其实和我们大多数人目前正在使用的安卓系统一样都是开源的,这也从侧面向我们透露出一些信息,就是未来会有更多的手机开始使用鸿蒙OS系统,并且会有更多国产iPhone13提前发布指纹识别回归,刘海缩小点击右上方关注,第一时间获取科技资讯技能攻略产品体验,私信我回复01,送你一份玩机技能大礼包。尽管iPhone12系列仅开售不到半年,但这并不能阻挡大家对新一代iPhone的关注。降价1100元后iPhone12还有多大降价空间?最近一段时间,苹果开启了降价模式,iPhone12全系在电商平台的优惠幅度高达1100多元。在线下渠道,苹果直营店价格依旧坚挺,其他手机卖场的iPhone12优惠幅度也在千元以上。千元手机别瞎买这三款机器目前最受欢迎,每一款都有亮点数码圈有一句很知名的话抛开需求谈性能都是扯随着智能机的发展,目前安卓机器更新迭代很快,有很多性能强悍堆料很足的旗舰机,同时也有一些性价比很高的中端机型,当然也有一些很有诚意的千元机盖茨夫妇财产分割大幕开启,全球最昂贵的代价,律师成最大受益者近日一则关于比尔盖茨离婚的消息,成功刷屏,成为了各大媒体的头版头条,65岁的比尔盖茨在社交平台宣布与56岁的梅琳达盖茨离婚,结束两人27年的婚姻关系。离婚原因更是普通人不能理解的,为分割1341亿美元,比尔盖茨夫妇各聘请一名贝索斯离婚案律师备受关注的盖茨夫妇离婚1341亿美元财产分割又有了新进展。据美国CNBC网站5月6日报道,比尔和梅琳达盖茨夫妇二人日前各自组建了律师团队。盖茨夫妇各聘请一名贝索斯离婚案律师,梅琳达曝中芯国际购买11台ASML光刻机,注意了不受美国的管制全球的芯片荒到底是什么原因导致的,产能问题,囤货问题,还是硅晶圆存量不足,需要探索新的材料,这个都是需要我们不断的寻求答案的!不过国产力量这两年一直在探索解决芯片设计和芯片生产的问对比美的格力,海尔空调怎么样?作为半成品的家用电器来说,空调售后服务譬如安装服务很重要。个人经历家里原来98年买的海尔空调,安装工统一着装,进门鞋套,安装完毕后打扫干净拿走垃圾,很正规(后来再买客厅柜机,安装工脚踏风火轮畅游街巷间哪吒V增加4款车型浙江合众新能源汽车有限公司你可能不知道,但如果提起该公司旗下的哪吒系列新能源汽车,你没开过恐怕也听过吧。毕竟,哪吒这个IP太强大,足以让人过目不忘。目前,哪吒汽车旗下共有哪吒01哪
SEO应该怎么做站内优化?站内优化包含了什么内容?一站内优化技巧站内优化,顾名思义就是指网站内部优化,即网站本身内部的优化,SEO站内优化包括代码和标签优化内容优化URL优化。1代码优化为网站减肥可以进行代码优化,代码优化最直接的为什么越来越多的人有2部手机?其实很多人问过我这个问题你为什么要用两部手机?首先说一下,我是一个手机控,从三年前开始同时拿两手机卡工作,倒也不是我业务有多忙。现在手机越做越大,电池却一点不让人省心。拿一部手机你苹果iPhoneSE2022即将到来,供应链已做好准备此前就报道指,苹果可能会在2022年第一季度推出第三代iPhoneSE,以抢占中端5G智能手机市场,预计2022年的产量将达到2500万到3000万台。在设计方面,第三代iPhon美女CEO胡玮炜套现15亿,烂摊子丢给美团,她有多聪明?文璋队长编辑璋队长幸福越与人共享,它的价值越增加。在时代的趋势下,共享经济的浪潮正在滚滚而来。胡玮炜作为摩拜单车创始人,正是明白了这个道理,抓住这个风口的那类人。她用短短3年成立了Pythonenumerate()使用计数器简化循环摘要当您需要计数和迭代中的值时,Pythonenumerate()允许您编写Pythonicfor循环。最大的优点enumerate()是它返回一个带有计数器和值的元组,因此您不必联想Moto发布会,友商骂骂咧咧退群!骁龙8881799,骁龙8卖2999联想MOTO走了雷军小米手机的路,这是要让雷军无路可走吗?最新的联想MOTO发布会,一个是配置骁龙888的6128G卖1799啊,新一代骁龙8的6428G卖2999。这价格友商看了开一个华为手机专卖店需要投资多少钱?大家好我是阿鑫1。华为专卖店,开店是有要求的必须在60平米以上,这是之前的要求。中间不能有柱子之类的东西。而且目前是一县一店制。除非县城特别大可能允许开两家专卖店。2。专卖店和别的徒手搭建Python单元测试框架稍微具有一定规模的企业对于软件开发一般都会要求写单元测试,虽然各自标准不同,有的可能要求覆盖率达到50即可,而像我司这种竟然要求行覆盖率和分支覆盖率都要到95以上。本文会手把手教你1亿拿下支付牌照,B站拥抱直播带货1亿拿下支付牌照,B站内测小黄车功能B站也要来分直播电商的蛋糕了。根据36氪报道,B站正筹备在直播间中上线小黄车功能,让用户能够边看直播边下单购物。根据消息人士称,B站在近期会对此苹果新款iMacPro渲染图泄露,采用深空灰配色并将配置多个Thunderbolt接口在上个月举办的春季新品发布会上,苹果宣布推出采用M1芯片的全新iMac。由于此前采用英特尔处理器的iMacPro已开始逐步停止出货,所以不少人猜测,采用自研芯片的继任者已经在路上了华为鸿蒙即将开源系统搭载鸿蒙系统的手机平板今年或将发售今日,在鸿蒙开发者创新大赛颁奖典礼上,华为消费者业务软件部总裁王成录表示鸿蒙即将开源系统,并在武汉大学等15个学校开展harmonyOS课程。HarmonyOS面向的是万物互联的操