Android开发之旅:活动与任务

简介:

 ——坚持就是胜利!关键是你能坚持吗?不能的话,你注定是个失败者。

引言

关于Android应用程序原理及术语,前面两篇:

介 绍了Android应用程序的进程运行方式:每一个应用程序运行在它自己的Linux进程中。当应用程序中的任何代码需要执行时,Android将启动进 程;当它不在需要且系统资源被其他应用程序请求时,Android将关闭进程。而且我们还知道了Android应用程序不像别的应用程序那样(有Main 函数入口点),它没有单一的程序入口点,但是它必须要有四个组件中的一个或几个:活动(Activities) 、服务(Services) 、广播接收者(Broadcast receivers) 、内容提供者(Content providers)。且分别介绍它们的作用,及如何激活和关闭它们、如何在清单文件(AndroidManifest.xml)中声明它们及 Intent过滤器。

在简单回顾之后,本篇还是继续介绍Android应用程序原理及术语——活动与任务(Activities and Tasks)。

  • 1、活动与任务概述
  • 2、亲和度和新任务(Affinities and new tasks)
  • 3、启动模式(Launch modes)
  • 4、清除栈(Clearing the stack)
  • 5、启动任务(Starting tasks)

1、活动与任务概述

如前所述,一个活动(activity)能启动另一个活动,包括定义在别的应用程序中的活动。再次举例说明,假设你想让用户显示某地的街道地图。而且已经有了一个活动能做这个事情(假设这个活动叫做地图查看器),因此你的活动要做的就是将请求信息放进一个Intent对象,然后将它传给startActivity()。地图查看器就启动并显示出地图。当用户点击返回按钮之后,你的活动就会重新出现在屏幕上。

对用户来说,这个地图查看器就好像是你的应用程序的活动一样,虽然它定义在其他的应用程序中且运行在那个应用程序的进程中。Android将这些活动保持在同一个任务task)中以维持用户的体验。简单地讲,任务是用户体验上的一个“应用程序”,是排成堆栈的一组相关活动。 栈底的活动(根活动)是起始活动——一般来讲,它是用户在应用程序启动器(也称应用程序列表,下同)中选择的一个活动。栈顶的活动是正在运行的活动——它 关注用户的行为(操作)。当一个活动启动另一个,新的活动被压入栈顶,变为正在运行的活动。前面那个活动保存在栈中。当用户点击返回按钮时,当前活动从栈 顶中弹出,且前面那个活动恢复成为正在运行的活动。(关于栈的先进后出特性不要我在这里讲吧!)

栈中包含对象,因此如果一个活动(再次说明:活动是Activity的子类) 启动了多个实例——例如多个地图查看器,则栈对每个实例有一个独立的入口。(可以这样理解:假设有四个活动以这样的顺序排在栈中——A-B-C-D,现在 又有一个C的实例,则栈变成A-B-C-D-C,这两个C的实例是独立的。)栈中的活动从不会被重新排列,只会被压入、弹出。这点很好理解,因为活动的调 用顺序是固定的。

任务是一栈的活动,而不是清单文件中声明的某个类或元素,因此无法独立于它的活动为任务赋值。整个任务的值是在栈底活动(根活动)设置的。例如,下节将讨论的“任务亲和度”,亲和度信息就是从任务的根活动中获取的。

一 个任务的所有活动作为一个整体运行。整个任务(整个活动栈)可置于前台或发送到后台。例如,假设当前任务有四个活动在栈中——三个活动在当前活动下面。用 户按下HOME键,切换到程序启动器,并选择一个新的应用程序(实际上是一个新的任务)。当前任务进入后台,新任务的根活动将显示。接着,过了一会,用户 回到主屏幕并再次选择之前的应用程序(之前的任务)。那个任务栈中的所有四个活动都变为前台运行。当用户按下返回键时,不是离开当前任务回到之前任务的根 活动。相反,栈顶的活动被移除且栈中的下一个活动将显示。

上面所描述的是活动和任务的默认行为,但是有方法来改变所有这些行为。活动与任务之间的联系及任务中活动的行为,是由启动活动的Intent对象的标志(flags)和清单文件中活动<activity>元素的属性共同决定的。

在这方面,主要的Intent标志有:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  • FLAG_ACTIVITY_SINGLE_TOP

主要的<activity>属性有:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

接下来的小节将讨论这些标志和属性的作用,他们怎么交互,及使用的注意事项。

2、亲和度和新任务(Affinities and new tasks)

默认情况下,一个应用程序的所有活动互相之间都有一个亲和度(affinity——也就是说,他们属于同一个任务的偏好(preference)。然而,也可以通过<activity>元素的taskAffinity属性为每个活动设置个体亲和度。定义在不同应用程序中的活动能够共享亲和度,同一个应用程序中的活动可以分配不一样的亲和度。亲和度发挥作用的两种情况:1)启动活动的Intent对象包含FLAG_ACTIVITY_NEW_TASK标志时;2)一个活动的allowTaskReparenting属性为"true"时。

  • FLAG_ACTIVITY_NEW_TASK标志

如前所述,默认情况下,一个新的活动被启动到调用startActivity()方法的活动所在的任务。它被压入调用它的活动的栈中。但是,如果传递给方法的Intent对象包含FLAG_ACTIVITY_NEW_TASK标志,系统找一个不同的任务容纳活动。通常,顾名思义它表示一个新任务。但是,他并非一定如此。如果已经存在一个任务与新活动亲和度一样,该活动将启动到该任务。如果不是,则启动一个新任务。

  • allowTaskReparenting属性

如果一个活动的allowTaskReparenting属性为"true", 它可以从启动它的任务转移到与它有亲和度并转到前台运行的任务中。例如,假设一个天气预报的活动,但选择城市是一个旅游应用程序的一部分。它与同一个应用 程序中的其他活动具有相同的亲和度,且允许重新选择父活动(reparenting)。你的一个活动启动天气预报活动,因此他初始是跟你的活动属于同一个 任务。但是,当旅游应用程序切换到前台运行时,天气预报活动将被重新分配和显示到该任务。

如果一个.apk文件,从用户的角度看包含不止一个“应用程序”,你可能要为与他们有关的些活动指定不一样的亲和度。

3、启动模式(Launch modes)

有四种不同的启动模式可以分配到<activity>元素的launchMody属性:

  • "standard"(默认模式)
  • "singleTop "
  • "singleTask"
  • "singleInstance"

这些模式的在以下四方面不同:

  • 哪个任务将持有响应意图(intent)的活动。对"standard"和"singleTop "模式,是产生意图的任务(调用startActivity()方法)——除非Intent对象包含FLAG_ACTIVITY_NEW_TASK标志。在那种情况下,像上一节亲和度和新任务(Affinities and new tasks)所描述的那样选择一个不同的任务。
    相反,"singleTask"和"singleInstance"模式,总是将活动标记为一个任务的根活动。他们定义一个任务,而从不启动到其他任务。
  • 活动是否可以实例化多次。"standard"或"singleTop "活动可以实例化多次。这些实例可以属于多个任务,且一个给定任务可以包含同一个活动的多个实例。
    相反,"singleTask"和"singleInstance"活动仅可以被实例化一次。因为这些活动是一个任务的根,这个限制意味着设备上一个时间只有不多于一个任务的实例。
  • 是否允许实例所在任务有其他活动。"singleInstance"活动所在任务只有它一个活动。如果他启动别的活动,那些活动将启动到不同的任务中,无论它的模式如何——就好像Intent对象包含FLAG_ACTIVITY_NEW_TASK标志。在所有其他方面,"singleInstance"模式等同于"singleTask"模式。
    其它三种模式允许多个活动属于一个任务。"singleTask"活动总是任务的根活动,但是它能启动其他活动到它的任务。"standard"或"singleTop "活动的实例可以出现的栈中的任何位置。
  • 响应一个意图时是否需要生成类的新实例。对于默认的"standard"模式,创建新的实例去响应每一个新的意图。每个实例仅处理一个意图。对于"singleTop "模式,一个类已存在的实例可以重新用了处理新的意图,如果它位于目标任务的活动栈的栈顶。如果不是在栈顶,就不可以重用。相反,将创建一个新的实例并压入栈顶。
    例如,一个任务的活动栈由根活动A、B、C和D组成,顺序为A-B-C-D。当一个意图到达请求类型D时,如果D是默认的"standard"模式,将产生D类的新实例且栈变为A-B-C-D-D。然而,如果D的启动模式是"singleTop ",已存在的D实例将去处理新的意图(因为它在栈顶)且栈仍然是A-B-C-D。
    如果,另一方面,到达的意图是请求类型B时,一个B的新实例将启动而不管B的模式是"standard"还是"singleTop "(因为B不是在栈顶),因此栈的结构为A-B-C-D-B。
    如前所述,"singleTask"和"singleInstance"活动仅可以被实例化一次,因此他们的实例将处理所有的新意图。一个"singleInstance"活动总是在栈顶(因为仅有一个活动在任务中),因此它总是在可以处理意图的位置。然而,一个"singleTask"活动在栈中可能有或可能没有其他活动在它上面。如果有,即它不在处理意图的位置,意图会被丢弃(即使意图被丢弃了,它的到来使任务转到并保持在前台运行)

