1

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

我的活动有一个内部类 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 类在主活动中实例化,并传递给活动中的其他子类,并使用 Wea​​kReference 保持它(检查这不是问题)

  • 使用失败的 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;
    }
}
4

1 回答 1

1

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

根据android开发者页面,runOnUiThread

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

请注意,Android Java runOnUiThread()调用postActivity 的处理程序与调用runOnUithread.

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

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

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

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

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

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

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

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

于 2013-05-08T15:56:51.830 回答