Android App 开发基础
本章介绍基于 Android 系统的 App 开发常识,包括以下几个方面: App 开发与其他软件开发有什么不一样,App 工程是怎样的组织结构又是怎样配置的,App 开发的前后端分离设计是如何运作实现的,App 的活动页面是如何创建又是如何跳转的。
App 的开发特点
本节介绍了 App 开发与其他软件开发不一样的特点,例如:App 能在哪些操作系统上运行、App 开发用到了哪些编程语言、App 能操作哪些数据库等,搞清楚了 App 的开发运行环境,才能有的放矢不走弯路。
App 的运行环境
App 是在手机上运行的一类应用软件,而应用软件依附于操作系统,无论电脑还是手机,刚开机都会显示桌面,这个桌面便是操作系统的工作台。个人电脑的操作系统主要有微软的 Windows 和苹果的 Mac OS,智能手机流行的操作系统也有两种,分别是安卓手机的 Android 和苹果手机的 iOS。本书讲述的 App 开发为 Android 上的应用开发,Android 系统基于 Linux 内核,但不等于 Linux 系统,故 App 应用无法在 Linux 系统上运行。
Android Studio 是谷歌官方推出的 App 开发环境,它提供了三种操作系统的安装包,分别是 Windows、Mac 和 Linux。这就产生一个问题:开发者可以在电脑上安装 Android Studio,并使用 Android Studio 开发 App 项目,但是编译出来的 App 在电脑上跑不起来。这种情况真是令人匪夷所思的,通常学习 C 语言、Java 或者 Python,都能在电脑的开发环境直接观看程序运行过程,就算是 J2EE 开发,也能在浏览器通过网页观察程序的运行结果。可是安卓的 App 应用竟然没法在电脑上直接运行,那该怎样验证 App 的界面展示及其业务逻辑是否正确呢?
为了提供 App 开发的功能测试环境,一种办法是利用 Android Studio 创建内置的模拟器,然后启动内置模拟器,再在模拟器上运行 App 应用,详细步骤参见第一章的“在模拟器上运行 App”。
另一种办法是使用真实手机测试 App,该办法在实际开发中更为常见。由于模拟器本身跑在电脑上面,占用电脑的 CPU 和内存,会拖累电脑的运行速度;况且模拟器仅仅是模拟而已,无法完全验证 App 的所有功能,因此最终都得通过真机测试才行。
利用真机调试要求具备以下 5 个条件:
- 使用数据线把手机连到电脑上
手机的电源线拔掉插头就是数据线。数据线长方形的一端接到电脑的 USB 接口,即可完成手机与电脑的连接。
- 在电脑上安装手机的驱动程序
一般电脑会把手机当作 USB 存储设备一样安装驱动,大多数情况会自动安装成功。如果遇到少数情况安装失败,需要先安装手机助手,由助手软件下载并安装对应的手机驱动。
- 打开手机的开发者选项并启用 USB 调试
手机出厂后默认关闭开发者选项,需要开启开发者选项才能调试 App。打开手机的设置菜单,进入“系统”→“关于手机”→“版本信息”页面,这里有好几个版本项,每个版本项都使劲点击七、八下,总会有某个版本点击后出现“你将开启开发者模式”的提示。继续点击该版本开启开发者模式,然后退出并重新进入设置页面,此时就能在“系统”菜单下找到“开发者选项”或“开发人员选项”了。进入“开发者选项”页面,启用“开发者选项”和“USB 调试”两处开关,允许手机通过 USB 接口安装调试应用。
4.将连接的手机设为文件传输模式,并允许计算机进行 USB 调试
手机通过 USB 数据线连接电脑后,屏幕弹出如图 2-1 所示的选择列表,请求选择某种 USB 连接方式。这里记得选中“传输文件”,因为充电模式不支持调试 App。 选完之后手机桌面弹出如图 2-2 所示的确认窗口,提示开发者是否允许当前计算机进行 USB 调试。这里勾选“始终允许使用这台计算机进行调试”选项,再点击右下角的确定按钮,允许计算机在手机上调试 App。


