我想有人向我解释为什么会失败......
我的活动有一个内部类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;
}
}
答案 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线程),并没有链接到以前的版本。