Android開發(fā)(30)——協(xié)程Coroutine和OkHttp請求

本節(jié)內(nèi)容

1.JavaThread下載數(shù)據(jù)回調(diào)

2.引入?yún)f(xié)程

3.launch和async

4.coroutineScope和CoroutineContext

5.WithContext切換線程

6.啰嗦OkHttp

7.okhtttp獲取數(shù)據(jù)

8.聚合數(shù)據(jù)頭條新聞API說明

9.使用OkHttp3獲取數(shù)據(jù)

10.手動創(chuàng)建數(shù)據(jù)模型

11.使用插件自動創(chuàng)建模型

12.使用retrofit獲取數(shù)據(jù)

一洪唐、JavaThread下載數(shù)據(jù)回調(diào)
1.Thread會阻塞當(dāng)前的線程 main-UI Thread殉摔,耗時比較短的小任務(wù)會放到主線程去做掌实。
2.有時候主線程上會有一些耗時很長的任務(wù)蓖议,它會阻塞主線程的其他任務(wù)。為了解決這個問題琼掠,可以開啟一個新的線程,把它稱為子線程停撞。
3.UI線程是提供給用戶進行交互的瓷蛙,盡量不要讓它被阻塞。
4.以下面的代碼為例戈毒,在實現(xiàn)按鈕的點擊事件時艰猬,我們打印完start,等待一會再打印end
 button.setOnClickListener {
                Log.v("swl","start  ${Thread.currentThread()}")
                Thread.sleep(2000)
                Log.v("swl","end  ${Thread.currentThread()}")
            }
  • 這樣的話埋市,第一次點擊按鈕之后冠桃,只有等這個事件過了,才能第二次點擊按鈕道宅。
直接阻塞主線程
5.為了不讓它阻塞主線程食听,我們可以創(chuàng)建一個新的線程,這樣每次點擊按鈕的時候污茵,就不用等待也能直接開始運行了樱报。
button.setOnClickListener {
Thread(object :Runnable{
            override fun run() {
            Log.v("swl","start  ${Thread.currentThread()}")
            Thread.sleep(2000)
            Log.v("swl","end  ${Thread.currentThread()}")
            }
        }
        ).start()
}
  • 這種由于參數(shù)繼承自一個接口,而該接口里面又只有一種方法省咨,所以可以使用lambda表達式肃弟。
button.setOnClickListener {
            Thread{
                Log.v("swl","start  ${Thread.currentThread()}")
                Thread.sleep(2000)
                Log.v("swl","end  ${Thread.currentThread()}")
            }.start()
        }
每次點擊按鈕后都開啟一個新的線程
  • Thread.sleep(2000)的意思是阻塞當(dāng)前線程玷室,如果把它直接寫在MainActivity里面的話零蓉,那么它就會阻塞主線程笤受,只有等待一段時間end打印完了之后,才能繼續(xù)點擊按鈕敌蜂。
  • 但是如果我們每次點擊都創(chuàng)建一個新的線程的話箩兽,第一次點擊之后先打印start,因為它被阻塞了2s章喉,所以不會立刻打印end汗贫。如果我立刻又點了一下按鈕的話,這個時候就又會創(chuàng)建一個新的線程秸脱,又打印了一個start落包。因為我在2S內(nèi)點了四次按鈕,所以打印了四個start摊唇,2S之后才打印end咐蝇。
6.Java開啟線程的弊端:
  • Java里面有線程池,每個線程池里面都只能放規(guī)定數(shù)量的線程巷查。如果超過了這個數(shù)量有序,那么超過的那個就要進入等待序列,直到線程池中有線程執(zhí)行完了岛请,它才能進入線程池執(zhí)行旭寿。
  • 線程是很消耗內(nèi)存的,所以不能大量地開辟線程崇败。當(dāng)線程達到一定程度的時候盅称,就會出現(xiàn)警告。
  • 線程之間的數(shù)據(jù)交互:①通過Handler來傳遞數(shù)據(jù)(回調(diào)) ②進行線程之間的切換(Rxjava)僚匆。
7.回調(diào)數(shù)據(jù)的方法
  • 定義一套接口微渠,實現(xiàn)兩個線程之間的數(shù)據(jù)回調(diào)。在需要傳遞數(shù)據(jù)的類里面定義一個接口(接口里面定義兩個方法)咧擂,在這個類里面還需要定義一個接口類型的listener逞盆。類里面還有一個方法,在里面需要判斷有沒有l(wèi)istener松申,如果有的話就進行相應(yīng)的操作云芦。①在接收數(shù)據(jù)的類里面,先繼承一下前面那個類的接口贸桶,然后實現(xiàn)里面的方法舅逸,并把該類作為它的listener。②這樣的話listener就和接口里的兩個方法分離開了皇筛,還有一種方法琉历。直接使用匿名類,讓listener等于這個匿名類,在里面實現(xiàn)接口里的兩個方法旗笔。(推薦使用第二種)
  • 傳遞數(shù)據(jù)的類:
class UtilNetWork {
    var listener: callBack? = null
    fun data(){
        Thread{
            Log.v("swl","開始下載彪置。。${Thread.currentThread()}")
            Thread.sleep(2000)
            Log.v("swl","下載結(jié)束蝇恶。拳魁。${Thread.currentThread()}")
            val result = "jack"
        }
        listener.let {
            
        }
    }

    interface callBack{
        fun onSuccess(data:String)
        fun onFailure(error:String)
    }
}
  • 接收數(shù)據(jù)的類,在MainActivity里面
 button.setOnClickListener {
           val util = UtilNetWork()
            util.listener = object :UtilNetWork.callBack{
                override fun onSuccess(data: String) {

                }

                override fun onFailure(error: String) {

                }
            }
        }
8.切換線程撮弧。
runOnUIThread{
//進行需要的操作
}
二潘懊、引入?yún)f(xié)程Coroutine
1.線程與協(xié)程的區(qū)別:
  • ①一個任務(wù)可以創(chuàng)建多個線程,但是線程的數(shù)量是有限的贿衍。因為線程數(shù)量越多授舟,那么消耗的內(nèi)存越多,速度越慢贸辈。②對于協(xié)程來說岂却,一共就兩個線程,主線程和子線程裙椭。在子線程上可以創(chuàng)建無數(shù)個協(xié)程躏哩,資源消耗量不大,就在一個線程上進行調(diào)度揉燃,可以有成千上百個協(xié)程同時執(zhí)行扫尺。協(xié)程會被阻塞,線程基本上不會被阻塞炊汤,因為一個線程上有很多和協(xié)程正驻。
  • 線程執(zhí)行任務(wù)是按順序的,如果線程上有任務(wù)在執(zhí)行抢腐,后面的必須等它執(zhí)行完了才能接著執(zhí)行姑曙。但是線程上如果有執(zhí)行時間很長的協(xié)程,那么就會把它掛起迈倍,讓它自己去執(zhí)行伤靠,然后在線程上接著執(zhí)行下一個協(xié)程。等到前面這個協(xié)程執(zhí)行完了之后啼染,又從掛起的那個地方恢復(fù)宴合。
2.協(xié)程的特點:
  • 輕量:您可以在單個線程上運行多個協(xié)程,因為協(xié)程支持掛起迹鹅,不會使正在運行協(xié)程的線程阻塞卦洽。掛起比阻塞節(jié)省內(nèi)存,且支持多個并行操作斜棚。
  • 內(nèi)存泄漏更少:使用結(jié)構(gòu)化并發(fā)機制在一個作用域內(nèi)執(zhí)行多項操作阀蒂。
  • 內(nèi)置取消支持取消操作會自動在運行中的整個協(xié)程層次結(jié)構(gòu)內(nèi)傳播该窗。
  • Jetpack 集成:許多 Jetpack 庫都包含提供全面協(xié)程支持的擴展。某些庫還提供自己的協(xié)程作用域蚤霞,可供您用于結(jié)構(gòu)化并發(fā)挪捕。
3.使用協(xié)程
  • 1.創(chuàng)建一個新的工程,在里面添加一個library争便,然后將以下依賴項添加到應(yīng)用的 build.gradle 文件中。(如果只是在kotlin里面使用断医,那么直接導(dǎo)入下面的依賴庫即可滞乙。但是如果是在安卓里面使用,那么還需要導(dǎo)入implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")這個依賴庫)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
  • 2.在library的包里面再創(chuàng)建一個MyClass類鉴嗤,在類外面寫一個main()方法斩启,在里面創(chuàng)建一個線程。
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()}")
}
運行結(jié)果如下圖所示:
運行結(jié)果
主線程不會被阻塞醉锅,所以main start之后立馬執(zhí)行main end兔簇。如果開啟新線程的時間很短,那么輸出順序也可能為main start ->start->main end->end
  • 3.使用協(xié)程的方式硬耍。(那么就不能使用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()}")
}

任何一個協(xié)程都有自己的CoroutineScope狸窘,在這個協(xié)程域里面可以創(chuàng)建無數(shù)個子協(xié)程。
如何創(chuàng)建一個CoroutineScope:(一般使用前面兩種)

  • launch :創(chuàng)建一個獨立的CoroutineScope坯认。同步翻擒,不返回數(shù)據(jù)。
  • async :異步牛哺,需要返回數(shù)據(jù)陋气。
  • runBlocking:它會在當(dāng)前線程上創(chuàng)建一個協(xié)程域,并且這個執(zhí)行會阻塞當(dāng)前的線程引润。
  • GlobalScope:創(chuàng)建一個全局的CoroutineScope巩趁,不推薦使用。作用域為整個app的lifecycle淳附。缺點:當(dāng)主線程結(jié)束秘案,不會等待GlobalScope的協(xié)程執(zhí)行完畢妄痪。它會創(chuàng)建一個新的線程。
suspend:掛起函數(shù)只能在另外一個掛起函數(shù)或者一個coroutineScope(協(xié)程域包括協(xié)程的所有使用方法,其中就有掛起功能)里面調(diào)用
GlobalScope運行結(jié)果
因為主線程執(zhí)行速度太快了辉川,調(diào)用load方法還要延遲2S鐘。所以新的線程還沒開始荔棉,主線程就已經(jīng)運行結(jié)束了邑狸。
  • 4.前面用的是GlobalScope,為了解決速度過快導(dǎo)致子線程無法開啟的問題,我們可以延長主線程的執(zhí)行時間颁督,也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運行結(jié)果
可以發(fā)現(xiàn)由于主線程有延遲屿讽,而且延遲時間多于子線程,所以子線程執(zhí)行完畢之后吠裆,主線程才結(jié)束伐谈。
三、lunch和asyno
1.只用runBlocking试疙,不用GlobalScope的話诵棵,那么整個runBlocking都是協(xié)程域,整個協(xié)程都被掛起祝旷,不會有阻塞履澳。
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()}")
}
執(zhí)行結(jié)果
很明顯它是按照順序執(zhí)行的,因為它們都在同一個域里面怀跛。
2.使用launch創(chuàng)建協(xié)程
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執(zhí)行結(jié)果
  • launch并沒有阻塞主線程的執(zhí)行距贷,如果再添加一個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執(zhí)行結(jié)果
  • 還是沒有阻塞主線程,但是在執(zhí)行1的時候吻谋,遇到了delay储耐,所以1被掛起,執(zhí)行2滨溉,然后1結(jié)束什湘,2結(jié)束。
  • 使用measureTimeMillis方法來計算掛起的時間
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
   val time = measureTimeMillis {
       launch {
           loadTask1()
       }
       launch {
           loadTask2()
       }
   }
    println("time $time")
    println("main end ${Thread.currentThread()}")
}
計算結(jié)果為15ms
  • 為什么會出現(xiàn)這個結(jié)果呢晦攒?因為我們打印的只是分配的時間闽撤,并不是執(zhí)行的時間。當(dāng)我們查看launch的源碼脯颜,發(fā)現(xiàn)里面有一個join方法哟旗,它的作用是掛起一個協(xié)程知道它結(jié)束為止。
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()}")
}
  • 調(diào)用join方法栋操,發(fā)現(xiàn)end main到最后去了闸餐。
調(diào)用join方法的執(zhí)行結(jié)果
  • 所以我們可以發(fā)現(xiàn)這里協(xié)程是同步執(zhí)行的,也就是執(zhí)行完了1才會執(zhí)行2矾芙,由于線程開啟關(guān)閉還需要時間舍沙,所以比2s多一點時間。
3.使用async創(chuàng)建協(xié)程剔宪。
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()}")
}
異步執(zhí)行結(jié)果
  • async是異步執(zhí)行的拂铡。誰先讀取完就執(zhí)行誰的壹无,沒有順序。很明顯異步執(zhí)行要比同步執(zhí)行的時間短感帅。