- 手机要能正常使用
锁屏状态下,Android Studio 向手机安装 App 的行为可能会被拦截,所以要保证手机处于解锁状态,才能顺利通过电脑安装 App 到手机上。
有的手机还要求插入 SIM 卡才能调试 App,还有的手机要求登录会员才能调试 App,总之如果遇到无法安装的问题,各种情况都尝试一遍才好。
经过以上步骤,总算具备通过电脑在手机上安装 App 的条件了。马上启动 Android Studio,在顶部中央的执行区域看到已连接的手机信息,如图 2-3 所示。此时的设备信息提示这是一台小米手机,单击手机名 称右边的三角运行按钮,接下来就是等待 Android Studio 往手机上安装 App 了。

App 的开发语言
基于安卓系统的 App 开发主要有两大技术路线,分别是原生开发和混合开发。原生开发指的是在移动平台上利用官方提供的编程语言(例如 Java、Kotlin 等)、开发工具包(SDK)、开发环境(Android Studio)进行 App 开发;混合开发指的是结合原生与 H5 技术开发混合应用,也就是将部分 App 页面改成内嵌的网页,这样无须升级 App、只要覆盖服务器上的网页,即可动态更新 App 页面。
不管是原生开发还是混合开发,都要求掌握 Android Studio 的开发技能,因为混合开发本质上依赖于原生开发,如果没有原生开发的皮,哪里还有混合开发的毛呢?单就原生开发而言,又涉及多种编程语言,包括 Java、Kotlin、C/C++、XML 等,详细说明如下。
- Java
Java 是 Android 开发的主要编程语言,在创建新项目时,弹出如图 2-4 所示的项目配置对话框,看见 Language 栏默认选择了 Java,表示该项目采用 Java 编码。

- Kotlin
Kotlin 是谷歌官方力推的又一种编程语言,它与 Java 同样基于 JVM(Java Virtual Machine,即 Java 虚拟机),且完全兼容 Java 语言。创建新项目时,在 Language 栏下拉可选择 Kotlin,此时项目结构对话框如图 2-9 所示。
一旦在创建新项目时选定 Kotlin,该项目就会自动加载 Kotlin 插件,并将 Kotlin 作为默认的编程语言。不过本书讲述的 App 开发采用 Java 编程,未涉及 Kotlin 编程。

