为什么可运行的回调会自动销毁活动?

时间:2017-08-16 09:36:59

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

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

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

有时,当应用程序运行那些splashscreen活动(对于Testing purpose)时,我正在创建的库可能会在UI中自动弹出一些活动。 但似乎如果这些活动发生在splashscreen活动并且splashscreen被销毁,那些活动(自动弹出)也将被销毁并记录“泄露的窗口”< / strong> logcat中的消息。

问题在于:

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

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

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

P.S。:请注意,通常情况下,我正在使用对话框来自动显示假设的活动。

更新

Diagram

图表说明:

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

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

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

8 个答案:

答案 0 :(得分:4)

  

有没有办法检测是否在应用程序中相对于库方创建了一些 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中是否有消息,但它不会告诉您确切的消息是什么。

我要采用的解决方案是取消计划活动RunnablepostDelayed的{​​{1}},因为如果onStop(来自库中的那个) )已启动,而Activity的{​​{1}}被调用:

onStop

答案 1 :(得分:2)

您需要更好地解释问题。我对启动画面与其他活动的关系感到困惑,如果问题与postDelayed()或活动生命周期有关。我建议用一个微小的图表来解释哪些活动会启动其他活动。

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

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

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

Runnable mLaunchSplashRunnable = new Runnable() { ... };
Runnable mLaunchContactsRunnable = new Runnable() { ... };

.
.

mHandler.postDelayed (mLaunchSplashRunnable, DELAY);
mHandler.postDelayed (mLaunchContactsRunnable, DELAY);

.
.

由于runnables现在不是匿名的,您可以随时将它们从队列中删除:

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

请注意,如果没有任何已发布的runnable,前一个方法不会失败,因此可以随时调用它。

查询Handler,如果某个特定Runnable排队,afaik的方法不存在,但您可以使用boolean标志,在runnable排队时设置它,并在运行Runnable时重置它,以指示可运行的待处理。

如果我更了解你的问题,我可以提供更多帮助。

答案 2 :(得分:2)

问题是,一旦你开始一个接一个地开始激活活动,经过一段时间你的应用程序消耗了大量内存,所以android假设以前的活动可以被销毁以优化系统并避免设备减速,这就是如何移动设备工作,因此系统会破坏消耗API的主要活动: 请理解活动生命周期:

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

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

希望这会对你有帮助......

答案 3 :(得分:2)

为什么不使用静态布尔变量来判断你的Splash Screen是否正在运行。

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

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

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

答案 6 :(得分:1)

因为我们还没有看到您的代码。我的答案太过一般了,很抱歉。

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

如果您的所有活动都处于标准模式,请尝试更改至少一个活动启动模式以禁用它,然后重试。如果这没有任何意义,请检查代码是否为finish()调用。

还没运气?然后我想这可能是ANR的情况,你应该检查内存泄漏,冻结你的应用程序,并可能关闭活动。可能那些可运行的东西在某种程度上破坏了你的应用程序。

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

答案 7 :(得分:1)

活动可能随时被破坏,所以当发生这种情况时,你需要小心 被破坏的对话框也是如此。在Activity的生命周期中,你需要销毁&#34;对话框&#34;另外,你会得到&#34;泄露的窗口&#34;。

参考活动的对话框也将受益于使用 对活动的弱引用,您将使用弱引用 报告用户对活动的操作。

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

在显示对话框之前,我还要确保活动没有完成:

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

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

    @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();
}