目录

初入 Android 开发,学习一下相关的开发知识。

Application Fundamentals

Android 应用可以采用 Kotlin、Java 以及 C++ 编程语言编写。Android SDK 工具将您的代码 - 连同任何数据和资源文件 - 编译到一个 APK:Android 软件包(Android package),即带有 .apk 后缀的存档文件中。一个 APK 文件包含 Android 应用的所有内容,它是基于 Android 系统的设备用来安装应用的文件。

每个 Android 应用都位于自己的安全沙箱(security sandbox)中,受以下 Android 安全功能的保护:

  • Android 操作系统是一个多用户 Linux 系统,其中每个应用都是不同的用户。
  • 默认情况下,系统会为每个应用分配一个唯一的 Linux 用户 ID(该 ID 仅由系统使用,应用并不知晓)。系统为应用中的所有文件设置权限,使得只有分配给该应用的用户 ID 才能访问这些文件。
  • 每个进程都具有自己的虚拟机 (VM),因此应用代码是在与其他应用隔离的环境中运行。
  • 默认情况下,每个应用都在其自己的 Linux 进程内运行。Android 会在需要执行任何应用组件时启动该进程,然后在不再需要该进程或系统必须为其他应用恢复内存时关闭该进程。

Android 系统实现了_最小权限原则(the principle of least privilege)_。也就是说,默认情况下,每个应用都只能访问执行其工作所需的组件,而不能访问其他组件。这样便营造出一个非常安全的环境,在这个环境中,应用无法访问系统中其未获得权限的部分。

不过,应用仍然可以通过一些途径与其他应用共享数据以及访问系统服务:

  • 可以安排两个应用共享同一 Linux 用户 ID,在这种情况下,它们能够相互访问彼此的文件。 为了节省系统资源,可以安排具有相同用户 ID 的应用在同一 Linux 进程中运行,并共享同一 VM。应用还必须使用相同的证书签署。
  • 应用可以请求访问设备数据(如用户的联系人、短信、可装载存储装置 [SD 卡]、相机、蓝牙等)的权限。用户必须明确授予这些权限。如需了解详细信息,请参阅使用系统权限

App components

应用组件是 Android 应用的基本构建基块。每个组件都是一个入口点(entry point),系统可以通过它进入您的应用。某些组建依赖于其它。

共有四种不同的应用组件类型:

  • Activities
  • Services - 服务
  • Broadcast receivers - 广播接收器
  • Content providers - 内容提供程序

每种类型都服务于不同的目的,并且具有定义组件的创建和销毁方式的不同生命周期。

Activities

Activity 是和用户交互的入口点。它表示具有用户界面的单一屏幕。例如,电子邮件应用可能具有一个显示新电子邮件列表的 activity、一个用于撰写电子邮件的 activity 以及一个用于阅读电子邮件的 activity。尽管这些 activity 通过协作在电子邮件应用中形成了一种紧密结合的用户体验,但每一个 activity 都独立于其他 activity 而存在。 因此,其他应用可以启动其中任何一个 activity(如果电子邮件应用允许)。 例如,相机应用可以启动电子邮件应用内用于撰写新电子邮件的 activity,以便用户共享图片。

您可以将 activity 视为桌面应用程序中的窗口或对话框的 Android 模拟,或经典 Web app 中的页面。 它代表了一大块用户界面,在某些情况下,它代表了 app 的一个独立入口点(例如,其他 app 链接到您的 app 的一种方式)。

通常情况下,一个 activity 将占用大部分屏幕,为时钟、信号强度指示器等一些「chrome」位留出空间。

但是,请记住,在某些设备上,用户将能够一次处理多个 activity,例如手机或平板电脑上的分屏模式。 所以,虽然很容易将 activity 视为等同于屏幕,但请记住这是一种简化,而现实更加复杂(现实往往是这样)。

Activity 有助于系统和应用之间的以下关键交互: - 跟踪用户目前关心的内容(屏幕上显示的内容)以确保系统持续运行托管 activity 的进程。 - 知道以前使用的包含用户可能返回的东西的进程(已停止的活动),因此更高优先级地保持这些过程。 - 帮助应用句柄使其进程终止,以便用户可以返回到以前状态恢复的 activity。 - 为应用提供一种在彼此之间实现用户流的方法,并使系统协调这些流。 (这里最经典的例子就是分享。)