- C/C++
不管是 Java 还是 Kotlin,它们都属于解释型语言,这类语言在运行之时才将程序翻译成机器语言,故而执行效率偏低。虽然现在手机配置越来越高,大多数场景的 App 运行都很流畅,但是涉及图像与音视频处理等复杂运算的场合,解释型语言的性能瓶颈便暴露出来。
编译型语言在首次编译时就将代码编译为机器语言,后续运行无须重新编译,直接使用之前的编译文件即可,因此执行效率比解释型语言高。C/C++ 正是编译型语言的代表,它能够有效弥补解释型语言的性能缺憾,借助于 JNI 技术(Java Native Interface,即 Java 原生接口),Java 代码允许调用 C/C++ 编写的程序。事实上,Android 的 SDK 开发包内部定义了许多 JNI 接口,包括图像读写在内的底层代码均由 C/C++ 编写,再由外部通过封装好的 Java 方法调用。
不过 Android 系统的 JNI 编程属于高级开发内容,初学者无须关注 JNI 开发,也不要求掌握 C/C++。
- XML
XML 全称为 Extensible Markup Language,即可扩展标记语言,严格地说,XML 并非编程语言,只是一种标记语言。它类似于 HTML,利用各种标签表达页面元素,以及各元素之间的层级关系及其排列组合。每个 XML 标签都是独立的控件对象,标签内部的属性以“android:”打头,表示这是标准的安卓属性,各属性分别代表控件的某种规格。比如下面是以 XML 书写的文本控件:
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />上面的标签名称为 TextView,翻译过来叫文本视图,该标签携带 4 个属性,说明如下:
- id:控件的编号。
- layout_width:控件的布局宽度,wrap_content 表示刚好包住该控件的内容。
- layout_height:控件的布局高度,wrap_content 表示刚好包住该控件的内容。
- text:控件的文本,也就是文本视图要显示什么文字。
综合起来,以上 XML 代码所表达的意思为:这是一个名为 tv_hello 的文本视图,显示的文字内容是“Hello World!”,它的宽度和高度都要刚好包住这些文字。
以上就是 Android 开发常见的几种编程语言,本书选择了 Java 路线而非 Kotlin 路线,并且定位安卓初学者教程,因此读者需要具备 Java 和 XML 基础。
App 连接的数据库
在学习 Java 编程的时候,基本会学到数据库操作,通过 JDBC 连接数据库进行记录的增删改查,这个数据库可能是 MySQL,也可能是 Oracle,还可能是 SQL Server。然而手机应用不能直接操作上述几种数据库,因为数据库软件也得像应用软件那样安装到操作系统上,比如 MySQL 提供了 Windows 系统的安装包,也提供了 Linux 系统的安装包,可是它没有提供 Android 系统的安装包呢,所以 MySQL 没法在 Android 系统上安装,手机里面的 App 也就不能直连 MySQL。
既然 MySQL、Oracle 这些企业数据库无法在手机安装,那么 App 怎样管理业务方面的数据记录呢?其实 Android 早已内置了专门的数据库名为 SQLite,它遵循关系数据库的设计理念,SQL 语法类似于 MySQL。不同之处在于,SQLite 无须单独安装,因为它内嵌到应用进程当中,所以 App 无须配置连接信息,即可直接对其增删改查。由于 SQLite 嵌入到应用程序,省去了配置数据库服务器的开销,因此它又被归类为嵌入式数据库。
可是 SQLite 的数据库文件保存在手机上,开发者拿不到用户的手机,又该如何获取 App 存储的业务数据?比如用户的注册信息、用户的购物记录,等等。如果像 Java Web 那样,业务数据统一保存在后端的数据库服务器,开发者只要登录数据库服务器,就能方便地查询导出需要的记录信息。
手机端的 App,连同程序代码及其内置的嵌入式数据库,其实是个又独立又完整的程序实体,它只负责手机上的用户交互与信息处理,该实体被称作客户端。而后端的 Java Web 服务,包括 Web 代码和数据库服务器,同样构成另一个单独运行的程序实体,它只负责后台的业务逻辑与数据库操作,该实体被称作服务端。客户端与服务端之前通过 HTTP 接口通信,每当客户端觉得需要把信息发给服务端,或者需要从服务端获取信息时,客户端便向服务端发起 HTTP 请求,服务端收到客户端的请求之后,根据规则完成数据处理,并将处理结果返回给客户端。这样客户端经由 HTTP 接口并借服务端之手,方能间接读写后端的数据库服务器(如MySQL),具体的信息交互过程如图 2-10 所示。
由此看来,一个具备用户管理功能的 App 系统,实际上并不单单只是手机上的一个应用,还包括与其对应的 Java Web 服务。手机里的客户端 App,面向的是手机用户,App 与用户之间通过手机屏幕交互;而后端的服务程序,面向的是手机 App,客户端与服务端之间通过 HTTP 接口交互。客户端和服务端这种多对一的架构关系如图 2-11 所示。

总结一下,手机 App 能够直接操作内置的 SQLite 数据库,但不能直接操作MySQL这种企业数据库。必须事先搭建好服务端程序(如 Java Web),然后客户端与服务端通过 HTTP 接口通信,再由服务端操作以MySQL 为代表的数据库服务器。
App 的工程结构
本节介绍 App 工程的基本结构及其常用配置,首先描述项目和模块的区别,以及工程内部各目录与配置文件的用途说明;其次阐述两种级别的编译配置文件 build.gradle,以及它们内部的配置信息说明;再次讲述运行配置文件 AndroidManifest.xml 的节点信息及其属性说明。
App 工程目录结构
App 工程分为两个层次,第一个层次是项目,依次选择菜单 File→New→New Project 即可创建新项目。另一个层次是模块,模块依附于项目,每个项目至少有一个模块,也能拥有多个模块,依次选择菜单 File→New→New Module 即可在当前项目创建新模块。一般所言的“编译运行 App”,指的是运行某个模块,而非运行某个项目,因为模块才对应实际的 App。单击 Android Studio 左上角竖排的 Project 标签,可见 App 工程的项目结构如图 2-12 所示。