四斗锭、CoroutineScope和CoroutineContext
1.當(dāng)我們執(zhí)行以下代碼時,會得到如下結(jié)果:
fun main(){
    runBlocking {
        println("1: ${Thread.currentThread()}")
        launch {
            println("2: ${Thread.currentThread()}")
        }
        println("3: ${Thread.currentThread()}")
    }
}
執(zhí)行結(jié)果
把launch改為async失球,結(jié)果也是一樣的岖是。這說明就算開啟協(xié)程的方式不同,但是線程是一樣的实苞,它并不會開啟新的線程豺撑。
2.CoroutineScope:使用launch和async時,都是創(chuàng)建一個新的scope
3.CoroutineContext:使用launch和async時硬梁,和parent scope在同一個context中
五、withContext切換線程
1.Dispatchers:調(diào)度器胞得。
2.線程的切換主要有:
  • Dispatchers.Main - 使用此調(diào)度程序可在 Android 主線程上運行協(xié)程荧止。此調(diào)度程序只能用于與界面交互和執(zhí)行快速工作。示例包括調(diào)用 suspend 函數(shù)阶剑,運行 Android 界面框架操作跃巡,以及更新 LiveData 對象。
  • Dispatchers.IO - 此調(diào)度程序經(jīng)過了專門優(yōu)化牧愁,適合在主線程之外執(zhí)行磁盤或網(wǎng)絡(luò) I/O素邪。示例包括使用 Room 組件、從文件中讀取數(shù)據(jù)或向文件中寫入數(shù)據(jù)猪半,以及運行任何網(wǎng)絡(luò)操作兔朦。
  • Dispatchers.Default - 此調(diào)度程序經(jīng)過了專門優(yōu)化,適合在主線程之外執(zhí)行占用大量 CPU 資源的工作磨确。用例示例包括對列表排序和解析 JSON沽甥。
2.模擬一下用戶登錄的過程。在網(wǎng)絡(luò)上先讀取用戶的id乏奥,再獲取用戶的信息摆舟。這是切換線程的一種方式,從io線程切換到main線程(在io線程運行結(jié)束后邓了,會自動切換到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")
}
運行結(jié)果如下:
運行結(jié)果
3.如果一個任務(wù)既要在主線程執(zhí)行,又要在子線程執(zhí)行骗炉,那么我建議先指定在主線程照宝,需要子線程的時候再指定IO線程,這樣它最終只會進行一次跳轉(zhuǎn)句葵。
fun main(){
    runBlocking {
     launch (Dispatchers.Main){
          launch (Dispatchers.IO){

          }
      }
    }
}
  • 還有一種就是在函數(shù)內(nèi)部就提前指定好線程硫豆。這樣直接調(diào)用函數(shù)更容易理解龙巨。
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 //默認(rèn)返回值
    }
}
suspend fun userInfo(id:Int):User{
  return  withContext(Dispatchers.IO){
        println("獲取用戶信息:$id")
        delay(1000)
        println("獲取用戶信息成功")
        User("jack")
    }
}
運行結(jié)果
4.使用withContext切換線程。這個還是沒有上面的那個好熊响。
 launch (Dispatchers.Main){
          withContext(Dispatchers.IO){
              login()
          }
          userInfo()
      }
    }
5.與基于回調(diào)的等效實現(xiàn)相比旨别,withContext() 不會增加額外的開銷。此外汗茄,在某些情況下秸弛,還可以優(yōu)化 withContext() 調(diào)用,使其超越基于回調(diào)的等效實現(xiàn)洪碳。例如递览,如果某個函數(shù)對一個網(wǎng)絡(luò)進行十次調(diào)用,您可以使用外部 withContext() 讓 Kotlin 只切換一次線程瞳腌。這樣绞铃,即使網(wǎng)絡(luò)庫多次使用 withContext(),它也會留在同一調(diào)度程序上嫂侍,并避免切換線程儿捧。此外,Kotlin 還優(yōu)化了 Dispatchers.DefaultDispatchers.IO 之間的切換挑宠,以盡可能避免線程切換菲盾。
  • 重要提示:利用一個使用線程池的調(diào)度程序(例如 Dispatchers.IO 或 Dispatchers.Default)不能保證塊在同一線程上從上到下執(zhí)行。在某些情況下各淀,Kotlin 協(xié)程在 suspend 和 resume 后可能會將執(zhí)行工作移交給另一個線程懒鉴。這意味著,對于整個 withContext() 塊碎浇,線程局部變量可能并不指向同一個值临谱。
6.使用withContext其實還是會阻塞主線程,如果想不阻塞主線程的話奴璃,另外開啟一個新線程來調(diào)用login()和userInfo()函數(shù)吴裤,這樣只會阻塞當(dāng)前線程,不會阻塞主線程溺健。
六麦牺、啰嗦OkHttp
1.先向gradle中導(dǎo)入依賴庫
androidTestImplementation 'androidx.test.espresso:espresso-android:3.3.0'
2.進入OkHttp官網(wǎng)https://square.github.io/okhttp/,找到Releases導(dǎo)入依賴庫鞭缭。
  androidTestImplementation 'androidx.test.espresso:espresso-android:3.3.0'
