首页mvp重构:
在上一次https://www.cnblogs.com/webor2006/p/12917520.html已经实现了首页的数据的上拉加载,下拉刷新功能,接下来咱们对它进行一下MVP的重构,也是如今企业中用得比较多的一种代码架构,所以直接开撸,不用过多的解释:
![](https://i-blog.csdnimg.cn/blog_migrate/42508d93005f91fa7d721d52dfec8267.png)
接下来定义一个对应的p层:
![](https://i-blog.csdnimg.cn/blog_migrate/747addb6fa205daf29beb8e652365f32.png)
接下来创建p层的一个具体实现类:
![](https://i-blog.csdnimg.cn/blog_migrate/29a0e3232948b3d97f0a87e41004857a.png)
而它里面需要用到HomeView,所以在构造中添加一下它的引用:
![](https://i-blog.csdnimg.cn/blog_migrate/2e5fdc5789af70a7972e276eb4c50981.png)
好,接下来咱们在HomeFragment实例化一下P:
![](https://i-blog.csdnimg.cn/blog_migrate/103e857c325b1f5fc55d870ff9224b46.png)
接下来开始抽离咱们HomeFragment中的代码到P层了,下面开始:
![](https://i-blog.csdnimg.cn/blog_migrate/1d9e82ba37cc37792634430760b26bdb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/125a7a186beb6b2629f83aeaeb668e49.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d127dafea5e6c4dd558fcf05cad06dab.png)
接着还有其它逻辑也得进行抽离:
package com.kotlin.musicplayer.ui.fragment
import android.graphics.Color
import android.view.View
import android.webkit.WebSettings
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.itheima.player.model.bean.HomeBean
import com.kotlin.musicplayer.MyApplication
import com.kotlin.musicplayer.R
import com.kotlin.musicplayer.adapter.HomeAdapter
import com.kotlin.musicplayer.base.BaseFragment
import com.kotlin.musicplayer.presenter.impl.HomePresenterImpl
import com.kotlin.musicplayer.utils.ThreadUtil
import com.kotlin.musicplayer.utils.URLProviderUtils
import com.kotlin.musicplayer.view.HomeView
import kotlinx.android.synthetic.main.fragment_home.*
import okhttp3.*
import java.io.IOException
/**
* 首页
*/
class HomeFragment : BaseFragment(), HomeView {
val adapter by lazy { HomeAdapter() }
val homePresenterImpl by lazy { HomePresenterImpl(this) }
override fun initView(): View? {
return View.inflate(context, R.layout.fragment_home, null)
}
override fun initListeners() {
super.initListeners()
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = adapter
//监听列表滑动
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//是否最后一条已经显示
val layoutManager = recyclerView.layoutManager
if (layoutManager is LinearLayoutManager) {
//由于RecycleView还有其它样式的列表,所以这里只有下拉列表类型才处理分页
val manager: LinearLayoutManager = layoutManager
val lastPosition = manager.findLastVisibleItemPosition()
if (lastPosition == adapter.itemCount - 1) {
//开始加载更多数据
homePresenterImpl.loadMoreDatas(adapter.itemCount - 1);
}
}
}
}
})
lay_refresh.setColorSchemeColors(Color.RED, Color.YELLOW, Color.BLUE)
lay_refresh.setOnRefreshListener {
homePresenterImpl.loadDatas()
}
}
override fun initData() {
super.initData()
homePresenterImpl.loadDatas()
}
private fun loadMoreDatas(offset: Int) {
val path = URLProviderUtils.getHomeUrl(offset, 5)
val client = OkHttpClient()
val request = Request.Builder()
.get()
.url(path)
.addHeader("User-Agent", WebSettings.getDefaultUserAgent(MyApplication.instance))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
showToast("获取数据出错")
ThreadUtil.runOnMainThread(object : Runnable {
override fun run() {
lay_refresh.isRefreshing = false
}
})
}
override fun onResponse(call: Call, response: Response) {
showToast("获取数据成功!")
val result = response?.body?.string()
val gson = Gson()
val list = gson.fromJson<HomeBean>(
result,
object : TypeToken<HomeBean>() {}.type
)
println("获取数据成功 list:" + list.songlist.size)
ThreadUtil.runOnMainThread(object : Runnable {
override fun run() {
lay_refresh.isRefreshing = false
adapter.loadMoreData(list.songlist)
}
});
}
})
}
private fun loadDatas() {
val path = URLProviderUtils.getHomeUrl(0, 5)
val client = OkHttpClient()
val request = Request.Builder()
.get()
.url(path)
.addHeader("User-Agent", WebSettings.getDefaultUserAgent(MyApplication.instance))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
showToast("获取数据出错")
ThreadUtil.runOnMainThread(object : Runnable {
override fun run() {
lay_refresh.isRefreshing = false
}
})
}
override fun onResponse(call: Call, response: Response) {
showToast("获取数据成功!")
val result = response?.body?.string()
val gson = Gson()
val list = gson.fromJson<HomeBean>(
result,
object : TypeToken<HomeBean>() {}.type
)
println("获取数据成功 list:" + list.songlist.size)
ThreadUtil.runOnMainThread(object : Runnable {
override fun run() {
lay_refresh.isRefreshing = false
adapter.setData(list.songlist)
}
});
}
})
}
}
此时又需要往P层抽一个方法:
![](https://i-blog.csdnimg.cn/blog_migrate/3190f2bbbc9f2412d384f4bc14c9e449.png)
接下来则具体实现一下,其实就是把在Fragment中的具体逻辑搬到这俩具体实现中,所以先不管这么多先挪代码:
![](https://i-blog.csdnimg.cn/blog_migrate/2e5240db70fa7bf03438e24a0f1b3115.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7e12b45a8b190db0c16ed3e9d6772d39.png)
报红了,接下来则来改造,先将toast都去掉,当时是为了调试接口加的,目前调通了暂时不需要了,这样就可以减少2个红了~~
![](https://i-blog.csdnimg.cn/blog_migrate/8e52c0e6332226c4204d1b49b4804624.png)
此时报的错都是回调处,而且是跟UI相关的,所以此时需要将它回调到HomeView上去:
![](https://i-blog.csdnimg.cn/blog_migrate/e88470883ca15c3931bceae83061ee0f.png)
先来处理失败的回调:
![](https://i-blog.csdnimg.cn/blog_migrate/a0b6d49178aeb89887321ff2b8dbfd50.png)
嗯,是的,对于目前这个情况只能在初始化块中来使用,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/f750b02f6e7955a3e1795cf6fe25ea99.png)
,这其实需要加一个关键字来解决:
![](https://i-blog.csdnimg.cn/blog_migrate/5ceacf16c710918ab326e1e824bc8313.png)
这是叫啥语法来着,反正是跟构造有关,也想不起来的,记着有这么个规则吧,
然后咱们在HomeView中增加这个onError():
![](https://i-blog.csdnimg.cn/blog_migrate/97fe6c358b017759e5a45e6f246f5c4b.png)
然后再将原来的处理逻辑放到这个onError()中来处理:
![](https://i-blog.csdnimg.cn/blog_migrate/31cfec0cbb7f324522f1e2d79eb05ea3.png)
好,此时看一下P还有几个报错:
![](https://i-blog.csdnimg.cn/blog_migrate/785d1bfd3d1dd3f46c84e1ad0290a51e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/64e38364e4897b25132be9a67a570e70.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0289df0272ffe2857ad126992b4256b8.png)
然后在HomeFragment中来实现一下:
![](https://i-blog.csdnimg.cn/blog_migrate/a75bfa028569f7081f75ec1963da1662.png)
![](https://i-blog.csdnimg.cn/blog_migrate/155ae8ad132cb79c8934ad1a6b891bdf.png)
这个不用多说,加个符号就可以了:
![](https://i-blog.csdnimg.cn/blog_migrate/0be030effb6ae352445a5f6052f469a8.png)
此时又报错了是因为setData的参数有可能为空,所以参数也得改一下:
![](https://i-blog.csdnimg.cn/blog_migrate/789af1f00449193932945858741398aa.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2a33e1bfae7531120051a810d4d73849.png)
可以用Kotlin来写编写将你一切为空的因素都扼杀在开发阶段了,还是相当不错的,那这个错怎么解决呢,加个判空吧:
![](https://i-blog.csdnimg.cn/blog_migrate/3e75c26b77aa50ba88bf76bb628a663d.png)
但是这个写法还不是Kotlin很纯的写法,下面改成纯纯的那种:
![](https://i-blog.csdnimg.cn/blog_migrate/aac4890ded513a2a1d0151c5cedf53f8.png)
也就是let里面的代码只有当list不为空时才会执行的,也就保证了空指针的问题了,咱们简单瞅一下这个let()的定义原型:
![](https://i-blog.csdnimg.cn/blog_migrate/a9a2cf3d42ce63b97da59b2b90ff5f52.png)
其实它就是一个扩展函数,最终会将T传到Lambda表达式当中,其实还可以这样写:
![](https://i-blog.csdnimg.cn/blog_migrate/6a146fee403d8299c97dbe001768b8b7.png)
好,目前咱们来看一下整个P层的loadDatas()就已经改造好了,接下来则还需要改造一下分页加载的方法:
![](https://i-blog.csdnimg.cn/blog_migrate/7be0fd3414f3e7920d3c5026432228c6.png)
同样的策略,将其回调到HomeFragment中来处理,这里直接贴出代码了:
![](https://i-blog.csdnimg.cn/blog_migrate/a5f57e53eec86170f5cdb5b55a9b656e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/105627daad6a51a0798664ad0edc1d0a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c78910114e95041ab2ffe7d5122c58ff.png)
![](https://i-blog.csdnimg.cn/blog_migrate/bd32cf7156078d424cab6662cb64eaf8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/23eccb86571bace547c16dc15ac38d40.png)
至此,首页MVP改造初步完成,运行也一切正常。
网络框架封装:
接下来继续优化代码,目前咱们请求网络没有进行统一的封装,每次都是重新生成的OkHttpClient:
![](https://i-blog.csdnimg.cn/blog_migrate/f446014274a6e7a2dd2bff67fac5fa70.png)
这里采用Volley的调用方式来进行封装,先来看一下Volley的基本写法:
RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("https://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);
接下来开整:首先新建一个包,专门用来存放网络框架相关的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/b2ff4353ffe63ba20c11996e680611c0.png)
开始先来封装Request:
![](https://i-blog.csdnimg.cn/blog_migrate/700c48e59cd60d8c1821d2f7a009256f.png)
接着再来封装一个回调:
![](https://i-blog.csdnimg.cn/blog_migrate/d4d09ec342887bec8f292bd928555278.png)
然后再放到请求构造中:
![](https://i-blog.csdnimg.cn/blog_migrate/c4951d190e95ad1630329391d656e4b3.png)
接下来则需要定义发送网络请求的管理类:
![](https://i-blog.csdnimg.cn/blog_migrate/89ccf0db9a5a48d30f5a741e3c156833.png)
首先将它变为单例,还记得在Kotlin中单例是如何写的么?嗯,不记得了,看下面:
![](https://i-blog.csdnimg.cn/blog_migrate/1e21f791619d10745c0790e87495ee6c.png)
接下来则来定义一个请求的方法:
![](https://i-blog.csdnimg.cn/blog_migrate/ad1617eb91b99a50c53b50d188562fbe.png)
接下来则拷贝一下之前写的网络请求的代码,基于它来进行再次封装:
![](https://i-blog.csdnimg.cn/blog_migrate/7cfca7c6c7341c57f124dc1b6e0015ed.png)
首先OkHttpClient对象不需要每次都创建,这时将其提到全局来:
![](https://i-blog.csdnimg.cn/blog_migrate/87c6848f4982ead9a8aceb329668f497.png)
接下来url改成直接从参数reqeust中获取:
![](https://i-blog.csdnimg.cn/blog_migrate/e87183548a09447b2f2aa10645fc4b69.png)
接下来则需要处理一下回调:
![](https://i-blog.csdnimg.cn/blog_migrate/d6d9759dd394106b7f7dbd661230491e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1c6c4bac82a96634f367950b230818e6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1faf8ff93ad018224fc4e9d4762605f5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d02da8fd797c1e04bafe3310a45890b8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a7a4fe79861f6ed3b390d550b0f33170.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8a857f860fea6244dc2c1bec21b02d78.png)
咱们可以将这个类型的转换放到MRequest当中,如下:
package com.kotlin.musicplayer.net
import com.google.gson.Gson
import java.lang.reflect.ParameterizedType
/**
* 网络请求
*/
class MRequest<RESPONSE>(val url: String, val handler: ResponseHandler<RESPONSE>) {
/**
* 解析网络请求结果
*/
fun parseResult(result: String?): RESPONSE {
val gson = Gson()
//获取泛型类型
val type = (this.javaClass
.genericSuperclass as ParameterizedType).getActualTypeArguments()[0]
val list = gson.fromJson<RESPONSE>(result, type)
return list
}
}
【注】:注意上面在Kotlin中是如何获取类中泛型的类型的。
此时成功的回调则可以这样写了:
![](https://i-blog.csdnimg.cn/blog_migrate/1c9efbf6e37ad87488c6b049ee6c7261.png)
这样网络请求就已经封装好了。
通过封装的网络框架加载首页:
接下来则应用一下咱们封装好的网络框架,先来新建首页的一个Request:
![](https://i-blog.csdnimg.cn/blog_migrate/c9b6ac6493ea100b9263d3697454436d.png)
报错了,看报的啥错:
![](https://i-blog.csdnimg.cn/blog_migrate/96e4b4a7d7057506149ac5a4e835ace9.png)
哦,这点是跟Java不同的,一个类想要被子类继承,则需要将它声明为open才行,所以:
![](https://i-blog.csdnimg.cn/blog_migrate/0c00d4db4be0bb527017ea84808d720f.png)
另外咱们指定的类型搞错了,修正一下:
![](https://i-blog.csdnimg.cn/blog_migrate/7e36c06ad91c801f3c7fad2ab9521006.png)
此时咱们则可以用它来替换我们之前没封装所写的网络请求的代码了,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/0a0f361ff582a057c030972c9e0af402.png)
同样的对于分页加载的网络请求也进行替换一下:
![](https://i-blog.csdnimg.cn/blog_migrate/7ab4cfeff0cd204a0ea47b8ad29a4397.png)
此时整体的网络框架就替换完了,不过还可以简化,咱们可以将执行的方法也封装一下:
![](https://i-blog.csdnimg.cn/blog_migrate/901bc00c2c4ac5e2307d551cdb74f361.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c3f6892667d937f324cecc0a52108f89.png)
此时则调用就可以简单化:
![](https://i-blog.csdnimg.cn/blog_migrate/9e4e193d8a622c4b7e2fa1fa14cc2407.png)
其实还可以进一步将代码做精简:
![](https://i-blog.csdnimg.cn/blog_migrate/8f985c1368a945b6c19de500553b7807.png)
所以修改一下:
![](https://i-blog.csdnimg.cn/blog_migrate/0bde8b9ae4d8e7e55e5d7214fe906bd2.png)
简单解决就是在请求时传一个type既可,先来定义一下TYPE类型:
![](https://i-blog.csdnimg.cn/blog_migrate/e69e15a1deaf545cea87e3a4fa5c6387.png)
这个也是Koltin的语法特性,需要记住~~在Java的接口中我们是可以定义常量的~~
![](https://i-blog.csdnimg.cn/blog_migrate/c88b08d2675045dd1fd2d4d1715b47a1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/064ab5108b72546d3ba280d29ae4e335.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9c2222abf2e46a8b55592856d74ec7a9.png)
接下来在成功回调中需要将type传回来:
![](https://i-blog.csdnimg.cn/blog_migrate/3d8f960a70c10d9d4c18eae47f5736e4.png)
修改一下回调方法的定义:
![](https://i-blog.csdnimg.cn/blog_migrate/40b749196c6202032e23014197792717.png)
此时咱们就可以在应用回调处进行区分了,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/0260726a028cab47c88d0b0920622886.png)
最后咱们有两个冗余代码:
![](https://i-blog.csdnimg.cn/blog_migrate/a008cf5639c7451e77544da86525ca6d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/488704a416dcfa41c48257c9ffd2b751.png)
至此,咱们经过网络的封装之后,代码也变得比较精简,也利于未来的维护。
优化内存泄漏点:
目前还有一个小小的优化点,就是现在的P层强引用了HomeFragment了,这样我们也知道会造成内存泄漏的,所以解决之道很简单,采用弱引用既可,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/41eb18041f433f4856f990abf67c12da.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6392866253a0923dff439e97f5f1e9e5.png)