Android網(wǎng)絡(luò)庫(kù)隔離框架

一、背景

在日常開發(fā)過程中齐遵,網(wǎng)絡(luò)請(qǐng)求功能是必不可少的寂玲,因此從中衍生出了一系列網(wǎng)絡(luò)加載庫(kù),如URLConnection梗摇,Volley拓哟,OkHttp,Retrofit等伶授。而在項(xiàng)目的開發(fā)過程中断序,隨著需求的改變,我們使用的網(wǎng)絡(luò)加載庫(kù)也可能會(huì)隨著改變(替換網(wǎng)絡(luò)加載庫(kù))糜烹。因此违诗,本章介紹的是如何設(shè)計(jì)一種網(wǎng)絡(luò)庫(kù)隔離的框架,當(dāng)出現(xiàn)網(wǎng)絡(luò)加載庫(kù)替換的情況時(shí)景图,盡可能小的改動(dòng)源代碼(即符合開閉原則,擴(kuò)展是開放的碉哑,修改是封閉的)挚币。

二、設(shè)計(jì)思路

首先要明白的是扣典,使用網(wǎng)絡(luò)請(qǐng)求功能的界面入口是非常多的(例如登錄妆毕,各種數(shù)據(jù)獲取,文件上傳等)贮尖,因此笛粘,第一個(gè)需要處理的問題就是,如何避免網(wǎng)絡(luò)加載庫(kù)與頁(yè)面請(qǐng)求直接交互湿硝。當(dāng)出現(xiàn)網(wǎng)絡(luò)庫(kù)替換時(shí)薪前,大量的直接交互,帶來的后果必然是大量的源代碼修改关斜,這顯示是違法了我們的開閉原則示括。

接著是,如何引入新替換的網(wǎng)絡(luò)加載庫(kù)痢畜,這相當(dāng)于是新添加了另外一個(gè)網(wǎng)絡(luò)庫(kù)的各種請(qǐng)求功能垛膝。最終的效果就是我們使用著不同的網(wǎng)絡(luò)庫(kù)來完成相同的功能,既然功能是一致的丁稀,那么我們就需要考慮如何規(guī)范他們的功能(函數(shù))定義吼拥。

基于以上兩點(diǎn)的考慮,我們采用代理模式來實(shí)現(xiàn)我們的網(wǎng)絡(luò)庫(kù)隔離框架线衫。

三凿可、設(shè)計(jì)模式

1.架構(gòu)圖
照片來自網(wǎng)絡(luò)搜索
2.說明

(1)代理模式:為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問
(2)Proxy代理類:用來替代實(shí)際的網(wǎng)絡(luò)加載庫(kù),避免界面代碼與實(shí)際的網(wǎng)絡(luò)加載直接交互
(3)RealSubject:真實(shí)請(qǐng)求類授账,在我們的案例中矿酵,就是一種網(wǎng)絡(luò)加載庫(kù)唬复。每添加一種網(wǎng)絡(luò)請(qǐng)求庫(kù),即添加一個(gè)對(duì)應(yīng)的真實(shí)請(qǐng)求類即可(這里就是根據(jù)不同的網(wǎng)絡(luò)加載庫(kù)全肮,實(shí)際進(jìn)行請(qǐng)求功能的地方)
(4)Subject:用來規(guī)范新添加的各種網(wǎng)絡(luò)加載庫(kù)以及代理類的功能使用敞咧。為什么要規(guī)范代理的功能?因?yàn)榇眍惞枷伲淼氖钦鎸?shí)類的功能行為休建,因此代理類需要與真實(shí)類的功能保持一致。

四评疗、Kotlin實(shí)現(xiàn)

1.Subject
// 代理模式中测砂,用于規(guī)范代理類與真實(shí)類的功能接口
// 我們暫且只定義了get和post功能
// ICallBack函數(shù)是自定義的請(qǐng)求回調(diào)類,后續(xù)介紹
interface IHttpProxy {
    fun getHttp(url: String, callback: ICallBack)
    fun postHttp(url: String, params: Map<String, Any>, callback: ICallBack)
}
2.Proxy
// object修飾百匆,是一種餓漢式單例模式
object HttpHelper:IHttpProxy {

    // 代理類中砌些,持有真實(shí)對(duì)象的引用
    private var httpProxyImpl :IHttpProxy? = null

    // 初始化真實(shí)代理對(duì)象
    fun init(httpImpl:IHttpProxy){
        httpProxyImpl = httpImpl
    }

    override fun getHttp(url: String, callback: ICallBack) {
        // 運(yùn)行時(shí),調(diào)用真實(shí)對(duì)象方法
        httpProxyImpl!!.getHttp(url,callback)
    }

