从手写最简单的Android程序到Android Studio项目模板的全面解析

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

先上对比图(左侧是我们手动构建的最简单的Android项目;右侧是Android Studio默认创建的项目模板工程):

contrast_hand_and_template

0 序言

进入正题前,先说个与手写程序(不借助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文件)

  • 3 在当前目录下运行javac A(执行class文件)

  • 4 就会看到Hello Java!被打印在控制台(终端)上
    hello_java

用记事本开发Java程序确实简单,三两句话就讲完了。Android程序有那么多文件(AndroidManifest.xml启动的Activitylayout文件icon图标strings.xmlstyles.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)

1 准备

前面丢了些悬念, 但我们还不能进入正题。先做或确保一些准备工作。
我们需要准备三样东西: Java SDKAndroid SDKGradle SDK
前两样是Android开发必备的环境, 作为Android开发者自然不用多说。(请确保配置了ANDROID_HOME)
第三样是项目自动化构建工具(gradle), 可以用于构建Android项目。

1.1 配置gradle

如果你是macOS用户, 只需执行一条命令即可配置好最新版本的gradle环境: brew install gradle; 如果你使用的是其他操作系统, 可以参看: https://gradle.org/install/
(确保配置了GRADLE_HOME)
配置完毕后运行gradle -v, 检查是否配置完成。
gradle-v

2 写一个最最简单的Android程序

开门见山, 先放目录结构。需要手动编写的就三个文件MainActivity.javaAndroidManifest.xmlbuild.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{
@Override
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
<?xml version="1.0" encoding="utf-8" ?>
<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
}

3 生成项目的构建文件

接下来才是真正见证奇迹的时刻。
demo(项目根目录)下执行gradle assemble, 如果你看到下图的执行结果说明运行成功了, 可以略过下文的错误说明, 请看执行完成gradle_assemble
如果你遇到如下错误, 则说明你未配置ANDROID_HOME环境变量。
without_andrid_home
解决方案有二:

  • 1) 配置ANDROID_HOME环境变量。下图是笔者电脑上的配置:
    android_home
  • 2) 在demo(项目根目录)下新建local.properties文件, 输入AndroidSDK所在路径:
    1
    sdk.dir=/Users/relish/Library/Android/sdk

下图是笔者电脑上的配置:
local_properties

执行完成

执行完成后目录结构如下:

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/apkdebugrelease文件下就可以看到生成的apk文件了。
安装apk文件后, 桌面图标如下(不同Android版本上显示的默认logo不同):
desktop_icon
运行可以看到如下画面:
mainactivity

也许看到这里的读者开始有些疑问了。为什么只要这么三个文件就够了?哪怕构建后也只多了两个文件夹?AndroidStudio生成的空项目里还有setting.gradle.idea文件夹、gradle文件夹、*.iml文件、gradlewgradlew.bat等, 这么多文件呢!

4 由简入繁

如果你觉得这篇文章就这么结束了, 那你就想得太简单了。
莫慌, 正片开始! 接下来我要讲的是, 这个项目如何一步一步转化为我们熟悉的AndroidStudio的默认创建项目。

4.1 Gradle Wrapper

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

gradlewgradlew.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版本。
  • 为什么要使用Wrapper?使用Wrapper有什么好处?

    因为使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。而且假设每个开发者电脑上未安装/配置gradle环境, 那么他/她仍能通过执行gradlew命令执行gradle相关任务。而且wrapper规定了使用的gradle版本, 在团队开发中, 执行gradlew命令运行的都是同一个版本的gradle。避免了每个团队成员电脑上配置的gradle版本不同而带来的执行结果的差异。

4.2 多工程配置

settings.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文件夹。

4.3 其他未介绍的文件

其实文章讲解到这里, 我们手动修改的项目已经和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

5 app主工程

文章讲解到这里, 我们手动修改的项目和AS项目模板的区别剩下app(主工程目录)目录下的文件内容、目录结构不同了。
contrast_hand_and_template

二者不同的地方都已经用黄框和蓝框标出来了。其中黄框标注的文件已经在前文介绍过它们的作用了。下面介绍一下蓝框里的文件/文件夹。

5.1 单元测试

androidTest文件夹和test文件夹分别是Android单元测试和Java单元测试相关的目录。AS模板项目所用的单元测试框架是Java单元测试框架junitAndroidJUnit、Android UI自动化测试框架espresso

由于篇幅原因,关于单元测试的用法就不在此文中详细描述,感兴趣的读者可以查阅文末的引用资料或自行搜索相关资料进行学习。

5.2 资源文件

所有的资源文件夹都有-v[api-level]的形式、-[各国语言缩写]国际化资源的形式。如:drawable-zh-ldpivalues-endrawable-v21

drawable

存放图片/图标文件以及样式相关的文件

文件夹 说明
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-v19drawable-v21分别有两个长得不同的图标文件, 如果运行的手机是Android5.1(API22)的, 那么它会加载drawable-v21下的ic_avatar.png;但如果仅在drawable-v19下放置了ic_avatar.pngdrawable-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 默认加载的布局文件
layout-land 横屏时加载的布局文件
layout-port 竖屏时加载的布局文件
布局文件加载顺序:
(根据屏幕状态而定)layout-landlayout-port->layout

mipmap

用法与drawable一致。区别在于: mipmap文件夹仅仅用于放置app的logo图标。

values

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>

<!-- from arrays.xml -->
<string-array name="sex">
<item></item>
<item></item>
</string-array>

<!-- from attrs.xml -->
<declare-styleable name="customView">
<attr name="customAttr" format="string" />
</declare-styleable>

<!-- from colors.xml -->
<color name="colorPrimary">#3F51B5</color>

<!-- from dimen.xml -->
<dimen name="title_text_size">16sp</dimen>

<!-- from strings.xml -->
<string name="app_name">demo</string>

<!-- from styles.xml -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- ... -->
</resources>

其他资源文件

这里还有一些常见资源文件/文件夹未提及, 将在下方表格中列出:

文件夹 说明
anim 动画文件
raw 媒体文件(音频、视频文件)
xml 其他的xml类型文件
menu 菜单文件

5.3 app/build.gradle

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自动化测试框架
}

implementation vs compile

Android Gradle plugin 3.0开始推荐使用implementationapi来替换原先的compile。理论上你可以把所有的compile替换成api。下面说说它们的区别:

命令 说明
compile 本module将会泄露其依赖的module的内容
api 同compile
implementation 本module不会通过自身的接口向外部暴露其依赖module的内容。推荐使用implementation来进行依赖(而不是api或compile),这会大大改善工程的构建时间

其他改动:

  • provided -> compileOnly
  • apk -> runtimeOnly
  • testCompile -> testImplementation // 编译测试用例时依赖, 不会打包到发布的产品中
  • androidTestCompile -> androidTestImplementation // 编译测试用例时依赖, 不会打包到发布的产品中

5.4 AndroidManifest.xml

AS项目模板的AndroidManifest.xml文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<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>

6 总结

本来只是想介绍一个不借助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

坚持原创技术分享,您的支持将鼓励我继续创作!
显示 Gitment 评论