转自王鑫 链接:http://relish.wang/posts/24586/ 感谢
先上对比图(左侧是我们手动构建的最简单的Android项目;右侧是Android Studio默认创建的项目模板工程):
进入正题前,先说个与手写程序(不借助IDE)类似的场景。我回想起以前初学Java时的场景—— 如何用记事本写一个HelloWorld。
1 先写个最简单的Java程序:
1 2 3 4 5 |
public class A{ public static void main(String[] args){ System.out.println("Hello Java!"); } } |
2 在当前目录下运行javac A.java
(编译A.java文件,生成A.class文件)
javac A
(执行class文件)Hello Java!
被打印在控制台(终端)上用记事本开发Java程序确实简单,三两句话就讲完了。Android程序有那么多文件(AndroidManifest.xml
、启动的Activity
、layout文件
、icon图标
、strings.xml
、styles.xml文件
),这可咋整啊?
要是你认同这句话, 说明你早就已经习惯于Android Studio为你生成的项目模板(以下简称”AS项目模板”), 而失去了作为一位Android开发者自我的判断——一个最简单的Android程序到底需要哪些文件。
AS项目模板(未在括号里标注的文件/文件夹都是gradle相关的, 下文会详细讲解):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
├── app(主工程) │ ├── app.iml(AS项目配置文件) │ ├── build(项目构建导出目录, 含apk) | ├── build.gradle │ ├── libs(空文件夹) │ ├── proguard-rules.pro(混淆配置) │ └── src(源码) ├── .gitignore(记录需要被git忽略的文件/文件夹) ├── build ├── build.gradle ├── demo.iml(AS项目配置文件) ├── gradle(文件夹) ├── gradle.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle |
我可以告诉你, 上面所说的文件里,其实只有AndroidManifest.xml是必须的(图形页面对于Android程序来说并不是必须的, 比如只运行在后台的Service)。但是为了本文章的展示效果, 还是保留Activity。(有Activity也并不意味着一定需要layout.xml)
前面丢了些悬念, 但我们还不能进入正题。先做或确保一些准备工作。
我们需要准备三样东西: Java SDK
、Android SDK
、Gradle SDK
。
前两样是Android开发必备的环境, 作为Android开发者自然不用多说。(请确保配置了ANDROID_HOME)
第三样是项目自动化构建工具(gradle), 可以用于构建Android项目。
如果你是macOS用户, 只需执行一条命令即可配置好最新版本的gradle环境: brew install gradle
;
如果你使用的是其他操作系统, 可以参看: https://gradle.org/install/
(确保配置了GRADLE_HOME)
配置完毕后运行gradle -v
, 检查是否配置完成。
开门见山, 先放目录结构。需要手动编写的就三个文件MainActivity.java、AndroidManifest.xml、build.gradle。
1 2 3 4 5 6 7 8 |
demo └─ src ├─ main │ ├─ java │ │ └─ wang.relish.demo │ │ └─ MainActivity.java │ └─ AndroidManifest.xml └─ build.gradle |
MainActivity.java
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package wang.relish.demo; import android.os.Bundle; import android.app.Activity; import android.widget.TextView; public class MainActivity extends Activity{ public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setText("Hello, beautiful world!"); setContentView(tv); } } |
AndroidManifest.xml
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="wang.relish.demo"> <applcation> <activity anroid:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> application> manifest> |
build.gradle
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.1.3' } } allprojects{ repositories{ jcenter() google() } } apply plugin: 'com.android.application' android { compileSdkVersion 27 } |
接下来才是真正见证奇迹的时刻。
在demo
(项目根目录)下执行gradle assemble
, 如果你看到下图的执行结果说明运行成功了, 可以略过下文的错误说明, 请看执行完成。
如果你遇到如下错误, 则说明你未配置ANDROID_HOME
环境变量。
解决方案有二:
local.properties
文件, 输入AndroidSDK所在路径:
1
|
sdk.dir=/Users/relish/Library/Android/sdk
|
执行完成后目录结构如下:
1 2 3 4 5 6 7 8 9 |
├── .gradle(gradle任务相关缓存) ├── build(执行assemble命令后生成的文件, apk文件也在这里面) ├── build.gradle └── src └── main ├── java │ └── wang.relish.demo │ └── MainActivity.java └── AndroidManifest.xml |
在/build/outputs/apk
的debug
和release
文件下就可以看到生成的apk文件了。
安装apk文件后, 桌面图标如下(不同Android版本上显示的默认logo不同):
运行可以看到如下画面:
也许看到这里的读者开始有些疑问了。为什么只要这么三个文件就够了?哪怕构建后也只多了两个文件夹?AndroidStudio生成的空项目里还有setting.gradle
、.idea
文件夹、gradle
文件夹、*.iml
文件、gradlew
、gradlew.bat
等, 这么多文件呢!
如果你觉得这篇文章就这么结束了, 那你就想得太简单了。
莫慌, 正片开始!
接下来我要讲的是, 这个项目如何一步一步转化为我们熟悉的AndroidStudio的默认创建项目。
Gradle Wrapper是对Gradle的一层包装, 便于团队开发过程中统一Gradle构建的版本。 我们在项目开发过程中, 用的都是Wrapper这种方式。所以前面用的gradle相关命令建议都改为gradlew的命令(如:gradle assemble
改为./gradlew assemble
)。
在demo
(项目根目录)下执行gradle wrapper
, 执行完成后目录结构下新增的文件/文件夹如下:
1 2 3 4 5 6 |
├─ gradle │ └─ wrapper │ ├─ gradle-wrapper.jar │ └─ gradle-wrapper.properties ├─ gradlew └─ gradlew.bat |
gradlew
和gradlew.bat
分别是Linux和Windows下的可执行脚本。gradle-wrapper.jar
是具体业务逻辑实现的jar包。gradlew最终是使用这个jar包来执行相关的Gradle操作。gradle-wrapper.properties
是配置文件, 用于配置使用哪个版本的Gradle等。
打开gradle-wrapper.properties
文件, 可以看到以下内容:
1 2 3 4 5 |
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists |
字段名 | 说明 |
---|---|
distributionBase | 下载的Gradle压缩包解压后储存的主目录 |
distributionPath | 相对于distributionBase的解压后的Gradle压缩包的路径 |
zipStoreBase | 同distributionBase, 只不过是存放zip压缩包的 |
zipStorePath | 同distributionPath, 只不过是存放zip压缩包的 |
distributionUrl | Gradle发型版压缩包的下载地址 |
这里我们基本只需关注distributionUrl
即可, 这个字段决定了你的gradle wrapper依赖哪个gradle版本。
因为使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。而且假设每个开发者电脑上未安装/配置gradle环境, 那么他/她仍能通过执行gradlew命令执行gradle相关任务。而且wrapper规定了使用的gradle版本, 在团队开发中, 执行gradlew命令运行的都是同一个版本的gradle。避免了每个团队成员电脑上配置的gradle版本不同而带来的执行结果的差异。
到这里为止, 先来看一下我们项目的目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
├─ .gradle(文件夹) ├─ build(文件夹, 可删) ├─ gradle(文件夹) ├─ gradlew ├─ gradlew.bat ├─ build.gradle ├─ local.properties(可选) └─ src └─ main ├─ java │ └─ wang.relish.demo │ └─ MainActivity.java └─ AndroidManifest.xml |
仔细观察发现, 少了settings.gradle
文件, 而且这个src
目录外面应该再包一层app
目录…即便这样app
里也少了一些其他的文件…莫慌, 我们一点点来分析。
settings.gradle
文件大多数的作用是为了配置子工程(moudle)。但由于我们这个”最简单的Android程序”是一个单工程的项目, 所以settings.gradle
并不是必须的。但是, 在实际开发过程中, 项目(Project)大多为多工程(module), 因此AS项目模板是会默认配置好settings.gradle
, 并且主工程也被包装成一个名为app
的子工程(moudle)。
settings.gradle
的内容也很简单:
1
|
include ':app'
|
如果有多个module则用逗号隔开(不过我们这次不需要):
1
|
include ':app', ':module1', ':module2'
|
在demo
(项目根目录)下, 新建app
文件夹,将demo
(项目根目录)下的src文件夹和build.gradle
移动/剪切到新建的app
目录下。
这时再次执行./gradlew assemble
, 已经可以生成apk文件了。但总觉得还是跟AS项目模板有些不同——根目录少了一个build.gradle文件。此时的工程目录:
1 2 3 4 5 6 7 8 9 |
├─ .gradle(文件夹) ├─ gradle(文件夹, 含wrapper配置) ├─ gradlew ├─ gradlew.bat ├─ local.properties(可选) ├─ settings.gradle(多工程配置) └─ app ├─ build.gradle(从根目录移动进来的) └─ src(源码文件夹) |
既然是多工程配置, 那么每个子工程(module)都需要的配置就可以做成项目(project)配置, 减少重复代码。
因此我们把./app/build.gradle
内的部分内容移动到根目录下的build.gradle
中。
./build.gradle内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
buildscript { // 一个在项目构建之前, 为项目进行前期准备和初始化相关配置依赖的地方 repositories { 配置需要依赖的gradle插件所在的仓库地址 jcenter() google() } dependencies { // 配置需要依赖的gradle插件 classpath 'com.android.tools.build:gradle:3.1.3' // Android Gradle 插件 } } allprojects{ repositories{ jcenter() google() } } |
./app/build.gradle内容:
1 2 3 4 |
apply plugin: 'com.android.application' android { compileSdkVersion 27 } |
可以看到根目录下的build.gradle已经和AS项目模板长得很像了, 区别在与下面这段代码:
1 2 3 |
task clean(type: Delete) { delete rootProject.buildDir } |
这里定义了一个名为clean的gradle任务(task),我们可以通过运行./gradlew clean
执行这个任务。它的执行结果就是删除主项目的build文件夹。
忘了说, 现在再执行./gradlew assemble
, build
文件夹会生成在app
目录下。再执行./gradlew clean
就会删除app
目录下的build
文件夹。
其实文章讲解到这里, 我们手动修改的项目已经和AS项目模板大致相同了。因为此文介绍的重点其实是gradle构建工具和一个最简单的Android项目需要那些文件。但鉴于这里还有一些文件未提及,笔者决定简单介绍一下它们。
文件 | 说明 |
---|---|
*.iml、.idea文件夹 | JetBrain家的IDEA系列IDE的项目配置文件(Android Studio是基于IntelliJ IDEA的), 可以删除,下次用Android Studio打开时会自动生成 |
gradle.properties | 可以放置gradle相关的全局常量声明和项目运行内存设置等 |
local.properties | 声明AndroidSDK和NDK所在路径 |
app/proguard-rules.pro | 代码混淆配置 |
app/libs | 存放jar/aar包 |
.gitignore、app/.gitignore | 记录需要被git忽略的文件/文件夹 |
.gitignore
文件通常需要填写的内容:
1 2 3 4 5 |
.gradle/ .idea/ build/ *.iml local.properties |
app/.gitignore文件通常需要填写的内容:
1 2 |
build/ *.iml |
文章讲解到这里, 我们手动修改的项目和AS项目模板的区别剩下app(主工程目录)目录下的文件内容、目录结构不同了。
二者不同的地方都已经用黄框和蓝框标出来了。其中黄框标注的文件已经在前文介绍过它们的作用了。下面介绍一下蓝框里的文件/文件夹。
androidTest
文件夹和test
文件夹分别是Android单元测试和Java单元测试相关的目录。AS模板项目所用的单元测试框架是Java单元测试框架junit、AndroidJUnit、Android UI自动化测试框架espresso。
由于篇幅原因,关于单元测试的用法就不在此文中详细描述,感兴趣的读者可以查阅文末的引用资料或自行搜索相关资料进行学习。
所有的资源文件夹都有-v[api-level]
的形式、-[各国语言缩写]
国际化资源的形式。如:drawable-zh-ldpi
、values-en
、drawable-v21
。
存放图片/图标文件以及样式相关的文件
文件夹 | 说明 |
---|---|
drawable | 放置selector、shape、vector文件 |
drawable-mdpi | 中分辨率图标(72*72, 320*480) |
drawable-hdpi | 高分辨率图标(尺寸标准:48*48, 对应手机分辨率:480*800) |
drawable-xhdpi | 超高分辨率图标(96*96, 720*1280) |
drawable-xxhdpi | 超超高分辨率图标(144*144, 1080*1920), 主流分辨率 |
drawable-xxxhdpi | 超超超高分辨率图标(192*192, 3840*2160) |
drawable-v19 | Android4.4(API19)及以上特别设置的样式/图标 |
drawable-v21 | Android5.0(API21)及以上特别设置的样式/图标 |
drawable-v24 | Android7.0(API24)及以上特别设置的样式/图标 |
drawable-v[API-version]: 此类文件夹下的同名图标会默认使用最高版本的。如: ic_avatar.png
分别在drawable-v19
和drawable-v21
分别有两个长得不同的图标文件, 如果运行的手机是Android5.1(API22)的, 那么它会加载drawable-v21
下的ic_avatar.png
;但如果仅在drawable-v19
下放置了ic_avatar.png
,drawable-v21
没有的话, 在这台手机上运行时就会加载drawable-v19
下的ic_avatar.png
;如果drawable-v[API-version]此类文件夹未放置ic_avatar.png
图标, 则会加载对应分辨率文件夹下的ic_avatar.png
; 要是各分辨率的文件夹里也没有ic_avatar.png
的话,就会加载drawable
文件夹下的ic_avatar.png
。
图标加载顺序:
drawable-v[对应的高API]
->drawble-v[低API]
->drawable-[对应高分辨率]dpi
->drawable-[低分辨率]dpi
->drawable
文件夹 | 说明 |
---|---|
layout | 默认加载的布局文件 |
layout-land | 横屏时加载的布局文件 |
layout-port | 竖屏时加载的布局文件 |
布局文件加载顺序:
(根据屏幕状态而定)layout-land
或layout-port
->layout
用法与drawable一致。区别在于: mipmap文件夹仅仅用于放置app的logo图标。
AS项目模板:
文件 | 说明 |
---|---|
colors.xml | 颜色值常量 |
strings.xml | 字符串常量 |
styles.xml | 样式常量 |
除此之外还可以有以下文件:
文件 | 说明 |
---|---|
ids.xml | id常量(int类型) |
arrays.xml | 数组常量 |
attrs.xml | 属性声明, 用于自定义View |
dimens.xml | 尺寸常量。长度(dp)、字体大小(sp)、像素值(px)等 |
… | … |
其实并不需要拘泥于这些文件名, 想取啥文件名都行, 甚至这些文件里的内容也可以全写在一个文件里(但推荐写法还是分开写, 各司其职):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<resources> <string-array name="sex"> <item>女item> <item>男item> string-array> <declare-styleable name="customView"> <attr name="customAttr" format="string" /> declare-styleable> <color name="colorPrimary">#3F51B5color> <dimen name="title_text_size">16spdimen> <string name="app_name">demostring> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimaryitem> <item name="colorPrimaryDark">@color/colorPrimaryDarkitem> <item name="colorAccent">@color/colorAccentitem> style> resources> |
这里还有一些常见资源文件/文件夹未提及, 将在下方表格中列出:
文件夹 | 说明 |
---|---|
anim | 动画文件 |
raw | 媒体文件(音频、视频文件) |
xml | 其他的xml类型文件 |
menu | 菜单文件 |
AS项目模板的app/build.gradle
文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
apply plugin: 'com.android.application' // 应用Android Gradle插件. (有些网友在介绍gradle的文章中把它描述为"声明是Android程序",纯属扯淡) android { // Android Gradle插件为Project对象添加的一个拓展 compileSdkVersion 26 // 编译版本 defaultConfig { applicationId "wang.relish.demo" // 应用包名 minSdkVersion 14 // 此app允许运行的最低的API版本的手机 targetSdkVersion 26 // 允许使用新特性的最高版本。也就是说API27及以上的手机也能安装这个app, 但仅仅有API26及以下的新特性 versionCode 1 // app版本编号 versionName "1.0" // app版本号。其实就是个字符串叫啥都可以。业内统一使用xxx.xxx.xxx的形式。 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // 单元测试: AndroidJUnit } buildTypes { release { // 是否进行混淆 minifyEnabled false // 混淆文件的位置 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { // 声明依赖 implementation fileTree(dir: 'libs', include: ['*.jar']) // 位于app/libs/下的jar包依赖 implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' testImplementation 'junit:junit:4.12' // 单元测试:junit androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' // AndroidUI自动化测试框架 } |
从Android Gradle plugin 3.0
开始推荐使用implementation
和api
来替换原先的compile
。理论上你可以把所有的compile
替换成api
。下面说说它们的区别:
命令 | 说明 |
---|---|
compile | 本module将会泄露其依赖的module的内容 |
api | 同compile |
implementation | 本module不会通过自身的接口向外部暴露其依赖module的内容。推荐使用implementation来进行依赖(而不是api或compile),这会大大改善工程的构建时间 |
其他改动:
AS项目模板的AndroidManifest.xml
文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="wang.relish.demo"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" // app桌面logo。不设置的话是Android系统自带的图标(不同Android版本长得不同)。 android:label="@string/app_name" // App名。不设置的话, 默认为启动的Activity的全类名 android:roundIcon="@mipmap/ic_launcher_round" // Android O 新特性, 原型图标 android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> application> manifest> |
本来只是想介绍一个不借助IDE可以手写出来的最简单的Android程序的教程。讲着讲着就想把Android Studio的模板工程目录结构、文件作用都说了一遍, 不知不觉写了这么多。也把我们这个最简单的Android程序一步步拓展成了Android Studio的模板工程。希望各位读者在体会到Android Studio为我们广大Android开发者的开发工作带来莫大的帮助的同时, 明白Android Studio为我们都做了哪些事。
Wrapper (gradlew):
https://www.zybuluo.com/xtccc/note/275168
Gradle官方文档:
https://docs.gradle.org/current/dsl/index.html
Android单元测试-如何开始?
https://www.jianshu.com/p/bc99678b1d6e
自适应图标(Adaptive icons):
https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive
迁移到插件3.0.0:
https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#new_configurations