为什么没有用于获取当前活动的Android API?

Bar*_*oyd 9 android android-intent android-service android-activity

问题是,我如何获得当前的活动?已经在Stackoverflow和其他网站上被问了几十次,并且有很多提议的方法.然而,它们都具有某种形式的缺点.

在这篇文章中,我假设Android的API中没有为此提供解决方案,例如:Application.getTask().getRootActivity().

如果有:-)那不是很好吗?

所以,要清楚,我不是要求答案我如何获得当前的活动?

相反,我问的是没有提供这种能力的原因.假设每个正在运行的应用程序都有一个任务(假设任务没有被清空)并且每个这样的任务都有一个root活动,那么提供对该根Activity的访问似乎很容易.

在没有提供这种访问的情况下,当它非常明确地需要时,这对我来说意味着Android架构有一些我不理解的基础.

我错过了什么?为什么Android API不提供此信息?

作为背景,这里是一个概述已经提出的一些方法的部分.我发现以下两个链接特别有用(下面的每个方法都在一个或两个链接中提供).

链接

途径

  • 静电钩
  • 反射
  • ActivityManager
  • 其他(Instrumentation,AccessibilityService,UsageStatsManager)`

ActivityManager

ActivityManager方法仅提供Activity类的名称,而不是当前的Activity实例.例如,对于Context实例c:

c.getSystemService().getActivityManager() .getAppTasks().get(0).getTaskInfo() .topActivity().getClassName()

反射

我最喜欢的是_AZ提出的反思,但这种方法很脆弱,因为它依赖于内部.我想从Android看到的是这种方法是通过标准API提供的,开发人员可以安全地依赖它.

静电钩

最常见的方法是使用静态挂钩来保存对当前运行的Activity的引用.钩子可以是按活动或按应用程序.通过保存/销毁钩子的值可以避免内存泄漏(例如,在onCreate()/ onDestroy(),onStart()/ onStop(),onPause()/ onResume()).但是,当涉及多个活动时(例如,由于生命周期重叠 - 见下文),可能会出现问题.

我实现了一个静态钩子方法,它执行以下操作(完全透明,我还没有实现#1 - 我目前正在使用per-Activity静态钩子,这是一个bug).

  1. 提供一个扩展Application以提供钩子的类.钩子包含一个堆栈; 堆栈中的每个节点都是一个简单的ActivityInfo类,它包含对Activity实例的引用以及该实例的状态(CREATED,STARTED,RESUMED).
  2. 提供一个名为ActivityTracker的类,用于扩展Activity.然后,我使用ActivityTracker扩展我的每个活动.ActivityTracker使用其生命周期回调来向/从堆栈推送/弹出自身并更新其状态 - 我的其他活动不必执行任何操作.

理论上,这将允许我始终知道任务的后台堆栈的完整状态 - 完整的活动集,包括根活动,以及它们的当前状态.然而,在实践中,有一个转折 - 当一个活动开始另一个活动时,它们的生命周期重叠.在此期间,在堆栈停止时偷看可能会产生意外的Activity实例.

来自:https://developer.android.com/guide/components/activities/activity-lifecycle.html#soafa,"协调活动":

以下是活动A启动活动B时发生的操作顺序:

  1. 活动A的onPause()方法执行.
  2. 活动B的onCreate(),onStart()和onResume()方法按顺序执行.(活动B现在具有用户关注点.)
  3. 然后,如果活动A在屏幕上不再可见,则执行onStop()方法

当然,这也可以管理.最重要的是,我们确实有一个可用于存储信息的全局上下文(应用程序),我们确实拥有有关活动生命周期转换的完整信息,因此我付出了足够的努力,我相信这种基于静态堆栈的方法可能会成为相当的子弹 - 证明.

不过最终

但最后我觉得我只是简单地重写代码,这些代码可能已经存在于内部,用于管理一个Activity后台堆栈,这就是为什么我要问(如果你忘记了):

为什么没有用于获取当前活动的Android API?


UPDATE


在这个更新中,我将总结我从这个主题以及我自己的实验和研究中学到的东西.希望这个摘要对其他人有用.

定义

我将根据https://developer.android.com/guide/components/activities/activity-lifecycle.html上的活动状态定义,对"活动可见性状态"使用以下定义.

-----------------------------------
Visibility State   Definition
-----------------------------------
Not Visible        Created+Stopped
Partially Visible  Started+Paused 
Fully Visible      Resumed
-----------------------------------
Run Code Online (Sandbox Code Playgroud)

问题

"当前活动"的定义是模糊的.当我使用它时,我指的是处于完全可见状态的单个活动.在任何给定的时刻,可能有也可能没有这样的活动.特别是,当活动A启动活动B时,A的onPause()被调用,然后是B的onCreate(),onStart()和onResume(),然后是A的onStop().在A的onPause()和B的onResume()之间有一段延伸,其中两者都不处于完全可见状态,因此没有当前活动(正如我定义的那样).当然,也存在后台线程可能想要访问当前活动的情况,并且可能存在或可能不存在活动,更不用说当前活动.

我也意识到我可能并不总是需要一个Current("完全可见")活动.在许多情况下,我可能只需要对现有活动的引用,无论它是否当前可见.此外,该引用可能只适用于任何Activity(对于我需要将一般的Activity引用传递给某些API方法的情况),或者它可能是一个特定的Activity子类实例(这样我就可以触发一些特定于该Activity的代码)子类).

最后,需要了解主UI循环器何时调用Activity生命周期回调以及如何处理配置更改等事件.例如,如果我使用当前处于"不可见"状态的Activity intance创建DialogFragment,它是否会显示,如果是,何时?类似地,事实证明由配置更改引起的onDestroy()和onCreate()方法包含在UI的消息队列中的相同消息中(请参阅Android UI线程消息队列调度顺序),因此不会有其他消息在这两个回调之间处理(在配置更改期间).理解这种级别的处理似乎是至关重要的,但是如果没有完全遗漏,那么它的文档就非常缺乏.

途径

以下是可用于解决上述大多数情况的方法集合.

背景

  • 为了讨论,假设活动A和活动B,其中A创建B.
  • 一般来说,可以通过在几乎任何类中使其"公共静态"来创建"全局"变量.从概念上讲,扩展Application类并将其添加到扩展类中会很好,但是如果这样做太多,它可以包含在(例如)其中一个Activity类中.

通用活动参考

  • 每当需要通用活动时都很有用.
  • 创建一个全局变量.在A和B中,让onCreate()将其设置为"this",onDestroy()将其设置为null.

最热门的活动参考

  • 每当您想要访问当前可见的活动时都很有用.
  • 创建一个全局变量.在A和B中,让onResume()将其设置为"this".除非所有活动都退出,否则此方法可以正常工作,在这种情况下,您可能需要创建一个单独的标志来指示该情况.(该标志可以是上面提到的通用活动参考实现.)

具体活动参考

  • 每当需要特定Activity子类实例的句柄时都很有用.
  • 在A和B中:在Activity子类本身中创建一个全局变量.让onCreate()将其设置为"this and onDestroy()将其设置为null.

应用背景

  • 每当需要跨越整个应用程序生命周期的上下文时,或者当您不关心使用特定的活动上下文时(例如,从后台线程创建Toast),这都很有用.
  • 您可以从Activity的getApplication()获取它并将其存储在静态钩子上.

处理配置更改

有时您可能希望仅在"活动"会话中停止/启动后台线程,其中我定义"会话"以包括可能由于配置更改而创建和销毁的一系列活动实例.在我的特定情况下,我有一个蓝牙聊天活动和一个相关的后台线程来处理网络连接.我不希望每次用户旋转设备时都会破坏和创建连接,因此我只需要在不存在连接时创建连接,并且只有在配置更改未进行时才销毁它.这里的关键是理解当由于配置更改而调用onDestroy()时.这可以使用或不使用片段来完成.通常情况下,我更喜欢非片段方法,因为片段方法对我来说似乎不值得额外的复杂性.

方法1:没有碎片

在onCreate()中,创建后台线程(如果它尚不存在).在onDestroy()中,仅当isFinally()返回false时才销毁后台线程.

方法2:使用碎片

这很有效,因为如果使用setRetainInstance(true),FragmentManager将在配置更改中存储片段实例.有关此示例,请参阅http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html.示例适用于AsyncTasks,但也可以应用于管理后台线程(只需在片段的onCreate()中创建线程而不是AsyncTask,然后销毁片段的onDestroy()中的线程).

闭幕

完全理解这些问题需要深入了解UI looper如何处理其消息队列 - 何时调用Activity回调,其他消息如何与它们交错,何时发生显示更新等.例如,如果使用以下方式创建DialogFragment一个不可见的活动的实例,它是否会显示,如果是,何时?也许有一天Android会为Tasks及其相关的backstack提供更深入的API,以及更详细地描述UI的消息处理和相关机制的文档.在此之前,更多的"源代码和/或......实证分析":-).

谢谢,

巴里

alz*_*zee -1

我猜想的简短答案是,在给定的应用程序中一次只能有一个活动处于活动状态,并且该活动显然知道它是谁(它本身)——因此任何活动可以获得的唯一答案是“什么活动”当前处于活动状态”只能是“你是,傻瓜”。

对于不同活动类别之间有明确划分的简单应用程序来说,这种方法效果很好,因此这在 Play 商店中的大多数应用程序中占很大比例。当你真正熟练地使用封装和多态性时,它就不会那么热了,我相信你已经发现了,但我不认为 Google 真正针对的是这些类型的开发人员。

只是我的 0.02 美元,我认为您不会在这里得到“官方”答案。