从图 2-12 中看到,该项目下面有两个分类:一个是 app(代表 app 模块);另一个是 Gradle Scripts。其中,app 下面又有 3 个子目录,其功能说明如下:
- manifests 子目录,下面只有一个 XML 文件,即 AndroidManifest.xml,它是 App 的运行配置文件。
- java 子目录,下面有3个 com.example.myapp 包,其中第一个包存放当前模块的 Java 源代码,后面两个包存放测试用的 Java 代码。
- res 子目录,存放当前模块的资源文件。res 下面又有 4 个子目录:
- drawable 目录存放图形描述文件与图片文件。
- layout 目录存放 App 页面的布局文件。
- mipmap 目录存放 App 的启动图标。
- values 目录存放一些常量定义文件,例如字符串常量 strings.xml、像素常量 dimens.xml、颜色常量 colors.xml、样式风格定义 styles.xml 等。
Gradle Scripts 下面主要是工程的编译配置文件,主要有:
- build.gradle,该文件分为项目级与模块级两种,用于描述 App 工程的编译规则。
- proguard-rules.pro,该文件用于描述 Java 代码的混淆规则。
- gradle.properties,该文件用于配置编译工程的命令行参数,一般无须改动。
- settings.gradle,该文件配置了需要编译哪些模块。初始内容为 include ':app',表示只编译 app 模块。
- local.properties,项目的本地配置文件,它在工程编译时自动生成,用于描述开发者电脑的环境 配置,包括 SDK 的本地路径、NDK 的本地路径等。
编译配置文件 build.gradle
新创建的App项目默认有两个 build.gradle,一个是 Project 项目级别的 build.gradle;另一个是 Module 模块级别的 build.gradle。
项目级别的 build.gradle 指定了当前项目的总体编译规则,打开该文件在 buildscript 下面找到 repositories 和 dependencies 两个节点,其中 repositories 节点用于设置 Android Studio 插件的网络仓库地址,而 dependencies 节点用于设置 gradle 插件的版本号。由于官方的谷歌仓库位于国外,下载速度相对较慢,因此可在 repositories 节点添加阿里云的仓库地址,方便国内开发者下载相关插件。修改之后的 buildscript 节点内容如下所示:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
// 以下四行添加阿里云的仓库地址,方便国内开发者下载相关插件
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
google()
jcenter()
}
dependencies {
// 配置gradle插件版本,下面的版本号就是Android Studio的版本号
classpath 'com.android.tools.build:gradle:4.1.0'
}
}
plugins {
alias(libs.plugins.android.application) apply false
}IMPORTANT
但从 Android Studio Bumblebee 开始,项目级别的 build.gradle 不再存放仓库地址,而是将仓库地址的 repositories 节点配置到了 settings.gradle。因此要在 settings.gradle 中补充如下的阿里云仓库地址。
Details
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
// 以下四行添加阿里云的仓库地址,方便国内开发者下载相关插件
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
// 以下四行添加阿里云的仓库地址,方便国内开发者下载相关插件
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
google()
mavenCentral()
}
}模块级别的 build.gradle 对应于具体模块,每个模块都有自己的 build.gradle,它指定了当前模块的详细编译规则。下面给 chapter02 模块的 build.gradle 补充文字注释,方便读者更好地理解每个参数的用途。 (完整代码见 chapter02\build.gradle)
android {
// 指定编译用的SDK版本号。比如30表示使用Android 11.0编译
compileSdkVersion 30
// 指定编译工具的版本号。这里的头两位数字必须与compileSdkVersion保持一致,具体的版本号可在sdk安装目录的“sdk\build-tools”下找到
buildToolsVersion "30.0.3"
defaultConfig {
// 指定该模块的应用编号,也就是App的包名
applicationId "com.example.chapter02"
// 指定App适合运行的最小SDK版本号。比如19表示至少要在Android 4.4上运行
minSdkVersion 19
// 指定目标设备的SDK版本号。表示App最希望在哪个版本的Android上运行
targetSdkVersion 30
// 指定App的应用版本号
versionCode 1
// 指定App的应用版本名称
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
// 指定App编译的依赖信息
dependencies {
// 指定引用jar包的路径
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 指定编译Android的高版本支持库。如AppCompatActivity必须指定编译appcompat库
//appcompat库各版本见 https://mvnrepository.com/artifact/androidx.appcompat/appcompat
implementation 'androidx.appcompat:appcompat:1.2.0'
// 指定单元测试编译用的junit版本号
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}为啥这两种编译配置文件的扩展名都是 Gradle 呢?这是因为它们采用了 Gradle 工具完成编译构建操作。Gradle 工具的版本配置在 gradle\wrapper\gradle-wrapper.properties,也可以依次选择菜单 File→Project Structure→Project,在弹出的设置页面中修改 Gradle Version。注意每个版本的 Android Studio 都有对应的 Gradle 版本,只有二者的版本正确对应,App 工程才能成功编译。比如 Android Studio 4.1 对应的 Gradle 版本为 6.5,更多的版本对应关系见 https://developer.android.google.cn/build/releases/gradle-plugin?hl=zh-cn#updating-plugin。
运行配置文件 AndroidManifest.xml
AndroidManifest.xml 指定了 App 的运行配置信息,它是一个 XML 描述文件,初始内容如下所示: (完整代码见 chapter02\src\main\AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>可见 AndroidManifest.xml 的根节点为 manifest,它的 package 属性指定了该 App 的包名。manifest 下面有个 application 节点,它的各属性说明如下:
- android:allowBackup,是否允许应用备份。允许用户备份系统应用和第三方应用的 apk 安装包和应用数据,以便在刷机或者数据丢失后恢复应用,用户即可通过 adb backup 和 adb restore 来进行对应用数据的备份和恢复。为 true 表示允许,为 false 则表示不允许。
- android:icon,指定 App 在手机屏幕上显示的图标。
- android:label,指定 App 在手机屏幕上显示的名称。
- android:roundIcon,指定 App 的圆角图标。
- android:supportsRtl,是否支持阿拉伯语/波斯语这种从右往左的文字排列顺序。为 true 表示支持,为 false 则表示不支持。
- android:theme,指定 App 的显示风格。
注意到 application 下面还有个 activity 节点,它是活动页面的注册声明,只有在 AndroidManifest.xml 中正确配置了 activity 节点,才能在运行时访问对应的活动页面。初始配置的 MainActivity 正是 App 的默认主页,之所以说该页面是 App 主页,是因为它的 activity 节点内部还配置了以下的过滤信息:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>其中 action 节点设置的 android.intent.action.MAIN 表示该页面是 App 的入口页面,启动 App 时会最先打开该页面。而 category 节点设置的 android.intent.category.LAUNCHER 决定了是否在手机屏幕上显示 App 图标,如果同时有两个 activity 节点内部都设置了 android.intent.category.LAUNCHER,那么桌面就会显示两个 App 图标。以上的两种节点规则可能一开始不太好理解,读者只需记住默认主页必须同时配置这两种过滤规则即可。
App 的设计规范
本节介绍了 App 工程的源码设计规范,首先 App 将看得见的界面设计与看不见的代码逻辑区分开,然后利用 XML 标记描绘应用界面,同时使用 Java 代码书写程序逻辑,从而形成 App 前后端分离的设计规约,有利于提高 App 集成的灵活性。
界面设计与代码逻辑
手机的功能越来越强大,某种意义上相当于微型电脑,比如打开一个电商 App,仿佛是在电脑上浏览网站。网站分为用户看得到的网页,以及用户看不到的 Web 后台;App 也分为用户看得到的界面,以及用户看不到的 App 后台。虽然 Android 允许使用 Java 代码描绘界面,但不提倡这么做,推荐的做法是将界面设计从 Java 代码剥离出来,通过单独的 XML 文件定义界面布局,就像网站使用 HTML 文件定义网页那样。直观地看,网站的前后端分离设计如图 2-13 所示,App 的前后端分离设计如图 2-14 所示。


把界面设计与代码逻辑分开,不仅参考了网站的 Web 前后端分离,还有下列几点好处。
- 使用 XML 文件描述 App 界面,可以很方便地在 Android Studio 上预览界面效果。比如新创建的 App 项目,默认首页布局为 activity_main.xml,单击界面右上角的 Design 按钮,即可看到如图 2-15 所示的预览界面。
如果 XML 文件修改了 Hello World 的文字内容,立刻就能在预览区域观看最新界面。倘若使用 Java 代码描绘界面,那么必须运行 App 才能看到 App 界面,无疑费时许多。 2. 一个界面布局可以被多处代码复用,比如看图界面,既能通过商城购物代码浏览商品图片,也能通过商品评价代码浏览买家晒单。 3. 反过来,一段 Java 代码也可能适配多个界面布局,比如手机有竖屏与横屏两种模式,默认情况 App 采用同一套布局,然而在竖屏时很紧凑的界面布局(见图 2-16),切换到横屏往往变得松垮乃至变形(见图 2-17)。



鉴于竖屏与横屏遵照一样的业务逻辑,仅仅是屏幕方向不同,若要调整的话,只需分别给出竖屏时候的界面布局,以及横屏时候的界面布局。因为用户多数习惯竖屏浏览,所以 res/layout 目录下放置的 XML 文件默认为竖屏规格,另外在 res 下面新建名为 layout-land 的目录,用来存放横屏规格的 XML 文件。land 是 landscape 的缩写,意思是横向,Android 把 layout-land 作为横屏 XML 的专用布局目录。然后在 layout-land 目录创建与原 XML 同名的 XML 文件,并重新编排界面控件的展示方位,调整后的横屏界面如图 2-18 所示,从而有效适配了屏幕的水平方向。

总的来说,界面设计与代码逻辑分离的好处多多,后续的例程都由 XML 布局与 Java 代码两部分组成。
利用 XML 标记描绘应用界面
在前面“App 的开发语言”末尾,给出了安卓控件的 XML 定义例子,如下所示:
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />注意到 TextView 标签以“<”开头,以“/>”结尾,为何尾巴多了个斜杆呢?要是没有斜杆,以左右尖括号包裹标签名称,岂不更好?其实这是 XML 的标记规范,凡是 XML 标签都由标签头与标签尾组成,标签头以左右尖括号包裹标签名称,形如<TextView>;标签尾在左尖括号后面插入斜杆,以此同标签头区分开,形如</TextView>。标签头允许在标签名称后面添加各种属性取值,而标签尾不允许添加任何属性,因此上述 TextView 标签的完整 XML 定义是下面这样的:
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!">
</TextView>考虑到 TextView 仅仅是个文本视图,其标签头和标签尾之间不会插入其他标记,所以合并它的标签头和标签尾,也就是让 TextView 标签以/>结尾,表示该标签到此为止。
然而不是所有情况都能采取简化写法,简写只适用于 TextView 控件这种末梢节点。好比一棵大树,大树先有树干,树干分岔出树枝,一些大树枝又分出小树枝,树枝再长出末端的树叶。一个界面也是先有根节点(相当于树干),根节点下面挂着若干布局节点(相当于树枝),布局节点下面再挂着控件节点(相当于树叶)。因为树叶已经是末梢了,不会再包含其他节点,所以末梢节点允许采用/>这种简写方式。
譬如下面是个 XML 文件的布局内容,里面包含了根节点、布局节点,以及控件节点: (完整代码见 chapter02\src\main\res\layout\activity_main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 这是个线性布局, match_parent意思是与上级视图保持一致-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 这是个文本视图,名字叫做tv_hello,显示的文字内容为“Hello World!” -->
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout>
</LinearLayout>上面的 XML 内容,最外层的 LinearLayout 标签为该界面的根节点,中间的 LinearLayout 标签为布局节点,最内层的 TextView 为控件节点。由于根节点和布局节点都存在下级节点,因此它们要有配对的标签头与标签尾,才能将下级节点包裹起来。根节点其实是特殊的布局节点,它的标签名称可以跟布局节点一样,区别之处在于下列两点:
- 每个界面只有一个根节点,却可能有多个布局节点,也可能没有中间的布局节点,此时所有控件节点都挂在根节点下面。
- 根节点必须配备“xmlns:android="http://schemas.android.com/apk/res/android"”,表示指定 XML 内部的命名空间,有了这个命名空间,Android Studio 会自动检查各节点的属性名称是否合法,如果不合法就提示报错。至于布局节点就不能再指定命名空间了。
有了根节点、布局节点、控件节点之后,XML 内容即可表达丰富多彩的界面布局,因为每个界面都能划分为若干豆腐块,每个豆腐块再细分为若干控件罢了。三种节点之外,尚有<!--说明文字-->这类注释标记,它的作用是包裹注释性质的说明文字,方便其他开发者理解此处的XML含义。
使用 Java 代码书写程序逻辑
在 XML 文件中定义界面布局,已经明确是可行的了,然而这只是静态界面,倘若要求在 App 运行时修改文字内容,该当如何是好?倘若是动态变更网页内容,还能在 HTML 文件中嵌入 JavaScript 代码,由 js 片段操作 Web 控件。但 Android 的 XML 文件仅仅是布局标记,不能再嵌入其他语言的代码了,也就是说,只靠 XML 文件自身无法动态刷新某个控件。
XML 固然表达不了复杂的业务逻辑,这副重担就得交给 App 后台的 Java 代码了。Android Studio 每次创建新项目,除了生成默认的首页布局 activity_main.xml 之外,还会生成与其对应的代码文件 MainActivity.java。赶紧打开 MainActivity.java,看看里面有什么内容,该 Java 文件中 MainActivity 类的内容如下所示:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}可见 MainActivity.java 的代码内容很简单,只有一个 MainActivity 类,该类下面只有一个 onCreate 方法。注意 onCreate 内部的 setContentView 方法直接引用了布局文件的名字 activity_main,该方法的意思是往当前活动界面填充 activity_main.xml 的布局内容。现在准备在这里改动,把文字内容改成中文。首先打开 activity_main.xml,在 TextView 节点下方补充一行 android:id="@+id/tv_hello",表示给它起个名字编号;然后回到 MainActivity.java,在 setContentView 方法下面补充几行代码,具体如下:
(完整代码见chapter02\src\main\java\com\example\chapter02\MainActivity.java)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
// 当前的页面布局采用的是在 res/layout/activity_main.xml
setContentView(R.layout.activity_main);
// 或许名叫 tv_hello 的 TextView 控件,注意添加导包语句 import android.widget.TextView;
TextView tv_hello = (TextView) findViewById(R.id.tv_hello);
tv_hello.setText("你好,世界");
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}新增的两行代码主要做了这些事情:先调用 findById 方法,从布局文件中取出名为 tv_hello 的 TextView 控件;再调用控件对象的 setText 方法,为其设置新的文字内容。
代码补充完毕,重新运行测试 App,发现应用界面变成了如图 2-19 所示的样子。

App 的活动页面
本节介绍了 App 活动页面的基本操作,首先手把手地分三步创建新的 App 页面,接着通过活动创建菜单快速生成页面源码,然后说明了如何在代码中跳到新的活动页面。
创建新的 App 页面
每次创建新的项目,都会生成默认的 activity_main.xml 和 MainActivity.java,它们正是 App 首页对应的 XML 文件和 Java 代码。若要增加新的页面,就得由开发者自行操作了,完整的页面创建过程包括 3 个步骤:创建 XML 文件、创建 Java 代码、注册页面配置,分别介绍如下:
创建 XML 文件
在 Android Studio 左上方找到项目结构图,右击 res 目录下面的 layout,弹出如图 2-20 所示的右键菜单。

在右键菜单中依次选择 New->XML->Layout XML File,弹出如图 2-21 所示的 XML 创建对话框。

在 XML 创建对话框的 Layout File Name 输入框中填写 XML 文件名,例如 activity_main2,然后单击窗口右下角的 Finish 按钮。之后便会在 layout 目录下面看到新创建的 XML 文件 activity_main2.xml,双击它即可打开该 XML 的编辑窗口,再往其中填写详细的布局内容。
创建 Java 代码
同样在 Android Studio 左上方找到项目结构图,右击 Java 目录下面的包名,弹出如图 2-22 所示的右键菜单。

在右键菜单中依次选择 New->Java Class,弹出如图 2-23 所示的代码创建窗口。

在代码创建窗口的 Name 输入框中填写 Java 类名,例如 Main2Activity,然后单击窗口下方的 OK 按钮。之后便会在 Java 包下面看到新创建的代码文件 Main2Activity,双击它即可打开代码编辑窗口,再往其中填写如下代码,表示加载来自 activity_main2 的页面布局。
(完整代码见 chapter02\src\main\java\com\example\chapter02\Main2Activity.java)
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
}注册页面配置
创建好了页面的 XML 文件及其 Java 代码,还得再项目中注册该页面,打开 AndroidManifest.xml,在 application 节点内部补充如下一行配置:
<activity android:name=".Main2Activity"></activity>添加了上面这行配置,表示给该页面注册身份,否则 App 运行时打开页面会提示错误“activity not found”。如果 activity 的标记头与标记尾中间没有其他内容,则节点配置也可以省略为下面这样:
<activity android:name=".Main2Activity /">完成以上 3 个步骤后,才算创建了一个合法的新页面。
快速生成页面源码
上一小节经过创建 XML 文件、创建 Java 代码、注册页面配置 3 个步骤,就算创建好了一个新页面,没想到区区一个页面也这么费事,怎样才能提高开发效率呢?其实 Android Studio 早已集成了快速创建页面的功能,只要一个对话框就能完成所有操作。
仍旧在项目结构图中,右击 java 目录下面的包名,弹出如图 2-24 所示的右键菜单。

右键菜单中依次选择 New->Activity->Empty Views Activity,弹出如图 2-25 所示的页面创建对话框。

在页面创建对话框的 Activity Name 输入框中填写页面的 Java 类名(例如 Main2Activity),此时下方的 Layout Name 输入框会自动填写对应的 XML 文件名(例如 activity_main2),单击对话框右下角的 Finish 按钮,完成新页面的创建动作。
回到 Android Studio 左上方的项目结构图,发现 res 的 layout 目录下多了个 activity_main2.xml,同时 java 目录下多了个 Main2Activity,并且 Main2Activity 代码已经设定了加载 activity_main2 布局。接着打开 AndroidManifest.xml,找到 application 节点发现多了下面这行配置:
<activity
android:name=".MainActivity2"
android:exported="false" />检查接过说明,只要填写一个创建页面对话框,即可实现页面创建的 3 个步骤。
跳到另一个页面
一旦创建好新页面,就得在合适的时候跳到该页面,假设出发页面为 A,到达页面为 B,那么跳转动作是从 A 跳到 B。由于启动 App 会自动打开默认主页 MainActivity,因此跳跃的起点理所当然在 MainActivity,跳跃的终点则为目标页面的 Activity。这种跳转动作翻译为 Android 代码,格式形如“startActivity(new Intent(源页面.this, 目标页面.class));”。如果目标页面名为 Main2Activity,跳转代码便是下面这样的:
// 活动页面跳转,从MainActivity跳到Main2Activity
startActivity(new Intent(MainActivity.this, Main2Activity.class));接下来做个实验,准备让 App 启动后在首页停留 3 秒,3 秒之后跳到新页面 Main2Activity。此处的延迟处理功能,用到了 Handler 工具的 postDelayed 方法,该方法的第一个参数为待处理的 Runnable 任务对象,第二个参数为延迟间隔(单位为毫秒)。为此在 MainActivity.java 中补充以下的跳转处理代码: (完整代码见 chapter02\src\main\java\com\example\chapter02\MainActivity.java)
@Override
protected void onResume() {
super.onResume();
goNextPage(); // 跳到下个页面
}
// 跳到下个页面
private void goNextPage() {
TextView tv_hello = findViewById(R.id.tv_hello);
tv_hello.setText("3 秒后进入下个页面");
new Handler(Looper.myLooper()).postDelayed(mGoNext, 3000);
}
private final Runnable mGoNext = () -> startActivity(new Intent(MainActivity.this, MainActivity2.class));运行测试 App,刚打开的 App 界面如图 2-26 所示,过了 3 秒发生跳转事件的 App 界面如图 2-27 所示,可见成功跳到了新页面。


当然,以上的跳转代码有些复杂,比如:Intent 究竟是什么东西?为何在 onResume 方法中执行跳转动作?Handler 工具的处理机制是怎样的?带着这些疑问,后续章节将会逐渐展开,一层一层拨开 Android 开发的迷雾。
小结
本章主要介绍了 App 开发必须事先掌握的基础知识,包括 App 的开发特点(App 的运行环境、App 的开发语言、App 访问的数据库)、App 的工程结构(App 工程的目录结构、编译配置文件 build.gradle、运行配置文件 AndroidManifest.xml)、App 的设计规范(界面设计与代码逻辑、利用 XML 标记描绘应用界面、使用 Java 代码书写程序逻辑)、App 的活动页面(创建新的 App 页面、快速生成页面源码、跳转到另一个页面)。
通过本章的学习,读者应该了解 App 开发的基本概念,并且熟悉 App 工程的组织形式,同时掌握使用 Android Studio 完成一些简单操作。