示例
//父类
class Animal {
String username
int legs
Animal(String name) {
username = name
}
void setLegs(int c) {
legs = c
}
String toString() {
return “This animal is $username, it has ${legs} legs.”
}
}
//子类
class Pig extends Animal {
int age
String owner
Pig(int age, String owner) {
super(“Pig”)
this.age = age
this.owner = owner
}
String toString() {
return super.toString() + " Its age is $age, its owner is $owner."
}
}
//创建的Extension是 暴露出来Animal 类型,创建extension名称是name,该extension的类型是Pig,后面2个是参数
Animal aAnimal = getExtensions().create(Animal, “animal”, Pig, 3, “hjy”)
//创建的Extension是 Pig 类型
Pig aPig = getExtensions().create(“pig”, Pig, 5, “kobe”)
animal {
legs = 4 //配置属性
}
pig {
setLegs 2 //这个是方法调用,也就是 setLegs(2)
}
task testExt << {
println aAnimal
println aPig
//验证 aPig 对象是 ExtensionAware 类型的
println “aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}”
}
增加Extension
- create() 方法会创建并返回一个 Extension 对象,
- add() 方法,唯一的差别是它并不会返回一个 Extension 对象
基于前面的这个实例,我们可以换一种写法如下:
getExtensions().add(Pig, “mypig”, new Pig(5, “kobe”))
mypig {
username = “MyPig”
legs = 4
age = 1
}
task testExt << {
def aPig = project.getExtensions().getByName(“mypig”)
println aPig
}
查找Extension
Object findByName(String name)
T findByType(Class type)
Object getByName(String name) //找不到会抛异常
T getByType(Class type) //找不到会抛异常
嵌套Extension 方式一
类似下面这样的配置应该随处可见:
outer {
outerName “outer”
msg “this is a outer message.”
inner {
innerName “inner”
msg “This is a inner message.”
}
}
可以通过下面的方式来创建
class OuterExt {
String outerName
String msg
InnerExt innerExt = new InnerExt()
void outerName(String name) {
outerName = name
}
void msg(String msg) {
this.msg = msg
}
//创建内部Extension,名称为方法名 inner
void inner(Action action) {
action.execute(inner)
}
//创建内部Extension,名称为方法名 inner
void inner(Closure c) {
org.gradle.util.ConfigureUtil.configure(c, innerExt)
}
String toString() {
return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
}
}
class InnerExt {
String innerName
String msg
void innerName(String name) {
innerName = name
}
void msg(String msg) {
this.msg = msg
}
String toString() {
return “InnerExt[ name = ${innerName}, msg = ${msg}]”
}
}
def outExt = getExtensions().create(“outer”, OuterExt)
outer {
outerName “outer”
msg “this is a outer message.”
inner {
innerName “inner”
msg “This is a inner message.”
}
}
task testExt doLast {
println outExt
}
关键在以下下面的方法
void inner(Action action)
void inner(Closure c)
定义在outer内部的inner,Gradle 解析时本质上会调用 outer.inner(……)方法,该方法的参数是一个闭包(Script Block) 所以在类OuterExt中必须定义inner方法
嵌套Extension方式二(NamedDomainObjectContainer)
使用场景
Gradle Extension 的时候,说到名为 android 的 Extension 是由 BaseExtension 这个类来实现的,里面对 buildTypes 是这样定义的:
private final NamedDomainObjectContainer buildTypes;
buildTypes 就是 NamedDomainObjectContainer 类型的,先来看看 buildTypes 在 Android 中是怎么使用的,下面这段代码应该都很熟悉了,它定义了 debug、relase 两种打包模式:
android {
buildTypes {
release {
// 是否开启混淆
minifyEnabled true
// 开启ZipAlign优化
zipAlignEnabled true
//去掉不用资源
shrinkResources true
// 混淆文件位置
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
// 使用release签名
signingConfig signingConfigs.hmiou
}
debug {
signingConfig signingConfigs.hmiou
}
}
}
当我们新建一个项目时候,默认会有debug和release这2个配置,那么debug、release可以修改其他名字吗,能增加其他名字来配置吗,比如想增加一个测试包配置test,还有就是release里面都能配置哪些属性呢
我来说下结果,如果不确定的,可以实际验证一下:
- debug、release 是可以修改成其他名字的,你可以替换成你喜欢的名字;
- 你可以增加任意不同名字的配置,比如增加一个开发版本的打包配置 dev ;
- 可配置的属性可参考接口:com.android.builder.model.BuildType ;
可以看到它是非常灵活的,可以根据不同的场景定义不同的配置,每个不同的命名空间都会生成一个 BuildType 配置。要实现这样的功能,必须使用 NamedDomainObjectContainer 类型。
什么是NamedDomainObjectContainer
顾名思义就是命名领域对象容器,它的主要功能有:
- 通过DSL创建指定type的对象实例
- 指定的type必须有一个public构造函数,且必须带有一个String name的参数
- 它是一个实现了SortedSet接口的容器,所以所有领域对象的name属性都必须是唯一的,在容器内部会用name属性来排序
named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type. Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.
创建NamedDomainObjectContainer
NamedDomainObjectContainer 需要通过 Project.container(…) API 来创建,其定义为:
NamedDomainObjectContainer container(Class type)
NamedDomainObjectContainer container(Class type, NamedDomainObjectFactory factory)
NamedDomainObjectContainer container(java.lang.Class type, Closure factoryClosure
来看个具体的实例:
//这是领域对象类型定义
class TestDomainObj {
//必须定义一个 name 属性,并且这个属性值初始化以后不要修改
String name
String msg
//构造函数必须有一个 name 参数
public TestDomainObj(String name) {
this.name = name
}
void msg(String msg) {
this.msg = msg
}
String toString() {
return “name = ${name}, msg = ${msg}”
}
}
//创建一个扩展
class TestExtension {
//定义一个 NamedDomainObjectContainer 属性
NamedDomainObjectContainer testDomains
public TestExtension(Project project) {
//通过 project.container(…) 方法创建 NamedDomainObjectContainer
NamedDomainObjectContainer domainObjs = project.container(TestDomainObj)
testDomains = domainObjs
}
//让其支持 Gradle DSL 语法
void testDomain(Action<NamedDomainObjectContainer> action) {
action.execute(testDomains)
}
void test() {
//遍历命名领域对象容器,打印出所有的领域对象值
testDomains.all { data ->
println data
}
}
}
//创建一个名为 test 的 Extension
def testExt = getExtensions().create(“test”, TestExtension, project)
test {
testDomain {
domain2 {
msg “This is domain2”
}
domain1 {
msg “This is domain1”
}
domain3 {
msg “This is domain3”
}
}
}
task myTask doLast {
testExt.test()
}
运行结果如下:
name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3
查找和遍历
NamedDomainObjectContainer 既然是一个容器类,与之相应的必然会有查找容器里的元素和遍历容器的方法:
//遍历
void all(Closure action)
//查找
T getByName(String name)
//查找
T findByName(String name)
还是接着前面的例子:
//通过名字查找
TestDomainObj testData = testDomains.getByName(“domain2”)
println “getByName: ${testData}”
//遍历命名领域对象容器,打印出所有的领域对象值
testDomains.all { data ->
println data
}
需要注意的是,Gradle 中有很多容器类的迭代遍历方法有 each(Closure action)、all(Closure action),但是一般我们都会用 all(…) 来进行容器的迭代。all(…) 迭代方法的特别之处是,不管是容器内已存在的元素,还是后续任何时刻加进去的元素,都会进行遍历。
Android的Extension
我们在gradle中会看到 android{}
defaultConfig、productFlavors、signingConfigs、buildTypes 这4个内部 Extension对象是怎么定义的,通过查看源码可以找到一个叫 BaseExtension 的类,里面的相关代码如下:
private final DefaultConfig defaultConfig;
private final NamedDomainObjectContainer productFlavors;
private final NamedDomainObjectContainer buildTypes;
private final NamedDomainObjectContainer signingConfigs;
public void defaultConfig(Action action) {
this.checkWritability();
action.execute(this.defaultConfig);
}
public void buildTypes(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.buildTypes);
}
public void productFlavors(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.productFlavors);
}
public void signingConfigs(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.signingConfigs);
}
在 app 的 build.gradle 里我们通常会采用插件 apply plugin: ‘com.android.application’ ,而在 library module 中则采用插件 apply plugin: ‘com.android.library’,AppPlugin 就是插件 com.android.application 的实现类,LibraryPlugin 则是插件 com.android.library 的实现类,接着再看看 AppPlugin 里是怎样创建 Extension 的:
public class AppPlugin extends BasePlugin implements Plugin {
@Inject
public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
super(instantiator, registry);
}
protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer buildTypeContainer, NamedDomainObjectContainer productFlavorContainer, NamedDomainObjectContainer signingConfigContainer, NamedDomainObjectContainer buildOutputs, ExtraModelInfo extraModelInfo) {
return (BaseExtension)project.getExtensions().create(“android”, AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
}
public void apply(Project project) {
super.apply(project);
}
//省略…
}
在 createExtension() 方法中,可以看到创建了一个名为 android 的 Extension,该 Extension 的类型为 AppExtension,而 AppExtension 的继承结构为 AppExtension -> TestedExtension -> BaseExtension,所以它的实现逻辑大部分都是在 BaseExtension 里实现的。
在Android 工程中的build.gradle 文件中,我们配置相关信息使用 android{} 节点,从 AppPlugin 也能看出其 Extension的名称为 android ,所以获取方法如下:
- project.extensions.getByName
- project.extensions.getByType
def getInfo() {
//或者 直接 project.android
BaseExtension extension = project.extensions.getByName(“android”)
def android = project.extensions.getByType(AppExtension)
project.android
println “buildToolsVersion:
e
x
t
e
n
s
i
o
n
.
b
u
i
l
d
T
o
o
l
s
V
e
r
s
i
o
n
"
p
r
i
n
t
l
n
"
c
o
m
p
i
l
e
S
d
k
V
e
r
s
i
o
n
:
{extension.buildToolsVersion}" println "compileSdkVersion:
extension.buildToolsVersion"println"compileSdkVersion:{extension.getCompileSdkVersion()}”
println “applicationId:
e
x
t
e
n
s
i
o
n
.
d
e
f
a
u
l
t
C
o
n
f
i
g
.
a
p
p
l
i
c
a
t
i
o
n
I
d
"
p
r
i
n
t
l
n
"
m
i
n
S
d
k
V
e
r
s
i
o
n
:
{extension.defaultConfig.applicationId}" println "minSdkVersion:
extension.defaultConfig.applicationId"println"minSdkVersion:{extension.defaultConfig.minSdkVersion}”
println “targetSdkVersion:
e
x
t
e
n
s
i
o
n
.
d
e
f
a
u
l
t
C
o
n
f
i
g
.
t
a
r
g
e
t
S
d
k
V
e
r
s
i
o
n
"
p
r
i
n
t
l
n
"
v
e
r
s
i
o
n
C
o
d
e
:
{extension.defaultConfig.targetSdkVersion}" println "versionCode:
extension.defaultConfig.targetSdkVersion"println"versionCode:{extension.defaultConfig.versionCode}”
println “versionName:${extension.defaultConfig.versionName}”
}
更详细的请参考
3、构建生命周期
三个阶段
每次构建的本质其实就是执行一系列的Task,某些Task可能依赖其他Task,那些没有依赖的Task总会被最先执行,而且每个Task只会被执行一遍,每次构建的依赖关系是在构建的配置阶段确定的,在gradle构建中,构建的生命周期主要包括以下三个阶段:
初始化(Initialization)
构建工具会根据每个build.gradle文件创建出一个Project实例,初始化阶段会执行项目根目录下的Settings.gradle文件,来分析哪些项目参与构建
include ‘:app’
include ‘:libraries:someProject’
配置(Configuration)
这个阶段通过执行构建脚本来为每个project创建并分配Task。配置阶段会去加载所有参与构建的项目的build.gradle文件,会将build.gradle文件实例化为一个Gradle的project对象,然后分析project之间的依赖关系,下载依赖文件,分析project下的task之间的依赖关系
执行(Execution)
这是Task真正被执行的阶段,Gradle会根据依赖关系决定哪些Task需要被执行,以及执行的先后顺序。
task是Gradle中的最小执行单元,我们所有的构建,编译,打包,debug,test等都是执行了某一个task,一个project可以有多个task,task之间可以互相依赖。例如我有两个task,taskA和taskB,指定taskA依赖taskB,然后执行taskA,这时会先去执行taskB,taskB执行完毕后在执行taskA。
在根目录和app目录下的build.gradle中会引用下面的插件
dependencies { classpath ‘com.android.tools.build:gradle:2.2.2’ }
apply plugin: ‘com.android.application’
Android 三个文件重要的gradle
Gradle项目有3个重要的文件需要深入理解:
- settings.gradle
settings.gradle 文件会在构建的 initialization 阶段被执行,它用于告诉构建系统哪些模块需要包含到构建过程中。对于单模块项目, settings.gradle 文件不是必需的。对于多模块项目,如果没有该文件,构建系统就不能知道该用到哪些模块。
- 项目根目录的 build.gradle
项目根目录的 build.gradle 文件用来配置针对所有模块的一些属性。它默认包含2个代码块:buildscript{…}和allprojects{…}。前者用于配置构建脚本所用到的代码库和依赖关系,后者用于定义所有模块需要用到的一些公共属性。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath ‘com.android.tools.build:gradle:2.3.2’
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
buildscript:定义了 Android 编译工具的类路径。repositories中, jCenter是一个著名的 Maven 仓库。
allprojects:中定义的属性会被应用到所有 moudle 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。
- 模块目录的 build.gradle
模块级配置文件 build.gradle 针对每个moudle 的配置,如果这里的定义的选项和顶层 build.gradle定义的相同。它有3个重要的代码块:plugin,android 和 dependencies。
常用gradle命令
//构建
gradlew app:clean //移除所有的编译输出文件,比如apk
gradlew app:build //构建 app module ,构建任务,相当于同时执行了check任务和assemble任务
//检测
gradlew app:check //执行lint检测编译。
//打包
gradlew app:assemble //可以编译出release包和debug包,可以使用gradlew assembleRelease或者gradlew assembleDebug来单独编译一种包
gradlew app:assembleRelease //app module 打 release 包
gradlew app:assembleDebug //app module 打 debug 包
//安装,卸载
gradlew app:installDebug //安装 app 的 debug 包到手机上
gradlew app:uninstallDebug //卸载手机上 app 的 debug 包
gradlew app:uninstallRelease //卸载手机上 app 的 release 包
gradlew app:uninstallAll //卸载手机上所有 app 的包
监听生命周期
在gradle的构建过程中,gradle为我们提供了非常丰富的钩子,帮助我们针对项目的需求定制构建的逻辑,如下图所示:
要监听这些生命周期,主要有两种方式:
- 添加监听器
- 使用钩子的配置块
关于可用的钩子可以参考Gradle
和Project
中的定义,常用的钩子包括:
Project
Project提供的生命周期回调方法有
//在 Project 进行配置前调用
void beforeEvaluate(Closure closure)
//在 Project 配置结束后调用
void afterEvaluate(Closure closure)
beforeEvaluate 必须在父模块的 build.gradle 对子模块进行配置才能生效,因为在当前模块的 build.gradle 中配置,它自己本身都没配置好,所以不会监听到。
settings.gradle 代码:
include “:app”
build.gradle 代码:
//对子模块进行配置
subprojects { sub ->
sub.beforeEvaluate { proj ->
println “子项目beforeEvaluate回调…”
}
}
println “根项目配置开始—”
task rootTest {
println “根项目里任务配置—”
doLast {
println “执行根项目任务…”
}
}
println “根项目配置结束—”
app/build.gradle 代码:
println “APP子项目配置开始—”
afterEvaluate {
println “APP子项目afterEvaluate回调…”
}
task appTest {
println “APP子项目里任务配置—”
doLast {
println “执行子项目任务…”
}
}
println “APP子项目配置结束—”
在根目录执行:gradle -q,结果如下:
根项目配置开始—
根项目里任务配置—
根项目配置结束—
子项目beforeEvaluate回调…
APP子项目配置开始—
APP子项目里任务配置—
APP子项目配置结束—
APP子项目afterEvaluate回调…
project.android 获取到AppExtension
:
Gradle
Gradle 提供的生命周期回调方法很多,部分与 Project 里的功能雷同:
//在project进行配置前调用,child project必须在root project中设置才会生效,root project必须在settings.gradle中设置才会生效
void beforeProject(Closure closure)
//在project配置后调用
afterProject(Closure closure)
//构建开始前调用
void buildStarted(Closure closure)
//构建结束后调用
void buildFinished(Closure closure)
//所有project配置完成后调用
void projectsEvaluated(Closure closure)
//当settings.gradle中引入的所有project都被创建好后调用,只在该文件设置才会生效
void projectsLoaded(Closure closure)
//settings.gradle配置完后调用,只对settings.gradle设置生效
void settingsEvaluated(Closure closure)
- beforeProject()/afterProject()
等同于Project
中的beforeEvaluate
和afterEvaluate
- settingsEvaluated()
settings脚本被执行完毕,Settings
对象配置完毕 - projectsLoaded()
所有参与构建的项目都从settings中创建完毕 - projectsEvaluated()
所有参与构建的项目都已经被评估完
我们修改 setting.gradle 的代码如下:
gradle.settingsEvaluated {
println “settings:执行settingsEvaluated…”
}
gradle.projectsLoaded {
println “settings:执行projectsLoaded…”
}
gradle.projectsEvaluated {
println “settings: 执行projectsEvaluated…”
}
gradle.beforeProject { proj ->
println “settings:执行KaTeX parse error: Expected 'EOF', got '}' at position 28: …beforeProject" }̲ gradle.afterPr…{proj.name} afterProject”
}
gradle.buildStarted {
println “构建开始…”
}
gradle.buildFinished {
println “构建结束…”
}
include “:app”
这个时候的执行结果如下:
settings:执行settingsEvaluated…
settings:执行projectsLoaded…
settings:执行test beforeProject
根项目配置开始—
根项目里任务配置—
根项目配置结束—
settings:执行test afterProject
settings:执行app beforeProject
子项目beforeEvaluate回调…
APP子项目配置开始—
APP子项目里任务配置—
APP子项目配置结束—
settings:执行app afterProject
APP子项目afterEvaluate回调…
settings: 执行projectsEvaluated…
构建结束…
可以看到 gradle.beforeProject 与 project.beforeEvaluate 是类似的,同样 afterProject 与 afterEvaluate 也是类似的。
除此之外,Gradle 还有一个通用的设置生命周期监听器的方法:addListener
上面的 BuildListener、ProjectEvaluationListener 等与前面的部分 API 功能是一致的,这里不再赘述了。
TaskExecutionGraph(Task执行图)
Gradle 在配置完成后,会对所有的 task 生成一个有向无环图,这里叫做 task 执行图,他们决定了 task 的执行顺序等。同样,Gradle 可以对 task 的执行生命周期进行监听。
//任务执行前掉用
void afterTask(Closure closure)
//任务执行后调用
void beforeTask(Closure closure)
//所有需要被执行的task已经task之间的依赖关系都已经确立
void whenReady(Closure closure)
通过 gradle.getTaskGraph() 方法来获取 task 执行图:
TaskExecutionGraph taskGraph = gradle.getTaskGraph()
taskGraph.whenReady {
println “task whenReady”
}
taskGraph.beforeTask { Task task ->
println “任务名称:KaTeX parse error: Expected 'EOF', got '}' at position 25: …e} beforeTask" }̲ taskGraph.afte…{task.name} afterTask”
}
生命周期回调的执行顺序:
gradle.settingsEvaluated->
gradle.projectsLoaded->
gradle.beforeProject->
project.beforeEvaluate->
gradle.afterProject->
project.afterEvaluate->
gradle.projectsEvaluated->
gradle.taskGraph.graphPopulated->
gradle.taskGraph.whenReady->
gradle.buildFinished
4、自定义插件开发
三种方式
类型 | 说明 |
---|---|
Build script | 把插件写在 build.gradle 文件中,一般用于简单的逻辑,只在该 build.gradle 文件中可见 |
buildSrc 项目 | 将插件源代码放在 rootProjectDir/buildSrc/src/main/groovy 中,只对该项目中可见,适用于逻辑较为复杂 |
独立项目 | 一个独立的 Groovy 和 Java 项目,可以把这个项目打包成 Jar 文件包,一个 Jar 文件包还可以包含多个插件入口,将文件包发布到托管平台上,供其他人使用。本文将着重介绍此类。 |
具体从插件开发可以参考
需要注意的是 在main目录下创建
1、resources/META-INF/gradle-plugins文件夹,
2、在gradle-plugins文件夹下创建一个xxx.properties文件,(com.learntransform.testtransform.properties)
注意:这个xxx就是在app下的build.gradle中引入时的名字,例如:apply plugin: ‘xxx’(apply plugin:‘com.learntransform.testtransform’)
3、在文件书写引用到插件 implementation-class=me.xsfdev.learntransform.Hotfix
插件的本地化
- 本地插件module
group = ‘com.learntranform’
version = ‘1.0.1’
uploadArchives {
repositories {
flatDir {
name “localRepository”
dir “…/app/localRepository/libs”
}
}
}
- 工程的gradle
buildscript {
ext.kotlin_version = ‘1.2.41’
repositories {
flatDir {
name ‘localRepository’
dir “app/localRepository/libs”
}
mavenLocal()
jcenter()
google()
}
dependencies {
classpath(group: ‘com.plugintest’, name: ‘hellodsl’, version: ‘1.0.0’) {
changing = true
}
classpath(group: ‘com.learntransform’, name: ‘learntransform’, version: ‘1.0.1’) {
changing = true
}
classpath ‘com.android.tools.build:gradle:3.1.2’
classpath “org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version”
classpath ‘com.novoda:bintray-release:0.8.0’ //jcenter添加
classpath ‘com.xsfdev:complexcriptdsl:1.0.0’
}
}
完整代码可以参考 LearnGradle
5、常用方法
android.applicationVariants
更多参考需要看源码
android.applicationVariants 返回的是
public DomainObjectSet getApplicationVariants() {
return applicationVariantList;
}
一层层追 相关的有
在BaseVariant中
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。
者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-0LM0LjeX-1711937946556)]
[外链图片转存中…(img-Dd5ZhR2C-1711937946557)]
[外链图片转存中…(img-xhKiLe1z-1711937946557)]
[外链图片转存中…(img-HQlz4TxP-1711937946557)]
[外链图片转存中…(img-XOYhkU88-1711937946557)]
[外链图片转存中…(img-9jEklq22-1711937946558)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-KBWOEg8T-1711937946558)]
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。