kotlin 协程在 Android 中的使用——Jetpack 中的协程、Retofit中使用协程及源码分析

从 kotlin1.3之后协程api 稳定了,kotlin 协程协什么鬼,又要学,学不动了😭,学了之后发现"真香"哈哈😄。协程的优点网上说了一大堆,确实很方便,好用。
在 Android 中使用kotlin 协程,真爽!😂
1、kotlin 现在是 Android 开发第一语言,使用协程当然也是可以的。
2、kotlin协程官方也提供 Android 方面的 api 方便 Android 使用(MainScope,本文并不打算介绍这个)
3、 Android 的 Jetpack 架构组件也开始出现配合使用 kotlin 协程的 api
4、现在很流行的网络框架Retrofit也开始支持 kotlin 协程了
以上几条可以说明kotlin 协程你值得拥有

1.Android Jetpack 中使用 kotlin 协程

在 ViewModel 中使用ViewModelScope, 对应依赖 androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01 及以上

class MyViewModel: ViewModel() {
    fun  test() {
        viewModelScope.launch {
            //协程代码
        }
    }
}

直接在 Activity 或 Fragment 中使用LifecycleScope, 对应依赖 androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01 及以上

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        lifecycleScope.launch {
          //协程代码
        }
    }
}

还可以使用liveData, 对应依赖androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01` 及以上

val user: LiveData<User> = liveData {
  	//协程代码
    val data = database.loadUser() 
    emit(data)
}

room 对协程的支持,在版本2.1及以上,对应依赖androidx.room:room-ktx:$room_version

@Insert
suspend fun add(user: User): Long

workmanager对协程的支持,对应依赖implementation "androidx.work:work-runtime-ktx:$work_version"

class MyWorker(context: Context, workerParams: WorkerParameters) :
    CoroutineWorker(context, workerParams) {
    
   override suspend fun doWork(): Result = coroutineScope { 
        //协程代码
        Result.success()
    }
}

参考链接: 官方文档介绍

2.在Retofit 使用kotlin协程

retofit 在2.6.0版本(在写此博客的时候,retofit 已经更新至2.6.2版本了)添加suspend函数的支持,使用方式如下

		@GET("user/{userId}")
    suspend fun getUserInfo(@Path("userId") userI: String): User

在函数前加上 suspend 函数直接返回你需要对象类型不需要返回Call对象。

3 kotlin协程+retrofit+ViewModel+LiveData的组合使用

本例中使用的是鸿洋大神玩Android中的api ,玩Android(Android 程序员值得拥有,哈哈) 地址https://www.wanandroid.com/

ApiService.kt

interface ApiService {
    @GET("article/list/{page}/json")
    suspend fun getArticleList(@Path("page") page: Int = 0): Result<PageEntity<Article>>
}

Result.kt

data class Result<T>(val code: Int, val errorMsg: String?, val data: T)

PageEntity.kt

data class PageEntity<T>(
    val curPage:Int,
    val offset:Int,
    val over:Boolean,
    val size:Int,
    val PageCount:Int,
    val total:Int,
    val datas:List<T>
)

Article.kt

data class Article(
 		@SerializedName("desc")
    val desc: String,
    @SerializedName("id")
    val id: Int,
    @SerializedName("title")
    val title: String,
    //…… 
)

ArticleViewModel.kt

class ArticleViewModel : ViewModel() {

    private val _articleListData = MutableLiveData<List<Article>>()
    //保证外部只能观察此数据,不同通过setValue修改
    val articleListData: LiveData<List<Article>> = _articleListData

    private val _errorMsg = MutableLiveData<String?>()
    val errorMsg: LiveData<String?> = _errorMsg

    fun fetchArticleList(page: Int) {
        viewModelScope.launch {
            try {
              //0️⃣ ⬇️
                val result = RetrofitManger.apiService.getArticleList(page)
              //1️⃣ ⬇️
                _articleListData.value = result.data.datas
            } catch (e: Exception) {
                _errorMsg.value = e.message
            }
        }
    }
}

上面使用viewModelScope.launch启动协程 ,当activity 或Fragment 销毁时,ViewModel也随之销毁,viewModelScope的协程也会被取消,我们不需要在ViewModel 的onCleared()方法取消(具体原因会在下面分析)

注释0️⃣ 处 Retofit发起网络是在子线程执行的,请求到数据后返回线程又切回到主线程,所有注释1️⃣已经是主线程了。如果网络请求出现异常,使用 try catch 捕获一下就行了。上面代码看上去就像同步代码一下,这就是kotlin协程的魅力。

ArticleActivity.kt

class ArticleActivity : AppCompatActivity() {
    private val adapter by lazy { ArticleAdapter() }
    private val viewModel: ArticleViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)

        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
        recyclerView.adapter = adapter
        //观察文章列表数据
        viewModel.articleListData.observe(this, Observer { list ->
            loadProgress.visibility = View.GONE
            adapter.submitList(list)
        })

        viewModel.errorMsg.observe(this, Observer {
            if (it != null) {
                toast(it)
            }
        })
        btn.setOnClickListener {
            loadProgress.visibility = View.VISIBLE
            viewModel.fetchArticleList(1) //请求数据
        }
    }
}

具体效果如图所示(以上源码见文章底部)

在这里插入图片描述

4. 问题分析:

1. viewModelScope的协程是如何随着Activity 或Fragment的销毁而取消的?

先看一下viewModelScope怎么来的

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
        get() {
          //0️⃣ ⬇️
            val scope: CoroutineScope? = this.getTag(JOB_KEY) 
            if (scope != null) {
                return scope
            }
          	//1️⃣ ⬇️
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
        }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
      //2️⃣ ⬇️
        coroutineContext.cancel()
    }
}

viewModelScope 是ViewModel 的扩展属性。ViewModel 中有一个Map对象

private final Map<String, Object> mBagOfTags = new HashMap<>();

在注释0️⃣ 处通过ViewModel的getTag 方法,从ViewModel的mBagOfTags(Map)属性中尝试获取CoroutineScope对象,如果为空,就调用ViewModel 的setTagIfAbsent 方法把CloseableCoroutineScope 对象添加到mBagOfTags中。CloseableCoroutineScope 类实现了Closeable, CoroutineScope两个接口。在注释2️⃣ 中调用了取消协程方法。那调用它的close什么时候被调用呢? CloseableCoroutineScope对象 被放到了ViewModel 的mBagOfTags 中。

在ViewModel类中找到了这么一个个方法

@MainThread
final void clear() {
    mCleared = true;
    // Since clear() is final, this method is still called on mock objects
    // and in those cases, mBagOfTags is null. It'll always be empty though
    // because setTagIfAbsent and getTag are not final so we can skip
    // clearing it
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
              //0️⃣ ⬇️  
              closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

  private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

从上面可以看出在clear方法中循环遍历mBagOfTags,在注释0️⃣处执行closeWithRuntimeException() 在closeWithRuntimeException中调用close()方法,所以CloseableCoroutineScope对象中的close是在这里调用的,那clear()方法 又是在哪里调用的呢。那这个就要说说ViewModel 的与Activity 或Fragment 的关系了。下面说一说ViewModel和Activity的关系。创建ViewModel 对象是通过ViewModelProvider的get()方法创建的,ViewModelProvider对象创建需要两个参数是(@NonNull ViewModelStore store, @NonNull Factory factory)

第一个参数ViewModelStore 是通过androidx.activity.ComponentActivity的getViewModelStore() 方法或者 androidx.fragment.app.Fragment的getViewModelStore()方法获取(ComponentActivity是AppCompatActivity是的爷爷类 )

在 ViewModelProvider 的get方法中 会创建一个ViewModel 对象并存放在ViewModelStore的map中

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
   //…… 省略ViewModel常见的代码
  
  //把ViewModel 对象放到ViewModelStore 中
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

ViewModelStore.java

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
    final ViewModel get(String key) {
        return mMap.get(key);
    }
  
    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

上面就是ViewModelStore类的所有代码(此代码版本是Viewmodel 2.2.0-rc01) 可以看到ViewModel 对象被存储在mMap中,这个类中也有个clear方法,方法中循环遍历mMap 执行ViewModel的clear() 哦,原来ViewModel 的clear()在这里调用的。那ViewModelStore 的clear()又是哪里调用的呢😂。ViewModelStore 的对象是从Activity 或Fragment,我们应该去他来源看一看,点击clear()方法,发现在ComponentActivity和FragmentManagerViewModel中有调用此方法,在ComponentActivity中是这样调用的,在ComponentActivity 的构造方法设置lifecycle 的状态改变监听,当处于destroy状态是就调用ViewModelStore的clear() ,Fragment 是FragmentMager 调用Fragment.performDestroy()方法前调用。

public ComponentActivity() {
 // 省略部分代码
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
}

简单梳理一下上面的流程

1.ViewModel 有个Map 对象,保存了CloseableCoroutineScope 引用

2.ViewModel 创建的的时候会被保存在从getViewModelStore()获取的对象中

3.当Activity 或Fragment 销毁时,ViewModelStore的clear会被执行,此方法就会触发ViewModel 的clear()方法,在ViewModel 的的clear()方法中触发CloseableCoroutineScope的close方法。

2.Retrofit 是怎么支持协程的?

suspend 是kotlin 关键词,那java 怎么知道一个方法是suspend修饰的呢?

利用android studio 的show Kotlin Bytecode工具

//kotlin 	
	@GET("article/list/{page}/json")
  suspend fun getArticleList(@Path("page") page:Int): Result<PageEntity<Article>>

	//kotlin字节码 反编译后的java代码
	@GET("article/list/{page}/json")
	@Nullable
	Object getArticleList(@Path("page") int var1, @NotNull Continuation var2);

发现会在原来的参数后面加上一个Continuation var2 参数,Retrofit 是不是就是根据这个参数判断的呢?

查看Retofit的create方法发现有一个代理,当调用接口方法时就会触发InvocationHandler的invoke方法,

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();
        private final Object[] emptyArgs = new Object[0];

        @Override public @Nullable Object invoke(Object proxy, Method method,
            @Nullable Object[] args) throws Throwable {
          // If the method is a method from Object then defer to normal invocation.
          if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
          }
          if (platform.isDefaultMethod(method)) {//接口方法设置默认实现(java8+)
            return platform.invokeDefaultMethod(method, service, proxy, args);
          }
          return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
        }
      });
}

方法最后loadServiceMethod(method) 会调用ServiceMethod的parseAnnotations()方法。

ServiceMethod.java

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(method,
          "Method return type must not include a type variable or wildcard: %s", returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

在 创建RequestFactory对象时,会调用RequestFactory的build()方法, 在这个方法中有这么一段代码 循环解析方法参数

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
  parameterHandlers[p] =
      parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}

parseParameter 方法部分代码

private @Nullable ParameterHandler<?> parseParameter(
    int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
	// 省略部分代码……
  if (result == null) {
    //0️⃣ ⬇️
    if (allowContinuation) {
      try {
        //1️⃣ ⬇️
        if (Utils.getRawType(parameterType) == Continuation.class) {
          isKotlinSuspendFunction = true;
          return null;
        }
      } catch (NoClassDefFoundError ignored) {
      }
    }
    throw parameterError(method, p, "No Retrofit annotation found.");
  }

  return result;
}

注释0️⃣ 处allowContinuation是的值是上面传入的 p == lastParameter 也就是判断是否是最后一个参数,在注释1️⃣处判断这个参数的类是否是Continuation.class 如果是就把 isKotlinSuspendFunction标记为 true。看到这个我们也就知道了Retrofit就是根据这个最后一个参数来判断是否支持kotlin 的 suuspend 函数

我们再继续看看ServiceMethod的parseAnnotations的方法吧,在构建RequestFactory对象时 判断了是否支持kotlin susupend 函数,方法的最后 调用的HttpServiceMethod的parseAnnotations 并将返回值返回

我们再去看看HttpServiceMethod的parseAnnotations 做了些什么。

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
    Retrofit retrofit, Method method, RequestFactory requestFactory) {
  boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
  boolean continuationWantsResponse = false;
  boolean continuationBodyNullable = false;
//0️⃣ ⬇️
  if (isKotlinSuspendFunction) {
    Type[] parameterTypes = method.getGenericParameterTypes();
    Type responseType = Utils.getParameterLowerBound(0,
        (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
    //1️⃣ ⬇️
    if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {//2
      // Unwrap the actual body type from Response<T>.
      responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
      continuationWantsResponse = true;
    } else {
      // TODO figure out if type is nullable or not
      // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
      // Find the entry for method
      // Determine if return type is nullable or not
    }

    adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
    annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
  } else {
    adapterType = method.getGenericReturnType();
  }
 // 省略部分代码……
  if (!isKotlinSuspendFunction) {
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
  } else if (continuationWantsResponse) {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    //2️⃣ ⬇️
    return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
        callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
  } else {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    //3️⃣ ⬇️
    return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
        callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
        continuationBodyNullable);
  }
}

在注释0️⃣处先判断是否支持kotlin的 suspend ,在注释1️⃣处又判断了方法返回值是不是Response类包裹的,如果是就把continuationWantsResponse 标记为true。在注释2️⃣和注释3️⃣ 处根据continuationWantsResponse返回不同的对象。看到这里。那上面写ApiService 接口是 suspend 方法的返回值,外面应该可以包裹Response的

@GET("article/list/{page}/json")
suspend fun getArticleList1(@Path("page") page:Int): Response<Result<PageEntity<Article>>>

上面写法写法也是可以的,返回的的是Response对象,是否请求成功需要自己写逻辑判断。如果返回值外面不包裹Response那么如果请求不成功,会抛出异常,异常需要我们自己处理。

在上面的例子中我们就是写的是注释3️⃣是返回值没有Response 包裹的情况,下面来分析如果走到注释3️⃣的情况吧

SuspendForBody(删除了源码中的注释)

static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
  private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
  private final boolean isNullable;

  SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
      Converter<ResponseBody, ResponseT> responseConverter,
      CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) {
    super(requestFactory, callFactory, responseConverter);
    this.callAdapter = callAdapter;
    this.isNullable = isNullable;
  }
  @Override protected Object adapt(Call<ResponseT> call, Object[] args) {
    call = callAdapter.adapt(call);
    Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
   
    try {
      return isNullable
          ? KotlinExtensions.awaitNullable(call, continuation)
          : KotlinExtensions.await(call, continuation);
    } catch (Exception e) {
      return KotlinExtensions.yieldAndThrow(e, continuation);
    }
  }
}

KotlinExtensions 中的方法就是使用的kotlin的suspendCancellableCoroutine 。先让函数挂起,当回调执行拿到数据时调用 resume 再恢复到之前的协程,就可以直接得到网络返回数据啦

suspend fun <T : Any> Call<T>.await(): T {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        if (response.isSuccessful) {
          val body = response.body()
          if (body == null) {
    				//省略部分代码
            continuation.resumeWithException(e)
          } else {
            continuation.resume(body)
          }
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }
      override fun onFailure(call: Call<T>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}

上面SuspendForBody(HttpServiceMethod是他爸爸,ServiceMethodd是他爷爷) 的adapt 是怎么调用的呢?

我们从头再看看当接口方法调用是触发的那个方法吧,在InvocationHandler 的invoke的方法 最后一句是

return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);

我们上面分析了那么多我们只是分析了loadServiceMethod(method)方法执行后的一系列实现,这个方法返回值还调用了invoke方法,那loadServiceMethod(method)方法返回值是什么,就是这个SuspendForBody对象(suspend 函数且函数返回值外面没有Response包裹时),

在HttpServiceMethod中 实现了 invoke方法,并条用了adapt方法

@Override final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}

以上就是Retofit协程请求方式的大致过程。

本文源码地址

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值