1 Android发展和历史
Android是由Android公司创造的手机操作系统,公司创始人是Andy Rubin,后来被Google收购,Google于2007年11月发布了Android 1.0手机操作系统,在2009年发布了Android 1.5,此后Android发展迅速。目前Android已经超出类手机操作系统的范畴,已经被广泛应用于TV、手表以及各种可穿戴设备等等。
2 Android平台架构
Android系统的底层建立在Linux上,采用一种称为软件叠层的方式进行构建,这种方式使得层与层之间相互分离,保证了层与层之间的低耦合。
Android体系结构如下(图源):

可以看到主要由6部分组成:
- 系统
App层 Java API框架层- 原生
C/C++库 Android运行时- 硬件抽象层(
HAL) Linux内核
下面进行详细说明。
2.1 系统App层
包含一系列核心App(电话拨号应用、电子邮件客户端、日历、相机等),这些应用程序通常使用Java编写。
2.2 Java API框架层
Java API框架提供了大量API供开发者使用,主要在这一层的上面进行App的开发。
2.3 原生C/C++库
Android包含一套被不同组件所使用的C/C++库的集合,一般来说是通过Java API框架层去调用这些原生的C/C++库。
一些简单的原生C/C++库如下:
WebKit:一个Web浏览器引擎,为Android浏览器提供支持,也为WebView提供支持OpenMAX:开放媒体加速层,目的在于使用统一的接口,加速处理大量多媒体资料Libc:一个从BSD系统诞生的标准C系统库,并为嵌入式Linux调整过Media Framework:基于PacketVideo的OpenCORE,支持播放和录制许多流行的音频和视频格式SGL:底层的2D图形引擎OpenGL ES:基于OpenGL ES API实现的3D系统,可以使用硬件3D加速和软件3D加速SQLite:供所有应用使用的功能强大的轻量级关系数据库
2.4 Android运行时
Android运行时由两部分组成:
Android核心库:核心库提供了Java语言核心库所能使用的绝大部分功能ART:负责运行Android应用程序
2.5 硬件抽象层
硬件抽象层(HAL)提供了对Linux内核驱动的封装,可以向上提供驱动音频、蓝牙、摄像头、传感器等设备的编程接口,向下可以隐藏底层的实现细节。
Android把对硬件的支持分为两层:
- 内核驱动层:处于
Linux内核中,值提供简单的硬件访问控制逻辑,开源 - 硬件抽象层:负责参数和流程控制,向上提供统一的编程接口,不开源,实现因厂家而异
2.6 Linux内核
Android是基于Linux的,Linux内核提供了安全性、内存管理、进程管理、网络协议栈和驱动模型等核心服务,也是系统硬件和软件叠层之间的抽象层。
3 Gradle
3.1 Gradle简介
Gradle是Android Studio采用的构建工具,Gradle与Ant、Maven相比,优势如下:
Gradle支持Ant、Maven的构建操作Gradle提供了强大的依赖管理Gradle使用Groovy编写构建文件,构建文件的功能更加灵活- 使用领域对象模型来描述构建
- 支持多项目构建
- 提供简单易用的自定义任务、自定义插件
3.2 目录介绍
以目前最新的7.5.1版本为例,下载之后解压会发现如下目录:
bin:包含Gradle的命令docs:包含用户手册、DSL参考文档、API文档lib:包含Gradle核心,以及依赖的JAR包init.d:初始化脚本,以.gradle结尾,比如test.gradle,构建时会执行,该文件夹默认为空src:Gralde源码
3.3 基础使用
Gradle解压后会有bin文件夹,其中包含gradle和gradle.bat,根据系统的不同选择其中一个即可运行。
运行如果没有指定参数,会在当前目录下搜索build.gradle,如果想让其他文件作为构建文件,可以使用-b/--buildfile参数。
使用Gradle的关键就是编写构建文件,构建文件的主要作用就是定义构建项目的各种任务和属性。每个任务可以包含多个动作,Gradle每次可运行多个任务。
3.4 项目层次结构
一个典型的Gradle项目层次结构如下:
root:项目根目录,存放全部资源
--src:源文件+资源文件
----main:存放与项目相关的源文件和资源
------java:Java源文件
------resources:项目相关资源
----test:存放与测试相关的源文件和资源
------java:测试源文件
------resources:测试相关资源
--build:存放编译后的class文件,与src具有对应关系
--libs:存放第三方JAR包
--build.gradle:Gradle构建文件
如果使用gradle命令构建项目,项目根目录就会多出一个.gradle文件夹,存放的是Gradle构建信息,一般不需要手动修改。
3.5 构建文件
3.5.1 概要
Gradle构建文件本质上是一个Groovy源文件,该文件完全符合Groovy语法。Gradle采用领域对象模型的概念来组织构建文件,在整个构建文件中涉及如下API:
Project:代表项目,通常一份构建文件代表一个项目,Project包含大量属性和方法TaskContainer:任务容器,每个Project都会维护一个TaskContainer类型的tasks属性,Project和TaskContainer具有一一对应的关系Task:代表要执行的一个任务,允许指定它依赖的任务以及任务的类型,也可以通过configure()配置任务,还提供了doFirst、doLast方法来添加Action
3.5.2 构建文件结构
Gradle构建文件结构如下:
Task1:
--Action1
--Action2
--Action3Task2:
--Action1
--Action2
--Action3Task3:
--Action1
--Action2
--Action3
3.5.3 Task创建
Task创建有两种方式:
- 调用
Project的task() - 调用
TaskContainer的create()
无论哪一种方式都可以为Task指定如下属性:
dependsOn:指定该Task所依赖的其他Tasktype:指定该Task的类型- 通过传入的代码块参数配置
Task
一个简单的Task示例如下:
task hello1{println "配置的第一个Task"
}
然后通过gradle hello1可以看到输出如下:

