【问题标题】:Why is runnable callbacks destroying activity automatically?为什么可运行的回调会自动销毁活动?
【发布时间】:2018-01-24 09:21:33
【问题描述】:

我想知道我们是否有可能在 android 上处理/检测可运行的回调(postDelayed 方法)?

例如,我的应用程序(用于测试目的的应用程序)上有一个或多个启动画面(使用 handler.postDelayed(new Runnable()... 运行)。在这个应用程序中,我还有一个库(我在应用程序中创建和使用它)和一些可用的类,它们在 IntentService 类上运行。

有时,当应用程序运行那些splashscreen 活动(用于Testing purpose)时,我正在创建的库可能会在 UI 中自动弹出一些活动。 但是,如果这些活动出现在 splashscreen 活动上并且 splashscreen 正在被销毁,那么这些活动(自动弹出)也将被销毁并记录 “泄漏窗口” 消息在 logcat 中。

问题是:

  • 那些自动出现在 UI 中的活动不应该 自动关闭,这是禁止的。它需要一个交互 用户关闭该活动并返回应用程序的正常行为。
  • 此外,该库对应用程序的 UI 一无所知。

所以我的问题是(相对于我正在创建的库方面,而没有关于 UI 应用程序流程的信息):

  • 有没有办法检测是否在应用程序中相对于库端创建了一些 postDelayed 方法?如果是,我该如何处理这个问题?

P.S.:请注意,通常情况下,我正在为自动出现的假设 Activity 使用 Dialog。

更新

图表说明:

现在我有一个 Splashscreen 正在执行的案例。

扩展 IntentService 类的类已收到来自 Internet 的请求,该请求将启动 Activity

同时启动画面在postdelayed 上,另一个Activity 已创建并显示在 UI 中。当 X 秒过去且另一个 Activity 尚未销毁时,会创建 Next Activity 并自动销毁另一个 Activity。这样做时,Android 会相对于 Activity 抛出“泄漏窗口”消息。

【问题讨论】:

  • 你能提供那些runnables和activity的相关代码吗?
  • @YvetteColomb 问题是代码是机密的......所以很遗憾我不能放一些代码......但我认为我的图表会有很大帮助,而且解释也很有帮助。我能做些什么让您更好地理解我的问题?我的解释有什么问题?
  • 关于您的问题,我仍然需要了解一件事,如果您的弹出活动是从 IntentService 创建的,我假设 NextActivity 对此一无所知,那么当 NextActivity 破坏您的弹出窗口时活动何时出现?如果上面还有其他活动,您的弹出活动是否会自动完成?
  • @Damiii 我认为您在问题中混合了各种术语,使您的问题过于笼统。如果您将问题分解为更具体、定义明确的问题,这将符合您的最大利益。
  • @Damii 你不能确定。 OnStop 肯定会被调用。 OnDestroy 可能会在此之后被调用,半小时后甚至不会被调用,如果有泄漏的对象。

标签: java android android-activity android-handler android-looper


【解决方案1】:

有没有办法检测是否在应用程序中相对于库端创建了一些 postDelayed 方法?

您可以使用MessageQueue.IdleHandler API。请参阅LooperIdlingResource espresso 如何确定是否是触发断言的适当时间。

@Override public boolean queueIdle() { QueueState queueState = myInterrogator.determineQueueState(); if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) { ... } else if (queueState == QueueState.BARRIER) { ... } return true; }

这将帮助您了解MessageQueue 中是否有消息,但它不会告诉您确切的消息是什么。

我要采用的解决方案是取消安排您在活动的onStop 内拥有的postDelayedRunnable,因为如果Activity(来自图书馆的那个)已经启动,那么@987654331正在调用SplashScreen 的@:

public class SplashActivity extends AppCompatActivity { private final Runnable myRunnable = () -> { // launch `NextActivity` }; private final Handler handler = new Handler(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); handler.postDelayed(myRunnable, 3000); } @Override protected void onStop() { super.onStop(); handler.removeCallbacks(myRunnable); } }

【讨论】:

    【解决方案2】:

    您需要更好地解释问题。我对初始屏幕和其他活动的关系感到困惑,以及问题是否与postDelayed() 或活动生命周期有关。我建议用一个小图表来解释哪些活动启动了其他活动。

    关于postDelayed(),一般来说,如果你这样做

     mHandler.postDelayed(new Runnable() { ... });
    

    您每次都发布一个匿名的、新鲜的可运行文件,因此您无法将其删除。我建议采用以下方法,将 Runnables 声明为库中的类成员:

    Runnable mLaunchSplashRunnable = new Runnable() { ... };
    Runnable mLaunchContactsRunnable = new Runnable() { ... };
    
    .
    .
    
    mHandler.postDelayed (mLaunchSplashRunnable, DELAY);
    mHandler.postDelayed (mLaunchContactsRunnable, DELAY);
    
    .
    .
    

    由于可运行对象现在不是匿名的,您可以随时将它们从队列中移除:

    void removeLibraryDelayedRunnables() {
       mHandler.removeCallbacks(mLaunchSplashRunnable);
       mHandler.removeCallbacks(mLaunchContactsRunnable);
    }
    

    请注意,如果没有任何已发布的可运行文件,之前的方法不会失败,因此可以随时调用它。

    如果特定的Runnable 排队,则查询Handler 的方法,afaik,不存在,但您可以使用boolean 标志,在可运行对象排队时设置它,并在运行时重置它Runnable 正在运行,表示一个可运行的对象处于挂起状态。

    如果我能更好地了解您的问题,我将能够提供更多帮助。

    【讨论】:

    • 好的,我会做一个图表。并对此进行更多解释。
    【解决方案3】:

    你为什么不使用静态布尔变量来确定你的启动画面是否正在运行,当你要调用它时。

    【讨论】:

    • 因为它是我正在创建的 SDK。这样,SDK 就没有任何逻辑,也没有来自正在导入 SDK 的应用程序的控制。
    【解决方案4】:

    问题是一旦你开始一个接一个地触发activity,一段时间后你的应用程序已经消耗了很多内存,所以android假设可以销毁以前的activity来优化系统并避免设备变慢,这就是如何移动设备可以工作,因此您使用 API 的主要活动被系统破坏: 请了解活动生命周期:

    https://developer.android.com/guide/components/activities/activity-lifecycle.html

    在 onStop() 之后查看...您的应用就是这种情况。

    希望对你有所帮助...

    【讨论】:

    • 好吧,我已经知道问题与活动生命周期有关,我想要的有点不同......在这种情况下尝试强制保持活动的视图并增加时间可运行的。 (或多或少)。
    【解决方案5】:

    我认为你应该倒转程序中的逻辑。活动应该从您的活动开始,而不是从服务开始。

    为了实现这一点,您可以在创建每个活动https://developer.android.com/reference/android/content/BroadcastReceiver.html 时注册广播接收器,并在需要启动活动时使用服务https://developer.android.com/guide/components/broadcasts.html 中的 sendBroadcast 来命令广播接收器启动所需的活动。

    【讨论】:

    • 问题是我正在创建一个 SDK,并且该 SDK 应该包含在任何应用程序中。在这种情况下,SDK 没有任何关于正在使用 SDK 的应用程序的视图/信息。
    • 但是您可以要求您的 SDK 用户调用一些方法来使用 SDK 创建活动。对我来说,这比试图跟踪用户的应用行为更好、更可靠
    【解决方案6】:

    对您的 SDK 使用具有时间耦合活动的回调可能不是一个很好的设计。考虑使用 observables 从网络层获取数据到需要数据的活动。

    通过向需要数据的活动添加观察者,观察网络调用是否完成,您只需将数据暴露给活动的观察者。这样,如果活动在调用完成之前关闭,则不会将数据“推送”到关闭的活动。

    这还允许您以无需担心泄漏窗口的方式创建对库的弱引用

    【讨论】:

      【解决方案7】:

      因为我们还没有看到您的代码。我的回答太笼统了,很抱歉。

      我认为您应该首先检查 logcat 以查看关闭这些活动是否有任何异常。如果没有什么。只需检查所有 try catch 块即可确定。因此,在您确定它与任何类型的异常无关后,请检查 AndroidManifest 文件以查看活动的“启动模式”。它也可能导致自动关闭。

      如果您的所有活动都处于标准模式,请尝试更改至少一种活动启动模式以使其无法关闭,然后重试。如果这也没有任何意义,请检查 finish() 调用的代码。

      还是没有运气?然后我想这可能是 ANR 的情况,您应该检查冻结您的应用程序并可能关闭活动的内存泄漏。可能这些可运行文件会以某种方式破坏您的应用程序。

      我认为你应该有一个像事件总线或广播接收器这样的机制来在你的屏幕之间进行内部通信。它们将在您的活动的生命周期中运行,不会导致任何类型的异常或 anr。

      【讨论】:

        【解决方案8】:

        活动随时可能被破坏,所以当这种情况发生时你需要小心 也被破坏的对话框。在 Activity 的生命周期中,您还需要销毁“Dialogs”,否则您将获得“Leaked Window”。

        引用活动的对话框也将受益于使用 活动的弱引用,然后您将使用弱引用 向 Activity 报告用户的操作。

        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            listener = new WeakReference<>((MyActionListener) activity);
            activityWeakReference = new WeakReference<>(activity);
        }
        

        在显示对话框之前,我还要确保 Activity 不在完成过程中:

        if (myActivityWeakReference.get() != null && !myActivityWeakReference.get().isFinishing()) {
          // show dialog
        }
        

        那么当你想控制设备的旋转时,Activity 所在的位置 重新创建后,您可以将以下内容与对话框一起使用:

            @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }
        
        /**
         * Prevent the dialog being dismissed when rotated, when using setRetainInstance
         */
        @Override
        public void onDestroyView() {
            if (getDialog() != null && getRetainInstance()) {
                getDialog().setDismissMessage(null);
            }
            super.onDestroyView();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多