2013-05-08 56 views
1

我希望有人能解释我为什么失败?从错误的线程+内部类叫..奇怪的行为

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

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

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

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

VERSUS

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 
     } 
    } 
} 

的第一种情况的堆栈跟踪:

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类的主要内部实例化活动,并通过编辑在活动的其他子类,即保持与WeakReference的(检查,这是没有问题的)

  • 使用失败做AppHelper的类:创建

public void story_loadfonts(String jsonFonts) { 

    final AppHelper appHelper=mWeakAppHelper.get(); // apphelper stored in a weak ref 

    try { 
     . 
     . 
     . 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        for (int i=0; i<NUMFONTS; i++) { 
         load_font_from_network(i); 
         appHelper.setThrobber("LOADING FONT "+i+"/"+NUMFONTS); 
        } 
       } catch (JSONException e) { 
        e.printStackTrace(); 
       } 
      } 
     }).start(); 
    } catch (Exception e) { 
     return; 
    } 
} 
+0

代码看起来不错。也许更多的代码会有帮助 – Blackbelt 2013-05-08 15:07:00

+0

是的,它也看起来很好!事情是,这个问题没有太多相关的代码。如果我只是切换runUiThread它完美的作品,但我不知道区别! – rupps 2013-05-08 15:08:58

+0

请分享实例化AppHelper并调用方法的代码。 – tbkn23 2013-05-08 15:21:35

回答

1

Looking at the ticked answer from Android: Accessing UI Element from timer thread , I wonder if the issue is to do with where the runOnUiThread电话。

据Android开发者页面,runOnUiThread

运行在UI线程上指定的操作。如果当前线程是UI线程,那么该动作立即执行。如果当前线程不是UI线程,则该操作将被发布到UI线程的事件队列中。从Android Java runOnUiThread()

注意对活动的处理程序调用post基本上与调用runOnUithread

因此,问题是Handler与第一种情况关联的方式与第二种情况的Handler不同。

在第二种情况下,我怀疑你保证发布到与主要活动相关的处理程序。

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

编辑:基于这样一个问题笔者进一步互动,主要内容如下:

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

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

然而,在第二种情况下,向runOnUiThread呼叫不再在该代码执行(称为应运而生预),而是将已更新处理程序runOnUiThread呼叫的主要活动内的方法。

总之:请确保调用runOnUiThread(实际上handler.post())总是在保证他们使用的是最新的live版的活动(和它的UI线程),而不是链接到的方式完成之前的版本。

+0

嗨Neil!非常有趣的指针! (+1)AppHelper确实是在Oncreate上创建的,但我必须检查一些与稍后使用的Handler有关的事情......我很确定它是在UI线程上,因为我在一百万个地方使用它该应用程序执行UI工作没有问题...但我对你的答案有疑问:runOnUiThread和Handler之间的关系是什么? runOnUiThread的处理程序是不是内部的东西? – rupps 2013-05-08 16:02:57

+0

顺便说一句,我怀疑这是与Synthesized方法访问内部类中的父方法有关,但为什么他们在runUiThread()之外执行是我不明白的...... – rupps 2013-05-08 16:24:30

+0

感谢RunOnUiThread上的文档,但有趣事情是我没有超过一个活动,并且AppHelper应该只存在一次。这就是为什么这个问题是我的X档案。我越来越确信它与内部类如何通过合成的静态获取器和设置器访问其父类有关,因为除此之外,我看不到任何其他差异。如果Activity的主要处理程序存在这样的问题,我想它会在其他地方显示,该应用程序会下载数百个资源,动态创建视图,共享一个处理程序,但不会出现问题... – rupps 2013-05-08 18:48:48