1、 前言
打开第一个 Android 程序, HelloWorld 项目,会看到如下图所示的项目结构:
任何一个新建的项目都会默认使用 Android 模式的项目结构,但这并不是项目真实的目录结构,而是被 Android Studio 转换过的。这种项目结构简洁明了,适合进行快速开发,但是对于新手来说可能不易于理解。
点击图中最上方的 Android 区域可以切换项目结构模式,如下图所示。
这里我们将项目结构模式切换成 Project 模式,这就是项目真实的目录结构了,如下图所示。
一开始看到这么多陌生的东西,一定会有点头晕吧。别担心,现在就对图中的内容进行讲解,之后再看这张图就不会感到那么吃力了。
2、『Project 模式』目录介绍
✎ .gradle 和 .idea
这两个目录下放置的都是 Android Studio 自动生成的一些文件,无须关心,也不要去手动编辑。
✎ app 目录
项目中的代码、资源等内容都是放置在这个目录下的,后面的开发工作也基本是在这个目录下进行的,待会儿还会对这个目录单独展开讲解。
✎ gradle 目录
这个目录下包含了 gradle wrapper 的配置文件,使用 gradle wrapper 的方式不需要提前将 gradle 下载好,而是会自动根据本地的缓存情况决定是否需要联网下载 gradle。
Android Studio 默认就是启用 gradle wrapper 方式的,如果需要更改成离线模式,可以点击 Android Studio 导航栏→File→Settings→Build, Execution, Deployment→Gradle,进行配置更改。
✎ .gitignore
这个文件是用来将指定的目录或文件排除在版本控制之外的。
✎ build.gradle
这是项目全局的 gradle 构建脚本,通常这个文件中的内容是不需要修改的。
✎ gradle.properties
这个文件是全局的 gradle 配置文件,在这里配置的属性将会影响到项目中所有的 gradle 编译脚本。
✎ gradlew 和 gradlew.bat
这两个文件是用来在命令行界面中执行 gradle 命令的,其中 gradlew 是在 Linux 或 Mac 系统中使用的,gradlew.bat 是在 Windows 系统中使用的。
✎ local.properties
这个文件用于指定本机中的 Android SDK 路径,通常内容是自动生成的,我们并不需要修改。除非你本机中的 Android SDK 位置发生了变化,那么就将这个文件中的路径改成新的位置即可。
✎ settings.gradle
这个文件用于指定项目中所有引入的模块。由于 HelloWorld 项目中只有一个 app 模块,因此该文件中也就只引入了 app 这一个模块。通常情况下,模块的引入是自动完成的,需要我们手动修改这个文件的场景可能比较少。
3、『app 』目录介绍
下面对 app 目录下的内容进行更为详细的分析:
✎ build 目录
这个目录和外层的 build 目录类似,也包含了一些在编译时自动生成的文件,不过它里面的内容会更加复杂,我们不需要过多关心。
✎ androidTest 目录
此处是用来编写 Android Test 测试用例的,可以对项目进行一些自动化测试。
✎ java 目录
毫无疑问,java 目录是放置所有 Java 代码的地方,展开该目录,将看到系统帮我们自动生成了一个 MainActivity 文件。
✎ res 目录
项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。这个目录下还有很多子目录,图片放在 drawable 目录下,布局放在 layout 目录下,字符串放在 values 目录下,所以不用担心会把整个 res 目录弄得乱糟糟的。
✎ AndroidManifest.xml
这是整个 Android 项目的配置文件,程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。
✎ test 目录
此处是用来编写 Unit Test 测试用例的,是对项目进行自动化测试的另一种方式。
✎ .gitignore
这个文件用于将 app 模块内指定的目录或文件排除在版本控制之外,作用和外层的 .gitignore 文件类似。
✎ build.gradle
这是 app 模块的 gradle 构建脚本,这个文件中会指定很多项目构建相关的配置。
✎ proguard-rules.pro
这个文件用于指定项目代码的混淆规则,当代码开发完成后打包成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
4、HelloWorld 项目究竟是怎么运行起来的?
首先,打开 Android-Manifest.xml 文件,从中可以找到如下代码:- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout><intent-filter>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout> <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout> <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout><category android:name="android.intent.category.LAUNCHER" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout></intent-filter>
- </activity>
复制代码 这段代码表示对 MainActivity 进行注册,没有在 AndroidManifest.xml 里注册的 Activity 是不能使用的。其中 intent-filter 里的两行代码非常重要, 和 表示 MainActivity 是这个项目的主 Activity,在手机上点击应用图标,首先启动的就是这个 Activity。
那 MainActivity 具体又有什么作用呢?
Activity 是 Android 应用程序的门面,凡是在应用中你看得到的东西,都是放在 Activity 中的。那我们快去看一下它的代码吧,打开 MainActivity,代码如下所示:- class MainActivity : AppCompatActivity() {
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>override fun onCreate(savedInstanceState: Bundle?) {
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout> <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>super.onCreate(savedInstanceState)
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout> <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>setContentView(R.layout.activity_main)
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>}
- }
复制代码 首先,可以看到MainActivity 是继承自 AppCompatActivity 的。AppCompatActivity 是 AndroidX 中提供的一种向下兼容的 Activity,可以使 Activity 在不同系统版本中的功能保持一致性。Activity 类是 Android 系统提供的一个基类,项目中所有自定义的 Activity 都必须继承它或者它的子类才能拥有 Activity 的特性(AppCompatActivity 是 Activity 的子类)。
然后,可以看到 MainActivity 中有一个 onCreate() 方法,这个方法是一个 Activity 被创建时必定要执行的方法,其中只有两行代码,并且没有“Hello World!”的字样。
那么程序运行显示的“Hello World!”是在哪里定义的呢?
其实 ,Android 程序的设计讲究逻辑和视图分离,因此是不推荐在 Activity 中直接编写界面的。一种更加通用的做法是,在布局文件中编写界面,然后在 Activity 中引入进来。可以看到,在 onCreate() 方法的第二行调用了 setContentView() 方法,就是这个方法给当前的 Activity 引入了一个 activity_main 布局,那“Hello World!”一定就是在这里定义的了!
我们来打开这个文件看一看。
布局文件都是定义在 res/layout 目录下的,当你展开 layout 目录,你会看到 activity_main.xml 这个文件。打开该文件并切换到 Text 视图,代码如下所示:- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>
复制代码 上面代码中有一个 TextView,这是 Android 系统提供的一个控件,用于在布局中显示文字。在 TextView 中可以看到“Hello World!”的字样,这样我们就将 HelloWorld 项目的目录结构以及基本的执行过程分析完了。
5、项目中的资源
➹ 所有以“drawable”开头的目录都是用来放图片的;
➹ 所有以“mipmap”开头的目录都是用来放应用图标的;
➹ 所有以“values”开头的目录都是用来放字符串、样式、颜色等配置的;
➹ 所有以“layout”开头的目录都是用来放布局文件的。
之所以有这么多“mipmap”开头的目录,其实主要是为了让程序能够更好地兼容各种设备。drawable 目录也是相同的道理,虽然 Android Studio 没有帮我们自动生成,但是我们应该自己创建 drawable-hdpi、drawable-xhdpi、drawable-xxhdpi 等目录。在制作程序的时候,最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些目录下,然后程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个目录下的图片。当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时把所有图片都放在 drawable-xxhdpi 目录下就好了,因为这是最主流的设备分辨率目录。
知道了 res 目录下每个子目录的含义,再来看一下如何使用这些资源吧。打开 res/values/strings.xml 文件,内容如下所示:- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>HelloWorld
复制代码 可以看到,这里定义了一个应用程序名的字符串,我们有以下两种方式来引用它。
● 在代码中通过 R.string.app_name 可以获得该字符串的引用。
● 在XML中通过 @string/app_name 可以获得该字符串的引用。
基本的语法就是上面这两种方式,其中 string 部分是可以替换的,如果是引用的图片资源就可以替换成 drawable,如果是引用的应用图标就可以替换成 mipmap,如果是引用的布局文件就可以替换成 layout,以此类推。
下面举一个简单的例子,打开 AndroidManifest.xml 文件,找到如下代码:- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>...
复制代码 其中,HelloWorld 项目的应用图标就是通过 android:icon 属性指定的,应用的名称则是通过 android:label 属性指定的。
6、详解 build.gradle 文件
最外层目录下的 build.gradle 文件,代码如下所示: 这些代码都是自动生成的,虽然语法结构看上去可能有点难以理解,但是如果我们忽略语法结构,只看最关键的部分,其实还是很好懂的。
首先,两处 repositories 的闭包中都声明了 google() 和 jcenter() 这两行配置,它们分别对应了一个代码仓库,google 仓库中包含的主要是 Google 自家的扩展依赖库,而 jcenter 仓库中包含的大多是一些第三方的开源库。声明了这两行配置之后,就可以在项目中轻松引用任何 google 和 jcenter 仓库中的依赖库了。
接下来,dependencies 闭包中使用 classpath 声明了两个插件:一个 Gradle 插件和一个 Kotlin 插件。
为什么要声明 Gradle 插件呢?
因为 Gradle 并不是专门为构建 Android 项目而开发的,Java、C++ 等很多种项目也可以使用 Gradle 来构建,因此如果我们要想使用它来构建 Android 项目,则需要声明这个插件。其中,最后面的部分是插件的版本号,它通常和当前 Android Studio 的版本是对应的。
另外一个 Kotlin 插件则表示当前项目是使用 Kotlin 进行开发的,如果是 Java 版的 Android 项目,则不需要声明这个插件。
下面再来看一下 app 目录下的 build.gradle 文件,代码如下所示: 首先第一行应用了一个插件,一般有两种值可选:com.android.application 表示这是一个应用程序模块,com.android.library 表示这是一个库模块。二者最大的区别在于,应用程序模块是可以直接运行的,库模块只能作为代码库依附于别的应用程序模块来运行。
接下来的两行应用了 kotlin-android 和 kotlin-android-extensions 这两个插件。如果你想要使用 Kotlin 来开发 Android 项目,那么第一个插件就是必须应用的,而第二个插件帮助我们实现了一些非常好用的 Kotlin 扩展功能。
紧接着是一个大的 android 闭包,在这个闭包中我们可以配置项目构建的各种属性。其中,compileSdkVersion 用于指定项目的编译版本;buildToolsVersion 用于指定项目构建工具的版本。
然后我们看到,android 闭包中又嵌套了一个 defaultConfig 闭包,defaultConfig 闭包中可以对项目的更多细节进行配置。其中:
✎ applicationId 是每一个应用的唯一标识符,绝对不能重复,默认会使用我们在创建项目时指定的包名,如果想在后面对其进行修改,那么就是在这里修改的。
✎ minSdkVersion 用于指定项目最低兼容的 Android 系统版本。
✎ targetSdkVersion 指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。比如 Android 6.0 系统中引入了运行时权限这个功能,如果你将 targetSdkVersion 指定成23或者更高,那么系统就会为你的程序启用运行时权限功能,而如果你将 targetSdkVersion 指定成22,那么就说明你的程序最高只在 Android 5.1 系统上做过充分的测试,Android 6.0 系统中引入的新功能自然就不会启用了。
✎ versionCode 用于指定项目的版本号。
✎ versionName 用于指定项目的版本名。
✎ testInstrumentationRunner 用于在当前项目中启用 JUnit 测试,可以为当前项目编写测试用例,以保证功能的正确性和稳定性。
分析完了 defaultConfig 闭包,接下来看一下 buildTypes 闭包。buildTypes 闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包:一个是 debug,一个是 release。debug 闭包用于指定生成测试版安装文件的配置,release 闭包用于指定生成正式版安装文件的配置。另外,debug 闭包是可以忽略不写的,因此我们看到上面的代码中就只有一个 release 闭包。
下面来看一下 release 闭包中的具体内容吧,minifyEnabled 用于指定是否对项目的代码进行混淆,true 表示混淆,false 表示不混淆。proguardFiles 用于指定混淆时使用的规则文件,这里指定了两个文件:第一个 proguard-android-optimize.txt 是在 /tools/proguard 目录下的,里面是所有项目通用的混淆规则;第二个 proguard-rules.pro 是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。需要注意的是,通过 Android Studio 直接运行项目生成的都是测试版安装文件。
这样整个 android 闭包中的内容就都分析完了,接下来还剩一个 dependencies 闭包。这个闭包的功能非常强大,它可以指定当前项目所有的依赖关系。通常 Android Studio 项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的 jar 包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对 jcenter 仓库上的开源项目添加依赖关系。
观察一下 dependencies 闭包中的配置,第一行的 implementation fileTree 就是一个本地依赖声明,它表示将 libs 目录下所有 .jar 后缀的文件都添加到项目的构建路径中。而 implementation 则是远程依赖声明,androidx.appcompat:appcompat:1.1.0 就是一个标准的远程依赖库格式,其中 androidx.appcompat 是域名部分,用于和其他公司的库做区分;appcompat 是工程名部分,用于和同一个公司中不同的库工程做区分;1.1.0 是版本号,用于和同一个库不同的版本做区分。加上这句声明后,Gradle 在构建项目时会首先检查一下本地是否已经有这个库的缓存,如果没有的话则会自动联网下载,然后再添加到项目的构建路径中。至于库依赖声明这里没有用到,它的基本格式是implementation project 后面加上要依赖的库的名称,比如有一个库模块的名字叫 helper,那么添加这个库的依赖关系只需要加入 implementation project(':helper') 这句声明即可。
剩下的 testImplementation 和 androidTestImplementation 都是用于声明测试用例库的。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |