第 1 章 开始启程,你的第一行Android代码

欢迎你来到Android世界!Android系统是目前世界上市场占有率最高的移动操作系统,不管你在哪里,都可以看到Android手机几乎无处不在。今天的Android世界可谓欣欣向荣,可是你知道它的过去是什么样的吗?我们一起来看一看它的发展史吧。

2003年10月,Andy Rubin等人一起创办了Android公司。2005年8月,Google公司收购了这家仅仅成立了22个月的公司,并让Andy Rubin继续负责Android项目。在经过了数年的研发之后,Google终于在2008年推出了Android系统的第一个版本。但自那之后,Android的发展就一直受到重重阻挠。乔布斯自始至终认为Android是一个抄袭iPhone的产品,里面剽窃了诸多iPhone的创意,并声称一定要毁掉Android。而本身就是基于Linux开发的Android操作系统,在2010年被Linux团队从Linux内核主线中除名。由于Android中的应用程序一开始都是使用Java开发的,甲骨文公司针对Android侵犯Java知识产权一事对Google提起了诉讼……

可是,似乎再多的困难也阻挡不了Android快速前进的步伐。由于Google的开放政策,任何手机厂商和个人都能免费获取Android操作系统的源码,并且可以自由地使用和定制。三星、HTC、摩托罗拉、索爱等公司相继推出了各自系列的Android手机,Android市场上百花齐放。仅仅在推出两年后,Android就超过了已经霸占市场逾十年的诺基亚Symbian,成为了全球第一大智能手机操作系统,并且每天还会有数百万台新的Android设备被激活。而近几年,国内的手机厂商也大放异彩,小米、华为、魅族等新兴品牌都推出了相当不错的Android手机,并且获得了市场的广泛认可,目前Android已经占据了全球智能手机操作系统70%以上的份额。

说了这些,想必你已经体会到Android系统炙手可热的程度,并且迫不及待地想要加入Android开发者的行列了吧。试想一下,10个人中有7个人的手机可以运行你编写的应用程序,还有什么能比这个更诱人的呢?那么从今天起,我就带你踏上学习Android的旅途,一步步引导你成为一名出色的Android开发者。

好了,现在我们就来一起初窥一下Android世界吧。

Android从面世以来到现在已经发布了20多个版本了。在这几年的发展过程中,Google为Android王国建立了一个完整的生态系统。手机厂商、开发者、用户之间相互依存,共同推进着Android的蓬勃发展。开发者在其中扮演着不可或缺的角色,因为如果没有开发者来制作丰富的应用程序,那么不管多么优秀的操作系统,也是难以得到大众用户喜爱的,相信没有多少人能够忍受没有QQ、微信的手机吧。而且,Google推出的Google Play更是给开发者带来了大量的机遇,只要你能制作出优秀的产品,在Google Play上获得了用户的认可,你就完全可以得到不错的经济回报,从而成为一名独立开发者,甚至是成功创业!

那我们现在就从一个开发者的角度,去了解一下这个操作系统吧。纯理论型的东西会比较无聊,怕你看睡着了,因此我只挑重点介绍,这些东西跟你以后的开发工作都是息息相关的。

为了让你能够更好地理解Android系统是怎么工作的,我们先来看一下它的系统架构。Android大致可以分为4层架构:Linux内核层、系统运行库层、应用框架层和应用层。

Linux内核层

Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。
 

系统运行库层

这一层通过一些C/C++库为Android系统提供了主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。

在这一层还有Android运行时库,它主要提供了一些核心库,允许开发者使用Java语言来编写Android应用。另外,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程中,并且拥有一个自己的虚拟机实例。相较于Java虚拟机,Dalvik和ART都是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。
 

应用框架层

这一层主要提供了构建应用程序时可能用到的各种API,Android自带的一些核心应用就是使用这些API完成的,开发者可以使用这些API来构建自己的应用程序。
 

应用层

所有安装在手机上的应用程序都是属于这一层的,比如系统自带的联系人、短信等程序,或者是你从Google Play上下载的小游戏,当然还包括你自己开发的程序。

结合图1.1你将会理解得更加深刻。

图1.1 Android系统架构(图片源自维基百科)

