val retrofit = Retrofit.Builder()
.baseUrl(RetrofitClient.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
2.创建service接口
interface RequestService {
@GET("wxarticle/chapters/json")
fun getDatas() : Call<DataBean>
}
3.发起请求
val service = retrofit.create(RequestService::class.java)
service.getDatas().enqueue(object : Callback<DataBean> {
override fun onFailure(call: retrofit2.Call<DataBean>, t: Throwable) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onResponse(call: retrofit2.Call<DataBean>, response: Response<DataBean>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
这只是描述了一个retrofit的简单请求方式,实际项目中基本上都会封装之后再使用,也为了提高代码的可读性,降低各部分的耦合性, 通俗点来说,只有各司其职才能把工作干好嘛,接下来咱们就围绕着各司其职来一个一个实现
协程实现
接下来把上面的请求换成协程的方式来实现
1.创建RetrofitClient
object为了使RetrofitClient 只能有一个实例
object RetrofitClient {
val BASE_URL = "https://wanandroid.com/"
val reqApi by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
return@lazy retrofit.create(RequestService::class.java)
}
}
2.创建service接口类
interface RequestService {
@GET("wxarticle/chapters/json")
fun getDatas() : Deferred<DataBean>
}
因为我们后续会使用到协程,所以这儿将Call换成了Deferred
3.发起请求
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO){
val dataBean = RetrofitClient.reqApi.getDatas().await()
}
//更新ui
}
上面用到了协程,这儿只讲述他的应用了,具体的移步官方文档进一步了解。 网络请求在协程中,并且在IO调度单元,所以不用担会阻塞主线程
协程 + ViewModel + LiveData实现
上面也只是简单的实现,只不过是换成了协程,在项目中,还可以进一步封装,方便使用前面也提到了MVVM,所以还用到了Android 新引入的组件架构之ViewModel和LiveData,先看ViewModel的实现
class ScrollingViewModel : ViewModel() {
private val TAG = ScrollingViewModel::class.java.simpleName
private val datas: MutableLiveData by lazy { MutableLiveData().also { loadDatas() } }
private val repository = ArticleRepository()
fun getActicle(): LiveData {
return datas
}
private fun loadDatas() {
GlobalScope.launch(Dispatchers.Main) {
getData()
}
// Do an asynchronous operation to fetch users.
}
private suspend fun getData() {
val result = withContext(Dispatchers.IO){
// delay(10000)
repository.getDatas()
}
datas.value = result
}
}
ViewModel将作为View与数据的中间人,Repository专职数据获取,下面看一下Repository的代码,用来发起网络请求获取数据
class ArticleRepository {
suspend fun getDatas(): DataBean {
return RetrofitClient.reqApi.getDatas().await()
}
}
在Activity中代码如下
private fun initData() {
model.getActicle().observe(this, Observer{
//获取到数据
toolbar.setBackgroundColor(Color.RED)
})
}
后续优化
1.内存泄漏问题解决方案
结和了各位大佬们的意见,将使用GlobalScope可能会出现内存泄漏的问题进行了优化。因为在协程进行请求的过程中,若此时ViewModel销毁,里面的协程正在请求的话,将无法销毁,出现内存泄漏,所以在ViewModel onCleared 里面,即使结束协程任务,参考代码如下。
open class BaseViewModel : ViewModel(), LifecycleObserver{
private val viewModelJob = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
//运行在UI线程的协程
fun launchUI( block: suspend CoroutineScope.() -> Unit) {
try {
uiScope.launch(Dispatchers.Main) {
block()
}
}catch (e:Exception){
e.printStackTrace()
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
当然,最好的方式是使用viewModelScope,但是我在引入该包的时候,会报错,由于最近比较忙暂时还没来得急解决,后续问题有时间我也会继续修改,还望各位大佬能帮忙指点
2.优化请求代码
先看下之前的请求代码
private suspend fun getData() {
val result = withContext(Dispatchers.IO){
// delay(10000)
repository.getDatas()
}
datas.value = result
}
每一次都需要写个withContext(),实际运用中,感觉有点不方便,于是乎想了一下,怎么才能给他封进请求方法里面? 代码如下
open class BaseRepository {
suspend fun request(call: suspend () -> ResponseData): ResponseData {
return withContext(Dispatchers.IO){ call.invoke()}
}
}
通过在BaseRepository里面写了一个专门的请求方法,这样每次只需执行request就行了 请求参考如下
class ArticleRepository : BaseRepository() {
suspend fun getDatas(): ResponseData<List> {
return request {
delay(10000)
Log.i(ScrollingViewModel::class.java.simpleName,“loadDatas1 run in ${Thread.currentThread().name}”)
RetrofitClient.reqApi.getDatas().await() }
}
}
注:这个 delay(10000)只是我测试用的,意思是休眠当前协程,防止萌新在自己项目中加上了,还是有必要说一下的
再看看ViewModel中就太简单了
class ScrollingViewModel : BaseViewModel() {
private val TAG = ScrollingViewModel::class.java.simpleName
private val datas: MutableLiveData<List> by lazy { MutableLiveData<List>().also { loadDatas() } }
private val repository = ArticleRepository()
fun getActicle(): LiveData<List> {
return datas
}
private fun loadDatas() {
launchUI {
Log.i(TAG,“loadDatas1 run in ${Thread.currentThread().name}”)
val result = repository.getDatas()
Log.i(TAG,“loadDatas3 run in ${Thread.currentThread().name}”)
datas.value = result.data
最后
我这里整理了一份完整的学习思维以及Android开发知识大全PDF。
当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
最后
我这里整理了一份完整的学习思维以及Android开发知识大全PDF。
[外链图片转存中…(img-twr1wo0y-1715785433644)]
当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!