3.5.4 Gradle构建过程
Gradle是一种声明式的构建工具,使用Gradle构建时,并不是直接按顺序执行build.gradle中的内容,构建过程可以分为两个阶段:
- 配置阶段:
Gradle读取build.gradle的全部内容来配置Project和Task - 执行阶段:按照依赖关系执行指定
Task
在创建Task时传入的代码用于配制该Task,因此上面的代码在配置阶段输出(Configure project),而不是在运行阶段输出。
3.5.5 Action添加
可以通过doFirst/doLast为Task添加Action,代码示例如下:
task hello2{println "配置的第二个Task"doLast{for(i in 0..<5){println i}}doFirst{def s = "test str"println "str is:$s"}
}
运行结果:

可以看到,Gradle构建过程分为配置和运行,配置阶段会配置整个Project和所有Task,而运行阶段会运行对应参数指定的Task。
3.5.6 通过TaskContainer创建Task
Project对象带有一个TaskContainer类型的tasks属性,可以在构建文件中通过它的create方法来创建task,示例如下:
tasks.create(name:'showTasks'){doLast{println 'tasks属性的类型: ${tasks.class}'tasks.each{ e->println e}}
}
通过gradle showTasks输出如下:

3.5.7 Task依赖以及类型
在创建Task的时候可以通过dependsOn属性指定该Task锁依赖的Task,同时也可以通过type指定Task的类型(如果不指定默认是DefaultTask类)。示例如下:
tasks.create(name:'task3',dependsOn:'hello2',type:Copy){from 'src.txt'into 'targetDir'
}
上面定义了一个叫task3的Task,依赖于hello2,类型为Copy(完成文件复制),from指定了复制的源文件,into指定了复制的目录位置(如果没有目录会新建)。
通过gradle tasks运行发现会首先运行hello2任务,同时会自动新建了into指定的文件夹,其中的内容是src.txt。
另一方面,使用Project的task方法同样可以指定type以及dependsOn属性,示例代码如下:
plugins {id 'java'
}task compile(type:JavaCompile){source=fileTree('src/main/java')classpath=sourceSets.main.compileClasspathdestinationDir=file('build/classes/main')options.fork=trueoptions.incremental=true
}task run(type:JavaExec,dependsOn:'compile'){classpath=sourceSets.main.runtimeClasspathmain='Main'
}
上面的代码首先应用了java插件,然后定义了compile和run任务,其中后者依赖于前者。compile任务类型为JavaCompile(详细属性说明可查看官方文档),其中:
source指定了源代码路径classpath指定类路径destinationDir指定了编译后的字节码文件保存位置options.fork表示是否编译之前创建一个新的进程,默认为falseoptions.incremental表示开启增量编译
而run的类型是JavaExec(文档说明此处),指定了类路径以及主类。
注意在运行之前先在当前与build.gradle同级的文件夹下创建src/main/java/Main.java,里面内容示例如下:
public class Main{public static void main(String[] args){System.out.println("Hello world");}
}
然后可以直接gradle run运行。
但是笔者在运行的时候报错如下:
> Task :compileJava FAILEDFAILURE: Build failed with an exception.* What went wrong:
Execution failed for task ':compileJava'.
> Could not find tools.jar.
笔者环境是m1 Mac+JDK8,解决方法是通过
/usr/libexec/java_home -V
查看JDK路径,一般会有两个,其中一个在lib下带有tools.jar,而另一个没有,只需要使用cp将tools.jar复制到另一个没有的lib下即可。
修复之后,任务正常运行,输出如下:

3.5.8 属性定义
Gradle允许为Project和Task指定或添加属性,也就是可以指定内置属性的属性值,也可以添加新的属性。
3.5.8.1 已有属性指定属性值
Project/Task本身具有内置属性,可以直接在构建文件制定属性值,示例:
version = 1.0
description = 'Project 描述'task showProps{description = 'Task 描述'doLast{println versionprintln descriptionprintln project.description}
}
输出如下:

在task外的就是为Project指定的属性,在task内的就是为该task指定的属性。
Project常用属性:
name:项目名字path:项目绝对路径description:项目描述信息buildDir:项目构建结果存放路径version:项目版本号
3.5.8.2 通过ext添加属性
Gradle中实现了ExtensionAware接口的API都可以通过ext添加属性,而Project和Task都实现了ExtensionAware,可以通过ext为其添加属性,示例:
ext.prop1='project prop1'
ext.prop2='project prop2'
ext{prop3='project prop3'prop4='project prop4'
}
task showExtProps{ext.prop1='task prop1'ext.prop2='task prop2'ext{prop3='task prop3'prop4='task prop4'}doLast{println prop1println prop2println prop3println prop4println project.prop1println project.prop2println project.prop3println project.prop4}
}
通过gradle showExtProps输出如下:

3.5.8.3 通过-P添加属性
执行gradle命令可以通过-P添加项目属性,示例:
task showCmdProps{doLast{println("系统显卡:${graphics}")println("系统显卡:${project.graphics}")}
}
如果直接执行gradle showCmdProps会报错,提示找不到该属性。
需要使用-P指定属性,执行gradle -P graphics=RTX4090Ti showCmdProps后,输出如下:

3.5.8.4 通过JVM参数添加属性
执行gradle时可以通过-D设置JVM参数从而添加项目属性,示例:
task showJVMProps{doLast{println("JVM参数添加的属性:${p1}")}
}
执行gradle -D org.gradle.project.p1=111 showJVMProps后,输出如下:

3.6 增量式构建
对于一些执行时间长的任务,如果每次执行该任务时没有发生改变,那么就没有必要执行该任务。因此Gradle引入了增量式构建,如果任务的执行和前一次执行比较没有改变,Gradle不会重复执行该任务。
Gradle通过任务的输入和输出去判断任务有没有改变。Task使用如下属性表示输入和输出:
inputs:代表任务输入,是一个TaskInputs类型的对象ouputs:代表任务输出,是一个TaskOutputs类型对象
两者都支持设置文件、文件集、目录、属性等,只要它们没有发生改变,Gradle就认为该任务的输入和输出没有改变。
示例代码:
task incrementalBuild{def source = fileTree('source')def dest = file('dist.txt')inputs.dir sourceoutputs.file destdoLast{dest.withPrintWriter{ writer->source.each{ s->writer.write(s.text)}}}
}
其中输入的部分指定了目录是source,输出部分指定了文件为dist.txt,只要两者没有发生改变,那么就认为任务没有发生改变,就不会执行任务。下面是两次执行任务的结果:

可以看到第二次并没有执行,只是更新到最新。
3.7 插件介绍
为了简化开发人员从头开始编写每一个Task以及属性,Gradle提供了插件机制。
开发插件其实很简单,无非就是在插件中预先定义大量的任务类型、任务、属性,然后开发人员只需要在build.gradle中使用apply plugin应用插件即可。
应用插件就相当于引入了该插件包含的所有任务类型、任务和属性,这样Gradle就能执行插件中预先定义好的任务。
比如之前的java插件,引入之后,可以通过gradle tasks --all查看引入的任务:

可以看到java插件分成几类添加了很多的任务,另外,该插件还定义了大量属性,比如:
sourceCompatibility用于指定编译源文件时使用的JDK版本archivesBaseName指定了打包生成的JAR包文件名
示例代码:
plugins{id 'java'id 'application'
}sourceSets{utilsmain{compileClasspath=compileClasspath+files(utils.output.classesDirs)}
}compileJava.dependsOn compileUtilsJava
mainClassName='Main'
run.classpath=sourceSets.main.runtimeClasspath+files(sourceSets.utils.output.classesDirs)
首先应用了java以及application插件,两个插件都定义了大量的属性以及任务。然后在其中的sourceSets添加了一个叫utils自定义的依赖,并在main中将utils编译生成的字节码添加到编译时的类路径中。接着通过配置compileJava的依赖任务compileUtilsJava(该任务是自动生成的,一个sourceSet对应了三个任务,比如此处的是utils,则会自动生成compileUtilsJava、processUtilsResources、utilsClasses三个任务)。
最后两行是application插件定义的属性,mainClassName指定运行的主类,run.classpath指定运行时的类路径。
测试项目代码结构如下:

代码:
package utils;
public class Utils{public static void print(){System.out.println("utils test");}
}import utils.Utils;
public class Main{public static void main(String[] args){Utils.print();}
}
运行gradle run可以看到输出如下:

3.8 依赖管理
通过Gradle配置依赖需要两步:
- 配置仓库
- 引入依赖
配置仓库在repositories中配置即可,例如:
repositories{// Maven默认中央仓库mavenCentral()// 通过URL自定义远程仓库或者本地仓库maven{// 定义远程仓库url "http://repo2.maven.org/maven2/"// 定义本地仓库url "/xxx/xx/xxx"}
}
然后可以通过配置组引入依赖。配置组的概念是由于项目编译时可能依赖一组JAR,而运行的时候又依赖另一组JAR,测试的时候可能又依赖另一组JAR,因此可以通过不同的组配置不同的依赖。
Gradle可以使用configurations来配置组,例如:
configurations{testConfig
}
定义配置组后就可以引入JAR包,Gradle使用dependencies来配置JAR包,配置方式与Maven相同,指定group、name、version即可。示例代码:
dependencies{testConfig 'org.apache.commons:commons-lang3:3.12.0'
}
另外,在引入之后,如果需要提供额外的配置,可以使用闭包,示例如下:
testConfig('org.apache.commons:commons-lang3:3.12.0'){// 额外配置
}
如果需要添加多个JAR包,可以使用数组的形式:
dependencies{testConfig 'org.apache.commons:commons-lang3:3.12.0','commons-io:commons-io:2.11.0'
}
定义好依赖后,可以在任务中使用该依赖,示例任务如下:
task showDependency{doLast{println configurations.testConfig.asPath}
}
执行任务之后就会输出依赖包在本地的路径,笔者输出是在Gradle的缓存目录下。
在实际开发中,通常不需要自己配置依赖组,应用java插件后,默认的依赖组有:
implementation:源代码依赖的组,最常用的一个依赖组compileOnly:源代码编译时才依赖的组runtimeOnly:源代码运行时才依赖的组testImplementation:测试代码依赖的组testCompileOnly:测试代码编译时依赖的组testRuntimeOnly:测试代码运行时依赖的组archives:打包时依赖的组
3.9 自定义任务
自定义任务就是一个实现Task接口的类,该接口定义了大量的抽象方法,因此一般自定义任务都会继承DefaultTask基类,自定义任务的累可以自定义多个方法,这些方法可作为Action使用。
自定义任务的Groovy可以直接定义在build.gradle中,或者定义在Groovy源文件中。示例:
class HelloWorldTask extends DefaultTask{@Internaldef message = '测试str'@TaskActiondef test(){println "test str: $message"}def info(){println "info: $message"}
}task hello(type:HelloWorldTask){doLast{info()}
}task hello1(type:HelloWorldTask){message = "测试 hello1"
}
上面定义了一个HelloWorldTask的任务类,自定义了一个message属性,且使用了@TaskAction修饰了test()方法作为Action。执行gradle hello hello1输出如下:

当需要定义大量的自定义任务时,直接写在build.gradle显然不是一个好办法,更好的办法是存放在buildSrc目录中。
buildSrc相当于另一个Gradle目录,该目录存放自定义任务的源代码。比如例子中的目录结构如下:
build.gradle
buildSrc
--src
----main
------groovy
--------com
----------company
------------TestTask.groovy
其中TestTask.groovy定义如下:
package com.companyimport org.gradle.api.*;
import org.gradle.api.tasks.*;class TestTask extends DefaultTask{@InternalFile file = new File('dist.txt')@TaskActiondef show(){println file.text}@TaskActiondef multiShow(){println "==========="println file.textprintln "==========="}
}
build.gradle如下:
task show(type:com.company.TestTask)task show1(type:com.company.TestTask){file = file("dist1.txt")
}
运行后会发现会输出文件的内容。
3.10 自定义插件
自定义插件其实就是实现一个Plugin<Project>接口的类,实现该接口要求必须实现apply()方法。插件的本质,就是为Project定义多个属性和任务,这样引入插件后即可直接使用这些属性和任务。示例:
class FirstPlugin implements Plugin<Project>{void apply(Project project){project.extensions.create("user",User)project.task('showName'){doLast{println '用户名:'+project.user.name}}project.tasks.create('showPass'){doLast{println '密码:'+project.user.pass}}}
}class User{String name='test username'String pass='test password'
}apply plugin:FirstPlugin
上面定义了一个叫FirstPlugin的插件,该插件重写了其中的apply()方法,通过project.extensions定义了一个扩展属性,同时定义了两个任务。
运行结果如下:

如果想独立定义插件,类似任务,需要放在buildSrc下。示例目录结构如下:
build.gradle
buildSrc
--build.gradle
--src
----main
------groovy
--------com
----------company
------------Item.groovy
------------ItemPlugin.groovy
根build.gradle如下:
plugins{id 'item-plugin'
}
item.name = 'tets name'
item.price = 12345
item.discount = 0.5
buildSrc下文件内容如下:
//build.gradle
plugins{id 'java-gradle-plugin'
}gradlePlugin{plugins{itemPlugin{id='item-plugin'implementationClass='com.company.ItemPlugin'}}
}//Item.groovy
class Item{String name = 'name'double price = 10double discount = 1.0
}//ItemPlugin.groovy
package com.companyimport org.gradle.api.*;class ItemPlugin implements Plugin<Project>{void apply(Project project){project.extensions.create("item",Item)project.task('showItem'){doLast{println '商品名:'+project.item.nameprintln '商品销售价:'+project.item.price*project.item.discount}}}
}
运行结果:

4 Android环境搭建
4.1 Android Studio安装
安装就略过了,直接到官网下载安装即可。
下载之后,准备好SDK、AVD等。
4.2 第一个程序
打开Android Studio选择创建应用:

接着设置包名并且选择位置即可:

然后等待依赖导入完成就可以直接运行了。
5 Android应用结构分析
5.1 Android项目结构分析
app目录代表一个模块,是一个典型的Gradle项目,目录树如下:
app
--build
--libs
--src
----androidTest
----main
------java
--------com/xxx/xxx/
------res
--------drawable
--------layout
--------mipmap-xxx
--------values
------AndroidManifest.xml
----test
--build.gradle
--gitignore
各部分含义如下:
build:存放的项目构建结果libs:存放第三方依赖库src:源代码和资源目录build.gradle:项目的构建文件androidTest:Android测试项目main:java目录保存Java源文件(如果是Kotlin源文件则存放在kotlin中)res:存放各种资源文件,比如layout存放布局文件,values存放各种XML格式的资源文件,比如字符串资源文件strings.xml、颜色资源文件colors.xml,drawable存放Drawable资源,比如drawable-ldpi等,与drawable对应的还有一个叫mipmap的目录,用于保存应用程序启动图标以及系统保留的Drawable资源AndroidManifest.xml:系统清单文件,用于控制应用的名称、图标、访问权限等整体属性
5.2 R.java
R.java位于app/build/outputs/apk/debug/app-debug.apk中:

打开apk中的dex文件(dex是为Dalvik设计的一种格式),并打开对应的包路径即可以看到R.java。
R.java是由AAPT工具根据应用中的资源文件自动生成的,规则如下:
- 每类资源对应于
R类的一个内部类,比如界面布局资源对应layout内部类,字符串资源对应string内部类 - 每个具体的资源都对应于内部类的一个
public static final int类型字段
5.3 AndroidManifest.xml
AndroidManifest.xml清单文件是每个Android项目所必须的,是整个Android应用的全局扫描文件,说明了该应用的名称、所使用的图标以及包含的组件等。
一般包含如下信息:
- 包名
- 包含的组件
- 兼容的最低版本
- 权限
6 基本组件介绍
6.1 Activity和View
Activity是Android应用中负责与用户交互的组件,View是所有UI控件、容器控件的基类,View组件需要放到容器中,或者使用Activity将它显示出来。显示可以通过Activity的setContentView方法。
6.2 Service
Service也代表一个单独的Android组件,通常位于后台运行,一般不需要与用户交互。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
6.3 BroadcastReceiver
BroadcastReceiver是Android应用中另一个重要的组件,代表广播消息接收器。类似于事件编程中的监听器,与普通监听器不同的是,普通事件监听器监听的是程序中的对象,BroadcastReceiver监听的是其他组件,相当于一个全局的事件监听器。
使用时,实现BroadcastReceiver子类即可,并重写onReceive即可,其他组件通过sendBroadcast/sendStickyBroadcast/setOrderedBroadcast发送广播消息。实现了之后可以通过Context.registReceiver()或在AndroidManifest.xml中注册。
6.4 ContentProvider
ContentProvider可用于为跨应用数据交换,实现时,需要实现如下方法:
insert():插入数据delete():删除数据update():更新数据query():查询数据
一般需要配合ContentResolver使用,一个应用使用ContentProvider暴露数据,另一个使用ContentResolver访问数据。
6.5 Intent和IntentFilter
Intent可启动应用中的另一个Activity,也可以启动一个Service,还可以发送广播消息触发BroadcastReceiver。也就是说,Activity、Service、BroadcastReceiver之间的通信都以Intent作为载体,Intent封装了当前组件需要启动或触发的目标组件信息。
Intent可以分为隐式以及显式Intent,显式Intent明确指定了需要启动或触发的组件类名,隐式Intent只是指定启动或触发的组件应满足怎么的条件。对于隐式Intent,需要依靠IntentFilter来声明自己所满足的条件。
7 签名
7.1 简介
Android使用包名作为唯一的标识,如果安装包名相同的应用,后面安装的会覆盖前面的应用。签名主要有两个作用:
- 确定发布者身份:由于可能存在包名相同的情况,使用签名可以避免相同包名的程序安装被替换
- 确保应用完整性:确保程序包中的文件不会被替换
7.2 使用Android Studio签名
根据菜单中的Build->Generate Signed Build/APK即可创建签名apk:

选择APK:

如果没有创建数字证书,可以选择Create new...:

随便输入即可:

完成后点击下一步,选择debug或release:

7.3 使用命令签名
7.3.1 创建key store
keytool -genkeypair -alias key -keyalg RSA -validity 400 -keystore key.jks

7.3.2 签名
使用apksigner签名:
apksigner sign --ks key.jks --ks-key-alias key --out sign.apk app-debug.apk
签完之后就会输出sign.apk。
8 出处
本文大部分摘自《疯狂Android讲义(第四版)》,作者李刚。
