2008年9月,Google正式发布了Android 1.0系统,这也是Android系统最早的版本。随后的几年,Google以惊人的速度不断地更新Android系统,2.1、2.2、2.3系统的推出使Android占据了大量的市场。2011年2月,Google发布了Android 3.0系统,这个系统版本是专门为平板计算机(简称“平板”)设计的,但也是Android为数不多的比较失败的版本,推出之后一直不见什么起色,市场份额也少得可怜。不过很快,在同年的10月,Google又发布了Android 4.0系统,这个版本不再对手机和平板进行差异化区分,既可以应用在手机上,也可以应用在平板上。2014年Google推出了号称史上版本改动最大的Android 5.0系统,使用ART运行环境替代了Dalvik虚拟机,大大提升了应用的运行速度,还提出了Material Design的概念来优化应用的界面设计。除此之外,还推出了Android Wear、Android Auto、Android TV系统,从而进军可穿戴设备、汽车、电视等全新领域。之后Android的更新速度更加迅速,每年都会发布一个新版本,到2019年Android已经发布到10.0系统了,这也是本书编写时最新的系统版本。

表1.1列出了目前主要的Android系统版本及其详细信息。当你看到这张表格时,数据可能已经发生了变化,查看最新的数据可以访问http://developer.android.google.cn/about/dashboards

表1.1 Android系统版本及其详细信息

从表1.1中可以看出,目前5.0以上的系统已经占据了超过85%的Android市场份额,并且这个数字还会继续扩大,因此我们本书中开发的程序也只面向5.0以上的系统,更早的系统版本就不再去兼容了。

预告一下,你马上就要开始真正的Android开发旅程了。不过别着急,在开始之前我们先来一起看一看,Android系统到底提供了哪些东西,可供我们开发出优秀的应用程序。

四大组件

Android系统四大组件分别是Activity、Service、BroadcastReceiver和ContentProvider。其中Activity是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在Activity中的。而Service就比较低调了,你无法看到它,但它会在后台默默地运行,即使用户退出了应用,Service仍然是可以继续运行的。BroadcastReceiver允许你的应用接收来自各处的广播消息,比如电话、短信等,当然,你的应用也可以向外发出广播消息。ContentProvider则为应用程序之间共享数据提供了可能,比如你想要读取系统通讯录中的联系人,就需要通过ContentProvider来实现。
 

丰富的系统控件

Android系统为开发者提供了丰富的系统控件,使得我们可以很轻松地编写出漂亮的界面。当然如果你品位比较高,不满足于系统自带的控件效果,完全可以定制属于自己的控件。
 

SQLite数据库

Android系统还自带了这种轻量级、运算速度极快的嵌入式关系型数据库。它不仅支持标准的SQL语法,还可以通过Android封装好的API进行操作,让存储和读取数据变得非常方便。
 

强大的多媒体

Android系统还提供了丰富的多媒体服务,如音乐、视频、录音、拍照等,这一切你都可以在程序中通过代码进行控制,让你的应用变得更加丰富多彩。

既然有Android这样出色的系统给我们提供了这么丰富的工具,你还用担心做不出优秀的应用吗?好了,纯理论的东西就介绍到这里,我知道你已经迫不及待地想要开始真正的开发之旅了,那我们就启程吧!

俗话说得好,“工欲善其事,必先利其器”,开着记事本就想去开发Android程序显然不是明智之举,选择一个好的IDE可以极大地提高你的开发效率,因此本节我就手把手地带着你把开发环境搭建起来。

开发Android程序需要准备的工具主要有以下3个。

JDK。JDK是Java语言的软件开发工具包,它包含了Java的运行环境、工具集合、基础类库等内容。

Android SDK。Android SDK是Google提供的Android开发工具包,在开发Android程序时,我们需要通过引入该工具包来使用Android相关的API。

Android Studio。在很早之前,Android项目都是使用Eclipse来开发的,相信所有Java开发者都一定会对这个工具非常熟悉,它是Java开发神器,安装ADT插件后就可以用来开发Android程序了。而在2013年,Google推出了一款官方的IDE工具Android Studio,由于不再是以插件的形式存在,Android Studio在开发Android程序方面要远比Eclipse强大和方便得多,因此本书中所有的代码都将在Android Studio上进行开发。

当然,上述软件并不需要一个个地下载,为了简化搭建开发环境的过程,Google将所有需要用到的工具都帮我们集成好了,到Android官网就可以下载最新的开发工具,下载地址是:https://developer.android.google.cn/studio。不过,Android官网有时访问会不太稳定,如果你无法访问上述网址,也可以到一些国内的代理站点进行下载,比如:http://www.android-studio.org

你下载下来的将是一个安装包,安装的过程也很简单,基本上一直点击“Next”就可以了。其中在安装的过程中有可能会弹出如图1.2所示的对话框。

图1.2 无法访问add-on list的警告对话框