    override fun postHttp(url: String, params: Map<String, Any>, callback: ICallBack) 
    {
        // 運(yùn)行時(shí)加匈,調(diào)用真實(shí)對(duì)象方法
        httpProxyImpl!!.postHttp(url,params,callback)
    }
}

(1)不了解Kotlin語(yǔ)法的存璃,請(qǐng)查看以下相關(guān)文章
【Kotlin_第一行代碼】 http://www.reibang.com/nb/35111692
(2)代理類,實(shí)現(xiàn)了上述定義的接口雕拼,并實(shí)現(xiàn)了對(duì)應(yīng)的功能纵东,并且可以看出,其功能都是直接調(diào)用真實(shí)類對(duì)象對(duì)應(yīng)功能函數(shù)
(3)代理類必須持有真實(shí)類的引用啥寇,否則無法實(shí)現(xiàn)對(duì)真實(shí)類的代理作用
(4)init方法表示的是傳入當(dāng)前需要被代理的真實(shí)類對(duì)象

3.RealSubject
// 實(shí)現(xiàn)代理模式中的接口
class OkHttpProxyImpl : IHttpProxy {

    // 聲名主線程handler
    val handler = Handler(Looper.getMainLooper())

    //實(shí)現(xiàn)對(duì)應(yīng)的get功能函數(shù) 
    override fun getHttp(url: String, callback: ICallBack) {
        // 創(chuàng)建okHttpClient對(duì)象
        val mOkHttpClient = OkHttpClient()
        //創(chuàng)建一個(gè)Request
        val request = Request.Builder()
            .url(url)
            .build()
        //new call
        val call = mOkHttpClient.newCall(request)
        //請(qǐng)求加入調(diào)度
        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                handler.post {
                    callback.onFailure(e.toString())
                }
            }

            override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                    val string = response.body()?.string()
                    handler.post {
                        callback.onSuccess(string!!)
                    }
                } else {
                    handler.post {
                        callback.onFailure(response.message())
                    }
                }
            }
        })
    }

    override fun postHttp(url: String, params: Map<String, Any>, callback: ICallBack) {
    }
}

(1)不對(duì)OkHttp的使用做介紹
(2)該類是我們使用OkHttp網(wǎng)絡(luò)加載庫(kù)實(shí)現(xiàn)的真實(shí)類偎球。實(shí)現(xiàn)了對(duì)應(yīng)的接口,并在對(duì)應(yīng)的函數(shù)上辑甜,實(shí)現(xiàn)真實(shí)的網(wǎng)絡(luò)請(qǐng)求功能衰絮,并利用自定義的回調(diào)函數(shù),將結(jié)果回調(diào)到使用的地方
(3)目前僅實(shí)現(xiàn)get函數(shù)的邏輯功能磷醋,post函數(shù)同理岂傲。

4.自定義回調(diào)函數(shù)(ICallBack,IHttpCallBack)
// 最底層的回調(diào)類子檀,String類型镊掖,表示網(wǎng)絡(luò)請(qǐng)求的返回的json,xml的格式文件褂痰,即網(wǎng)絡(luò)請(qǐng)求返回的第一手?jǐn)?shù)據(jù)亩进,未進(jìn)行任何操作的數(shù)據(jù)
interface ICallBack {
    fun onSuccess(result: String)
    fun onFailure(result: String)
}
//基于ICallBack之上,再次封裝的抽象回調(diào)類缩歪,并對(duì)泛型進(jìn)行處理
abstract class IHttpCallBack<T> : ICallBack {
    // 直接實(shí)現(xiàn)對(duì)應(yīng)的onSuccess函數(shù)归薛,并對(duì)json進(jìn)行解析以及泛型處理
    override fun onSuccess(result: String) {
        val obj = (Gson().fromJson(result, getRealType(this)))
        val realObj: T? = try {
            obj as T
        } catch (e: Exception) {
            null
        }
        onSuccess(realObj!!) // 返回最終以及解析完成的泛型對(duì)象
    }
    
    abstract fun onSuccess(result: T)  // 最終解析后的回調(diào)函數(shù)

    /**
     * 獲取泛型的真實(shí)對(duì)象
     */
    private fun getRealType(any: Any): Class<*> {
        val genType = any.javaClass.genericSuperclass
        val params = (genType as ParameterizedType).actualTypeArguments
        return params[0] as Class<*>
    }

}
5.界面請(qǐng)求
// 一個(gè)TextView + 一個(gè)Button的簡(jiǎn)單布局
class MainActivity : AppCompatActivity() {