Activity 作为 Activity 的子类实现,您可以在 Activity 开发者指南中了解有关它的更多详情。

Services

Activities 是短暂的,可以随时关闭,例如用户按 BACK 按钮。 另一方面,service 设计用于在需要时独立于任何 activity 持续运行一段时间。

服务(service)是一个通用的入口点,用于由于各种原因使应用在后台运行。它是在后台运行的组件,用于执行长时间运行的操作或执行远程进程的工作。服务不提供用户界面。例如,服务可能在用户处于不同应用程序时在后台播放音乐,或者可能通过网络获取数据而不阻止用户与某个 activity 的交互。另一个组件(如 activity)可以启动服务并让它运行或绑定到它以便与之交互。实际上有两种截然不同的语义服务告诉系统如何管理应用:已启动的服务告诉系统保持它们运行直到其工作完成。这可能是在用户离开应用后在后台同步某些数据或播放音乐。在后台同步数据或播放音乐也代表了两种不同类型的启动服务,它们修改了系统处理它们的方式: - 音乐播放是用户直接意识到的事情,所以应用告诉系统这一点,说它想通过通知告诉用户它是否在前台;在这种情况下,系统知道它应该非常努力地保持该服务的进程运行,因为如果它消失,用户将不高兴。 - 常规的后台服务不是用户直接意识到的,因此系统在管理其进程方面有更多的自由。如果它需要 RAM 来处理用户更直接关注的事情,它可能会让它被杀死(然后在稍后重新启动服务)。

绑定服务运行,因为其他一些应用程序(或系统)已经表示要使用该服务。这基本上是为另一个进程提供 API 的服务。因此,系统知道这些进程之间存在依赖关系,因此如果进程 A 绑定到进程 B 中的服务,则知道它需要保持进程 B(及其服务)为 A 运行。此外,如果进程 A 是用户关心的事情,那么它也知道将进程 B 视为用户也关心的事情。由于它们的灵活性(无论好坏),服务已经成为各种高级系统概念非常有用的基石。动态壁纸、通知侦听器、屏幕保护程序、输入法、辅助功能服务以及许多其他核心系统功能都是作为应用程序实现的服务和系统绑定到应该运行的服务而构建的。

服务是作为 Service 的子类实现的。有关 Service 类的更多信息,请参阅 Services 开发人员指南

注意:如果您的应用目标为 Android 5.0(API 级别 21)或更高版本,请使用 JobScheduler 类来安排操作。 JobScheduler 的优势在于通过优化调度作业来降低功耗并通过使用 Doze API 来节省电池。

Broadcast Receivers

广播接收器(broadcast receiver)是一个组件,它使系统能够在常规用户流之外将事件传送到应用,从而允许应用响应系统范围的广播通知。由于广播接收器是另一个定义良好的应用入口,系统甚至可以将广播传送到当前未运行的应用。因此,例如,应用可以安排警报发布通知以告知用户即将发生的事件……并通过将该警报发送给应用的 BroadcastReceiver,应用程序不需要继续运行,直到警报熄灭。许多广播源自系统 - 例如广播宣布屏幕已关闭、电池电量不足或拍摄了照片。应用还可以启动广播 - 例如,让其他应用程序知道某些数据已下载到设备并可供其使用。尽管广播接收器不显示用户界面,但他们可能会创建状态栏通知,以在发生广播事件时提醒用户。然而,更常见的情况是,广播接收器只是通往其他组件的门户(gateway),并且打算做很少量的工作。例如,它可以调度 JobService 根据 JobScheduler 的事件执行一些工作。

广播接收器作为 BroadcastReceiver 的子类实现,每个广播都作为 Intent 对象提供。

Content Providers

