这篇文章不是用来讲概念的, 只是用来谈论一些关于Android 进程\协程那些问题
1. android 子线程中的异常会引发crash闪退吗?
答案是会的
Thread{
throw RuntimeException("this is a error")
}.start()
异常
21741 2379 E AndroidRuntime: java.lang.RuntimeException: this is a error
21741 2379 E AndroidRuntime: at com.xxxx.app.ui.feed.holder.CommentViewHolder$setComment$1$3$1.run(CommentViewHolder.kt:97)
21741 2379 E AndroidRuntime: at java.lang.Thread.run(Thread.java:929)
2. android能捕获error"异常"吗
我们知道Exception表示异常, 还有一种是Error, 表示系统严重错误, 他们都继承Throwable.
Exception能被捕获, 那Error能被捕获吗?
答案是可以
try {
if (true)
throw Error("this is an error")
} catch (e: java.lang.Exception) {
logcat("catch by exception")
} catch (e: Throwable) {
logcat("catch by throw able")
}
日志如下:
01-20 15:37:56.128 3813 3813 E lklog : catch by throw able
3. android kotlin协程中异常会引发crash闪退吗?
首先要明确, Android kotlin中的协程是一套线程框架而已, 具体要分为两种情况
3.1 launch中异常会引发闪退吗?
答案是会的
GlobalScope.launch {
throw RuntimeException("this is exception")
}
异常
01-20 15:20:05.294 28906 4222 E AndroidRuntime: java.lang.RuntimeException: this is exception
01-20 15:20:05.294 28906 4222 E AndroidRuntime: at com.xxxxx.app.ui.feed.holder.CommentViewHolder$setComment$1$3$1.invokeSuspend(CommentViewHolder.kt:98)
01-20 15:20:05.294 28906 4222 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
01-20 15:20:05.294 28906 4222 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
01-20 15:20:05.294 28906 4222 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
01-20 15:20:05.294 28906 4222 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
01-20 15:20:05.294 28906 4222 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
3.2 async中异常会引发闪退吗?
答案是不会, 但是在await的时候会抛出异常, 并且异常会抛在await所在的线程
GlobalScope.launch(Dispatchers.Main) {
val deferred = GlobalScope.async(Dispatchers.IO) {
if (true)
throw RuntimeException("this is aysnc exception")
true
}
deferred.await()
}
如上, 我们在IO线程中抛出一个异常, 并且在UI线程中进行await
发生闪退, 日志如下
01-20 15:25:21.254 32198 32198 E AndroidRuntime: java.lang.RuntimeException: this is aysnc exception
01-20 15:25:21.254 32198 32198 E AndroidRuntime: at com.xxxx.app.ui.feed.holder.CommentViewHolder$setComment$1$1$deferred$1.invokeSuspend(CommentViewHolder.kt:70)
01-20 15:25:21.254 32198 32198 E AndroidRuntime: at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
01-20 15:25:21.254 32198 32198 E AndroidRuntime: at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
01-20 15:25:21.254 32198 32198 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
01-20 15:25:21.254 32198 32198 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
01-20 15:25:21.254 32198 32198 E AndroidRuntime: at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
并且可以看到, 进程号和线程号都是32198, 证明异常抛在主线程, 也就是await所在的线程
当去掉await之后, 就可以看到没有异常抛出了
我们顺着async的代码一步一步走过去, 就会发现以下代码
/**
* Runs given block and completes completion with its exception if it occurs.
* Rationale: [startCoroutineCancellable] is invoked when we are about to run coroutine asynchronously in its own dispatcher.
* Thus if dispatcher throws an exception during coroutine start, coroutine never completes, so we should treat dispatcher exception
* as its cause and resume completion.
*/
private inline fun runSafely(completion: Continuation<*>, block: () -> Unit) {
try {
block()
} catch (e: Throwable) {
completion.resumeWith(Result.failure(e))
}
}
可以看到异常被捕获了, 所以async是不会有异常抛出的
4. 使用okhttp之后, 网络请求到底在主线程执行, 还是子线程
Android系统规定不能在主线程发起网络请求, 但是我使用okhttp之后, 到底是在哪里发起网络请求的?
- 同步网络请求, 就在当前线程发起, 如果当前线程是主线程, 就会爆出异常
- 异步网络请求, okhttp会维护一个线程池, 所以是在子线程中发起的.
主线程同步发起网络请求代码:
val client = OkHttpClient()
val request = Request.Builder().url("http://www.baidu.com").get().build()
val result = client.newCall(request).execute()
val body = result.body()?.string().toString()
logcat("body:$body")
异常
01-20 15:49:03.926 7247 7247 E AndroidRuntime: android.os.NetworkOnMainThreadException
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1565)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:115)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at java.net.InetAddress.getAllByName(InetAddress.java:1152)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at okhttp3.Dns$1.lookup(Dns.java:40)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:185)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.java:149)
01-20 15:49:03.926 7247 7247 E AndroidRuntime: at okhttp3.internal.connection.RouteSelector.next(RouteSelector.java:84)
主线程异步发起网络请求
val client = OkHttpClient()
val request = Request.Builder().url("http://www.baidu.com").get().build()
client.newCall(request).enqueue(object:Callback{
override fun onFailure(call: Call, e: IOException) {
logcat("onFail")
}
override fun onResponse(call: Call, response: Response) {
logcat("onResponse")
val body = response.body()?.string().toString()
logcat("body:$body")
}
})
当然不会闪退, 我们深入okhttp的源码看一下
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
....
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
//executorService 返回一个线程池
asyncCall.executeOn(executorService());
}
return isRunning;
}
这篇文章管Retrofit什么事
okhttp+retrofit应该是目前Android上最流行的搭配之一
retrofit本质上就是用动态代理的方法来封装了okhttp的网络请求
注, 至于是同步网络请求还是异步网络请求, 和返回值类型有关系, 目前返回Observer(RxJava)和Deferred(kotlin)的是异步网络请求
看一下源码, 这是Retrofit中的OkHttpCall类
@Override public synchronized Request request() {
//同步网络请求
okhttp3.Call call = rawCall;
if (call != null) {
return call.request();
}
....
}
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
... 异步网络请求
call.enqueue(new okhttp3.Callback() {}
...
}