Android打包相关概念
|由于只有半调子的客户端开发经验,导致遇见一些环境问题时都需要使用搜索大法挨个试,本文决定整理一下Android 打包过程中的一些概念和流程,方便后续定位问题。
<!--more-->
1. Java
1.1. 编译型语言和解释型语言
参考:编译型语言和解释型语言的区别 使用高级编程语言编写的编码,主要是给程序员读的,计算机只认识0和1组成的二进制指令。
源码想要被执行,就需要先转换成二进制指令,对于这个步骤,不同的编程语言有不同的实现
- 有的编程语言要求必须提前将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序(比如大家都熟悉的
.exe
文件),这种语言称为编译型语言(常见的有C
、GoLang
等),将源码转成可执行程序的工具称为编译器 - 有的编程语言可以一边执行一边转换,需要哪些源代码就转换哪些源代码,不会生成可执行程序,这类语言被称为解释型语言(常见的有
JavaScript
、Python
、PHP
等),使用的工具称为解释器
从运行的过程可以看出,由于可执行程序在运行时无需再翻译源码,相当于“一次编译、终生运行”,相较于解释型语言每次运行都要解析而言,编译型语言的运行效率理论上应该会更高,但由于不同平台对应的CPU可识别的二进制指令存在差异,需要为不同的平台生成不同的可执行文件。
Java
、C#
等语言采用了折中的方案:先将源代码转成字节码文件,再将该文件放在可跨平台的虚拟机中平行,各个平台只需要安装对应的虚拟机就行。
解释型语言为什么不存在跨平台的问题?因为解释型语言无法脱离解释器单独开发和运行,因此安装对应语言的开发环境时会安装对应的解释器,而解释器会由语言官方为不同的平台编写进行兼容。因此解释型语言的可移植性就非常强。
从这个角度看,Java虚拟机也可以看做是平台特定的解释器。
1.2. Java SE、 Java EE 、Java ME是什么?
- Java SE(Java Platform,Standard Edition),应该先说这个,因为这个是标准版本。
- Java EE (Java Platform,Enterprise Edition),java 的企业版本
- Java ME(Java Platform,Micro Edition),java的微型版本。
1.3. Java、JVM、JDK、JRE分别是什么?
Java是编程语言
JVM(Java Virtual Machine,Java虚拟机),是整个Java实现跨平台的最核心的部分,负责解释执行字节码文件,是可运行Java字节码文件的虚拟计算机
JRE(Java Runtime Environment, Java运行环境),顾名思义是java运行时环境,也就是Java程序运行时所依赖的环境,包含了JVM,java基础类库,主要是给想运行Java程序的人使用的
JDK(Java Development Kit,Java开发工具包),顾名思义是java开发工具包,使用Java开发Java程序所需的工具包,主要是给程序员使用的,JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具
1.4. Java8和Java1.8 命名是什么?
是一样的,最开始用1.x
命名,在2004年JavaOne会议后版本数提升为5.0
,之后沿用x.0
命名。
而JDK则在 Java1.0 到 Java9 对应每一个版本号 :JDK1.0、JDK1.2 ... JDK1.8、JDK1.9,在Java10以后JDK对应名称为:JDk10、JDK11、JDK12。
JDK是个非常核心的基础设施,除了安全漏洞,基本上是不会再去动生产环境JDK了,Java8是目前最主流的版本。
1.5. Java运行流程
下面是一段Java代码从源码到运行时经历的步骤
Java源文件 *.java
--编译--> Java字节码文件 *.class
--加载--> JVM --解释--> 机器指令执行
- 编译:通过语法分析、语义分析、注解处理 最后才生成会class文件
- 加载:这个环节又分为了三个部分
- 装载:将class文件装载到JVM中
- 连接:校验class信息、分配内存空间及赋默认值
- 初始化: 变量初始化
- 解释:把字节码转换成操作系统可识别的执行指令
- 执行:调用系统的硬件执行最终的程序指令
1.6. 编译
编译就是源码文件*.java
到字节码文件*.class
的过程,将代码编译成字节码,JVM虚拟机才能识别
源代码
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
执行javac Main.java
文件,得到Main.class
文件,即编译后的文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.company;
public class Main {
public Main() {
}
public static void main(String[] var0) {
System.out.println("Hello World!");
}
}
1.7. 打包
在字节码文件中,JVM如果要加载一个类,它需要知道从哪儿去找到这个类,这是通过classpath
来实现的。
classpath
跟系统环境变量的作用非常相似,本质上就是一组目录的集合,比如classpath
是
.;/workspace/bin;/usr/bin/;
当JVM加载com.company.Main
这个类的时候,会依次查找,当找到目标文件后,JVM就会停止查询,不再往后搜索
当前目录/com/company/Main.class
/workspace/bin/com/company/Main.class
/usr/bin/com/company/Main.class
一个项目可能会有多个class文件,分散在各个目录下,将这堆文件全丢给JVM显然不太合适,因此需要归档。
把所有文件合并在一起,首先想到的是zip
压缩文件,在Java中对应的压缩文件被称为jar
(JavaTM Archive file)。
jar就是将一个META-INF/MANIFEST.MF
清单文件和一堆编译后的class文件合并起来,压缩在一起形成的产物。
下面简单梳理一下通过IDEA打jar包的流程
路径 File -> Project Structure -> Artifacts进行配置
配置完成之后,点build
然后在项目 out/artifacts目录下就可以看看见对应的jar包了
一个jar文件可以用于
- 用于发布和使用类库
- 作为应用程序和扩展的构建单元
- 作为组件、applet 或者插件程序的部署单位
- 用于打包与组件相关联的辅助资源
1.8. 外部依赖
大部分项目都或多或少会依赖一些外部模块,怎么在项目中使用外部jar包呢?
第一种方式是直接下载已经构建好的jar包,然后将这个jar包的位置添加进classpath
,参考:向IntelliJ IDEA创建的项目导入Jar包的两种方式。
这种方式跟Web前端开发中,下载一份js
文件,然后通过script标签引入如出一辙,比较原始,手动管理外部依赖也比较麻烦。
第二种就是使用maven
,maven是专门为Java项目打造的管理和构建工具
1.9. maven
不管是javac
编译成字节码,还是jar
打包成jar包,对于项目而言,这些都是比较机械和重复的工作,因此需要构建工具来解决这些工作。
linux上面一个叫make的工具,可以通过Makefile
来执行工程的构建。java由于是跨平台的,就写了一个叫ant
的工具,用来定义构建任务,最后通过一个命令执行各个任务,完成项目构建
ant的缺点在于无法管理项目的第三方依赖,打包时需要开发者自己手动去把正确的版本拷到lib下面。因此就出现了maven
。
maven提出了仓库的概念,所有依赖包都放在仓库里面,项目里面只需要声明依赖什么版本的包,在构建时就会自动将外部包打进来。
一个maven项目的标准结构如下
a-maven-project
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
└── target
pom.xml
是项目描述文件,类似于前端项目中的package.json
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.copmpany</groupId>
<artifactId>javademo</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
...
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.21</version>
</dependency>
</dependencies>
</project>
需要理解的是maven下载和加载依赖的一些流程
maven通过jar包坐标替代jar包本身,这样就不用将所有外部依赖一起打包了,极大减少了项目构建的jar包的体积
项目初始化时,声明依赖的第三方的jar包都会下载到本地仓库
下面是maven几个仓库的概念,加载顺序为
- 本地缓存:当运行项目的时候,maven会自动根据配置文件查找本地仓库,再从本地仓库中调用jar包使用。
- 远程仓库:当本地仓库中没有项目所需要的jar包时,那么maven会继续查找远程仓库,一般远程仓库指的是公司搭建的私有服务器,也叫私服;
- 中央仓库:就是maven官方仓库,地址传送门。
除了管理依赖,maven还负责项目的编译、打包等构建流程,非常方便
2. Android
2.1. Android、Android应用程序、Android SDK、Android NDK
- Android是一个基于Linux的操作系统
- Android应用程序是用Java语言编写的程序,编译后的字节码、其他数据和资源文件等,通过appt工具绑定在一起组成的
apk
文件,也称为Android包 - Android SDK (Android 开发工具包) 是专门为 Android 开发的基于 Java 的程序库,当然现在也有kotlin版本的Android SDK,此外还包括构建、调试、虚拟机等
- Android NDK(Native Development Kit),是一套实现能够在 Android 应用中使用 C 和 C++ 代码的工具,深度操作Android 系统,在应用层面一般无需用到
Android开发环境需要JDK、Android SDK、Android Studio,而Android Studio构建系统基于Gradle。
2.2. Android studio中project和module
在IntelliJ IDEA中Project是最顶级的结构单元,一个Project是由一个或者多个Module组成。一些主流大型项目结构基本上都是由多个Module的结构组成。
Android studio中,
- 一个Project代表一个完整的APP,
- Module表示APP中的一些依赖库或独立开发的模块。
使用Android Studio初始化的Android项目中,默认包含一个app
的module。
module又可以分为下面几种类型,主要的区别是生成内容不同
app module
生成 apk 程序文件java module
生成 jar 文件library module
生成 aar 文件,aar 除了能携带编译好的程序以外,还能携带资源文件
2.3. minSdk、compileSdk和targetSdk
在Android配置文件中可以看到的几个关于Android SDK版本
- minSdkVersion 代表着最低版本,在编译的时候兼容到该参数指定最低版本api。
- compileSdkVersion代表着编译的时候,会采用该api的规范进行代码检查和警告,但是并不会编译进apk中。
- targetSdkVersion代表着目标版本,在编译的时候会将该版本的api编译进apk中,通过调节该版本可以让App适应更广泛的手机系统版本,
各版本号的大小关系就是:compileSdkVersion>=targetSdkVersion>=minSdkVersion
2.4. Android打包流程
参考:
- 配置build,先看看官方文档
- Android Apk 编译打包流程,了解一下~
下面是文档里面给出的打包流程
字节码文件可以在JVM中运行,但如果要在 Android 运行时环境中执行还需要特殊的处理,那就是 dex 处理,它会对 .class 文件进行翻译、重构、解释、压缩等操作,得到dex
Dalvik 可执行文件,其中包括在 Android 设备上运行的字节码。
整个打包还有很多细节,下面是另外一张流传比较广的apk打包流程图
2.5. gradle
参考
maven已经足够优秀了,但由于xml语法、以及约定大于配置导致的不灵活性,导致无法实现某些定制功能,因此就出现了gradle。
gradle继承了maven仓库、依赖等优秀思想,通过groovy语法和task自定义任务,能够更灵活地完成构建任务。
Android Studio使用gradle来自动执行和管理构建流程。
gradle中的一些概念
- gradle,提供核心构建流程,但不提供具体构建逻辑
- gradle plugin,是运行在gradle机制上的一些具体构建逻辑,比如Android构建流程就是通过
Android Gradle Plugin
实现的 - gradle Daemon,用于提升速度的后台进程
- gradle wrapper,对gradle的包装,增加了自动下载安装 Gradle 环境的能力
gradle
:使用系统环境变量定义的 Gradle 环境进行构建;gradlew
:使用 Gradle Wrapper 执行构建。
为什么要提供一个gradle wrapper
自动安装gradle环境呢?因为gradle的迭代频率比较高,向后兼容性也比较差,因此,提供一个自动安装项目所需的gradle环境是很有必要的。
gradle构建流程,主要分为下面三个步骤
- 初始化阶段
- 执行 Init 脚本,有多种设置初始化脚本的方式,如
gradle —init-script <file>
、USER_HOME/.gradle/init.gradle
等,可以用来设置全局属性、日志打印等 - 实例化settings接口,主要是解析并执行
settings.gradle
- 实例化 Project 接口实例,解析
include
声明的模块,并为每个模块build.gradle
文件实例化 Project 接口实例
- 执行 Init 脚本,有多种设置初始化脚本的方式,如
- 配置阶段,执行
build.gradle
中的构建逻辑,完成project的配置- 下载插件和依赖
- 执行脚本代码
- 构造Task DAG,根据 Task 的依赖关系构造一个有向无环图,以便在执行阶段按照依赖关系执行 Task
- 执行阶段,按照Task DAG定义的依赖关系执行Task
2.6. 签名
参考:为应用签名
Android系统要求每一个应用都必须经过数字签名才能被安装,这里暂时先不扩展
3. 小结
本文整理了
- Java中的一些概念如Java版本、JRE、JVM等,然后介绍了Java运行流程和打包原理
- Android中的一些概念如Android SDK、NDK,然后介绍了Android构建流程,以及使用的gradle构建工具
至此,对于Java打包和Android打包都有了一些了解,当然本文还有很多细节待完善,如果文章内容存在错误,欢迎指正。