Android开发(30)——协程Coroutine和OkHttp请求

本节内容

1.JavaThread下载数据回调

2.引入协程

3.launch和async

4.coroutineScope和CoroutineContext

5.WithContext切换线程

6.啰嗦OkHttp

7.okhtttp获取数据

8.聚合数据头条新闻API说明

9.使用OkHttp3获取数据

10.手动创建数据模型

11.使用插件自动创建模型

12.使用retrofit获取数据

一、JavaThread下载数据回调
1.Thread会阻塞当前的线程 main-UI Thread,耗时比较短的小任务会放到主线程去做。
2.有时候主线程上会有一些耗时很长的任务,它会阻塞主线程的其他任务。为了解决这个问题,可以开启一个新的线程,把它称为子线程。
3.UI线程是提供给用户进行交互的,尽量不要让它被阻塞。
4.以下面的代码为例,在实现按钮的点击事件时,我们打印完start,等待一会再打印end
 button.setOnClickListener {
                Log.v("swl","start  ${Thread.currentThread()}")
                Thread.sleep(2000)
                Log.v("swl","end  ${Thread.currentThread()}")
            }
  • 这样的话,第一次点击按钮之后,只有等这个事件过了,才能第二次点击按钮。
直接阻塞主线程
5.为了不让它阻塞主线程,我们可以创建一个新的线程,这样每次点击按钮的时候,就不用等待也能直接开始运行了。
button.setOnClickListener {
Thread(object :Runnable{
            override fun run() {
            Log.v("swl","start  ${Thread.currentThread()}")
            Thread.sleep(2000)
            Log.v("swl","end  ${Thread.currentThread()}")
            }
        }
        ).start()
}
  • 这种由于参数继承自一个接口,而该接口里面又只有一种方法,所以可以使用lambda表达式。
button.setOnClickListener {
            Thread{
                Log.v("swl","start  ${Thread.currentThread()}")
                Thread.sleep(2000)
                Log.v("swl","end  ${Thread.currentThread()}")
            }.start()
        }
每次点击按钮后都开启一个新的线程
  • Thread.sleep(2000)的意思是阻塞当前线程,如果把它直接写在MainActivity里面的话,那么它就会阻塞主线程,只有等待一段时间end打印完了之后,才能继续点击按钮。
  • 但是如果我们每次点击都创建一个新的线程的话,第一次点击之后先打印start,因为它被阻塞了2s,所以不会立刻打印end。如果我立刻又点了一下按钮的话,这个时候就又会创建一个新的线程,又打印了一个start。因为我在2S内点了四次按钮,所以打印了四个start,2S之后才打印end。
6.Java开启线程的弊端:
  • Java里面有线程池,每个线程池里面都只能放规定数量的线程。如果超过了这个数量,那么超过的那个就要进入等待序列,直到线程池中有线程执行完了,它才能进入线程池执行。
  • 线程是很消耗内存的,所以不能大量地开辟线程。当线程达到一定程度的时候,就会出现警告。
  • 线程之间的数据交互:①通过Handler来传递数据(回调) ②进行线程之间的切换(Rxjava)。
7.回调数据的方法
  • 定义一套接口,实现两个线程之间的数据回调。在需要传递数据的类里面定义一个接口(接口里面定义两个方法),在这个类里面还需要定义一个接口类型的listener。类里面还有一个方法,在里面需要判断有没有listener,如果有的话就进行相应的操作。①在接收数据的类里面,先继承一下前面那个类的接口,然后实现里面的方法,并把该类作为它的listener。②这样的话listener就和接口里的两个方法分离开了,还有一种方法。直接使用匿名类,让listener等于这个匿名类,在里面实现接口里的两个方法。(推荐使用第二种)
  • 传递数据的类:
class UtilNetWork {
    var listener: callBack? = null
    fun data(){
        Thread{
            Log.v("swl","开始下载。。${Thread.currentThread()}")
            Thread.sleep(2000)
            Log.v("swl","下载结束。。${Thread.currentThread()}")
            val result = "jack"
        }
        listener.let {
            
        }
    }