内容提供程序管理您可以存储在文件系统中、SQLite数据库中、Web 上或应用可以访问的任何其他永久性存储位置的共享应用数据集。通过内容提供程序,如果内容提供程序允许,其他应用可以查询或修改数据。例如,Android 系统提供了一个管理用户联系信息的内容提供程序。因此,任何具有适当权限的应用程序都可以查询内容提供程序(如 ContactsContract.Data)来读取和编写关于特定人员的信息。将内容提供程序视为数据库的抽象是很有诱惑力的,因为在这种常见的情况下,他们内置了许多 API 和支持。但是,从系统设计的角度来看,它们具有不同的核心目的。对于系统来说,内容提供程序是进入应用的入口点,用于发布由 URI 方案标识的命名数据项。因此,应用可以决定如何将其包含的数据映射到 URI 命名空间,然后将这些 URI 分发给其他实体,这些实体又可以使用它们访问数据。有一些特殊的事情可以让系统管理应用程序:

  • 分配 URI 并不要求应用保持运行,所以 URI 可以在其拥有的应用退出后继续存在。系统只需要确保自己的应用在它必须从相应的 URI 中检索应用程序的数据时仍在运行。
  • 这些 URI 也提供了一个重要的细粒度安全模型(fine-grained security model)。例如,应用可以将剪贴板上的图像放入 URI,但将其内容提供程序锁定,以便其他应用程序无法自由访问它。当第二个应用程序尝试访问剪贴板上的该 URI 时,系统可以允许该应用程序通过临时 URI 权限授予访问数据,以便允许其仅在该 URI 后面访问数据,但第二个应用程序中没有其他的东西。

内容提供者(content providers)为存储在设备上的多个应用程序可访问的任何数据提供抽象级别。 Android 开发模型鼓励您将自己的数据提供给您自己的应用程序以及其他应用程序 - 构建内容提供程序可以让您执行此操作,同时保持对数据访问方式的一定程度的控制。

因此,例如,如果您有一个代表用户下载的 PDF 文件,并且您希望允许用户查看该 PDF 文件,则可以创建一个内容提供者以使该 PDF 文件可用于其他应用程序。 然后,您可以启动一项能够查看该 PDF 的活动,其中 Android 和用户将确定哪些 PDF 查看 activity 处理该请求。

内容提供程序也可用于读取和写入应用专用且不共享的数据。例如,Note Pad 示例应用使用内容提供者来保存笔记。

内容提供程序被实现为 ContentProvider 的子类,并且必须实现一组标准的API,以使其他应用能够执行事务。

Android system design

Android 系统设计的独特之处在于,任何应用都可以启动其他应用的组件。 例如,如果您想让用户使用设备的相机拍摄照片,很可能有另一个应用可以执行该操作,那么您的应用就可以利用该应用,而不是开发一个 Activity 来自行拍摄照片。 您不需要集成甚至链接到该相机应用的代码,而是只需启动拍摄照片的相机应用中的 Activity。 完成拍摄时,系统甚至会将照片返回您的应用,以便您使用。对用户而言,就好像相机真正是您应用的组成部分。

当系统启动某个组件时,会启动该应用的进程(如果尚未运行),并实例化该组件所需的类。 例如,如果您的应用启动相机应用中拍摄照片的 Activity,则该 Activity 会在属于相机应用的进程,而不是您的应用的进程中运行。因此,与大多数其他系统上的应用不同,Android 应用并没有单一入口点(例如,没有 main() 函数)。

由于系统在单独的进程中运行每个应用,且其文件权限会限制对其他应用的访问,因此您的应用无法直接启动其他应用中的组件, 但 Android 系统却可以。因此,要想启动其他应用中的组件,您必须向系统传递一则消息,说明您想启动特定组件的 Intent。 系统随后便会为您启动该组件。

Activating components - 启动组件

四种组件类型中的三种 — Activity、服务和广播接收器 — 通过名为 Intent 的异步消息进行启动。Intent 会在运行时将各个组件相互绑定。您可以将 Intent 视为从其他组件请求操作的信使,无论组件属于您的应用还是其他应用。

Intent 使用 Intent 对象创建,它定义的消息用于启动特定组件或特定类型的组件 — Intent 可以是显式的,也可以是隐式的。

对于 Activity 和服务, Intent 定义要执行的操作(例如,「查看」或「发送」某个内容),并且可以指定要执行操作的数据的 URI(以及正在启动的组件可能需要了解的信息)。 例如, Intent 传达的请求可以是启动一个显示图像或打开网页的 Activity。 在某些情况下,您可以启动 Activity 来接收结果,在这种情况下,Activity 也会在 Intent 中返回结果。例如,您可以发出一个 Intent,让用户选取某位联系人并将其返回给您 - 返回 Intent 包括指向所选联系人的 URI。

对于广播接收器, Intent 只会定义要广播的通知(例如,指示设备电池电量不足的广播只包括指示「电池电量不足」的已知操作字符串)。