3.OkHttp3實際上一個封裝剖膳,封裝的內(nèi)容包括:
  • OkHttpClient:提供給用戶,用戶通過這個來創(chuàng)建OkHttp岭辣,也就是對象吱晒。
  • Request:包括請求的地址和其他信息。
  • Call接口:里面定義了一些操作沦童。真正來操作的是一個RealCall對象仑濒。
  • okio:真正做傳輸?shù)暮诵摹?/h6>
4.application->okhttp3->Caching或服務(wù)器叹话。在application與okhttp3之間還有一個攔截器(NetWorkInterpreter,攔截網(wǎng)絡(luò)的每一個操作墩瞳,也就是獲取詳細(xì)信息)
  • OkHttpClient里面有一個call對象指向RealCall驼壶,還有一個Dispatchers,Caching
  • Request包括url喉酌,method
5.具體的使用詳見https://square.github.io/okhttp/recipes/
七热凹、okhttp獲取數(shù)據(jù)
1.數(shù)據(jù)有兩種類型:
  • XML :幾乎不用
  • JSON:將JSON的數(shù)據(jù)轉(zhuǎn)化為kotlin里面的數(shù)據(jù)類型。
這里需要使用聚合數(shù)據(jù)泪电,我們先提前在聚合數(shù)據(jù)里面申請一個賬號般妙,然后選擇API里面的免費項目,比如新聞頭條相速,按照它的格式發(fā)送請求
請求詳情
在網(wǎng)頁中輸入以下網(wǎng)址:https://v.juhe.cn/toutiao/index?type=&page=&page_size=&is_filter=&key=4494d20d3e853ec01a1dafc8b901e716碟渺,然后打開一個JSON解析器,將網(wǎng)頁內(nèi)的數(shù)據(jù)轉(zhuǎn)化為我們能看懂的代碼突诬。
解析之后
解析之后折疊起來苫拍,只有三個元素,相當(dāng)于三個map(包含key和value)
展開result:
result展開之后
2.布局一下activity_main攒霹,我的布局加了一個按鈕和一個progressBar:
activity_main布局
3.新建一個工程怯疤,向里面導(dǎo)入我們需要的依賴庫浆洗。
//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.我們要根據(jù)前面獲取到的信息做一個新聞?wù)故卷撁娲呤褂肕VVM模式,所以要另外創(chuàng)建一個類作為ViewModel伏社。
class NewsViewModel:ViewModel() {
    val news:MutableLiveData<String?> = 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.運行成功之后就能看到打印出來的結(jié)果了抠刺。
八、聚合數(shù)據(jù)頭條新聞API說明
1.OkHttp3和Retrofit請求數(shù)據(jù)要使用的知識點
  • 聚合數(shù)據(jù)API使用
  • OkHttp3請求數(shù)據(jù)
  • Gson解析json數(shù)據(jù)
  • json To kotlin 插件使用
  • Retrofit請求數(shù)據(jù)的步驟
2.進入聚合數(shù)據(jù)官網(wǎng)https://www.juhe.cn/首頁摘昌,選擇生活服務(wù)速妖,進入新聞頭條板塊。
新聞頭條
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.將我們按照需求設(shè)計好的網(wǎng)址在瀏覽器中輸入稿饰,最后可以得到一串?dāng)?shù)據(jù)锦秒,將其放在json解析器里面進行解析。解析結(jié)果差不多如下圖所示:
解析結(jié)果
九喉镰、使用OkHttp3獲取數(shù)據(jù)
1.進入github官網(wǎng)旅择,搜一下okhttp,點進去第一個,查看詳細(xì)信息侣姆。https://github.com/square/okhttp
2.添加依賴庫生真。
implementation("com.squareup.okhttp3:okhttp:4.9.1")
3.我們新建一個項目工程先測試一下沉噩,把依賴庫導(dǎo)進去,然后在manifest里面添加以下代碼柱蟀。后面那個是在<application>里面川蒙。
<uses-permission android:name="android.permission.INTERNET"/>
 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","下載的內(nèi)容為:$BodyStr")
                    }
                }

            })
        }
        return super.onTouchEvent(event)
    }
}
最后的結(jié)果如下所示产弹,這說明我們解析成了派歌。
運行結(jié)果
十、手動創(chuàng)建數(shù)據(jù)模型
1.按照前面的方法痰哨,數(shù)據(jù)是獲取到了胶果,但是無法直接加進我們的項目中,所以我們要先建一個數(shù)據(jù)模型斤斧。
2.打開github早抠,搜索Gson,點擊第一個撬讽。進入以下網(wǎng)住https://github.com/google/gson
3.根據(jù)上面的網(wǎng)址蕊连,導(dǎo)入一下依賴庫。
 implementation 'com.google.code.gson:gson:2.8.7'