这个对话框是在询问我们,无法访问Android SDK的add-on list,是否要配置代理。由于我们使用的网络访问Google的一些服务是受到限制的,因此才会弹出这样一个对话框。不过这并不影响我们接下来的环境搭建,因此直接点击“Cancel”就可以了。

之后一直点击“Next”,直到完成安装,然后启动Android Studio。首次启动会让你选择是否导入之前Android Studio版本的配置,由于这是我们首次安装,选择不导入即可,如图1.3所示。

图1.3 选择不导入配置

点击“OK”按钮会进入Android Studio的配置界面,如图1.4所示。

{%}

图1.4 Android Studio的配置界面

然后点击“Next”开始进行具体的配置,如图1.5所示。

图1.5 选择安装类型

这里我们可以选择Android Studio的安装类型,有Standard和Custom两种。Standard表示一切都使用默认的配置,比较方便;Custom则可以根据用户的特殊需求进行自定义。简单起见,这里我们就选择Standard类型了。继续点击“Next”会让你选择Android Studio的主题风格,如图1.6所示。

图1.6 选择Android Studio的主题风格

Android Studio内置了深色和浅色两种风格的主题,你可以根据自己的喜好选择。这里我就选择默认的浅色主题了,继续点击“Next”完成配置工作,如图1.7所示。

图1.7 完成Android Studio配置

现在点击“Finish”按钮,配置工作就全部完成了。然后Android Studio会尝试联网下载一些更新,等待更新完成后再点击“Finish”按钮,就会进入Android Studio的欢迎界面,如图1.8所示。

图1.8 Android Studio的欢迎界面

目前为止,Android开发环境就已经全部搭建完成了。那现在应该做什么呢?当然是写下你的第一行Android代码了,让我们快点开始吧。

任何一个编程语言写出的第一个程序毫无疑问都是Hello World,这是自20世纪70年代流传下来的传统,在编程界已成为永恒的经典,那我们当然也不会搞例外了。

在Android Studio的欢迎界面点击“Start a new Android Studio project”,会打开一个让你选择项目类型的界面,如图1.9所示。

图1.9 选择项目类型界面

这里我们不仅可以选择创建手机和平板类型的项目,还可以选择创建可穿戴设备、电视,甚至汽车等类型的项目。不过手机和平板才是本书讨论的重点,其他类型的项目我们就不去关注了。另外,Android Studio还提供了很多种内置模板,不过由于我们才刚刚开始学习,用不着这么多复杂的模板,这里直接选择“Empty Activity”,创建一个空的Activity就可以了。

点击“Next”会进入项目配置界面,如图1.10所示。

图1.10 项目配置界面

其中,Name表示项目名称,这里我们填入“HelloWorld”即可。

Package name表示项目的包名,Android系统就是通过包名来区分不同应用程序的,因此包名一定要具有唯一性。Android Studio会根据应用名称来自动帮我们生成合适的包名,如果你不想使用默认生成的包名,也可以自行修改。

Save location表示项目代码存放的位置,如果没有特殊要求的话,这里也保持默认即可。

接下来的Language就很重要了,这里默认选择了Kotlin。在过去,Android应用程序只能使用Java来进行开发,本书的前两个版本也都是用Java语言讲解的。然而在2017年,Google引入了一款新的开发语言——Kotlin,并在2019年正式向广大开发者公布了Kotlin First的消息。因此,本书第3版决定响应Google的号召,全书代码都使用Kotlin语言来进行编写。那么你可能会担心了,我不会Kotlin怎么办?没关系,本书除了会讲解Android方面的知识之外,还会非常全面地讲解Kotlin方面的知识,并不需要你有任何Kotlin语言的基础。

紧接着,Minimum API level可以设置项目的最低兼容版本。前面已经说过,Android 5.0以上的系统已经占据了超过85%的Android市场份额,因此这里我们将Minimum SDK指定成API 21就可以了。

最后的两个复选框,一个是用于支持instant apps免安装应用的,这个功能必须配合Google Play服务才能使用,在国内是用不了的,因此不在本书的讨论范围内;另一个用于在项目中启用AndroidX。AndroidX的主要目的是取代过去的Android Support Library,虽然Google给出了一个过渡期,但是在我使用的Android Studio 3.5.2版本中,这个复选框已经被强制勾选了。如果你使用了更新的Android Studio版本,看不到这个复选框也不用感到奇怪,因为未来所有项目都会默认启用AndroidX。想要了解更多AndroidX与Android Support Library的区别,可以关注我的微信公众号(见封面),回复“AndroidX”即可。

现在点击“Finish”按钮,并耐心等待一会儿,项目就会创建成功了,如图1.11所示。

图1.11 项目创建成功

