前言
这一篇会很长,如果要把整篇文章看完是十分难的,如果能把整篇文章从头到尾看完,那说明你拥有超乎常人的毅力。这篇文章的篇幅较长,各位可以按照需要学习的内容来阅读所需要学习的部分,还是那句话:“知己知彼,百战不殆。”
应用清单文件
在讲四大组件之前,我们需要先了解一下应用清单文件——AndroidManifest.xml,前面虽然对该文件或多或少的讲了一点,但是这里我们要更加的深入了解一下AndroidManifest.xml这个应用清单文件。
应用清单文件必须声明应用的软件包名称、应用的组件、应用为访问系统或其他应用的受保护部分所需的权限、应用需要的硬件和软件功能。如果开发者是使用 Android Studio构建的应用,则系统会为开发者创建清单文件,并在开发者构建应用时(尤其是在使用代码模板时,例如使用Activity 模板时)添加大部分基本清单元素。
软件包名称和应用 ID
清单文件的根元素需包含应用软件包名称的属性,应用软件包名称与项目目录结构相匹配,例如应用软件包名称为"com.example.myapp";
应用软件包名称属性是十分重要的,例如某app在应用清单文件中声明了以下根元素的属性:
...
从上方的清单中得知该app的包名为com.example.myapp,那我们可以推断出R.java类在com.example.myapp下创建,因为Android 构建工具会使用 package 属性的值(也就是包名)用作应用所生成 R.java 类的命名空间(命名空间看起来很高级,其实命名空间就是类所在的包名),这是其一;其二,Android 构建工具会使用包名解析清单文件中声明的任何相关类名称,例如:系统会将声明为 的 Activity 解析为 ,可以发现Android 构建工具把包名自动拼接在了android:name属性的值.MainActivity的前面。
因此,清单文件中的package属性应始终与项目中保存 Activity 以及其他应用代码的基础软件包的名称相匹配。当然你也可以在项目中加入其他子软件包(例如基础软件包为com.example.myapp,子软件包名称为purchases,那么子软件包完整包名为com.example.myapp.purchases),子软件包可以包含其他类文件,用于实现特定功能或模块,但无论是位于基础软件包中还是子软件包中的类文件,在使用R.java类时,都需要使用package属性的值来作为导入R.java类的命名空间。
当Android构建工具根据package属性的值执行上述任务(根据包名生成R.java类、解析清单文件中声明的任何相关类名称)后,Android构建工具会将package属性的值替换为项目的build.gradle文件中定义的applicationId属性的值。build.gradle文件是用于配置和构建Android项目的文件,在build.gradle文件中,开发者可以定义应用的applicationId属性的值,用于指定应用的唯一标识符。因为package 属性的这一最终值必须是通用唯一值,因为这是能确保在系统和 Google Play 中识别应用的唯一方式。
因此,总的来说package属性的最终值将由build.gradle文件中的applicationId属性的值决定。这个最终值必须是一个通用唯一标识符,以确保在系统和Google Play中能够唯一识别和标识应用。但请注意,APK 编译完成后,package 属性还可表示应用的通用唯一应用 ID,只是将package属性的值替换为项目的build.gradle文件中定义的applicationId属性的值,是值的替换,而不是属性的替换!
有时候清单中的 package 名称与 build.gradle 文件中 applicationId 的区别可能会令人感到有点困惑,但只要能保持二者一致,便无需担心出现相关问题。
应用组件
对于在应用中创建的每个应用组件,应用清单文件中必须声明相应的 XML 元素,例如:
如果在应用中创建此类组件的任何子类,但未在清单文件中对其进行声明,则系统便无法启动该子类。
必须使用 android:name 属性指定子类的名称,且其必须使用完整的软件包名称。但是对组件的声明有两种声明方法,下面简单讲讲这两种声明方法:
第一种:
如上所示android:name属性的值是activity组件的完整路径,而第二种就是之前讲过的android:name属性值的第一个字符是英文句号,应用的软件包名称应用的软件包名称。
第二种:
...
如果开发者拥有位于子软件包中(如在 com.example.myapp.purchases 中)的应用组件,则 name 值必须添加缺失的子软件包名称(如 ".purchases.PayActivity")或者使用完全限定的软件包名称(如"com.example.myapp.purchases.PayActivity")。
Intent 过滤器
在讲intent过滤器之前,先讲讲intent是什么:
应用的 Activity、服务和广播接收器均由 Intent 激活, Intent 是由 Intent 对象定义的消息,intent译为中文是"意图",用于描述要执行的操作,其中包括要执行操作的数据、应执行操作的组件类别以及其他相关说明。看起来intent很麻烦,但其基本用例主要包括以下三个:
启动 Activity
Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity(),开发者可以启动新的 Activity 实例。Intent 用于描述要启动的 Activity,并携带任何必要的数据。
如果开发者希望在一个Activity完成后接收结果,他们可以使用startActivityForResult()方法来启动另一个Activity。在被启动的Activity完成后,系统会将结果作为一个独立的Intent对象传递给调用者的Activity,并通过onActivityResult()回调方法将结果返回。这样,开发者就可以在调用者的Activity中处理返回的结果,并根据需要采取相应的操作。通过这种方式,开发者可以实现不同Activity之间的交互和数据传递。
启动服务
Service 是一个不使用用户界面而在后台执行操作的组件。
使用 Android 5.0(API 级别 21)及更高版本,开发者可以启动包含 JobScheduler 的服务。如需了解有关 JobScheduler 的详细信息,还请参阅作业调度程序 |安卓开发者 (google.cn)。
对于 Android 5.0(API 级别 21)之前的版本,开发者是可以使用 Service 类的方法来启动服务。通过将 Intent 传递给 startService(),开发者可以启动服务执行一次性操作(例如,下载文件)。Intent 用于描述要启动的服务,并携带任何必要的数据。
如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),开发者可以从其他组件绑定到此服务。
传递广播
广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast() 或 sendOrderedBroadcast(),开发者可以将广播传递给其他应用。
Intent 分为两种类型:
image-20230629230814043.png (27.05 KB, 下载次数: 0)
下载附件
2023-11-15 20:51 上传
隐式 Intent 如何通过系统传递以启动其他 Activity:
第一步、 Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。
第二步、 Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。找到匹配项之后,进行第三步操作。
第三步、 该系统通过调用匹配 Activity (Activity B) 的 onCreate() 方法并将其传递给 Intent,以此启动匹配 Activity。
使用隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
启动 Service 时,一定要始终使用显式 Intent,且不要为服务声明 Intent 过滤器。官方文档原文如下:
注意:为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会抛出异常。
具体的怎么构造intent、怎么接收隐式intent、怎么使用待定的intent等操作就不一一讲述了,有兴趣的可以移步到安卓官方文档Intent 和 Intent 过滤器 | Android 开发者 | Android Developers (google.cn)。
了解完了什么是intent,下面我们来正式讲讲什么是intent过滤器:
Intent 过滤器是应用清单文件中的一个表达式,用于指定该组件要接收的 Intent 类型。例如,通过为 Activity 声明 Intent 过滤器,开发者可以使其他应用能够直接使用某一特定类型的 Intent 启动 Activity。同样,如果开发者没有为 Activity 声明任何 Intent 过滤器,则 Activity 只能通过显式 Intent 启动。简而言之,intent过滤器就是帮助组件筛选指定要接收的隐式intent类型。
要公布应用可以接收哪些隐式 Intent,需要在应用清单文件中使用元素为每个应用组件声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 Intent 类型。仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。而显式 Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器如何均是如此。
至于怎么声明一个组件的intent过滤器我就不详解了,有兴趣的还是可以移步到安卓官方文档Intent 和 Intent 过滤器 | Android 开发者 | Android Developers (google.cn)。
图标和标签
以下为安卓官方文档的原文(我觉得说的挺好的,我就直接一字不差的截取了下来):
许多清单元素拥有 icon 和 label 属性,二者分别用于向对应应用组件的用户显示小图标和文本标签。
任何情况下,在父元素中设置的图标和标签都会成为所有子元素的默认 icon 和 label 值。 例如,在 元素中设置的图标和标签即为每个应用组件(如所有 Activity)的默认图标和标签。
只要以实现 Intent 的选项形式呈现组件,系统便会向用户显示在该组件的 中设置的图标和标签。 默认情况下,此图标继承自为父组件( 或 元素)声明的任何图标,但如果 Intent 过滤器提供唯一操作,且您希望该操作在选择器对话框中有更好的指示,则您可能需更改此图标。
权限
应用权限有助于保护对以下数据和操作的访问/执行权限,从而为保护用户隐私提供支持:
安卓应用程序必须要请求权限才能访问敏感的用户数据(如联系人和短信)或某些系统功能(如相机和互联网接入)。如果安卓应用程序需要在声明权限的情况下提供功能,那么需要在应用程序的清单文件中声明相应的权限。例如,需要发送短信的应用必须在清单中添加以下代码行:
...
在Android 6.0及更高版本中,用户可以在运行时授予或拒绝某些应用权限。但是,无论应用程序支持哪个Android版本,都必须在清单文件中使用元素声明所有权限请求。如果用户授予了相应的权限,应用程序就可以使用受保护的功能。否则,在应用程序尝试使用这些功能时,请求将失败。
除了保护用户数据和设备功能外,应用程序还可以使用权限来保护自身的组件。它可以使用Android定义的任何权限,如在android.Manifest.permission中列出的权限,也可以使用其他应用程序中声明的权限。此外,应用程序还可以定义自己的权限。新权限可以使用元素进行声明。
这意味着,应用程序可以使用现有的权限来限制对其组件的访问,或者可以定义自己的权限来控制对特定功能或数据的访问。通过为组件或功能添加权限,开发者就可以提高应用程序的安全性,并确保只有经过授权的用户可以访问相关部分。在向用户请求权限并根据其授予或拒绝的权限来调整和保护应用程序的行为时,开发者也可以为用户提供更好的用户体验和数据保护。
下面了解一下应用权限的工作流:
image-20230630213434798.png (86.31 KB, 下载次数: 0)
下载附件
2023-11-15 20:52 上传
英文难以看懂,那么下面为上图的中文意思:
(1):你能在不声明权限的情况下提供功能吗?
回答:如果是的,那就进行(2a)所示操作,如果不是,那就进行(2b)所示操作。
(2a):在不使用权限的情况下完成用例。
(2b):在应用程序的清单文件中声明权限。
如若进行(2b)操作,那下一步将进行(3)所示判断。如若进行(2a)操作,那就无需进一步操作。
(3):该权限是否为运行时权限?
回答:如果是的,那就进行(4)所示操作,如果不是,那就无需进一步操作。
(4):请求用户在运行时授予权限。
应用必须访问受限数据或执行受限操作才能实现某个用例,那么就声明相应的权限,否则无需声明任何权限。有些权限是用户安装应用时自动授予的权限,称为安装时权限。其他权限则需要应用在运行时进一步请求权限,此类权限称为运行时权限。
Android 将权限分为不同的类型,包括安装时权限、运行时权限和特殊权限。每种权限类型都指明了当系统授予应用该权限后,应用可以访问的受限数据范围以及应用可以执行的受限操作范围。
如果对权限具体内容感兴趣的可以去看Android 中的权限 | Android 开发者 | Android Developers (google.cn),这里我就不一一说明了。
设备兼容性
在应用清单文件中,还可以声明应用需要哪些类型的硬件或软件功能,进而声明应用与哪些类型的设备兼容。元素可以用于声明应用所需的硬件和软件功能。例如,如果应用在不带罗盘传感器的设备上无法获得基本功能,则可以使用以下清单标记将罗盘传感器声明为必需功能:
...
文件约定
本部分为安卓官方文档中的内容,介绍了通常适用于清单文件中的所有元素和属性的约定和规则,我只对其添加了部分解释。
元素
只有 和 元素是必需的。这两个元素都只能出现一次。大多数其他元素可以出现零次或多次。不过,为了使清单文件有用,其中某些元素必须存在。
所有值均通过属性进行设置,而不是通过元素内的字符数据设置。直白来说,就是在每个元素中需要使用属性来指定该元素的属性值,而不是在元素内部添加字符数据。
同一级别的元素通常不分先后顺序。例如,、 和 元素可按任意顺序放置。此规则主要有两种例外情况:
属性
严格意义上来说,所有属性都是可选的。不过,必须指定许多属性,这样元素才能实现其目的。
除了 根元素的某些属性之外,所有属性名称都以 android: 前缀开头,例如 android:alwaysRetainTaskState。由于该前缀是通用的,因此在按名称引用属性时,文档中通常会将其省略。
多个值
如果可以指定多个值,元素几乎总是重复,而不是在单个元素中列出多个值。例如,一个 intent 过滤器可以列出多项操作:
...
资源值
某些属性具有显示给用户的值,如 activity 的标题或您的应用图标。这些属性的值可能因用户的语言或其他设备配置不同而异(例如,根据设备的像素密度提供不同的图标大小),因此应该从资源或主题设置这些值,而不是将其硬编码到清单文件中。实际值随后可以根据不同设备配置提供的备用资源发生变化。
资源表示为值,格式如下:
"@[package:]type/name"
如果资源由您的应用提供(包括资源由库依赖项提供的情况,因为库资源会合并到您的资源中),则您可以省略 package 名称。当您想要使用来自 Android 框架的资源时,唯一一个其他有效的软件包名称是 android。
type 是资源的类型,如 string 或 drawable;name 是标识特定资源的名称。示例如下:
如需详细了解如何为项目添加资源,请参阅应用资源概览。
如需应用在主题背景中定义的值,第一个字符必须是 ?,而不是 @:
"?[package:]type/name"
字符串值
如果属性值是一个字符串,则使用双反斜线 (\\) 转义字符,如 \\n 表示换行符,\\uxxxx 表示 Unicode 字符。
清单元素参考
下表为安卓官方文档提供的 AndroidManifest.xml 文件中所有有效元素的简单说明。
[table]
[tr]
[td][/td]
[td][/td]
[/tr]
[tr]
[td]