class NetMainActivity : AppCompatActivity() {
var TAG = "NetMainActivity"
var client :OkHttpClient = OkHttpClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_net_main)
btn1.setOnClickListener {
testGet()
}
}
fun testGet() {
var request = Request.Builder().url("http://www.baidu.com").build()
thread {
//错误写法
Log.e(TAG, "testGet: Threadname1=${Thread.currentThread().name}")
var response = client.newCall(request).execute()
textView.post {
Log.e(TAG, "testGet: Threadname2=${Thread.currentThread().name}")
textView.text = response.body?.string()
}
//正确写法
Log.e(TAG, "testGet: Threadname1=${Thread.currentThread().name}")
var response = client.newCall(request).execute()
var str = response.body?.string()
textView.post {
Log.e(TAG, "testGet: Threadname2=${Thread.currentThread().name}")
textView.text = str
}
}
}
}
上面是简单使用okHttp访问网络获取返回值,然后把返回结果文本展示到textview中。
很简单的代码,最开始写的是上面错误代码,安装app后,有时候运行能访问网络并成功展示,有时候多点几下就出现crash,并提示android.os.NetworkOnMainThreadException异常。异常堆栈如下:
2022-07-21 17:36:36.260 8066-8066/com.example.helloworld E/NetMainActivity: testGet: Threadname2=main
2022-07-21 17:36:36.280 8066-8066/com.example.helloworld E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.helloworld, PID: 8066
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513)
at java.net.SocketInputStream.read(SocketInputStream.java:175)
at java.net.SocketInputStream.read(SocketInputStream.java:144)
at okio.InputStreamSource.read(JvmOkio.kt:90)
at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:129)
at okio.RealBufferedSource.request(RealBufferedSource.kt:206)
at okio.RealBufferedSource.require(RealBufferedSource.kt:199)
at okio.RealBufferedSource.readHexadecimalUnsignedLong(RealBufferedSource.kt:381)
at okhttp3.internal.http1.Http1ExchangeCodec$ChunkedSource.readChunkSize(Http1ExchangeCodec.kt:429)
at okhttp3.internal.http1.Http1ExchangeCodec$ChunkedSource.read(Http1ExchangeCodec.kt:408)
at okhttp3.internal.connection.Exchange$ResponseBodySource.read(Exchange.kt:276)
at okio.RealBufferedSource.read(RealBufferedSource.kt:189)
at okio.RealBufferedSource.exhausted(RealBufferedSource.kt:197)
at okio.GzipSource.read(GzipSource.kt:88)
at okio.Buffer.writeAll(Buffer.kt:1642)
at okio.RealBufferedSource.readString(RealBufferedSource.kt:95)
at okhttp3.ResponseBody.string(ResponseBody.kt:187)
at com.example.helloworld.activity.net.NetMainActivity$testGet$1.invoke$lambda-0(NetMainActivity.kt:34)
at com.example.helloworld.activity.net.NetMainActivity$testGet$1.lambda$o7kqc81vUHfiH5ZjT9CxIqj9Fhs(Unknown Source:0)
at com.example.helloworld.activity.net.-$$Lambda$NetMainActivity$testGet$1$o7kqc81vUHfiH5ZjT9CxIqj9Fhs.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6820)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:922)
Suppressed: android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513)
at java.net.SocketInputStream.read(SocketInputStream.java:175)
at java.net.SocketInputStream.read(SocketInputStream.java:144)
at okio.InputStreamSource.read(JvmOkio.kt:90)
at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:129)
at okio.RealBufferedSource.indexOf(RealBufferedSource.kt:427)
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:320)
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:105)
at okhttp3.internal.http1.Http1ExchangeCodec$ChunkedSource.readChunkSize(Http1ExchangeCodec.kt:426)
at okhttp3.internal.http1.Http1ExchangeCodec$ChunkedSource.read(Http1ExchangeCodec.kt:408)
at okhttp3.internal.Util.skipAll(Util.kt:337)
at okhttp3.internal.Util.discard(Util.kt:358)
at okhttp3.internal.http1.Http1ExchangeCodec$ChunkedSource.close(Http1ExchangeCodec.kt:450)
at okio.ForwardingSource.close(ForwardingSource.kt:34)
at okhttp3.internal.connection.Exchange$ResponseBodySource.close(Exchange.kt:309)
at okio.RealBufferedSource.close(RealBufferedSource.kt:477)
at okio.RealBufferedSource.close(RealBufferedSource.kt:477)
at okio.InflaterSource.close(InflaterSource.kt:136)
at okio.GzipSource.close(GzipSource.kt:171)
at okio.RealBufferedSource.close(RealBufferedSource.kt:477)
at kotlin.io.CloseableKt.closeFinally(Closeable.kt:60)
at okhttp3.ResponseBody.string(ResponseBody.kt:186)
... 10 more
2022-07-21 17:36:36.343 1030-1116/? E/InputDispatcher: channel 'b58ae29 com.example.helloworld/com.example.helloworld.activity.net.NetMainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
NetworkOnMainThreadException异常代表有在主线程执行网络请求相关操作。可是
var response = client.newCall(request).execute()
网络请求的这部分代码明明是在子线程执行的啊。
接着我在代码中打印了两行日志。日志内容为
2022-07-21 17:36:36.122 8066-8111/com.example.helloworld E/NetMainActivity: testGet: Threadname1=Thread-2
2022-07-21 17:36:36.260 8066-8066/com.example.helloworld E/NetMainActivity: testGet: Threadname2=main
2022-07-21 17:36:36.280 8066-8066/com.example.helloworld E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.helloworld, PID: 8066
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513)
...
可以看到client的excute()是在子线程执行的。并且当我们获取到response对象,把response的body展示到textview上是在主线程执行的,这一点从log和代码都能看出来。
那究竟是哪里的网络操作在主线程中操作了呢?
我尝试了下先在子线程中把response.body.string()字符串先在子线程中获取到了,再在主线程中设置textview。就是上面正确的代码。
结果我发现怎么点击都能正常访问网络并成功展示了。那看来response.body.string()中应该是有一些网络操作了。之前以为只要拿到Response对象就以为已经执行完网络操作返回结果了,看来是自己天真了。
来看Response#string()的源码,里面有一句话特别重要:This method loads entire response body into memory.If the response body is very large thismay trigger an [OutOfMemoryError]意思是这个函数是用来加载整个response结果到内存中,response结果可能会很大,所以重点来啦,这是一个耗时动作!!!耗时动作是不能在主线程中执行的,所以我们上面在主线程中执行string()引发了crash。
/**
* Returns the response as a string.
*
* If the response starts with a
* [Byte Order Mark (BOM)](https://en.wikipedia.org/wiki/Byte_order_mark), it is consumed and
* used to determine the charset of the response bytes.
*
* Otherwise if the response has a `Content-Type` header that specifies a charset, that is used
* to determine the charset of the response bytes.
*
* Otherwise the response bytes are decoded as UTF-8.
*
* This method loads entire response body into memory. If the response body is very large this
* may trigger an [OutOfMemoryError]. Prefer to stream the response body if this is a
* possibility for your response.
*/
@Throws(IOException::class)
fun string(): String = source().use { source ->
source.readString(charset = source.readBomAsCharset(charset()))
}
response.body.string()具体怎样引发的crash后面有时间再分析吧!!