【问题标题】:called from wrong thread + inner classes .. strange behaviour从错误的线程+内部类调用..奇怪的行为
【发布时间】:2013-05-08 15:03:12
【问题描述】:

我想请人解释一下为什么会失败...

我的活动有一个内部类 AppHelper,它导出一个函数 setThrobber。为简单起见,我省略了所有的初始化代码等。

此函数 setThrobber 旨在在 UI 线程之外调用,通常在网络加载期间,以报告进度...然后,这些类使用内部类 AppHelper 和 setThrobber 方法从网络加载器执行此操作线程。

令我惊讶的是,第一种方法失败了(见最后的错误),第二种方法成功了。为什么第一个不在UI线程中执行而第二个是???更奇怪的是,在错误堆栈跟踪中,它看起来像是从 UI 线程调用的,即使 Android 抛出了 Called From Wrong Thread 异常。为什么从线程的角度来看两个代码块不等价?

PD- 我也尝试了 handler.post() ,结果相同! PD2- AppHelper 类在 onCreate 内部实例化

提前致谢!!

public class MyApplication extends Activity {

    private ProgressDialog progressDialog;

    void setThrobber_internal (String message) {
         progressDialog.setMessage(message);
    }

    public class AppHelper {

        public setThrobber(final String msg) {
             MyApplication.this.runOnUiThread(new Runnable() {
                 @Override 
                 public void run() {
                     setThrobber_internal(msg);
                     // This throws CalledFromWrongThread (!!)
                 }
             }); 
        }
    }
}

对比

public class MyApplication extends Activity {

    private ProgressDialog progressDialog;

    private void setThrobber_internal(final String msg) {

         // runUiThread here instead of in inner class wrapper

         runOnUiThread(new Runnable() {
             @Override 
             public void run() {
                 progressDialog.setMessage(msg);
             }
         }); 
    } 

    public class AppHelper {

        public void setThrobber(final String msg) {
             setThrobber_internal(msg); // this works OK
        }
    }
}

第一种情况的Stack trace:

E/AndroidRuntime(17677): FATAL EXCEPTION: main
E/AndroidRuntime(17677): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime(17677):    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
E/AndroidRuntime(17677):    at android.view.ViewRootImpl.invalidateChild(ViewRootImpl.java:722)
E/AndroidRuntime(17677):    at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:771)
E/AndroidRuntime(17677):    at android.view.ViewGroup.invalidateChild(ViewGroup.java:4005)
E/AndroidRuntime(17677):    at android.view.View.invalidate(View.java:8576)
E/AndroidRuntime(17677):    at android.view.View.invalidate(View.java:8527)
E/AndroidRuntime(17677):    at android.widget.TextView.checkForRelayout(TextView.java:6760)
E/AndroidRuntime(17677):    at android.widget.TextView.setText(TextView.java:3306)
E/AndroidRuntime(17677):    at android.widget.TextView.setText(TextView.java:3162)
E/AndroidRuntime(17677):    at android.widget.TextView.setText(TextView.java:3137)
E/AndroidRuntime(17677):    at com.android.internal.app.AlertController.setMessage(AlertController.java:261)
E/AndroidRuntime(17677):    at android.app.AlertDialog.setMessage(AlertDialog.java:185)
E/AndroidRuntime(17677):    at android.app.ProgressDialog.setMessage(ProgressDialog.java:314)
----------------------------------
E/AndroidRuntime(17677):    at com.regaliz.libneo.NativeStory.setThrobber_internal(NativeStory.java:269)
E/AndroidRuntime(17677):    at com.regaliz.libneo.NativeStory$AppHelper$8.run(NativeStory.java:865)
----------------------------------
E/AndroidRuntime(17677):    at android.os.Handler.handleCallback(Handler.java:605)
E/AndroidRuntime(17677):    at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime(17677):    at android.os.Looper.loop(Looper.java:137)

请求额外代码:

  • AppHelper 类在主 Activity 中被实例化,并被传递给 Activity 中的其他子类,使用 Wea​​kReference 保持它(检查这不是问题)

  • 使用失败的 AppHelper 的类会:

public void story_loadfonts(String jsonFonts) {

    最后的 AppHelper appHelper=mWeakAppHelper.get(); // apphelper 存储在弱引用中

    尝试 {
        .
        .
        .
        新线程(新可运行(){
            @覆盖
            公共无效运行(){
                尝试 {
                    for (int i=0; i

【问题讨论】:

  • 代码对我来说看起来不错。也许更多的代码会有所帮助
  • 是的,它在我看来也不错!问题是,没有更多与该问题相关的代码。如果我只是切换 runUiThread 它可以完美运行,但我不知道有什么区别!
  • 请分享实例化AppHelper并调用该方法的代码。
  • 嗨,tbkn!我正在发布该代码,但是为什么它会相关?问题的根源不是从 RunOnUiThread 开始吗?
  • 出于兴趣,是NativeStory.java的第269行?

标签: android multithreading user-interface inner-classes


【解决方案1】:

查看Android: Accessing UI Element from timer thread 打勾的答案,我想知道问题是否与runOnUiThread 调用的创建位置有关。

根据android开发者页面,runOnUiThread

在 UI 线程上运行指定的操作。如果当前线程是 UI 线程,则立即执行该操作。如果当前线程不是UI线程,则将动作发布到UI线程的事件队列中。

Android Java runOnUiThread() 指出,在 Activity 的处理程序上调用 post 与调用 runOnUithread 基本相同。

所以,问题是第一种情况下的Handler与第二种情况下的Handler有什么不同。

在第二种情况下,我怀疑您肯定会发布到与主要活动相关的处理程序。

在第二种情况下,我推断是与创建 AppHelper 的线程关联的处理程序,从您所说的应该是相同的。

编辑:基于与问题作者的进一步互动,关键如下:

似乎onPause 有效地停止了活动 UI 线程死亡,而onResume 创建了一个新线程。所以我们可以访问它的处理程序也会被这个进程更新。

(最终的、弱的)appHolder 实例化与旧的 UI 线程处理程序相关联,因此如果在其中执行 runOnUiThread,则引用旧的持有者(通过 runOnUiThread)。这是第一种情况的情况。

然而,在第二种情况下,对runOnUiThread 的调用不再在该代码中执行(被称为pre-onPause),而是在主活动内的一个方法中执行,该方法将更新处理程序@ 987654333@ 来电。

简而言之:确保对runOnUiThread(实际上是handler.post())的调用始终以确保他们使用最新的live Activity 版本(并且它是UI 线程)的方式完成,并且没有链接到以前的版本。

【讨论】:

  • 嗨,尼尔!非常有趣的指针! (+1)AppHelper 确实是在 Oncreate 上创建的,但我必须检查一些与我稍后使用的处理程序相关的事情......我很确定它在 UI 线程上,因为我在超过一百万个地方使用它该应用程序可以毫无问题地执行 UI 工作......但我对你的回答有疑问:runOnUiThread 和 Handler 之间有什么关系? runOnUiThread 的 handler 不是内部的东西吗?
  • BTW-我怀疑这与访问内部类中的父方法的 Synthesized 方法有关,但是为什么它们在 runUiThread() 之外执行是我不明白的...
  • 感谢 RunOnUiThread 上的文档,但有趣的是我没有多个活动,并且 AppHelper 应该只存在一次。这就是为什么这个问题对我来说是 X 文件。我越来越相信这与内部类如何通过合成的静态 getter 和 setter 访问它们的父级有关,因为除此之外,我看不出任何其他区别。如果 Activity 的主处理程序存在这样的问题,我猜它会在其他地方表现出来,应用程序会下载数百个资源,动态创建视图,共享一个大量使用而没有问题的处理程序......
  • 很奇怪!我想知道对 appHandler 使用弱引用是否意味着它在使用时已经改变。它是仅在 onPause/onResume 之后发生还是在之前发生?
  • 尼尔!我终于发现了发生了什么。您为我指明了可能的活动变化方向。事情是,在创建 apphelper 之后,我在 oncreate 中初始化了一个 Voice Synth 库,它涉及检查 Voice 是否已安装的外部意图,因此活动进入 onPause 毫秒然后 onResume。虽然不涉及 OnDestroy/OnCreate,但这会以某种方式影响 runOnUiThread,可能是在应用程序处于睡眠状态时尝试执行它,或者其他什么......我将 Voice Synth 例程移到其他地方,事情按预期工作。感谢您的努力:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-06
  • 1970-01-01
  • 2020-08-23
  • 1970-01-01
  • 2015-01-27
相关资源
最近更新 更多