不像 activity、服务和广播接收器,Intent 不会启动另一个组件类型 - 内容提供程序,后者会在成为 ContentResolver 的请求目标时启动。 内容解析程序通过内容提供程序处理所有直接事务,使得通过提供程序执行事务的组件可以无需执行事务,而是改为在 ContentResolver 对象上调用方法。 这会在内容提供程序与请求信息的组件之间留出一个抽象层(以确保安全)。

每种类型的组件有不同的启动方法:

如需了解有关 Intent 用法的详细信息,请参阅 Intent 和 Intent 过滤器文档。

Widgets, Containers, and Resources

大部分关注 Android 应用程序开发的内容都在 UI 层和 activities 上。 尽管欢迎您使用 2D(Canvas)和 3D(OpenGL)API 以及更专用的 GUI,但大多数 Android activities 都使用所谓的「the widget framework(小部件框架)」来渲染其用户界面。

在 Android 术语中,小部件(widget)是用户界面的「微(micro)」单位。 字段(field)、按钮(button)、标签(label)、列表(list)等都是小部件。 因此,您的 activity 的 UI 由一个或多个这些小部件组成。 这是大多数 UI 工具箱中常见的方法,因此很可能您已经在以前的其他地方使用过按钮、标签、字段和类似的小部件。

如果你有多个小部件 - 这是相当典型的 - 你需要告诉 Android 这些小部件是如何在屏幕上组织的。为此,您将使用各种称为布局管理器(layout manager)的容器类。这些将允许您根据需要将事物(thing)放入行、列或更复杂的布局中。

要描述容器(container)和小部件的连接方式,通常会创建一个布局资源文件(layout resoure file)。 Android 中的资源(resoure)指的是像应用程序使用的图像、字符串和其他材料,但不是某些编程语言源代码的形式。用户界面布局(UI layout)是另一种资源。您将使用结构化工具(如 IDE 的拖放式 GUI 构建器)或手工以 XML 格式创建这些布局。

有时,您的用户界面(UI)可以在各种设备上工作:手机、平板电脑、电视机等。有时,您的用户界面(UI)需要针对不同的环境量身定制。您将能够将资源投入到资源集(resource sets)中,以指示在什么情况下可以使用这些资源(例如,将这些资源用于普通大小的屏幕,但将其他资源用于大屏幕)。

Apps and Package

给定源代码和相关的资源,Android 构建工具将为您提供一个应用程序。该应用程序以 APK 文件的形式出现。这是您将上传到 Play 商店或通过其他方式发布的 APK 文件。

每个 Android 应用程序都有一个包名称,也称为应用程序 ID。包名必须满足三个要求:

  1. 它必须是一个有效的 Java 包名称,因为某些 Java 源代码将由此包中的 Android 构建工具生成
  2. 在同一部设备上不能同时存在具有同一个应用程序 ID 的两个应用程序
  3. 没有具有相同应用程序 ID 的两个应用程序可以上传到 Play 商店

当您创建 Android 项目 - 源代码和那些资源的仓库,您将声明要为您的应用使用什么包名称。

通常,您将按照 Java 软件包名称「反向域名」约定(例如,com.example.android.foo)选择软件包名称。这样,域名系统可以确保您的软件包名称前缀(com.example)是唯一的,并且由您确定软件包名称的其余部分将您的应用程序与其他应用程序区分开来。

Dalvik and ART

就 Android 而言,Dalvik 和 ART 是虚拟机(VM)。 虚拟机被许多编程语言使用,如Java、Perl 和 Smalltalk。 Dalvik 和 ART 的设计工作与Java VM 非常相似,但针对嵌入式 Linux 环境进行了优化。

首先,两者之间的区别在于,ART 在 Android 5.0 和更高版本上使用,而Dalvik 则在较旧的设备上使用。 事实上,这个故事比这更复杂,但是现在是这样做的。

所以,当有人编写 Android 应用程序时,真正发生的事情是:

  1. 开发人员编写 Java 语法源代码,利用 Android 项目和第三方发布的类库。
  2. 开发人员使用 Java SDK 附带的 javac 编译器将源代码编译为 Java VM 字节码。
  3. 开发人员将 Java VM 字节码转换为 Dalvik VM 字节码,该字节码与其他文件一起打包到带有 .apk 扩展名(APK 文件)的 ZIP 存档中。
  4. Android 设备或模拟器运行 APK 文件,导致字节码由 Dalvik 或 ART VM 的实例执行。

从您的角度来看,这些大部分都被构建工具隐藏起来。您将 Java 源代码倒入顶端,APK 文件出现在底部。

