Android Kotlin okhttp Retrofit 线程协程那些事

这篇文章不是用来讲概念的, 只是用来谈论一些关于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() {
    
    }
    ...
  }

猜你喜欢

转载自blog.csdn.net/weixin_43662090/article/details/112877273