由于Android Studio自动为我们生成了很多东西,因而你现在不需要编写任何代码,HelloWorld项目就已经可以运行了。但是在此之前,还必须有一个运行的载体,可以是一部Android手机,也可以是Android模拟器。这里我们暂时先使用模拟器来运行程序,如果你想立刻就将程序运行到手机上的话,可以参考9.1节的内容。

那么我们现在就来创建一个Android模拟器,观察Android Studio顶部工具栏中的图标,如图1.12所示。

图1.12 顶部工具栏中的图标

中间的按钮就是用于创建和启动模拟器的,点击该按钮,会弹出如图1.13所示的窗口。

图1.13 创建模拟器

可以看到,目前我们的模拟器列表中还是空的,点击“Create Virtual Device”按钮就可以立刻开始创建了,如图1.14所示。

图1.14 选择要创建的模拟器设备

这里有很多种设备可供我们选择,不仅能创建手机模拟器,还可以创建平板、手表、电视等模拟器。

那么我就选择创建Pixel这台设备的模拟器了,这是我个人非常钟爱的一台设备。点击“Next”,如图1.15所示。

图1.15 选择模拟器的操作系统版本

这里可以选择模拟器所使用的操作系统版本,毫无疑问,我们肯定要选择最新的Android 10.0系统。但是由于目前我的本机还不存在Android 10.0系统的镜像,因此需要先点击“Download”下载镜像。下载完成后继续点击“Next”,出现如图1.16所示的界面。

图1.16 确认模拟器配置

在这里我们可以对模拟器的一些配置进行确认,比如说指定模拟器的名字、分辨率、横竖屏等信息,如果没有特殊需求的话,全部保持默认就可以了。点击“Finish”完成模拟器的创建,然后会弹出如图1.17所示的窗口。

图1.17 模拟器列表

可以看到,现在模拟器列表中已经存在一个创建好的模拟器设备了,点击Actions栏目中最左边的三角形按钮即可启动模拟器。模拟器会像手机一样,有一个开机过程,启动完成之后的界面如图1.18所示。

图1.18 启动后的模拟器界面

很清新的Android界面出来了!看上去还挺不错吧,你几乎可以像使用手机一样使用它,Android模拟器对手机的模仿度非常高,快去体验一下吧。

现在模拟器已经启动起来了,那么下面我们就将HelloWorld项目运行到模拟器上。观察Android Studio顶部工具栏中的图标,如图1.19所示,其中左边的锤子按钮是用来编译项目的。中间有两个下拉列表:一个是用来选择运行哪一个项目的,通常app就是当前的主项目;另一个是用来选择运行到哪台设备上的,可以看到,我们刚刚创建的模拟器现在已经在线了。右边的三角形按钮是用来运行项目的。

图1.19 顶部工具栏中的图标

现在点击右边的运行按钮,稍微等待一会儿,HelloWorld项目就会运行到模拟器上了,结果应该和图1.20中显示的是一样的。

图1.20 运行HelloWorld项目

HelloWorld项目运行成功!并且你会发现,模拟器上已经安装HelloWorld这个应用了。打开启动器列表,如图1.21所示。

图1.21 查看启动器列表

这个时候你可能会说我坑你了,说好的第一行代码呢?怎么一行还没写,项目就已经运行起来了?这个只能说是因为Android Studio太智能了,已经帮我们把一些简单的内容自动生成了。你也别心急,后面写代码的机会多着呢,我们先来分析一下HelloWorld这个项目吧。

回到Android Studio中,首先展开HelloWorld项目,你会看到如图1.22所示的项目结构。

图1.22 Android模式的项目结构

任何一个新建的项目都会默认使用Android模式的项目结构,但这并不是项目真实的目录结构,而是被Android Studio转换过的。这种项目结构简洁明了,适合进行快速开发,但是对于新手来说可能不易于理解。点击图1.22中最上方的Android区域可以切换项目结构模式,如图1.23所示。

图1.23 切换项目结构模式

这里我们将项目结构模式切换成Project,这就是项目真实的目录结构了,如图1.24所示。

{%}

图1.24 Project模式的项目结构

一开始看到这么多陌生的东西,你一定会有点头晕吧。别担心,我现在就对图1.24中的内容进行讲解,之后你再看这张图就不会感到那么吃力了。

.gradle和.idea

这两个目录下放置的都是Android Studio自动生成的一些文件,我们无须关心,也不要去手动编辑。
 

app

项目中的代码、资源等内容都是放置在这个目录下的,我们后面的开发工作也基本是在这个目录下进行的,待会儿还会对这个目录单独展开讲解。
 