    interface callBack{
        fun onSuccess(data:String)
        fun onFailure(error:String)
    }
}
  • 接收数据的类,在MainActivity里面
 button.setOnClickListener {
           val util = UtilNetWork()
            util.listener = object :UtilNetWork.callBack{
                override fun onSuccess(data: String) {

                }

                override fun onFailure(error: String) {

                }
            }
        }
8.切换线程。
runOnUIThread{
//进行需要的操作
}
二、引入协程Coroutine
1.线程与协程的区别:
  • ①一个任务可以创建多个线程,但是线程的数量是有限的。因为线程数量越多,那么消耗的内存越多,速度越慢。②对于协程来说,一共就两个线程,主线程和子线程。在子线程上可以创建无数个协程,资源消耗量不大,就在一个线程上进行调度,可以有成千上百个协程同时执行。协程会被阻塞,线程基本上不会被阻塞,因为一个线程上有很多和协程。
  • 线程执行任务是按顺序的,如果线程上有任务在执行,后面的必须等它执行完了才能接着执行。但是线程上如果有执行时间很长的协程,那么就会把它挂起,让它自己去执行,然后在线程上接着执行下一个协程。等到前面这个协程执行完了之后,又从挂起的那个地方恢复。
2.协程的特点:
  • 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  • 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。
3.使用协程
  • 1.创建一个新的工程,在里面添加一个library,然后将以下依赖项添加到应用的 build.gradle 文件中。(如果只是在kotlin里面使用,那么直接导入下面的依赖库即可。但是如果是在安卓里面使用,那么还需要导入implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")这个依赖库)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
  • 2.在library的包里面再创建一个MyClass类,在类外面写一个main()方法,在里面创建一个线程。
fun main(){
    println("main start ${Thread.currentThread()}")
    Thread{
        println("start ${Thread.currentThread()}")
        Thread.sleep(2000)
        println("end ${Thread.currentThread()}")
    }.start()

    println("main end ${Thread.currentThread()}")
}
运行结果如下图所示:
运行结果
主线程不会被阻塞,所以main start之后立马执行main end。如果开启新线程的时间很短,那么输出顺序也可能为main start ->start->main end->end
  • 3.使用协程的方式。(那么就不能使用Thread,而要使用delay,因为Thread会阻塞线程,delay不会阻塞线程)
fun main(){
    println("main start ${Thread.currentThread()}")
    GlobalScope.launch {
        load() }
    println("main end ${Thread.currentThread()}")
}
suspend fun load(){
    println("start ${Thread.currentThread()}")
    delay(2000)
    println("end ${Thread.currentThread()}")
}

任何一个协程都有自己的CoroutineScope,在这个协程域里面可以创建无数个子协程。
如何创建一个CoroutineScope:(一般使用前面两种)

  • launch :创建一个独立的CoroutineScope。同步,不返回数据。
  • async :异步,需要返回数据。
  • runBlocking:它会在当前线程上创建一个协程域,并且这个执行会阻塞当前的线程。
  • GlobalScope:创建一个全局的CoroutineScope,不推荐使用。作用域为整个app的lifecycle。缺点:当主线程结束,不会等待GlobalScope的协程执行完毕。它会创建一个新的线程。
suspend:挂起函数只能在另外一个挂起函数或者一个coroutineScope(协程域包括协程的所有使用方法,其中就有挂起功能)里面调用
GlobalScope运行结果
因为主线程执行速度太快了,调用load方法还要延迟2S钟。所以新的线程还没开始,主线程就已经运行结束了。
  • 4.前面用的是GlobalScope,为了解决速度过快导致子线程无法开启的问题,我们可以延长主线程的执行时间,也delay一下,但是delay是一个挂起方法,所以我们使用sunBlocking。
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    GlobalScope.launch {
        load() }
     delay(3000)
    println("main end ${Thread.currentThread()}")
}
suspend fun load(){
    println("start ${Thread.currentThread()}")
    delay(2000)
    println("end ${Thread.currentThread()}")
}
runBlocking运行结果
可以发现由于主线程有延迟,而且延迟时间多于子线程,所以子线程执行完毕之后,主线程才结束。
三、lunch和asyno
1.只用runBlocking,不用GlobalScope的话,那么整个runBlocking都是协程域,整个协程都被挂起,不会有阻塞。
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    loadTask1()
    println("main end ${Thread.currentThread()}")
}
suspend fun loadTask1(){
    println("start1 ${Thread.currentThread()}")
    delay(1000)
    println("end1 ${Thread.currentThread()}")
}
执行结果
很明显它是按照顺序执行的,因为它们都在同一个域里面。
2.使用launch创建协程
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    launch {
        loadTask1()
    }
    println("main end ${Thread.currentThread()}")
}
suspend fun loadTask1(){
    println("start1 ${Thread.currentThread()}")
    delay(1000)
    println("end1 ${Thread.currentThread()}")
}
launch执行结果
  • launch并没有阻塞主线程的执行,如果再添加一个launch
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    launch {
        loadTask1()
    }
    launch {
        loadTask2()
    }
    println("main end ${Thread.currentThread()}")
}
suspend fun loadTask1(){
    println("start1 ${Thread.currentThread()}")
    delay(1000)
    println("end1 ${Thread.currentThread()}")
}
suspend fun loadTask2(){
    println("start2 ${Thread.currentThread()}")
    delay(1000)
    println("end2 ${Thread.currentThread()}")
}
两个launch执行结果
  • 还是没有阻塞主线程,但是在执行1的时候,遇到了delay,所以1被挂起,执行2,然后1结束,2结束。
  • 使用measureTimeMillis方法来计算挂起的时间
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
   val time = measureTimeMillis {
       launch {
           loadTask1()
       }
       launch {
           loadTask2()
       }
   }
    println("time $time")
    println("main end ${Thread.currentThread()}")
}
计算结果为15ms
  • 为什么会出现这个结果呢?因为我们打印的只是分配的时间,并不是执行的时间。当我们查看launch的源码,发现里面有一个join方法,它的作用是挂起一个协程知道它结束为止。
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
   val time = measureTimeMillis {
       val job1 =launch {
           loadTask1()
       }
       job1.join()
      val job2= launch {
           loadTask2()
       }
       job2.join()
   }
    println("time $time")
    println("main end ${Thread.currentThread()}")
}
  • 调用join方法,发现end main到最后去了。
