首頁mvp重構:
在上一次https://www.cnblogs.com/webor2006/p/12917520.html已經實現了首頁的數據的上拉加載,下拉刷新功能,接下來咱們對它進行一下MVP的重構,也是如今企業中用得比較多的一種代碼架構,所以直接開擼,不用過多的解釋:
接下來定義一個對應的p層:
接下來創建p層的一個具體實現類:
而它里面需要用到HomeView,所以在構造中添加一下它的引用:
好,接下來咱們在HomeFragment實例化一下P:
接下來開始抽離咱們HomeFragment中的代碼到P層了,下面開始:
接着還有其它邏輯也得進行抽離:
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層抽一個方法:
接下來則具體實現一下,其實就是把在Fragment中的具體邏輯搬到這倆具體實現中,所以先不管這么多先挪代碼:
報紅了,接下來則來改造,先將toast都去掉,當時是為了調試接口加的,目前調通了暫時不需要了,這樣就可以減少2個紅了~~
此時報的錯都是回調處,而且是跟UI相關的,所以此時需要將它回調到HomeView上去:
先來處理失敗的回調:
嗯,是的,對於目前這個情況只能在初始化塊中來使用,如下:
,這其實需要加一個關鍵字來解決:
這是叫啥語法來着,反正是跟構造有關,也想不起來的,記着有這么個規則吧,
然后咱們在HomeView中增加這個onError():
然后再將原來的處理邏輯放到這個onError()中來處理:
好,此時看一下P還有幾個報錯:
然后在HomeFragment中來實現一下:
這個不用多說,加個符號就可以了:
此時又報錯了是因為setData的參數有可能為空,所以參數也得改一下:
可以用Kotlin來寫編寫將你一切為空的因素都扼殺在開發階段了,還是相當不錯的,那這個錯怎么解決呢,加個判空吧:
但是這個寫法還不是Kotlin很純的寫法,下面改成純純的那種:
也就是let里面的代碼只有當list不為空時才會執行的,也就保證了空指針的問題了,咱們簡單瞅一下這個let()的定義原型:
其實它就是一個擴展函數,最終會將T傳到Lambda表達式當中,其實還可以這樣寫:
好,目前咱們來看一下整個P層的loadDatas()就已經改造好了,接下來則還需要改造一下分頁加載的方法:
同樣的策略,將其回調到HomeFragment中來處理,這里直接貼出代碼了:
至此,首頁MVP改造初步完成,運行也一切正常。
網絡框架封裝:
接下來繼續優化代碼,目前咱們請求網絡沒有進行統一的封裝,每次都是重新生成的OkHttpClient:
這里采用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);
接下來開整:首先新建一個包,專門用來存放網絡框架相關的代碼:
開始先來封裝Request:
接着再來封裝一個回調:
然后再放到請求構造中:
接下來則需要定義發送網絡請求的管理類:
首先將它變為單例,還記得在Kotlin中單例是如何寫的么?嗯,不記得了,看下面:
接下來則來定義一個請求的方法:
接下來則拷貝一下之前寫的網絡請求的代碼,基於它來進行再次封裝:
首先OkHttpClient對象不需要每次都創建,這時將其提到全局來:
接下來url改成直接從參數reqeust中獲取:
接下來則需要處理一下回調:
咱們可以將這個類型的轉換放到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中是如何獲取類中泛型的類型的。
此時成功的回調則可以這樣寫了:
這樣網絡請求就已經封裝好了。
通過封裝的網絡框架加載首頁:
接下來則應用一下咱們封裝好的網絡框架,先來新建首頁的一個Request:
報錯了,看報的啥錯:
哦,這點是跟Java不同的,一個類想要被子類繼承,則需要將它聲明為open才行,所以:
另外咱們指定的類型搞錯了,修正一下:
此時咱們則可以用它來替換我們之前沒封裝所寫的網絡請求的代碼了,如下:
同樣的對於分頁加載的網絡請求也進行替換一下:
此時整體的網絡框架就替換完了,不過還可以簡化,咱們可以將執行的方法也封裝一下:
此時則調用就可以簡單化:
其實還可以進一步將代碼做精簡:
所以修改一下:
簡單解決就是在請求時傳一個type既可,先來定義一下TYPE類型:
這個也是Koltin的語法特性,需要記住~~在Java的接口中我們是可以定義常量的~~
接下來在成功回調中需要將type傳回來:
修改一下回調方法的定義:
此時咱們就可以在應用回調處進行區分了,如下:
最后咱們有兩個冗余代碼:
至此,咱們經過網絡的封裝之后,代碼也變得比較精簡,也利於未來的維護。
優化內存泄漏點:
目前還有一個小小的優化點,就是現在的P層強引用了HomeFragment了,這樣我們也知道會造成內存泄漏的,所以解決之道很簡單,采用弱引用既可,如下: