2024年最全GitHub标星9(1),2024年最新华为安卓面试题

尾声

在我的博客上很多朋友都在给我留言,需要一些系统的面试高频题目。之前说过我的复习范围无非是个人技术博客还有整理的笔记,考虑到笔记是手写版不利于保存,所以打算重新整理并放到网上,时间原因这里先列出面试问题,题解详见:


展示学习笔记

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

app 模块:用于独立运行 app,它直接依赖组件模块,只要添加一些简单的配置,即可实现组件独立运行。

组件半自动拆分

有了解耦的方法,剩下的就是采取行动拆分组件了,拆组件是一个很头疼的问题,它非常考虑一个人的细心与耐心,由于无法准确知道有哪些代码要被拆走,也不能直观的知晓依赖关系,移动变得非常的困难且容易出错,一旦不能一次性拆分成功,到处都是编译错误,便只能靠人肉一点一点的挪。

工欲善其事,必先利其器。为了解决这个问题,我们开发了一个辅助工具 RefactorMan: 它可以递归的解析出工程中所有源码的引用和被引用情况,同时会根据预设规则自动分析出所有不合理的依赖,在开发人员根据提示解决了不合理依赖之后,即可将组件一键移出,大大减少了拆组件的工作量。我们在组件化初期曾经走过一些弯路,最初拆出的八个组件工程的的部分源码经历了几次的反复移动才得出最优解,而有了 RefactorMan,我们可以面对反复的拆分和组合组件有恃无恐。

Bonus :由于可以分析和移动资源,所以额外获得了清理无用资源的功能。

联合编译完整包

单独运行组件 app 并不能完整的覆盖所有的 case,尤其是在给 QA 测试的时候,还是需要编译完整的主工程包的,所以我们需要一个直接编译完整包的方案:

最初我们的实现方式只针对组件,比较简单:

首先在 setting.gradle 中动态引入组件 module:

def allComponents = [“base”, “account” … “template” …]

allComponents.forEach({ name ->

if (shouldUseSource(name)) {

// 动态引入外部模块

include “😒{name}”

project(“😒{name}”).projectDir = getComponentDir(name);

}

})

然后在 app/build.gradle 中切换依赖,需要将所有被间接依赖的组件全部 exclude 以防止同时依赖了一个组件的 module 和 aar:

allComponents.forEach({ name ->

if (shouldUseSource(name)) {

implementation(project(“😒{name}”)) { exclude group: COMPONENT_GROUP }

} else {

implementation(“ C O M P O N E N T G R O U P : {COMPONENT_GROUP}: COMPONENTGROUP:{name}😒{versions[name]}”) { exclude group: COMPONENT_GROUP }

}

})

由于所有组件的 group 都是一样的,所以这样做并没有什么问题,但是后来一些基础 SDK 也出现了这种需求,这时候就需要一种通用的源码依赖方案,因此做了一下修改,直接使用 gradle 提供的依赖替换功能,只需要修改 setting.gradle 即可:

// … 忽略读取配置代码 …

configs.forEach { artifact, prj ->

include “😒{prj.name}”

project(“😒{prj.name}”).projectDir = new File(prj.dir)

}

gradle.allprojects { project ->

if (project == project.rootProject) {

return

}

project.configurations.all {

resolutionStrategy.dependencySubstitution {

configs.forEach { artifact, prj ->

// 在这里进行替换

substitute module(artifact) with project(“😒{prj.name}”)

}

}

}

}

而 build.gradle 的依赖写法与普通的工程完全一样。

普通状态下的的主工程:

源码引用 template 组件后的主工程:

这样我们就可以像之前在单工程中一样写代码了。

得益于源码引用,我们直接在提交组件代码的时候,CI 会自动联合主工程编译出完整包,QA 会根据完整包进行测试,在测试通过后即可自动发布到公司的仓库,并通过内部的集成平台集成到主工程

小 tip :工程 .idea/vcs.xml 中定义了当前工程关联的 Git 仓库,可以在联合编译的同时通过修改 vcs.xml 来把组件目录也关联到主工程 Git 配置中,在开发过程中就可以使用 Android Studio 的内置 Git 功能了

包含子业务线的组件

我们当前的组件,绝大部分是一个组件一个仓库的,对于一般的组件来说,并没有什么问题,但是对于有的业务线,本身规模比较大,包含了若干个子业务,比如知乎大学,电子书、live 和私家课等子业务,这些子业务本身功能独立,但是共享整个业务线的基础代码,同时大业务线也有会一些汇总所有子业务的页面,它们的关系是这个样子:

这几个业务如果都要拆分出去独立成组件,然后抽离公共部分成为也成为一个业务线基础组件,这时候会面临一个很大的问题:由于几条业务线都属于同一个主业务线,做活动或者上新 Feature 的时候,这几个组件经常会发生联动,需要先更新 base 再更新其他业务线,提交 mr 也要同时提多个仓库,出现频繁的连锁更新;而如果不拆的话,业务线代码本身就已经很庞大,即使是单独编译组件 app 也会很慢,并且随着时间的推移,各个业务线的代码边界会像组件化之前的主工程一样逐渐劣化,耦合会越来越严重。

所以现在需求变成了这个样子:

  1. 对外保持只有一个组件:有联动需求的时候,组件仍然只发布一次更新

  2. 各个子业务仍旧保持互相独立和隔离,可以独立运行

我们曾经试图使用 sourceSets 的方式将不同的业务代码放到不同的文件夹,但是 sourceSets 的问题在于,它并不能限制各个 sourceSet 之间互相引用,base 模块甚至可以直接引用最上层的代码,虽然可以在编译期进行检查,但是总有一些后知后觉的意味,并且使用 sourceSets 想让各个模块单独跑起来配置也比较麻烦。而 Android Studio 的 module 天然具有隔离的优势。所以我们的解决方案是在组件工程中使用多 Module 结构:

各个子业务线分别拆成同一个工程中不同的 Module:它们共同依赖 base ,同时各个业务线互相不依赖,这些子业务又在一个主 Module 中汇集起来,正如上面图片所示那样

对于外界来说只有一个 main 组件,如果直接通过 ./gradlew :main:uploadArchives 来发布,那么就只能把 main Module 的代码发布上去,其他 Module 的代码是无法被连带上去的,所以我们需要在发布的时候将所有的代码合并到 main 中去。这时候只能使用添加 sourceSet 的方式,而一旦使用了 sourceSet,代码就不再隔离了。所以我们使用了一个动态的策略:编译时使用 sourceSet 依赖,其他时候使用 module 依赖,这样可以同时拥有两者的优势。

也就是说:表面看起来,这是一个普通的多模块的工程,但是实际上,他们的关系是动态的:写代码时是七个葫芦娃,编译时是葫芦小金刚:

如何做到呢,可以简单的判断当前启动的 Task,一般我们只在 assemble、install、upload 的时候使用合体操作,而其他时候使用普通的 project 依赖,示例代码如下:

boolean useSource = gradle.startParameter.taskNames.any {

it.contains(“assemble”) || it.contains(“install”) || it.contains(“upload”))

}

subProject.forEach { subProject ->

if (useSource) {

android.sourceSets.main {

java.srcDirs += file(“…/${subProject}/src/main/java”)

res.srcDirs += file(“…/${subProject}/src/main/res”)

}

} else {

dependencies { implementation project(“:$subProject”) }

}

}

其他资源例如 resources、assets、aidl、renderscript、jni、jniLibs、shaders 以及 aar 和 jar 文件,它们都是多文件的,可以使用与上面类似的方法添加。

但是 manifest 不同,一个 module 中只有一个 AndroidManifest.xml ,所以需要有一个方法将子业务的 manifest 合并。我们使用了官方提供的 ManifestMerger 实现了 manifest 的合并,这里不再展开合并的具体代码,有兴趣的同学可以自己去看源码。

将上面代码封装了一个方法 using,主 module 就可以这样引用子 module 了:

dependencies {

using “base”

using “sub1”

using “sub2”

using ‘sub3’

using ‘sub4’

}

由于每个子业务组件都是独立的,仍然可以单独配置独立编译独立运行,由于每个业务的代码量相对整个业务线来说大大减少了,所以得到了更快的编译速度。

总结


最近两年很多公司都开始了 App 的组件化,组件化的基础思想都是相通的,但是并没有一个放之四海而皆准的通用解决方案,各个公司在组件化的过程中都会根据自身的情况不断的调整方案,适合自身发展的,才是最好的。一些组件化初期看起来不起眼的问题,可能进行到后期才会慢慢显现出来,这时候就要及时调整方案。知乎的组件化也是在不断的变动中逐渐完善的,并且以后肯定也会随着业务和代码的变动不断的进行优化,这会是一个持续的过程,后续我们也会持续分享一些组件化遇到的问题和解决方案。

最后

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

img

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

这四个字就是我的建议!!!

  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值