本節(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.Default
與Dispatchers.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)題和前面不一樣票灰。