但是,Dalvik VM 和传统 Java VM 之间的差异将会不时地影响应用程序开发人员。

Processes and Threads

当你的应用程序运行时,它会在它自己的进程中运行。 这与其他传统操作系统没有显着不同。 Dalvik 的一部分魔力使得许多进程可以同时运行多个Android应用程序,而不会消耗大量的 RAM。

Android 也会设置一批线程来运行你的应用程序。 您的代码将在大多数时间执行的线程被不同地称为「主应用程序线程」或「UI 线程」。 你不必设置它,但是,你需要注意你在那个线程上做了什么,不做什么。 欢迎您 fork 自己的线程开展工作,这很常见,但在某些地方,Android 会在幕后为您处理。

Gradle

Gradle Files

Android Studio 项目通常有两个 build.gradle 文件,一个在项目级别(project level),一个在「模块(module)」级别。

The Project-Level File

项目目录中的 build.gradle 文件控制项目中所有模块的 Gradle 配置。 现在,很可能你只有一个模块,许多应用程序只使用一个模块。 但是,您可以在此项目中添加其他模块。

一个典型的顶级 build.gradle 文件:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        

        // 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

在 Groovy 术语中,「closure(闭包)」是用大括号({})包装的代码块。

Gradle 中的 buildscript 闭包是您配置 JAR 的地方,Gradle 本身将用于解释文件的其余部分。因此,在这里你没有像配置构建本身那样配置你的项目。

buildscript 闭包内的 repositories 闭包表明插件可以来自哪里。 在这里,jcenter() 是一个内置的方法,教授 Gradle 关于 JCenter 的知识,这是获取开源库的热门地点。同样,google() 是一个内置的方法,教 Gradle 关于一个可以从 Google 下载插件的网站。

dependencies 闭包表明能够运行构建脚本的其余部分需要什么。 · classpath 'com.android.tools.build:gradle:3.0.0' 并没有被 Gradle 团队特别详细记录。但'com.android.tools.build:gradle:3.0.0' 部分意思是:

  • 找到 com.android.tools.build 插件组
  • 在该组中找到 Gradle artifact
  • 确保我们有插件的 3.0.0 版本

第一次使用如上所示的 buildscript 闭包运行构建时,Gradle 会注意到你没有这个依赖关系。然后,它会从 Google 下载该 artifact,因为 Google 现在在其自己的网站上提供其插件。

有时,版本的最后一部分被替换为 + 号(例如,3.0.+)。 这会告诉 Gradle 下载最新版本,从而自动升级到最新的修补程序级别(例如,某些时候为3.0.1)。

allprojects

allprojects 闭包说「将这些设置应用于此项目中的所有模块」。 在这里,我们将 jcenter()google() 设置为查找项目中任何模块中使用的库的位置。 我们将在我们的项目中使用大量类库 - 在 allprojects 中设置这些「repositories」可以让我们更简单地请求它们。

The Module-Level Gradle File

在你的 app/ 模块中,你还会找到一个 build.gradle 文件。这个模块具有独特的设置,独立于您的项目将来可能使用的任何其他模块。

一个典型的模块级 build.gradle 文件:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.commonsware.myapplication"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

dependencies

这个 build.gradle 文件也有一个 dependencies 闭包。然而顶级 build.gradle 文件中的 buildscript 闭包中的 dependencies 闭包是针对构建过程使用的库,而模块的 build.gradle 文件中的 dependencies 闭包用于该模块中代码使用的库。

android

android 闭包包含所有 Android 特定的配置信息。这个闭包是 Android 插件启用的地方,插件本身来自顶部的 apply plugin: 'com.android.application' 行,以及来自项目级 build.gradle 文件的 classpath 行。

android 闭包中的内容与清单文件中的内容有关。

the Manifest file - 清单文件

在 Android 系统启动应用组件之前,系统必须通过读取应用的 AndroidManifest.xml 文件(「清单」文件)确认组件存在。 您的应用必须在此文件中声明其所有组件,该文件必须位于应用项目目录的根目录中。

除了声明应用的组件外,清单文件还有许多其他作用,如:

  • 确定应用需要的任何用户权限,如互联网访问权限或对用户联系人的读取权限
  • 根据应用使用的 API,声明应用所需的最低 API 级别
  • 声明应用使用或需要的硬件和软件功能,如相机、蓝牙服务或多点触摸屏幕
  • 应用需要链接的 API 库(Android 框架 API 除外),如 Google 地图库

