前言
在使用
okhttp3
请求接口数据时,发现在call.enqueue
回调的onResponse
设置TextView.setText()
正常,但使用Toast
时报错:java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
public void testOkHttp3(String url) {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String s = response.body().string();
//tv.setText(s);
Toast.makeText(MainActivity.this, "onFailure", Toast.LENGTH_SHORT).show();
}
});
}
onResponse和onFailure处于子线程?
OkHttp3
有execute
和enqueue
两种请求
-
execute
是同步请求,会直接在主线程中进行请求,因为android中主线程不能进行耗时操作,所以此时应该手动开启子线程进行请求,所以很少使用同步请求,不开启线程会发生ANR。 -
enqueue
是异步请求,此时OkHttp3
会创建子线程进行请求,并且将请求结果以Callback
回调,然而此时并没有切换到主线程,所以onResponse
和onFailure
都还处于子线程中。
在onResponse
中添加测试打印log:
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
Log.e("onResponse", "在主线程:" + Thread.currentThread().getName());
} else {
Log.e("onResponse", "在子线程:" + Thread.currentThread().getName());
}
// 执行testOkHttp3("http://www.baidu.com");测试,打印信息如下
// E/onResponse: 在子线程:OkHttp http://www.baidu.com/...
上述代码说明了onResponse
确实处于子线程(worker Thread)。
setText为什么不报异常?
没有搞明白
onResponse
处于子线程时,我直接写了setText
来更新数据,没有报错,一度让我以为onResponse
转到了主线程。
来看一下setText源码,其中更新UI是因为执行了
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
mTextSetFromXmlOrResourceId = false;
if (text == null) {
text = "";
}
...
if (mLayout != null) {
checkForRelayout();
}
...
}
重点在于checkForRelayout()
,接着看一下源码:
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;
}
// 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;
}
}
// 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();
}
}
主要是讲是否需要去获取新的View来更新布局。request a new view layout with a new text layout.
,这时,会执行requestLayout()
。
其中有两个return,直接使用了之前的View,之后讲text layout一下。不会执行requestLayout()
。
- 固定的宽高;
- 固定的宽,动态计算后高度没变化。
View的requestLayout()
了解WindowManagerService
后知道,View的添加,更新,删除操作是由WindowManager
管理着,ViewRootImpl
是View中的最高层级,属于所有View的根,实现了View和WindowManager
之间的通信协议,实现的具体细节在WindowManagerGlobal
这个类当中。
简单理解一下View和ViewRootImple
关系,那就是,View触发布局请求,在此过程中会调用ViewRootImpl
的requestLayout
重新进行测量、布局、绘制。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
其中checkThread()
用来检查是否为主线程。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
总结
- setText可以在子线程中更新,但必须布局宽高没发生变化。
- UI更新只能在主线程中执行,是因为
ViewRootImpl
会执行checkThread()
来检查线程
参考:
https://blog.csdn.net/m0_46496806/article/details/105356056
https://blog.csdn.net/stven_king/article/details/78775166