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

17

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

例如,我的应用程序中有一个或多个闪屏界面(使用handler.postDelayed(new Runnable()...运行),该应用程序是为测试目的创建的。在这个应用程序中,我也有一个库(我正在创建并在应用程序中使用它),以及一些可在IntentService类上运行的类。

有时,当应用程序正在运行那些测试目的splashscreen活动时,我正在创建的库可能会自动在UI中弹出一些活动。

但问题是:如果那些活动出现在一个splashscreen活动上,并且splashscreen被销毁了,那些自动弹出的活动也会被销毁,并在日志记录器中记录一个"leaked window"消息。

  • 自动出现在UI中的那些活动不应该自动关闭,这是被禁止的。需要用户交互才能关闭该活动并返回到应用程序的正常行为。
  • 而且,库不知道应用程序的UI任何信息。

因此,我的问题是(相对于我正在创建的库端,没有关于应用程序UI流程的信息):

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

P.S.:请注意,通常我在显示那些自动弹出活动的情况下使用对话框。

UPDATE

Diagram

图表的解释:

目前我有一个案例,一个Splashscreen正在被执行。

这个类继承了IntentService类,已经从互联网接收到了请求,这将启动一个Activity

同时,当闪屏界面在postdelayed时,另一个Activity已经被创建并显示在UI中。当X秒钟过去并且另一个Activity没有被销毁时,下一个Activity将被创建并自动销毁另一个Activity。这样做时,安卓会报"leaked window"的错误信息与Activity相关。


1
你能提供那些可运行和活动的相关代码吗? - petey
@YvetteColomb 问题在于代码是保密的,所以我不能公开一些代码。但我认为我的图表和解释会很有帮助。我该怎么做才能让你更好地理解我的问题?我的解释有什么问题吗? - Damiii
如果“Activity”被显示并且“NextActivity”由于“onPostDelayed”方法所经过的时间而立即执行和显示,那么“Activity”将自动销毁,而无需用户进行任何交互。用户应该通过自己的手退出此活动。@BinhTran - Damiii
2
@Damiii,我认为你在问题中混淆了各种术语,使得你的问题过于笼统。如果你将问题分解成更具体、定义明确的问题,对你会更有利。 - Alex.F
1
@Damii,你不能确信这一点。OnStop肯定会被调用。如果有泄漏对象存在,OnDestroy可能会在此之后的半小时甚至永远都不会被调用。 - rupps
显示剩余5条评论
8个回答

4
有没有办法检测是否在应用程序中相对于库创建了一些postDelayed方法?你可以利用MessageQueue.IdleHandler API实现,查看LooperIdlingResource中如何确定何时适合进行断言。以下代码片段可以帮助您了解是否在MessageQueue中有消息,但它不会告诉您确切的消息:

    @Override
    public boolean queueIdle() {
QueueState queueState = myInterrogator.determineQueueState(); if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) { ... } else if (queueState == QueueState.BARRIER) { ... }
return true; }
我的解决方案是在活动的onStop中取消计划内postDelayedRunnable,因为如果已启动库中的Activity,则会调用SplashScreenonStop

public class SplashActivity extends AppCompatActivity {
private final Runnable myRunnable = new Runnable() -> { // 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
问题在于,一旦您开始一个接一个地启动活动,当您的应用程序消耗了大量内存时,Android会认为可以销毁之前的活动以优化系统并避免设备变慢。这就是移动设备的工作方式,因此您正在使用API的主要活动将被系统销毁。请了解活动生命周期:https://developer.android.com/guide/components/activities/activity-lifecycle.html。请注意,在onStop()之后...这就是您的应用程序的情况。希望这能帮到您...

我已经知道问题相对于活动生命周期而言,我想要的有点不同...尝试强制 Activity 的视图保持并在此情况下增加可运行的时间。(大致如此)。 - Damiii

2

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


因为我正在创建一个SDK。这样,SDK就不会受到导入SDK的应用程序的任何逻辑或控制。 - Damiii

2

您需要更好地解释问题。我对启动画面与其他活动之间的关系感到困惑,不确定问题是与postDelayed()还是与活动生命周期相关。建议提供一个简单的图形示意图,说明哪些活动启动了其他活动。

关于postDelayed(),一般而言,如果您执行

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

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

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 是否排队了特定 Runnable 的方法,但您可以使用一个布尔标志,在将 Runnable 排队时设置它,当 Runnable 运行时重置它,以指示还有一个待处理的 Runnable。
如果我更好地理解了您的问题,我将能够提供更多帮助。

好的,我会制作一个图表,并进一步解释。 - Damiii

1

问题在于我正在创建一个SDK,这个SDK应该被包含在任何应用程序中。在这种情况下,SDK没有关于使用SDK的应用程序的任何视图/信息。 - Damiii
但是您可以要求SDK用户在使用SDK创建活动时调用某些方法。对我来说,这比尝试跟踪用户的应用程序行为更好、更可靠。 - Anton Malyshev

1
使用回调函数来处理时间耦合的活动可能不是SDK设计的最佳选择。考虑使用可观察对象从网络层获取数据,以便提供给需要这些数据的活动。通过向需要数据的活动添加一个观察者来观察网络调用是否完成,您只会将数据暴露给处于活跃状态的观察者。这样一来,如果在调用完成前关闭了该活动,则不会将数据“推送”到已关闭的活动。
这还允许您以一种无需担心窗口泄漏的方式创建对库的弱引用。

1
由于我们还没有看到你的代码,我的答案会比较笼统,对此我很抱歉。
我认为你应该首先检查logcat以查看是否有任何异常导致这些活动关闭。如果没有关于此的任何信息,请检查所有的try catch块以确保它不是由于任何异常引起的。所以,在确定不是任何类型的异常之后,检查AndroidManifest文件以查看活动的“启动模式”。这也可能导致自动关闭。
如果您的所有活动都处于标准模式,则尝试将至少一个活动的启动模式更改为禁用它关闭并再次尝试。如果这也没有任何意义,请检查代码中的finish()调用。
仍然没有运气?那么我想这可能是ANR的情况,您应该检查内存泄漏,这会使您的应用程序冻结并可能关闭活动。可能那些可运行项正在破坏您的应用程序。
我认为您应该拥有像eventbus或广播接收器这样的机制来在屏幕之间进行内部通信。它们将在您的活动生命周期上运行,并且不会导致任何类型的异常或anr。

1
活动随时可能被销毁,因此当发生这种情况时,您需要注意正在被销毁的对话框。在Activity的生命周期中,您还需要销毁“对话框”,否则您将获得“泄露的窗口”。
此外,引用Activity的对话框可以受益于使用对Activity的弱引用,然后您将使用弱引用将用户操作报告给Activity。
@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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接