【问题标题】:Updating Destroyed Activity UI from Handler Runnable从 Handler Runnable 更新销毁的 Activity UI
【发布时间】:2015-11-11 19:44:01
【问题描述】:

以下代码更新 TextView 直到某个条件评估为 false,然后 Handler postDelayed 不会被进一步调用。

但是,如果 Activity 被销毁,它会尝试更新一个 null TextView,那么正确的处理方法是什么?我知道我可以对 TextView 进行防御性空检查,但这仍然不是真正的线程安全。

@Override
protected void onCreate(Bundle savedInstanceState) {
     Handler durationHandler = new Handler();
     durationHandler.postDelayed(updateSeekBarTime, 50);
}

private Runnable updateSeekBarTime = new Runnable() {
            public void run() {

                timeElapsed = mediaPlayer.getCurrentPosition();

                double timeRemaining = finalTime - timeElapsed;

                timeLeft.setText(String.format("%d", TimeUnit.MILLISECONDS.toSeconds((long) timeRemaining)));

                if (timeRemaining >= 1000)
                    durationHandler.postDelayed(this, 200);
            }
        };

换句话说,updateSeekBarTime 可以在任何执行点尝试访问已销毁活动的数据成员,如何防止这种情况发生?

【问题讨论】:

    标签: java android multithreading runnable android-handler


    【解决方案1】:

    在 onResume() 中启动您的处理程序。 在 onPause() 中用

    停止处理程序
    handler.removeCallbacksAndMessages(null); //null removes everything
    

    【讨论】:

    • 我认为 .removeCallbacksAndMessages 将摆脱任何尚未开始执行的 Runnables。我的问题非常具体,关于如何防止访问取消分配的 TextView
    • 猜猜你不会绕过空检查。或者像这个人一样:stackoverflow.com/a/5844433/2001247
    • 正如该人指出的那样,终止开关只能阻止启动 Runnable,但不能阻止 Runnable 中间
    • 错了,run() 里面的东西在 kill 开关之后永远不会执行... private void run() { if(killMe) return; /* 做你的工作 */ }
    • 所以假设在 if(killMe) 条件执行后 killMe 变为 true,那么 run() 中的其余代码仍将执行
    【解决方案2】:

    我正在构建自己的音乐播放器应用并使用

    durationHandler.removeCallbacks(updateSeekBarTime);
    

    在 onStop() 中。它对我有用。

    编辑:

    我防止 Activity 被使用破坏了这一事实,这对上述行有所帮助

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }
    

    这确保了 Activity 被最小化而不是被销毁,因此当再次打开时,它非常活泼。

    编辑 2:

    private Runnable updateSeekBarTime = new MyRunnable();
    
    private class MyRunnable extends Runnable {
            private boolean dontWriteText = false;
            @Override
            public void run() {
    
                timeElapsed = mediaPlayer.getCurrentPosition();
    
                double timeRemaining = finalTime - timeElapsed;
    
            if(!dontWriteText)
                timeLeft.setText(String.format("%d", TimeUnit.MILLISECONDS.toSeconds((long) timeRemaining)));
    
                if (timeRemaining >= 1000)
                    durationHandler.postDelayed(this, 200);
            }
            public void dontWriteText() {
                dontWriteText = true;
            }
        };
    

    然后在 onDestroy() 或 onStop() 中调用 updateSeekBarTime.dontWriteText()。我更喜欢 onStop()。

    【讨论】:

    • 这是有道理的,因为任何待处理的 Runnable 都已从您的处理程序队列中删除,但仍然可能与主 UI 线程并行执行的运行呢?
    • 所以你的活动 onDestroy() 永远不会被调用?活动轮换如何?除非系统杀死您的活动,否则我认为将调用 onDestroy()。
    • @2cupsOfTech 即使在我旋转屏幕时,我也绝对从未遇到过问题,即使 postDelayed() 有 100 毫秒的延迟。无论如何,你看到我更新的答案了吗?它应该可以满足您的需求。
    • 是的,我看到了您的回答,我认为这是解决问题的一种聪明/骇人听闻的方式,但请查看我发布的答案。
    • @2cupsOfTech 昨天我正在考虑建议 WeakReference,但决定反对它,因为您没有将任何弱或强引用传递给可运行对象。无论如何,很高兴知道您找到了自己的答案。
    【解决方案3】:

    所以经过一些代码搜索和阅读博客后,我在Communicating with the UI Thread的示例代码中找到了答案

    即使您可以并且应该从处理程序中删除回调:

    handler.removeCallbacksAndMessages(null)
    

    但上述并不能阻止现有正在运行的 Thread 访问已销毁的 Activity 或其视图。

    我正在寻找的答案是WeakReference

    创建对您将要使用的 TextView 或 UI 元素的弱引用 使用权。弱引用可以防止内存泄漏和崩溃,因为 它会自动跟踪它所支持的变量的“状态”。如果 引用变得无效,弱引用被垃圾收集。 这种技术对于引用属于 组件生命周期。使用硬引用可能会导致内存泄漏 随着价值的不断变化;更糟糕的是,如果 底层组件被破坏。使用对 a 的弱引用 视图确保引用在本质上更具暂时性。

    您仍应检查 null 引用,但现在视图将由活动线程/可运行线程设置为 null,因此您不会面临竞争条件。

    【讨论】:

    • 如果你在 UI 线程上发布 runnable(你应该这样做)然后在生命周期事件中将其删除,那么 runnable 将无法执行,因为 event 和 runnable 不能一起运行 在同一个线程上。使用 Wea​​kReference 是一个好主意,但这种方法还有更多内容,即使可运行对象成为静态内部类,并向活动添加标志 mIsDestroyed。不要将销毁与收集的垃圾混为一谈。
    • @GilVegliach 请在其他答案中查看 cmets
    • 我读过它们,但我仍然认为你在混淆。对于初学者来说,被破坏的活动!= 有资格被 gc'd 的活动。即使在您的问题陈述中,如果您持有已销毁的活动,当可运行文件运行时,其中的文本视图也不会为空。也就是说,使用弱引用 let the activity be gc'd 是一个好主意,只要记住 destroyed 在这里(在 Android 中)有一个非常具体的含义,见:developer.android.com/reference/android/app/…
    • @GilVegliach 当我说“被破坏的活动”时,我的意思是没有对活动或其任何观点进行引用。如果我引用它的任何观点,这实际上是内存泄漏:android-developers.blogspot.com/2009/01/…
    • 确切地说,您的意思是“有资格获得 gc”,而不是被销毁。在这种情况下,还要确保将匿名内部可运行对象转换为静态内部类,否则您仍然会泄漏。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    • 2016-06-14
    • 2022-12-16
    • 1970-01-01
    相关资源
    最近更新 更多