当一个已存在的活动被请求处理一个新的意图,Intent对象将通过onNewIntent()调用传到活动。(产生启动活动的意图对象可以由getIntent()获取。)

注意到当一个活动的新实例被创建去处理新意图时,用户总是可以按返回键返回到之前的状态(之前的活动)。但是当一个已存在的活动实例去处理新意图是,用户不可以按返回键返回到意图到达之前的状态。

4、清除栈(Clearing the stack)

如果用户离开一个任务很长时间,系统将会清除根活动之外的活动。当用户再次返回到这个任务时,像用户离开时一样,仅显示初始的活动。这个想法是,一段时间后,用户可能已经放弃之前做的东西,及返回任务做新的事情。这是默认情况,有些活动属性可以用来控制和改变这个行为。

  • alwaysRetainTaskState属性
    如果在任务的根活动中这个属性被设置为"true",刚才描述的默认行为将不会发生。任务将保留所有的活动在它的栈中,甚至是离开很长一段时间。
  • clearTaskOnLaunch属性
    如果在任务的根活动中这个属性被设置为"true",只有用户离开就清除根活动之外的活动。换句话说,它与alwaysRetainTaskState截然相反。用户总是返回到任务的初始状态,甚至是只离开一会。
  • finishOnTaskLaunch属性
    这个属性类似于clearTaskOnLaunch,但是它作用于单个活动,而不是整个任务。而且它能移除任何活动,包括根活动。当它被设置为"true",任务本次会话的活动的部分还存在,如果用户离开并返回到任务时,它将不再存在。

有其他的方法强制从栈中移除活动。如果Intent对象包含FLAG_ACTIVITY_CLEAR_TOP标志,目标任务已经有一个指定类型的活动实例,栈中该实例上面的其它活动将被移除而使它置于栈顶响应意图。如果指定的活动的启动类型是"standard",它自己也将被移除出栈,且一个新的实例将被启动去处理到来的意图。这是因为当模式是"standard"时,总是创建一个新的实例去处理新的意图。

FLAG_ACTIVITY_CLEAR_TOP标志经常与FLAG_ACTIVITY_NEW_TASK一起使用。当一起使用时,这些标志的方式是定位到另一个任务中的已存在的活动并把它放到可以处理意图的位置。

5、启动任务(Starting tasks)

通过给定活动一个意图过滤器"android.intent.action.MAIN"作为指定行为(action)和"android.intent.category.LAUNCHER"指定种类(category),活动就被设置为任务的入口点了。上篇Android开发之旅:应用程序基础及组件(续)第四节Intent过滤器中我们举了这样一个例子,它将导致该活动的图标(icon)和标签(label)显示在应用程序启动器,给用户启动它或启动之后任意时候返回到它。

它的第二个功能非常重要:用户可以离开任务且之后可以返回到它。基于这个原因,两个启动模式"singleTask"和"singleInstance"标记活动总是初始化一个任务来响应意图,仅可以使用在有MAINLAUNCHER过滤器的活动中。想象一下,如果没有这个过滤器将会发生什么:一个意图启动一个"singleTask"活动,开始一个新任务,用户在任务中做一些操作。然后用户按下HOME键,任务现在退到后台运行且被主屏幕遮蔽住。而且,由于活动不在应用程序启动器中显示,用户无法再返回。