调用join方法的执行结果
  • 所以我们可以发现这里协程是同步执行的,也就是执行完了1才会执行2,由于线程开启关闭还需要时间,所以比2s多一点时间。
3.使用async创建协程。
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
   val time = measureTimeMillis {
       val job1 =async {
           loadTask1()
       }
      val job2= async {
           loadTask2()
       }
      job1.await()
      job2.await()
   }
    println("time $time")
    println("main end ${Thread.currentThread()}")
}
异步执行结果
  • async是异步执行的。谁先读取完就执行谁的,没有顺序。很明显异步执行要比同步执行的时间短。
四、CoroutineScope和CoroutineContext
1.当我们执行以下代码时,会得到如下结果:
fun main(){
    runBlocking {
        println("1: ${Thread.currentThread()}")
        launch {
            println("2: ${Thread.currentThread()}")
        }
        println("3: ${Thread.currentThread()}")
    }
}
执行结果
把launch改为async,结果也是一样的。这说明就算开启协程的方式不同,但是线程是一样的,它并不会开启新的线程。
2.CoroutineScope:使用launch和async时,都是创建一个新的scope
3.CoroutineContext:使用launch和async时,和parent scope在同一个context中
五、withContext切换线程
1.Dispatchers:调度器。
2.线程的切换主要有:
  • Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用 suspend 函数,运行 Android 界面框架操作,以及更新 LiveData 对象。
  • Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。
  • Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。