build

这个目录主要包含了一些在编译时自动生成的文件,你也不需要过多关心。
 

gradle

这个目录下包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。Android Studio默认就是启用gradle wrapper方式的,如果需要更改成离线模式,可以点击Android Studio导航栏→File→Settings→Build, Execution, Deployment→Gradle,进行配置更改。
 

.gitignore

这个文件是用来将指定的目录或文件排除在版本控制之外的。关于版本控制,我们将在第6章中开始正式的学习。
 

build.gradle

这是项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的。稍后我们将会详细分析gradle构建脚本中的具体内容。
 

gradle.properties

这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
 

gradlew和gradlew.bat

这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。
 

HelloWorld.iml

iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJ IDEA开发的),用于标识这是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容。
 

local.properties

这个文件用于指定本机中的Android SDK路径,通常内容是自动生成的,我们并不需要修改。除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。
 

settings.gradle

这个文件用于指定项目中所有引入的模块。由于HelloWorld项目中只有一个app模块,因此该文件中也就只引入了app这一个模块。通常情况下,模块的引入是自动完成的,需要我们手动修改这个文件的场景可能比较少。

现在整个项目的外层目录结构已经介绍完了。你会发现,除了app目录之外,大多数的文件和目录是自动生成的,我们并不需要进行修改。想必你已经猜到了,app目录下的内容才是我们以后的工作重点,展开之后的结构如图1.25所示。

图1.25 app目录下的结构

那么下面我们就来对app目录下的内容进行更为详细的分析。

build

这个目录和外层的build目录类似,也包含了一些在编译时自动生成的文件,不过它里面的内容会更加复杂,我们不需要过多关心。
 

libs

如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包会被自动添加到项目的构建路径里。
 

androidTest

此处是用来编写Android Test测试用例的,可以对项目进行一些自动化测试。
 

java

毫无疑问,java目录是放置我们所有Java代码的地方(Kotlin代码也放在这里),展开该目录,你将看到系统帮我们自动生成了一个MainActivity文件。
 

res

这个目录下的内容就有点多了。简单点说,就是你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。当然这个目录下还有很多子目录,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下,所以你不用担心会把整个res目录弄得乱糟糟的。
 

AndroidManifest.xml

这是整个Android项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。由于这个文件以后会经常用到,我们等用到的时候再做详细说明。
 

test

此处是用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
 

.gitignore

这个文件用于将app模块内指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。
 

app.iml

IntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。
 

build.gradle

这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置,我们稍后将会详细分析gradle构建脚本中的具体内容。
 

proguard-rules.pro

这个文件用于指定项目代码的混淆规则,当代码开发完成后打包成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。

这样整个项目的目录结构就都介绍完了,如果你还不能完全理解的话也很正常,毕竟里面有太多的东西你都还没接触过。不过不用担心,这并不会影响你后面的学习。等你学完整本书再回来看这个目录结构图时,你会觉得特别地清晰和简单。

接下来我们一起分析一下HelloWorld项目究竟是怎么运行起来的吧。首先打开Android- Manifest.xml文件,从中可以找到如下代码:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

这段代码表示对MainActivity进行注册,没有在AndroidManifest.xml里注册的Activity是不能使用的。其中intent-filter里的两行代码非常重要,<action android:name="android.intent.action.MAIN"/> 和<category android:name="android.intent.category.LAUNCHER" />表示MainActivity是这个项目的主Activity,在手机上点击应用图标,首先启动的就是这个Activity。

那MainActivity具体又有什么作用呢?我在介绍Android四大组件的时候说过,Activity是Android应用程序的门面,凡是在应用中你看得到的东西,都是放在Activity中的。因此你在图1.20中看到的界面,其实就是MainActivity。那我们快去看一下它的代码吧,打开MainActivity,代码如下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

}

首先可以看到,MainActivity是继承自AppCompatActivity的。AppCompatActivity是AndroidX中提供的一种向下兼容的Activity,可以使Activity在不同系统版本中的功能保持一致性。而Activity类是Android系统提供的一个基类,我们项目中所有自定义的Activity都必须继承它或者它的子类才能拥有Activity的特性(AppCompatActivity是Activity的子类)。然后可以看到MainActivity中有一个onCreate()方法,这个方法是一个Activity被创建时必定要执行的方法,其中只有两行代码,并且没有“Hello World! ”的字样。那么图1.20中显示的“Hello World! ”是在哪里定义的呢?