    // wanandroid開放的api,非常感謝鴻洋大神,獲取公眾號(hào)列表
    val URL = "https://wanandroid.com/wxarticle/chapters/json"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         // button的點(diǎn)擊事件
        json_get_btn.setOnClickListener {
            // 這里使用的是代理對(duì)象。直接與界面交互的是代理對(duì)象,而非具體的網(wǎng)絡(luò)加載類對(duì)象主籍。 
            // Author是自定義的JavaBean习贫,根據(jù)對(duì)應(yīng)的json編寫即可,不做介紹 
            HttpHelper.getHttp(URL, object : IHttpCallBack<Author>() {
                // 請(qǐng)求成功千元,返回的是已經(jīng)經(jīng)過泛型處理的回調(diào)類
                // 因?yàn)閭魅氲幕卣{(diào)類是IHttpCallBack苫昌,不是ICallBack
                override fun onSuccess(result: Author) {
                    // json_result_tv是布局中的TextView,用于顯示結(jié)果
                    json_result_tv.text = result.getInfo()
                    Toast.makeText(
                        this@MainActivity,
                        "請(qǐng)求成功", Toast.LENGTH_SHORT
                    ).show()
                }
                // 請(qǐng)求失敗后的回調(diào)類
                override fun onFailure(result: String) {
                    json_result_tv.text = result
                    Toast.makeText(
                        this@MainActivity,
                        "請(qǐng)求失敗", Toast.LENGTH_SHORT
                    ).show()
                }
            })
        }
    }
}

(1)需要注意幸海,使用代理類前祟身,需要先傳入被代理類的對(duì)象,該案例是在Application初始化時(shí)設(shè)置

class MyApp :Application() {
    override fun onCreate() {
        super.onCreate()
        HttpHelper.init(OkHttpProxyImpl()) // 設(shè)置真實(shí)代理對(duì)象
    }
}
6.替換網(wǎng)絡(luò)加載庫(kù)步驟

(1)仿照OkHttpProxyImpl物独,實(shí)現(xiàn)對(duì)應(yīng)網(wǎng)絡(luò)加載庫(kù)的真實(shí)類袜硫,如VolltyProxyImpl。

// Volley網(wǎng)絡(luò)加載庫(kù)真實(shí)請(qǐng)求類
class VolleyProxyImpl :IHttpProxy {
    override fun postHttp(url: String, params: Map<String, Any>, callback: ICallBack) {
        // 具體的volley post請(qǐng)求
    }
    override fun getHttp(url: String, callback: ICallBack) {
        // 具體的volley get請(qǐng)求
    }
}

(2)替換代理類中被代理的對(duì)象挡篓,即修改MyApp中的代碼

class MyApp :Application() {
    override fun onCreate() {
        super.onCreate()
        HttpHelper.init(VolltyProxyImpl()) // 設(shè)置為新網(wǎng)絡(luò)加載類對(duì)象
    }

}

7.最后

至此婉陷,我們的網(wǎng)絡(luò)隔離庫(kù)框架雛形已搭建完畢,更多的功能請(qǐng)自定擴(kuò)展官研。如有任何不正確地方秽澳,歡迎批評(píng)指正。
非常感謝【騰訊課堂-Android高級(jí)開發(fā)專題課】

【項(xiàng)目地址】:https://github.com/y0000c/HttpProxyMode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阀参,一起剝皮案震驚了整個(gè)濱河市肝集,隨后出現(xiàn)的幾起案子瞻坝,更是在濱河造成了極大的恐慌蛛壳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件所刀,死亡現(xiàn)場(chǎng)離奇詭異衙荐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浮创,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門忧吟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人斩披,你說我怎么就攤上這事溜族。” “怎么了垦沉?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵煌抒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我厕倍,道長(zhǎng)寡壮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮况既,結(jié)果婚禮上这溅,老公的妹妹穿的比我還像新娘。我一直安慰自己棒仍,他們只是感情好悲靴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著降狠,像睡著了一般对竣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榜配,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天否纬,我揣著相機(jī)與錄音,去河邊找鬼蛋褥。 笑死临燃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烙心。 我是一名探鬼主播膜廊,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼淫茵!你這毒婦竟也來了爪瓜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤匙瘪,失蹤者是張志新(化名)和其女友劉穎铆铆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丹喻,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薄货,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碍论。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谅猾。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鳍悠,靈堂內(nèi)的尸體忽然破棺而出税娜,到底是詐尸還是另有隱情,我是刑警寧澤藏研,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布敬矩,位于F島的核電站,受9級(jí)特大地震影響遥倦,放射性物質(zhì)發(fā)生泄漏谤绳。R本人自食惡果不足惜占锯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缩筛。 院中可真熱鬧消略,春花似錦、人聲如沸瞎抛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桐臊。三九已至胎撤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間断凶,已是汗流浹背伤提。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留认烁,地道東北人肿男。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像却嗡,于是被迫代替她去往敵國(guó)和親舶沛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355