2.模拟一下用户登录的过程。在网络上先读取用户的id,再获取用户的信息。这是切换线程的一种方式,从io线程切换到main线程(在io线程运行结束后,会自动切换到main线程)。
data class User(val name:String)
fun main(){
    runBlocking {
      val result =  async (Dispatchers.IO) {
               login()
        }
     val userInfo = async(Dispatchers.IO) {
         userInfo(result.await())
     }
       println(userInfo.await().name)
    }
}

suspend fun login():Int{
    println("开始login")
    delay(1000)
    println("login成功")
    return 1001
}
suspend fun userInfo(id:Int):User{
    println("获取用户信息:$id")
    delay(1000)
    println("获取用户信息成功")
    return User("jack")
}
运行结果如下:
运行结果
3.如果一个任务既要在主线程执行,又要在子线程执行,那么我建议先指定在主线程,需要子线程的时候再指定IO线程,这样它最终只会进行一次跳转。
fun main(){
    runBlocking {
     launch (Dispatchers.Main){
          launch (Dispatchers.IO){

          }
      }
    }
}
  • 还有一种就是在函数内部就提前指定好线程。这样直接调用函数更容易理解。
fun main(){
    runBlocking {
         val  id= login()
         val user= userInfo(id)
          println("user: ${user.name}")
    }
}
  suspend fun login():Int{
  return withContext(Dispatchers.IO){
        println("开始login")
        delay(1000)
        println("login成功")
         1001 //默认返回值
    }
}
suspend fun userInfo(id:Int):User{
  return  withContext(Dispatchers.IO){
        println("获取用户信息:$id")
        delay(1000)
        println("获取用户信息成功")
        User("jack")
    }
}
运行结果
4.使用withContext切换线程。这个还是没有上面的那个好。
 launch (Dispatchers.Main){
          withContext(Dispatchers.IO){
              login()
          }
          userInfo()
      }
    }
5.与基于回调的等效实现相比,withContext() 不会增加额外的开销。此外,在某些情况下,还可以优化 withContext() 调用,使其超越基于回调的等效实现。例如,如果某个函数对一个网络进行十次调用,您可以使用外部 withContext() 让 Kotlin 只切换一次线程。这样,即使网络库多次使用 withContext(),它也会留在同一调度程序上,并避免切换线程。此外,Kotlin 还优化了 Dispatchers.DefaultDispatchers.IO 之间的切换,以尽可能避免线程切换。
  • 重要提示:利用一个使用线程池的调度程序(例如 Dispatchers.IO 或 Dispatchers.Default)不能保证块在同一线程上从上到下执行。在某些情况下,Kotlin 协程在 suspend 和 resume 后可能会将执行工作移交给另一个线程。这意味着,对于整个 withContext() 块,线程局部变量可能并不指向同一个值。
6.使用withContext其实还是会阻塞主线程,如果想不阻塞主线程的话,另外开启一个新线程来调用login()和userInfo()函数,这样只会阻塞当前线程,不会阻塞主线程。
六、啰嗦OkHttp
1.先向gradle中导入依赖库
androidTestImplementation 'androidx.test.espresso:espresso-android:3.3.0'
2.进入OkHttp官网https://square.github.io/okhttp/,找到Releases导入依赖库。
  androidTestImplementation 'androidx.test.espresso:espresso-android:3.3.0'
3.OkHttp3实际上一个封装,封装的内容包括:
  • OkHttpClient:提供给用户,用户通过这个来创建OkHttp,也就是对象。
  • Request:包括请求的地址和其他信息。
  • Call接口:里面定义了一些操作。真正来操作的是一个RealCall对象。
  • okio:真正做传输的核心。
4.application->okhttp3->Caching或服务器。在application与okhttp3之间还有一个拦截器(NetWorkInterpreter,拦截网络的每一个操作,也就是获取详细信息)
  • OkHttpClient里面有一个call对象指向RealCall,还有一个Dispatchers,Caching
  • Request包括url,method
