从错误的线程+内部类调用..奇怪的行为

时间:2013-05-08 15:03:12

标签: android multithreading user-interface inner-classes

我想有人向我解释为什么会失败......

我的活动有一个内部类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 (!!)
                 }
             }); 
        }
    }
}

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

1 个答案:

答案 0 :(得分:1)

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

根据android开发者页面runOnUiThread

  

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

Android Java runOnUiThread()注意,在活动的处理程序上调用post与调用runOnUithread基本相同。

所以,问题是Handler与第一个案例的关联与第二个案例中的Handler不同。

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

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

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

似乎onPause有效地阻止了活动UI线程死亡,onResume创建了一个新的。因此,我们可以通过此过程更新我们可以访问它的处理程序。

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

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

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