类似的困难也出现在FLAG_ACTIVITY_NEW_TASK标志。如果这个标志导致一个活动开始一个新的任务且用户按HOME键离开它,就必须要有某种方法是用户能够导航回来。一些实体(如通知管理器)总是在外部任务启动活动,从不作为他们自己的一部分,因此他们总是将带FLAG_ACTIVITY_NEW_TASK标志的意图传到startActivity()方法启动活动。如果你有活动能调用外部实体,可以使用此标志,注意用户有一个独立的方式返回到开始的任务。

如果您希望用户离开活动后就不能再回到这个活动,可以将<activity>元素的finishOnTaskLaunch属性设置为"true"。可以参见清除栈那节。


相关文章
|
1月前
|
存储 IDE 开发工具
探索Android开发之旅:从新手到专家
【10月更文挑战第26天】在这篇文章中,我们将一起踏上一段激动人心的旅程,探索如何在Android平台上从零开始,最终成为一名熟练的开发者。通过简单易懂的语言和实际代码示例,本文将引导你了解Android开发的基础知识、关键概念以及如何实现一个基本的应用程序。无论你是编程新手还是希望扩展你的技术栈,这篇文章都将为你提供价值和启发。让我们开始吧!
|
2月前
|
存储 安全 Android开发
探索Android开发之旅:从新手到专家的蜕变之路
【10月更文挑战第8天】在这篇文章中,我们将共同踏上一段激动人心的旅程,深入探索Android开发的奥秘。无论你是初涉编程世界的新手,还是渴望提升技能的开发者,这里都有你需要的知识与启示。通过简洁明了的语言和实际案例,我们将一起解锁Android开发的核心概念、掌握关键技能,并最终实现从新手到专家的华丽转变。
|
3月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
414 3
|
4月前
|
存储 IDE Java
探索安卓应用的构建之旅:从新手到专家
【8月更文挑战第31天】 本文是一篇面向初学者和有一定基础的开发者的技术性文章。我们将一起踏上一段激动人心的旅程,深入了解如何从零开始构建一个安卓应用。文章将引导你理解安卓开发的基础知识,掌握核心概念,并通过实际代码示例加深你的理解和技能。无论你是刚刚接触安卓开发,还是希望提升现有技能,这篇文章都将为你提供宝贵的信息和实用的技巧。准备好了吗?让我们一起开始吧!
|
5月前
|
算法 JavaScript 前端开发
代码之旅:从新手到熟练工的蜕变
【7月更文挑战第14天】编程世界如同一片未知的海洋,每个初学者都是初次扬帆出海的探险者。本文将通过个人的学习历程,揭示如何从编程新手成长为一名能够独立解决问题的熟练工。我们将探索学习方法、实践技巧以及心态调整等关键要素,旨在为同样处于旅程中的编程爱好者提供一份实用的指南。
|
7月前
|
程序员 Android开发 Java
android开发基础机构,真的太香了
android开发基础机构,真的太香了
|
机器学习/深度学习 人工智能 弹性计算
【MindStudio训练营第一期】--【新手班】学习笔记②
【MindStudio训练营第一期】--【新手班】学习笔记②
|
7月前
|
弹性计算 小程序 数据安全/隐私保护
大咖与小白的日常:10分钟部署一个年会抽奖程序
本教程指导您3分钟部署一个年会抽奖程序,大屏幕滚动起来吧!
177 6
大咖与小白的日常:10分钟部署一个年会抽奖程序
|
小程序
第一款小游戏做完了,邀请好朋友来提前体验
陆陆续续的大半个月的时间,一边做开发,一边写教程着实不轻松,不过,小蚂蚁的第一款小游戏终于完成了。今天已经将其提交到微信小游戏平台开始审核,如果顺利的话,大概一两个天之后,游戏会正式上线。
91 0
|
iOS开发 MacOS Python
抽奖过程公布,我用了一款有故事的抽奖工具
之前学委发表了一篇文末抽奖的文章:Python中处理字符串的常用函数汇总【文末送书】
206 0
抽奖过程公布,我用了一款有故事的抽奖工具