场景:
我在测试 fragment 中的线程时遇到了一个奇怪的问题。
我有一个用 Kotlin 编写的 fragment ,在 onResume() 中有以下 fragment :
override fun onResume() {
super.onResume()
val handlerThread = HandlerThread("Stuff")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
Thread.sleep(2000)
tv_name.setText("Something something : " + isMainThread())
}
}
is MainThread() 是一个检查当前线程是否为主线程的函数,如下所示:
private fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper()
我看到我的 TextView 在 2 秒后更新为文本“Something something : false”
看到 false 告诉我这个线程当前不是 UI/Main 线程。
我觉得这很奇怪,所以我创建了相同的 fragment ,但用 Java 编写,而不是使用来自 onResume() 的以下 fragment :
@Override
public void onResume() {
super.onResume();
HandlerThread handlerThread = new HandlerThread("stuff");
handlerThread.start();
new Handler(handlerThread.getLooper()).post(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("Something something...");
}
});
}
应用程序按预期崩溃并出现以下异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7313)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1161)
我做了一些研究,但我无法真正找到可以解释这一点的东西。另外,请假设我的观点都被正确夸大了。
问题:
当我在用 Kotlin 编写的 Fragment 的 UI 线程之外运行的可运行对象中修改我的 TextView 时,为什么我的应用程序没有崩溃?
如果某些文档中的某处解释了这一点,有人可以请我引用吗?
我实际上并没有尝试在 UI 线程之外修改我的 UI,我只是好奇为什么会这样。
如果你们需要更多信息,请告诉我。非常感谢!
更新: 正如@Hong Duan 所说, requestLayout() 没有被调用。这与 Kotlin/Java 无关,而与 TextView 本身有关。
我搞砸了,没有意识到我的 Kotlin fragment 中的 TextView 的 layout_width 为“match_parent”。而我的 Java fragment 中的 TextView 的 layout_width 为“wrap_content”。
TLDR:用户错误 + requestLayout(),线程检查并不总是发生。
最佳答案
CalledFromWrongThreadException
仅在必要时抛出,但并非总是如此。在您的情况下,它会在 ViewRootImpl.requestLayout()
期间调用 ViewRootImpl.checkThread()
时抛出,这是来自 ViewRootImpl.java
:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
对于TextView
,当我们更新它的文本时并不总是需要重新布局,我们可以在source code中看到逻辑:
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return; // return with out relayout
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return; // return with out relayout
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
可以看到,在某些情况下,没有调用requestLayout()
,所以没有引入主线程检查。
所以我认为关键点不在于 Kotlin 或 Java,而在于 TextView
的布局参数决定是否调用 requestLayout()
。
关于java - 修改 UI 线程 View 时 Android UI 不会崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51275494/