5.具体的使用详见https://square.github.io/okhttp/recipes/
七、okhttp获取数据
1.数据有两种类型:
  • XML :几乎不用
  • JSON:将JSON的数据转化为kotlin里面的数据类型。
这里需要使用聚合数据,我们先提前在聚合数据里面申请一个账号,然后选择API里面的免费项目,比如新闻头条,按照它的格式发送请求
请求详情
在网页中输入以下网址:https://v.juhe.cn/toutiao/index?type=&page=&page_size=&is_filter=&key=4494d20d3e853ec01a1dafc8b901e716,然后打开一个JSON解析器,将网页内的数据转化为我们能看懂的代码。
解析之后
解析之后折叠起来,只有三个元素,相当于三个map(包含key和value)
展开result:
result展开之后
2.布局一下activity_main,我的布局加了一个按钮和一个progressBar:
activity_main布局
3.新建一个工程,向里面导入我们需要的依赖库。
//coroutine
    androidTestImplementation 'androidx.test.espresso:espresso-android:3.3.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
    //okhttp
    implementation("com.squareup.okhttp3:okhttp:4.9.0")

    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha02")
    // LiveData
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha02")
    // Lifecycles only (without ViewModel or LiveData)
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha02")

4.我们要根据前面获取到的信息做一个新闻展示页面,使用MVVM模式,所以要另外创建一个类作为ViewModel。
class NewsViewModel:ViewModel() {
    val news:MutableLiveData = MutableLiveData()

    init {
        news.value = null
    }

    fun loadNews(){
        viewModelScope.launch {
           news.value = realLoad()
        }
    }

    suspend fun realLoad():String? {
       return withContext(Dispatchers.IO) {
            val client = OkHttpClient()
            val request = Request.Builder()
                    .url("http://v.juhe.cn/toutiao/index?type=&key=4494d20d3e853ec01a1dafc8b901e716")
                    .get()
                    .build()
          val response = client.newCall(request).execute()
                if (response.isSuccessful) {
                     delay(2000)
                     response.body?.string()
                } else {
                   null
                }
            }
        }
    }
MainActivity代码如下:
class MainActivity : AppCompatActivity() {
    private lateinit var viewModel:NewsViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        ViewModelProvider(this,ViewModelProvider.NewInstanceFactory())
            .get(NewsViewModel::class.java)
        viewModel.news.observe(this){value->
            if(value!=null){
                progressBar.visibility = View.GONE
                Log.v("swl","$value")
            }
        }
        button.setOnClickListener {
            progressBar.visibility = View.VISIBLE
           viewModel.loadNews()
        }
    }
}
5.运行成功之后就能看到打印出来的结果了。
八、聚合数据头条新闻API说明
1.OkHttp3和Retrofit请求数据要使用的知识点
  • 聚合数据API使用
  • OkHttp3请求数据
  • Gson解析json数据
  • json To kotlin 插件使用
  • Retrofit请求数据的步骤
2.进入聚合数据官网https://www.juhe.cn/首页,选择生活服务,进入新闻头条板块。
新闻头条
3.http://v.juhe.cn/toutiao/index?type=top&key=APPKEY,按照这个格式就可以得到一个接口的地址http://v.juhe.cn/toutiao/index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716,type和key见下面的图片。
type和key
4.将我们按照需求设计好的网址在浏览器中输入,最后可以得到一串数据,将其放在json解析器里面进行解析。解析结果差不多如下图所示:
解析结果
九、使用OkHttp3获取数据
1.进入github官网,搜一下okhttp,点进去第一个,查看详细信息。https://github.com/square/okhttp
2.添加依赖库。
implementation("com.squareup.okhttp3:okhttp:4.9.1")
3.我们新建一个项目工程先测试一下,把依赖库导进去,然后在manifest里面添加以下代码。后面那个是在里面。

 android:usesCleartextTraffic="true"
4.在MainActivity里面编写一下代码。
class MainActivity : AppCompatActivity() {
    private val xinwen_url = "http://v.juhe.cn/toutiao/index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716"

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

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if(event?.action == MotionEvent.ACTION_DOWN){
            val httpClient = OkHttpClient()
            val request = Request.Builder()
                    .url(xinwen_url)
                    .build()
            httpClient.newCall(request).enqueue(object:Callback{
                override fun onFailure(call: Call, e: IOException) {
                    e.printStackTrace()
                }

                override fun onResponse(call: Call, response: Response) {
                    if(response.isSuccessful){
                       val BodyStr =  response.body?.string()
                        Log.v("swl","下载的内容为:$BodyStr")
                    }
                }

            })
        }
        return super.onTouchEvent(event)
    }
}
最后的结果如下所示,这说明我们解析成了。
运行结果
十、手动创建数据模型
1.按照前面的方法,数据是获取到了,但是无法直接加进我们的项目中,所以我们要先建一个数据模型。
2.打开github,搜索Gson,点击第一个。进入以下网住https://github.com/google/gson
3.根据上面的网址,导入一下依赖库。
 implementation 'com.google.code.gson:gson:2.8.7'
4.创建一个数据类,NewsModel,里面包含的数据和json解析器里面解析出来的数据类似。
data class NewsModel(
    val reason:String,
    val result:Result,
    val error_code:Int
)
data class Result (val data:List, )
data class New(val title:String)
十一、使用插件自动创建模型
1.前面我们手动来创建模型,其实是很麻烦的,对于结构比较清晰的数据来说没问题,但如果对于结构比较复杂的数据就很麻烦了,很容易出错。这时候就可以使用插件了。
2.在Android Studio里面打开设置,点击plungs,搜索JSON,下载第一个即可。
下载插件
3.然后new一个kotlin data class file from json,把http://v.juhe.cn/toutiao/index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716这个网址的内容全部拷贝进去,一个都不能漏,Annotation记得勾Gson,其他都不变。
创建过程
4.自动创建好的代码如下图所示,我已经把不需要的删掉了。
data class NewsModel(
    @SerializedName("result")
    val result: Result
)
data class Result(
    @SerializedName("data")
    val data: List,
)
data class Data(
    @SerializedName("author_name")
    val authorName: String,
    @SerializedName("category")
    val category: String,
    @SerializedName("date")
    val date: String,
    @SerializedName("is_content")
    val isContent: String,
    @SerializedName("thumbnail_pic_s")
    val thumbnailPicS: String,
    @SerializedName("title")
    val title: String,
    @SerializedName("uniquekey")
    val uniquekey: String,
    @SerializedName("url")
    val url: String
)
5.在MainActivity里面,在前面的代码后面进行解析。
if(response.isSuccessful){
                        val bodyStr =  response.body?.string()
                        val gson = Gson()
                        val model = gson.fromJson(bodyStr,NewsModel::class.java)

                        model.result.data.forEach {
                            Log.v("swl",it.title)
                        }
                    }
6.运行结果如下图所示:
解析成功
十二、使用retrofit获取数据
1.retrofit相比于okhttp更简单,更简洁,所以用它更好。去https://square.github.io/retrofit/网站查看它的使用方法。
2.导入一下依赖库。
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha02")
3.创建一个接口,作为API。
interface NewsAPI {
    @GET("index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716")
    suspend fun getNews():NewsModel
}
4.在MainActivity里面写一个useRetrofit()方法,使用Retrofit来获取数据。在onTouchEvent里面调用这个方法。
fun useRetrofit(){
        val retrofit = Retrofit.Builder()
                .baseUrl("http://v.juhe.cn/toutiao/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        val api =  retrofit.create(NewsAPI::class.java)
           lifecycleScope.launch {
           val news = api.getNews()
           news.result.data.forEach{
               Log.v("swl",it.title)
           }
       }
    }
运行结果
  • 因为新闻每3分钟就会刷新一次,所以新闻标题和前面不一样。

你可能感兴趣的:(Android开发(30)——协程Coroutine和OkHttp请求)