任何 Android 应用程序的基础都是清单(manifest)文件:AndroidManifest.xml。这将在您典型 Android Studio 项目的应用模块的src/main/ 目录(主要源代码集)中。

在这里您将声明应用程序内部的内容 - activity、服务等等。您还可以指出这些块(piece)如何附加到整个 Android 系统上;例如,您可以指示哪些 activity(或多个 activities)应该出现在设备的主菜单(a.k.a.,启动器 - launcher)上。

当您创建应用程序时,您将获得为您生成的初始清单(starter manifest)。对于一个简单的应用程序,提供一个单一的 activity,没有别的,自动生成的清单可能会工作得很好,或者可能需要一些小修改。另一方面,Android API 演示套件的清单文件长度超过 1,000 行。您的 Android 应用产品可能会落在中间的某个地方。

一些条目可以定义在清单和 build.gradle 文件中。将这些东西放入清单仍然有效。对于 Android Studio 用户,您可能会使用 Gradle 文件,而不是在清单中定义这些通用元素。

Declaring components

清单文件的主要任务是告知系统有关应用组件的信息。可以使用以下元素声明所有的应用组件:

有关清单文件的更多信息,可参看 AndroidManifest.xml

Manifest 和 Gradle 相同之处

有几个关键条目可以在 manifest 中定义,并可以在 build.gradle 语句中重写。这些条目对我们的 Android 应用程序的开发和操作也非常重要。

Package Name and Application ID

所有 manifest 文件的根(root)都是一个显而易见的 manifest 要素:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.commonsware.empublite">

请注意 android 命名空间声明。您只能在许多属性上使用命名空间,而不是元素(例如,,而不是android:manifest)。

您需要在 元素上提供的最大信息是 package 属性。

package 属性将始终需要在 manifest 中,即使对于 Android Studio 项目也是如此。 package 属性将控制为我们生成的一些源代码的位置,特别是一些 R 和 BuildConfig 类。

由于 package 值用于 Java 代码生成,因此它必须是有效的 Java 包名称。 Java 约定认为软件包名称应该基于您拥有域名的反向域名(例如,com.example.myapplication)。那样的话,任何人都不可能不小心碰到同一个名字。

package 也可作为我们应用程序的默认「应用程序ID(application ID)」。 这需要是一个唯一的标识符,以便:

  • 使用相同的应用程序 ID 不能同时在同一台设备上安装两个应用程序
  • 没有两个应用程序可以使用相同的应用程序 ID 上传到 Play 商店(并且其他发行渠道可能具有相同的限制)

默认情况下,应用程序 ID 是 package 的值,但 Android Studio 用户可以在其 Gradle 构建文件中覆盖它。具体来说,在 android 闭包内部可以是一个 defaultConfig 闭包,并且里面可以有一个 applicationId 语句:

android {
    // other stuff

    defaultConfig {
        applicationId "com.commonsware.myapplication"
        // more other stuff
    }
}

Android Studio 用户不仅可以在 defaultConfig 闭包中覆盖应用程序ID,还可以针对不同的场景使用不同的应用程序ID值,例如调试版本与发布版本。

minSdkVersion and targetSdkVersion

您的模块的 build.gradle 文件中的 android 闭包中的 defaultConfig 闭包有一对名为 minSdkVersion 和 targetSdkVersion 的属性。从技术上讲,这些覆盖值可以通过 manifest 中的 元素定义,尽管现在很少有项目会有这样的元素。

其中更关键的是 minSdkVersion。这表明什么是您使用您的应用程序测试的最旧版本的 Android。该属性的值是一个表示 Android API 级别的整数。因此,如果您仅在 Android 4.1 和更新版本的 Android上 测试应用程序,则可以将 minSdkVersion 设置为 16。初始值是您在 Android Studio 创建项目时请求的值。

您也可以指定 targetSdkVersion。这表示您在编写代码时所考虑的 Android 版本。如果您的应用程序在较新版本的 Android 上运行,Android 可能会尝试一些方法来提高您的代码与新版 Android 所做更改的兼容性。如今,大多数Android 开发者都应该指定 15 或更高版本的目标 SDK 版本。

Version Code and Version Name

