项目结构总览
本文基于前一篇博文【开始你的 Hello World 】详述 Android 的项目结构,Android Studio 版本为 4.0 。
回到 Android Studio 我们的 HelloWorld 项目,默认会看到如下项目结构:
新建项目默认使用的是 Android 模式,适合快速开发,但不利于我们了解它的真正的项目结构,我们可以通过 “Android 下拉”按钮切换项目结构模式,我们将其切换成 Project 如下:
- .gradle 存放 Android Studio 自动生成的文件
- .idea 存放 Android Studio 自动生成的文件
- app 存放代码、资源(图片等)等内容,后边补充说明
- build 存放编译时自动生成的文件
- gradle 包含 gradle wrapper 配置文件,使用 gradle wrapper 的方式不需要提前将 gradle 下载好,二是自动根据本地的缓存情况决定是否需要联网下载 gradle。Android Studio 默认就是启用 gradle wrapper 的,如果需要改成离线模式,可点击 IDE 导航栏 ->File->Settings->build,Execution,Deployment->Gradle,进行配置更改
- .gitignore 指定需要忽略在 Git 版本控制之外的目录或文件
- build.gradle 项目全局的 gradle 构建脚本,通常不用修改,后边补充说明
- gradle.properties 项目全局的 gradle 配置文件,这个文件的配置属性会影响整个项目的 gradle 编译脚本
- gradlew 用于Linux 或 Mac 系统命令行界面执行 gradle 命令
- gradle.bat 用于 Windows 系统命令行界面执行 gradle 命令
- local.properties 指定本机的 Android SDK 路径,一般自动生成;如果 Android SDK 路径发生变化,将此文件中的路径更改即可
- settings.gradle 用于指定项目中所引入的模块;HelloWorld 项目只包含 app 一个模块,通常新增模块这个文件也会自动修改
详解 app 模块
见过前面简单的介绍,我们应该可以知道,app 文件夹是我们项目开发最重点关注的对象,下面我们来详细了解一下,展开它如下:
- build 与外层的 build 类似,存放自动生成的文件,无需过多关心
- libs 存放第三方项目 jar 包,这个目录下的 jar 包会自动添加到项目的构建路径中
- src/androidTest 存放 Android Test 测试用例,用于项目自动化测试
- src/main/java 存放项目的 Java 代码(Kotlin 代码也是在这里),默认已经生成 MainActivity
- src/main/res 存放项目资源文件:图片、布局、字符串等资源都应该放在这里。这个目录分为多个子文件夹,drawable 存放图片,layout 存放布局,values 存放字符串
- src/main/AndroidManifest.xml 整个 Android 项目的配置文件,在程序定义的四大组件都要在这个文件注册,并且还可以在这个文件给应用程序添加权限说明,如果你以后经常参与 Android 开发,你会更多的了解它的
- test/java 存放 Unit Test 测试用例,也是用于项目自动化测试
- .gitignore 与外层 .gitignore 类似用于在 app 模块指定目录或文件排除在版本控制之外
- build.gradle app 模块的 gradle 构建脚本,用于指定项目构建相关的配置
10.proguard-rules.pro 用于指定混淆规则,如果开发的项目打包成的安卓文件不希望被别人反编译破解,可以进行代码混淆,从而杜绝绝大部分破解手段
探究 HelloWorld 的运行过程
这里,我们需要从 AndroidManifest.xml 文件开始看,打开如下:
我们看到文件中的 <activity android:name=".MainActivity">
节点,表示对 MainActivity 进行注册,没有注册的 Activity 是不能用的哦,然后看到 <intent-filter>
里的 <action android:name="android.intent.action.MAIN" />
和 <category android:name="android.intent.category.LAUNCHER" />
表示 MainActivity 就是项目的主 Activity,点击手机上的启动图标,首先启动的就是这个 Activity。
那么,MainActivity 具体是什么呢,怎么关联的呢,我们看到上图中 manifest 节点有个属性 package=“com.example.helloworld” 和 <activity android:name=".MainActivity">
的值组合起来是 com.example.helloworld.MainActivity,这个其实就对应到了我们默认生成的 MainActivity 文件,打开它看到如下:
我这里因为使用 kotlin 进行编码,因此文件是 .kt 的扩展名,MainActivity.kt 文件中 package com.example.helloworld 和 class MainActivity
即相对应表示对应关系。然后,可以看到 MainActivity 继承了 AppCompatActivity,AppCompatActivity 是 AndroidX 提供的一个向下兼容的 Activity,可以使得 Activity 在不同的版本系统中功能保持一致性。Activity 是 Android 的一个基类,项目中所定义的 Activity 都必须继承它或者它的子类(AppCompatActivity 也继承了它),才能拥有 Activity 的特性。再然后,我们可以看到 MainActivity 定义了一个 onCreate() 方法,这个是每一个 Activity 被创建时都必须执行的方法,然后由于 Android 程序推荐视图与逻辑分离,所以使用了 setContentView(R.layout.activity_main)
代码来引入布局文件,我们可以在前面所说的 src/main/res/layout 目录中找到名为“activity_main”的 XML 文件,这个就是 MainActivity 所对应引入的布局文件了,到这里小伙伴们不要纠结为什么要这样些,这些文件为什么要分那么多地方呢,这些约定式编码布局以后你遇到大型项目开发的时候就能体会其中的奥妙了,我们打开 activity_main.xml 文件如下:
打开后默认是 Design 视图,我们可以通过图右上角的按钮进行视图切换,我们点击 Code,切换到 Code 视图如下:
我们可以在 Design 视图看到“Hello World!”或者 Code 视图中看到 android:text="Hello World!"
,这样,你应该基本上知道初始化项目的 Hello World 如何而来了吧。
理解项目中的资源:res
我们再来看一下 res 目录,打开默认有如下子目录:
这么多目录干什么的呢?主要分为四类:
- drawable 开头的目录存放图片
- layout 开头的目录存放布局
- mipmap 开头的目录存放应用图标
- values 开头的目录存放字符串、样式、颜色等配置
mipmap 有那么多后缀的文件是用来兼容各种设备的,同样我们 drawable 也可以这样,手工创建 drawable-hdpi、drawable-xhdpi、drawable-xxhdpi,成行会根据当前设备情况咨询加载不同目录下的图片,不过如果你只有一份图片,就放 drawable-xxhdpi 下好了,它是最主流社保分辨率目录。
那我们如何使用这些资源呢?我们看到 res/values/strings.xml 文件内容如下:
<resources>
<string name="app_name">HelloWorld</string>
</resources>
里边定义了应用程序的名称,我们可以通过以下两种方式使用它:
- 在代码中使用
R.string.app_name
来引用该字符串 - 在 XML 文件中使用
@string/app_name
来引用该字符串
我们再来看 AndroidManifest.xml 文件,看到如下代码:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
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>
其中 android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
代码 android:icon 用来指定应用图标,android:label 迎来指定应用名称,就这么简单了。
分析 build.gradle 文件
如果你接触编程很久了,那么可能都用过基于 XML 配置的 Ant 或 Maven 项目构建工具,不过 Android Studio 是使用基于 Groovy 的领域特定语言(DSL)来进行项目配置的 Gradle 来构建项目的。
Android Studio 有两个 build.gradle 配置文件如下:
- 外层 build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- buildscript 包含执行脚本所需的依赖
- allprojects 项目本身所需的依赖
- task clean(type: Delete)
外层的 build.gradle 配置文件就相当于 Maven 的最外层 pom.xml 文件,如果你想配置所有模块或子项目都需要的配置,那么你应该在这里修改。
repositories 大括号闭包声明了 google() 和 jcenter() 这两行配置,其中 google() 表示依赖 google 仓库的扩展依赖,jcenter() 表示第三方仓库的扩展依赖。
dependencies 大括号闭包使用 classpath 声明了两个依赖:Gradle 插件和 Kotlin 插件。Kotlin 还可以编译 Java、C# 等其他语言,在这里使用 com.android.tools.build:gradle:4.0.1 声明 Gradle 插件的版本,这个版本通常是与 Android Studio 对应的。如果你选择使用 Java 开发的话,Kotlin 这个插件是可以不用声明的。
- app 目录下 build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.helloworld"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
apply plugin:
- 第一行的值一般有两种 ‘com.android.application’ 或者 ‘com.android.library’,前者表示一个应用程序模块,后者表示一个库模块,库模块是要依附于应用程序模块来运行的。
- 第二行的值表示依赖 Kotlin 的安卓开发插件,如果是使用 Kotlin 来开发程序,那么它是必须的
- 第三行的值表示依赖了 Kotlin 的一些好用的扩展功能
android 闭包
- compileSdkVersion 30 表示使用 Android 10.0 进行编译
- buildToolsVersion “30.0.2” 指定项目构建工具的版本
- defaultConfig 嵌套闭包,
applicationId "com.example.helloworld"
是每一个应用的唯一标识,默认是创建项目指定的包名,如果你想改则就在这里改。minSdkVersion 21
指定项目最低兼容的 Android 版本,21 表示最低兼容到 Android 5.0。targetSdkVersion 30
表示你已经在指定的目标版本上做了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。比如 Android 6.0 引入了运行时权限的功能,而你将 targetSdkVersion 指定成 22,那么 6.0 引入的运行时权限就不会在你的 APP 中启用。versionCode 1
用于指定项目的版本号,versionName "1.0"
用于指定项目的版本名。testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
用于在当前项目启用 JUnit 测试,一般来说我们都需要为项目编写测试用例,来保证功能的正确性和稳定性。 - buildTypes 嵌套闭包,用于指定生成安装文件的相关配置,一般只有两个闭包:debug 和 release。debug 闭包用于指定生成测试版安装文件的配置,release 闭包用于指定生产正式版安装文件的配置,debug 是可以忽略不谢的。
minifyEnabled false
用于指定是否启用代码混淆,true 表示混淆,false 表示不混淆,proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro
用于指定混淆时使用的规则文件,proguard-android-optimize.txt 指在/tools/proguard 目录下的项目通用混淆规则,proguard-rules.pro 指当前项目根目录下的当前项目特有的混淆规则。
dependencies 闭包
这个闭包可能是我们最需要常用的地方了,它可以指定当前 app 模块的所有依赖,包括本地依赖、库依赖和远程依赖。本地依赖指可以对本地的 jar 或目录添加依赖关系,库依赖指可以对项目中的库模块添加依赖关系,远程依赖则指可以对 jcenter 上的开源项目添加依赖关系。
implementation fileTree(dir: "libs", include: ["*.jar"])
是一个本地依赖,指将 libs 下的所有 .jar 后缀文件都添加到项目的构建路径中。implementation 'androidx.appcompat:appcompat:1.1.0'
则是一个远程依赖,androidx.appcompat 是域名部分,appcompat 是工程名部分,1.1.0 是版本号。库依赖的声明方式是implementation project(': util')
。testImplementation 'junit:junit:4.12'
用于声明 JUnit 测试依赖,androidTestImplementation 'androidx.test.ext:junit:1.1.1' 和 androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
用于 Android 测试依赖。
日志工具 Log
最开始学习 Java 的时候,我非常喜欢用 System.out.println()
,因为他实在太方便了,但后来发现它没有日志开关,没有日志级别,更没有日志标签,所以我后来学会了使用 log4j,logback 等,Android 所提供的 Log 类也非常简单实用,我们一定要掌握好它。
Android 的日志工具类是 android.util.Log,它提供了 5 个方法来打印日志,如下:
- Log.v()
用于打印最详细的日志信息,对应 verbose - Log.d()
用于打印调试的日志信息,对应 debug - Log.i()
用于打印一些比较重要的日志信息,对应 info - Log.w()
用于打印一些警告信心,提示程序风险,最好处理一下,对应 warn - Log.e()
用于打印异常信息,提示程序异常,需尽快处理,对应 error
我们在 MainActivity.kt 文件中添加一行代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(MainActivity::class.java.name, "HelloWorld MainActivity Started");
}
}
Log.d() 方法的第一个参数 tag 使用 MainActivity::class.java.name
,表示获取 MainActivity 的类名,第二个参数 msg 表示需要打印的日志,我们重启一下 HelloWorld。
然后,我们可以打开左下角的 Logcat 窗口,然后可以找到如上图红色框中内容,这就是我们自己定义的需要打印的日志信息。Logcat 第一行的工具栏,第一个下拉框表示当前运行的模拟器,第二个表示可以选择运行的程序实例,第三个表示可以过滤日志显示级别,第四个表示可以搜索日志内容,第五个表示可以设置自定义过滤器(还可以设置是否启用正则匹配)。