其实Android程序的设计讲究逻辑和视图分离,因此是不推荐在Activity中直接编写界面的。一种更加通用的做法是,在布局文件中编写界面,然后在Activity中引入进来。可以看到,在onCreate()方法的第二行调用了setContentView()方法,就是这个方法给当前的Activity引入了一个activity_main布局,那“Hello World!”一定就是在这里定义的了!我们快打开这个文件看一看。

布局文件都是定义在res/layout目录下的,当你展开layout目录,你会看到activity_main.xml这个文件。打开该文件并切换到Text视图,代码如下所示:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <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!”的字样!哈哈!终于找到了,原来就是通过android:text="Hello World!"这句代码定义的。

这样我们就将HelloWorld项目的目录结构以及基本的执行过程分析完了,相信你对Android项目已经有了一个初步的认识,下一小节中我们就来学习一下项目中所包含的资源。

如果你展开res目录看一下,其实里面的东西还是挺多的,很容易让人看得眼花缭乱,如图1.26所示。

图1.26 res目录下的结构

看到这么多的子目录也不用害怕,其实归纳一下,res目录中的内容就变得非常简单了。所有以“drawable”开头的目录都是用来放图片的,所有以“mipmap”开头的目录都是用来放应用图标的,所有以“values”开头的目录都是用来放字符串、样式、颜色等配置的,所有以“layout”开头的目录都是用来放布局文件的。怎么样,是不是突然感觉清晰了很多?

之所以有这么多“mipmap”开头的目录,其实主要是为了让程序能够更好地兼容各种设备。drawable目录也是相同的道理,虽然Android Studio没有帮我们自动生成,但是我们应该自己创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等目录。在制作程序的时候,最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些目录下,然后程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个目录下的图片。当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时你把所有图片都放在drawable-xxhdpi目录下就好了,因为这是最主流的设备分辨率目录。

知道了res目录下每个子目录的含义,我们再来看一下如何使用这些资源吧。打开res/values/strings.xml文件,内容如下所示:

<resources>
    <string name="app_name">HelloWorld</string>
</resources>

可以看到,这里定义了一个应用程序名的字符串,我们有以下两种方式来引用它。

● 在代码中通过R.string.app_name可以获得该字符串的引用。

● 在XML中通过@string/app_name可以获得该字符串的引用。

基本的语法就是上面这两种方式,其中string部分是可以替换的,如果是引用的图片资源就可以替换成drawable,如果是引用的应用图标就可以替换成mipmap,如果是引用的布局文件就可以替换成layout,以此类推。

下面举一个简单的例子来帮助你理解,打开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">
    ...
</application>

其中,HelloWorld项目的应用图标就是通过android:icon属性指定的,应用的名称则是通过android:label属性指定的。可以看到,这里对资源引用的方式正是我们刚刚学过的在XML中引用资源的语法。

经过本小节的学习,如果你想修改应用的图标或者名称,相信已经知道该怎么办了吧。

不同于Eclipse,Android Studio是采用Gradle来构建项目的。Gradle是一个非常先进的项目构建工具,它使用了一种基于Groovy的领域特定语言(DSL)来进行项目设置,摒弃了传统基于XML(如Ant和Maven)的各种烦琐配置。

在1.3.4小节中我们已经看到,HelloWorld项目中有两个build.gradle文件,一个是在最外层目录下的,一个是在app目录下的。这两个文件对构建Android Studio项目都起到了至关重要的作用,下面我们就来对这两个文件中的内容进行详细的分析。

先来看一下最外层目录下的build.gradle文件,代码如下所示:

buildscript {
    ext.kotlin_version = '1.3.61'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

这些代码都是自动生成的,虽然语法结构看上去可能有点难以理解,但是如果我们忽略语法结构,只看最关键的部分,其实还是很好懂的。

首先,两处repositories的闭包中都声明了google()和jcenter()这两行配置,那么它们是什么意思呢?其实它们分别对应了一个代码仓库,google仓库中包含的主要是Google自家的扩展依赖库,而jcenter仓库中包含的大多是一些第三方的开源库。声明了这两行配置之后,我们就可以在项目中轻松引用任何google和jcenter仓库中的依赖库了。

接下来,dependencies闭包中使用classpath声明了两个插件:一个Gradle插件和一个Kotlin插件。为什么要声明Gradle插件呢?因为Gradle并不是专门为构建Android项目而开发的,Java、C++等很多种项目也可以使用Gradle来构建,因此如果我们要想使用它来构建Android项目,则需要声明com.android.tools.build:gradle:3.5.2这个插件。其中,最后面的部分是插件的版本号,它通常和当前Android Studio的版本是对应的,比如我现在使用的是Android Studio 3.5.2版本,那么这里的插件版本号就应该是3.5.2。而另外一个Kotlin插件则表示当前项目是使用Kotlin进行开发的,如果是Java版的Android项目,则不需要声明这个插件。我在编写本书时,Kotlin插件的最新版本号是1.3.61。

这样我们就将最外层目录下的build.gradle文件分析完了,通常情况下,你并不需要修改这个文件中的内容,除非你想添加一些全局的项目构建配置。

下面我们再来看一下app目录下的build.gradle文件,代码如下所示:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.helloworld"
        minSdkVersion 21
        targetSdkVersion 29
        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-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx: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'
}

这个文件中的内容就要相对复杂一些了,下面我们一行行地进行分析。首先第一行应用了一个插件,一般有两种值可选:com.android.application表示这是一个应用程序模块,com.android.library表示这是一个库模块。二者最大的区别在于,应用程序模块是可以直接运行的,库模块只能作为代码库依附于别的应用程序模块来运行。

接下来的两行应用了kotlin-android和kotlin-android-extensions这两个插件。如果你想要使用Kotlin来开发Android项目,那么第一个插件就是必须应用的。而第二个插件帮助我们实现了一些非常好用的Kotlin扩展功能,在后面的章节中,你将能体会到它所带来的巨大便利性。

紧接着是一个大的android闭包,在这个闭包中我们可以配置项目构建的各种属性。其中,compileSdkVersion用于指定项目的编译版本,这里指定成29表示使用Android 10.0系统的SDK编译。buildToolsVersion用于指定项目构建工具的版本,目前最新的版本就是29.0.2,如果有更新的版本时,Android Studio会进行提示。

然后我们看到,android闭包中又嵌套了一个defaultConfig闭包,defaultConfig闭包中可以对项目的更多细节进行配置。其中,applicationId是每一个应用的唯一标识符,绝对不能重复,默认会使用我们在创建项目时指定的包名,如果你想在后面对其进行修改,那么就是在这里修改的。minSdkVersion用于指定项目最低兼容的Android系统版本,这里指定成21表示最低兼容到Android 5.0系统。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是在<Android SDK>/tools/proguard目录下的,里面是所有项目通用的混淆规则;第二个proguard-rules.pro是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。需要注意的是,通过Android Studio直接运行项目生成的都是测试版安装文件,关于如何生成正式版安装文件,我们将会在第15章中学习。

这样整个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都是用于声明测试用例库的,这个我们暂时用不到,先忽略它就可以了。

通过上一节的学习,你已经成功创建了你的第一个Android程序,并且对Android项目的目录结构和运行流程都有了一定的了解。现在本应该是你继续前行的时候,不过我想在这里给你穿插一点内容,讲解一下Android中日志工具的使用方法,这对你以后的Android开发之旅会有极大的帮助。

Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志。

● Log.v()。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。

● Log.d()。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。

● Log.i()。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为的数据。对应级别info,比debug高一级。

● Log.w()。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。

● Log.e()。用于打印程序中的错误信息,比如程序进入了catch语句中。当有错误信息打印出来的时候,一般代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。

其实很简单,一共就5个方法,当然每个方法还会有不同的重载,但那对你来说肯定不是什么难理解的地方了。我们现在就在HelloWorld项目中试一试日志工具好不好用吧。

打开MainActivity,在onCreate()方法中添加一行打印日志的语句,如下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d("MainActivity", "onCreate execute")
    }

}

Log.d()方法中传入了两个参数:第一个参数是tag,一般传入当前的类名就好,主要用于对打印信息进行过滤;第二个参数是msg,即想要打印的具体内容。

现在可以重新运行一下HelloWorld这个项目了,点击顶部工具栏上的运行按钮,或者使用快捷键Shift + F10(Mac系统是control + R)。等程序运行完毕,点击Android Studio底部工具栏的“Android Monitor”,在Logcat中就可以看到打印信息了,如图1.27所示。

{%}

图1.27 Logcat中的打印信息

其中,你不仅可以看到打印日志的内容和tag名,就连程序的包名、打印的时间以及应用程序的进程号都可以看到。

当然,Logcat中不光会显示我们所打印的日志,还会显示许多其他程序打印的日志,因此在很多情况下还需要对日志进行过滤,下一小节中我们就会学习这部分内容。

另外,不知道你有没有注意到,你的第一行代码已经在不知不觉中写出来了,我也总算是交差了。