同样,defaultConfig 闭包有 versionCode 和 versionName 属性。原则上,它们会在 manifest 中的根 元素上覆盖 android:versionName 和 android:versionCode 属性,尽管您不会找到许多使用这些 XML 属性的项目。

这两个值代表您的应用程序的版本。versionName 值是用户在其「设置」应用程序的「应用程序详细信息」屏幕中为您的应用程序查看版本指示器的内容。此外,如果您以这种方式分发您的应用程序,则 Play 商店列表会使用版本名称。版本名称可以是您想要的任何字符串值。

另一方面,versionCode 必须是一个整数,而较新的版本必须具有比旧版本更高的版本代码。Android 和 Play 商店会将新 APK 的版本代码与已安装应用程序的版本代码进行比较,以确定新 APK 是否确实是更新。典型的方法是在 1 处启动版本代码,并在应用程序的每个产品版本中增加版本代码,但是如果您愿意,您可以选择其他约定。在开发过程中,你可以单独留下这些,但当你转向生产时,这些属性将会非常重要。

其他需关注的 Gradle 条目

android 闭包有一个 compileSdkVersion 属性。compileSdkVersion 指定要编译的 API 级别,通常是一个简单的 API 级别整数(例如 19)。这表示在编写应用程序时可以使用哪些 Java 类、方法等。通常,将其设置为 Android 的最新版本。

android 闭包可能有一个 buildToolsVersion 属性。 buildToolsVersion 表示您希望用于此项目的 Android SDK 构建工具的版本。Android Gradle 插件实际上是一系列「构建工具」的简洁包装,它可以处理从项目中创建 APK 的大部分工作。如果你的 android 闭包没有 buildToolsVersion,那么 Android Gradle 插件将使用它自己的这些构建工具的默认版本,而对于许多项目来说,这就足够了。

Manifest 的其余部分

并非在 manifest 中的所有内容都可以在 Gradle 构建文件中重写。以下是一些总是在 manifest 中定义的关键项目,而不是在 build.gradle 文件中。

应用的 application

在您的初始项目 manifest 中, 元素的主要子元素是 元素。

默认情况下,当您创建一个新的 Android 项目时,您会在 元素中获得一个 元素:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.commonsware.myapplication"
  xmlns:android="http://schemas.android.com/apk/res/android">

  <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>
  
</manifest>

元素为实现 activity 的类提供 android:name,为 activity 的显示名称提供 android:label,以及(有时)为描述此 activity 将在什么条件下显示的 子元素。元素将您的 activity 设置为在启动器(launcher)中显示,因此用户可以选择运行它。

在这种情况下,android:name 属性具有纯 Java 类名(MainActivity)。 有时候,您会看到带有完全限定名称的 android:name(例如,com.example.myapplication.MainActivity)。 有时,您会看到一个带有单个点作为前缀的 Java 类名(例如,.MainActivity)。MainActivity 和 .MainActivity 都引用了项目包中的 Java 类 - 它是在 元素的 package 属性中声明的。

支持多种屏幕

Android 设备具有各种屏幕尺寸,从 2.8 英寸的微型智能手机到 46 英寸的电视。Android 根据物理尺寸和通常查看的距离将它们分成四个桶:

  1. Small - 小(3“ 以下)
  2. Normal - 正常(3“ 到 4.5” 左右)
  3. Large - 大(4.5“ 到 10” 左右)
  4. Extra-large - 超大(超过 10 英寸)

默认情况下,您的应用程序将支持小屏幕和正常屏幕。它还将通过内置于 Android 的一些自动转换代码支持大型和超大型屏幕。

要真正支持所需的所有屏幕大小,您应该考虑在 manifest 中添加 元素。这枚举了您明确支持的屏幕大小。例如,如果您为大型或超大型屏幕提供自定义 UI 支持,则您将需要 元素。所以,当 manifest 文件开始起作用时,处理多种屏幕大小是您想要考虑的事情。

你用一个元素类似于:

<supports-screens
  android:largeScreens="true”
  android:normalScreens="true"
  android:smallScreens="false"
  android:xlargeScreens="true" />

其他的东西

您会发现将其他元素添加到 manifest 中,例如:

  • ,告诉用户您需要使用某些设备功能的权限,例如访问 Internet
  • ,告诉 Android 您需要该设备具有某些功能(例如相机),因此您的应用不应安装在缺乏此功能的设备上
  • ,用于获取特定 Android 扩展所需的各种信息,如FileProvider。

References

  1. Application Fundamentals