4.創(chuàng)建一個數(shù)據(jù)類游昼,NewsModel甘苍,里面包含的數(shù)據(jù)和json解析器里面解析出來的數(shù)據(jù)類似。
data class NewsModel(
    val reason:String,
    val result:Result,
    val error_code:Int
)
data class Result (val data:List<New>, )
data class New(val title:String)
十一烘豌、使用插件自動創(chuàng)建模型
1.前面我們手動來創(chuàng)建模型载庭,其實是很麻煩的,對于結(jié)構(gòu)比較清晰的數(shù)據(jù)來說沒問題廊佩,但如果對于結(jié)構(gòu)比較復(fù)雜的數(shù)據(jù)就很麻煩了囚聚,很容易出錯。這時候就可以使用插件了标锄。
2.在Android Studio里面打開設(shè)置顽铸,點擊plungs,搜索JSON料皇,下載第一個即可谓松。
下載插件
3.然后new一個kotlin data class file from json,把http://v.juhe.cn/toutiao/index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716這個網(wǎng)址的內(nèi)容全部拷貝進去践剂,一個都不能漏鬼譬,Annotation記得勾Gson,其他都不變舷手。
創(chuàng)建過程
4.自動創(chuàng)建好的代碼如下圖所示拧簸,我已經(jīng)把不需要的刪掉了。
data class NewsModel(
    @SerializedName("result")
    val result: Result
)
data class Result(
    @SerializedName("data")
    val data: List<Data>,
)
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<NewsModel>(bodyStr,NewsModel::class.java)

                        model.result.data.forEach {
                            Log.v("swl",it.title)
                        }
                    }
6.運行結(jié)果如下圖所示:
解析成功
十二贾富、使用retrofit獲取數(shù)據(jù)
1.retrofit相比于okhttp更簡單,更簡潔牺六,所以用它更好颤枪。去https://square.github.io/retrofit/網(wǎng)站查看它的使用方法。
2.導(dǎo)入一下依賴庫淑际。
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.創(chuàng)建一個接口畏纲,作為API。
interface NewsAPI {
    @GET("index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716")
    suspend fun getNews():NewsModel
}
4.在MainActivity里面寫一個useRetrofit()方法春缕,使用Retrofit來獲取數(shù)據(jù)盗胀。在onTouchEvent里面調(diào)用這個方法。
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)
           }
       }
    }
運行結(jié)果
  • 因為新聞每3分鐘就會刷新一次锄贼,所以新聞標(biāo)題和前面不一樣票灰。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宅荤,隨后出現(xiàn)的幾起案子屑迂,更是在濱河造成了極大的恐慌,老刑警劉巖冯键,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惹盼,死亡現(xiàn)場離奇詭異,居然都是意外死亡惫确,警方通過查閱死者的電腦和手機手报,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雕薪,“玉大人昧诱,你說我怎么就攤上這事晓淀∷” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵凶掰,是天一觀的道長燥爷。 經(jīng)常有香客問我,道長懦窘,這世上最難降的妖魔是什么前翎? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮畅涂,結(jié)果婚禮上港华,老公的妹妹穿的比我還像新娘。我一直安慰自己午衰,他們只是感情好立宜,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布冒萄。 她就那樣靜靜地躺著,像睡著了一般橙数。 火紅的嫁衣襯著肌膚如雪尊流。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天灯帮,我揣著相機與錄音崖技,去河邊找鬼。 笑死钟哥,一個胖子當(dāng)著我的面吹牛迎献,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腻贰,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼忿晕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了银受?” 一聲冷哼從身側(cè)響起践盼,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宾巍,沒想到半個月后咕幻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡顶霞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年肄程,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片选浑。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蓝厌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出古徒,到底是詐尸還是另有隱情拓提,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布隧膘,位于F島的核電站代态,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疹吃。R本人自食惡果不足惜蹦疑,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萨驶。 院中可真熱鬧歉摧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至券盅,卻和暖如春帮哈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锰镀。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工娘侍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泳炉。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓憾筏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親花鹅。 傳聞我的和親對象是個殘疾皇子氧腰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容