我相信很多的Java新手会非常喜欢使用System.out.println()方法来打印日志,在Kotlin中与之对应的是println()方法,不知道你是不是也喜欢这么做。不过在真正的项目开发中,是极度不建议使用System.out.println()或println()方法的,如果你在公司的项目中经常使用这两个方法来打印日志的话,就很有可能要挨骂了。

为什么System.out.println()和println()方法会这么不受待见呢?经过我仔细分析之后,发现这两个方法除了使用方便一点之外,其他就一无是处了。方便在哪儿呢?在Android Studio中你只需要输入“sout”,然后按下代码提示键,方法就会自动出来了,相信这也是很多Java新手对它钟情的原因。那缺点又在哪儿了呢?这个就太多了,比如日志开关不可控制、不能添加日志标签、日志没有级别区分……

听我说了这些,你可能已经不太想用System.out.println()和println()方法了,那么Log就把上面所说的缺点全部改好了吗?虽然谈不上全部,但我觉得Log已经做得相当不错了。我现在就来带你看看Log和Logcat配合的强大之处。

首先,Logcat中可以很轻松地添加过滤器,你可以在图1.28中看到我们目前所有的过滤器。

图1.28 Logcat中的过滤器

目前只有3个过滤器,Show only selected application表示只显示当前选中程序的日志; Firebase是Google提供的一个开发者工具和基础架构平台,我们可以不用管它;No Filters相当于没有过滤器,会把所有的日志都显示出来。那可不可以自定义过滤器呢?当然可以,我们现在就来添加一个过滤器试试。

点击图1.28中的“Edit Filter Configuration”,会弹出一个过滤器配置界面。我们给过滤器起名叫data,并且让它对名为data的tag进行过滤,如图1.29所示。

图1.29 过滤器配置界面

点击“OK”,你会发现多出了一个data过滤器。当选中这个过滤器的时候,刚才在onCreate()方法里打印的日志就不见了,这是因为data这个过滤器只会显示tag名称为data的日志。你可以尝试在onCreate()方法中把打印日志的语句改成Log.d("data", "onCreate execute"),然后再次运行程序,你就会在data过滤器下看到这行日志了。

不知道你有没有体会到使用过滤器的好处,可能现在还没有吧。不过当你的程序打印出成百上千行日志的时候,你就会迫切地需要过滤器了。

看完了过滤器,再来看一下Logcat中的日志级别控制吧。Logcat中主要有5个级别,分别对应上一小节介绍的5个方法,如图1.30所示。

图1.30 Logcat中的日志级别

当前我们选中的级别是Verbose,也就是最低等级。这意味着不管我们使用哪一个方法打印日志,这条日志都一定会显示出来。而如果我们将级别选中为Debug,这时只有我们使用Debug及以上级别方法打印的日志才会显示出来,以此类推。你可以做一下实验,当你把Logcat中的级别选中为Info、Warn或者Error时,我们在onCreate()方法中打印的语句是不会显示的,因为我们打印日志时使用的是Log.d()方法。

日志级别控制的好处就是,你可以很快地找到你所关心的那些日志。相信如果让你从上千行日志中查找一条崩溃信息,你一定会抓狂吧。而现在你只需要将日志级别选中为Error,那些不相干的琐碎信息就不会再干扰你的视线了。

最后,我们再来看一下关键字过滤。如果使用过滤器加日志级别控制还是不能锁定到你想查看的日志内容的话,那么还可以通过关键字进行进一步的过滤,如图1.31所示。

图1.31 关键字输入框

我们可以在输入框里输入关键字的内容,这样只有符合关键字条件的日志才会显示出来,从而能够快速定位到任何你想查看的日志。另外,还有一点需要注意,关键字过滤是支持正则表达式的,有了这个特性,我们就可以构建出更加丰富的过滤条件。

关于Android中日志工具的使用,我就准备讲到这里,Logcat中其他的一些使用技巧就要靠你自己去摸索了。今天你已经学到了足够多的东西,我们来总结和梳理一下吧。

你现在一定会觉得很充实,甚至有点沾沾自喜。确实应该如此,因为你已经成为一名真正的Android开发者了。通过本章的学习,你首先对Android系统有了更加充足的认识,然后成功将Android开发环境搭建了起来,接着创建了你自己的第一个Android项目,并对Android项目的目录结构和执行过程有了一定的认识,在本章的最后还学习了Android日志工具的使用,这难道还不够充实吗?

不过你也不要过于满足,相信你很清楚,Android开发者和出色的Android开发者还是有很大的区别的,要想成为一名出色的Android开发者,你还需要付出更多的努力才行。现在你可以非常安心地休息一段时间,因为今天你已经做得非常不错了。储备好能量,准备进入下一章的旅程当中吧。