我的學習手冊 - Glide了解了一下下

目錄

我的學習手冊 - 熱更新了解了一下下
我的學習手冊 - Glide了解了一下下
我的學習手冊 - 進程保活了解了一下下
我的學習手冊 - EventBus了解了一下下
我的學習手冊 - ARouter了解了一下下

Glide

概況

首先為什么要用Glide呢,最主要的原因就是緩存機制和它的生命周期綁定

一、緩存機制

所有的圖片緩存機制都是使用的三級緩存,即內(nèi)存緩存览效,磁盤緩存,服務器存儲。


三級緩存.png

除此之外澡绩,Glide的緩存的數(shù)據(jù)是由Glide的各種設(shè)置決定的,例如一張圖片100x100設(shè)置寬高80x80和50x50的圖片兩種類型展示俺附,那么緩存中會存在有80x80和50x50的兩張圖片肥卡,當你再次設(shè)置的時候就直接讀取這兩張圖片,而不是讀取原圖重新設(shè)置寬高事镣,這里采用的是使用空間換取時間的策略步鉴,這就是見仁見智了,由于目前手機的內(nèi)存會變得越來越大璃哟,所以這種方案也是沒有問題的氛琢。

二、生命周期綁定

Glide.with(context)
在使用Glide的時候會傳入當前上下文随闪,但是通過上下文是無法獲取當前Activity生命周期回調(diào)的阳似,所以Glide使用的策略是在當前Activity中添加一個空白的Fragment,通過該Fragment來進行回調(diào)當前界面的生命周期铐伴,在當前Fragment的銷毀的時候撮奏,將內(nèi)存中的綁定當前Activity的bitmap回收,可以將占用的內(nèi)存進行釋放当宴。這個也是我最喜歡Glide的原因畜吊,其他的圖片加載框架都是事先占用部分的手機內(nèi)存,只有在App退出的時候才會進行釋放户矢,內(nèi)存允許的情況下玲献,可以達到當前App所有的圖片都放在內(nèi)存中,加載效率更快∏嘧裕可是在實際情況中株依,不同頁面出現(xiàn)相同圖片的概率還是比較小的,而且就算是出現(xiàn)相同的圖片延窜,那也只是很少的一部分恋腕,所以我更欣賞Glide的做法,將bitmap和生命周期綁定逆瑞,生命周期結(jié)束bitmap回收荠藤,這也是更符合我們的編程思想。
這里有一個比較重要的一點就是如何將bitmap與當前的Activity進行綁定获高。這里使用的方案是創(chuàng)建一個HashMap<Int, ArrayList<String>>哈肖,第一個參數(shù)放入Activity的hashCode,用來對應Activity念秧,第二個參數(shù)放入內(nèi)存緩存中存入bitmap的key淤井,那么我們可以通過找到當前Activity的hashCode來獲取所有的內(nèi)存緩存的bitmap的key集合,再通過這些key來獲取bitmap進行回收摊趾。

手擼Glide (非官方)

緩存

那么首先仿照Glide的樣子來創(chuàng)建一個Glide

        Glide.with(this)
                .load(url)
                .placeHolder(R.mipmap.loading)
                .error(R.mipmap.error)
                .listener({
                    log("圖片加載成功 url = $url " +
                            "width = ${it.width} " +
                            "height = ${it.height}")
                }, {
                    log("圖片加載失敗")
                })
                .into(imageView)

那么對于Glide來說with方法創(chuàng)建了一個BitmapRequest币狠,所以with方法返回一個bitmap的請求實體

Glide
object Glide {
    fun with(activity: AppCompatActivity): BitmapRequest {
        return BitmapRequest(activity)
    }
}
BitmapRequest

對于一個BitmapRequest來說我們需要知道這一張圖片的基本數(shù)據(jù),地址url砾层,圖片imageView漩绵,然后我們可以添加一些友好設(shè)置,加載占位圖placeHolderRes肛炮,加載失敗圖errorRes止吐,加載監(jiān)聽listener成功和失敗。對于圖片來說侨糟,我們并不想直接持有碍扔,所以采用軟引用的方式SoftReference<ImageView>,此外對于緩存來說需要一個key所以還需要一個參數(shù)uriMd5

class BitmapRequest(private var activity: Activity) {
    
    private var imageUrl: String? = null
    
    private var uriMd5: String? = null
    
    private var mSoftImageView: SoftReference<ImageView>? = null
    
    private var requestListener: BitmapRequestListener? = null
    
    private var placeHolderRes: Int? = null
    
    private var errorRes: Int? = null
    
    fun load(imageUrl: String): BitmapRequest {
        this.imageUrl = imageUrl
        uriMd5 = MD5Utils.toMD5(imageUrl)
        return this
    }
    
    fun placeHolder(placeHolderRes: Int): BitmapRequest {
        this.placeHolderRes = placeHolderRes
        return this
    }
    
    fun error(errorRes: Int): BitmapRequest {
        this.errorRes = errorRes
        return this
    }
    
    fun listener(requestListener: BitmapRequestListener): BitmapRequest {
        this.requestListener = requestListener
        return this
    }
    
    fun listener(onResourceReady: (bitmap: Bitmap) -> Unit, onException: () -> Unit): BitmapRequest {
        listener(object : BitmapRequestListener {
            /**
             * 圖片加載成功
             */
            override fun onResourceReady(bitmap: Bitmap) {
                onResourceReady(bitmap)
            }
            
            /**
             * 圖片加載異常
             */
            override fun onException() {
                onException()
            }
        })
        return this
    }
    
    fun into(imageView: ImageView) {
        mSoftImageView = SoftReference(imageView)
        imageView.tag = uriMd5
        RequestManager.INSTANCE.addBitmapRequest(this)
    }

    fun getActivity():Activity = activity
    
    @Nullable
    fun getImageUrl(): String? = imageUrl
    
    @Nullable
    fun getUriMd5(): String? = uriMd5
    
    @Nullable
    fun getPlaceHolder(): Int? = placeHolderRes
    
    @Nullable
    fun getError(): Int? = errorRes
    
    @Nullable
    fun getRequestListener(): BitmapRequestListener? = requestListener
    
    @Nullable
    fun getImageView(): ImageView? = mSoftImageView?.get()
    
    override fun toString(): String {
        return "BitmapRequest(activity=$activity, imageUrl=$imageUrl, uriMd5=$uriMd5, mSoftImageView=$mSoftImageView, requestListener=$requestListener, placeHolderRes=$placeHolderRes, errorRes=$errorRes)"
    }
    
}


interface BitmapRequestListener {
    /**
     * 圖片加載成功
     */
    fun onResourceReady(bitmap:Bitmap)

    /**
     * 圖片加載異常
     */
    fun onException()
}
BitmapManager

由于圖片加載肯定不會是一張一張的加載粟害,這里就存在并發(fā)的問題蕴忆,那么我們就需要構(gòu)建一個加載隊列,將一個個圖片加載請求放入這個請求隊列去進行加載圖片悲幅。那么我們可以看手機的cpu核心數(shù)來進行分配幾條流水線來進行加載套鹅。

class RequestManager private constructor() {


    /*** 請求調(diào)度 給予多個來解決并發(fā)問題 ***/
    private val dispatchers: ArrayList<RequestDispatcher> = arrayListOf()


    companion object {
        val INSTANCE: RequestManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            val manager = RequestManager()
            manager.start()
            manager
        }
    }

    /*** 請求隊列 ***/
    private val requestQueue = LinkedBlockingQueue<BitmapRequest>()

    /**
     * 添加一個網(wǎng)絡請求
     */
    fun addBitmapRequest(request: BitmapRequest) {
        //如果隊列中沒有這個圖片請求 添加
        if (!requestQueue.contains(request)) {
            requestQueue.add(request)
        } else {
            log("當前任務已存在\n$request")
        }
    }

    /**
     * 開啟請求,通過RequestDispatcher進行調(diào)度 獲取bitmap
     */
    private fun start() {
        stop()
        val threadCount: Int = Runtime.getRuntime().availableProcessors()
        for (i in 0 until threadCount) {
            val requestDispatcher = RequestDispatcher(requestQueue)
            //開始加載bitmap
            requestDispatcher.start()
            dispatchers.add(requestDispatcher)
        }
    }

    /**
     * 關(guān)閉線程
     */
    private fun stop() {
        if (dispatchers.isNotEmpty()) {
            for (dispatcher: RequestDispatcher in dispatchers) {
                //如果線程沒有被中斷 中斷線程
                if (!dispatcher.isInterrupted) {
                    dispatcher.interrupt()
                }
            }
            dispatchers.clear()
        }
    }

}
RequestDispatcher

在執(zhí)行加載圖片的時候是需要開啟一個死循環(huán)汰具,由于是阻塞式的所以不糊造成界面的卡頓卓鹿,無法理解的可以想想Handler的死循環(huán)讀取message,異曲同工留荔。
在這里我們進行一些對于圖片加加載吟孙,從內(nèi)存澜倦、磁盤、網(wǎng)絡中讀取圖片杰妓,分別進行不同的操作來存放bitma
我們在BitmapRequest中也放置了一些參數(shù)藻治,在這里都可以用上
!O锘印桩卵!這里是繼承Thread的,所以更新ui的操作都需要轉(zhuǎn)到主線程中倍宾,這里采用handler來進行線程調(diào)度

class RequestDispatcher(requestQueue: BlockingQueue<BitmapRequest>) : Thread() {

    /*** Handler 用戶跳轉(zhuǎn)到主線程進行展示圖片 ***/
    private val handler by lazy { Handler(Looper.getMainLooper()) }

    /*** 請求隊列 ***/
    private val queue: BlockingQueue<BitmapRequest> = requestQueue

    /**
     * 運行
     */
    override fun run() {
        //死循環(huán) 由于是阻塞式 在子線程中 所以不會發(fā)生界面卡頓
        while (!isInterrupted) {
            //獲取當前的任務
            try {
                val bitmapRequest: BitmapRequest = queue.take()
                //給圖片設(shè)置占位圖
                showPlaceHolder(bitmapRequest)
                //1雏节、獲取bitmap 先從緩存中獲取 沒有再從網(wǎng)絡獲取
                val bitmap: Bitmap? = getBitmap(bitmapRequest)
                //2、通過Handler到ui線程展示圖片
                showImageInUiThread(bitmapRequest, bitmap)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    /**
     * 顯示占位圖
     */
    private fun showPlaceHolder(request: BitmapRequest) {
        request.getPlaceHolder()?.let { placeHolder ->
            request.getImageView()?.let { imageView ->
                handler.post {
                    imageView.setImageResource(placeHolder)
                }
            }
        }
    }


    /**
     * 從緩存中獲取bitmap
     */
    private fun getBitmap(bitmapRequest: BitmapRequest): Bitmap? {
        //從緩存中獲取bitmap 先從內(nèi)存中獲取 再從磁盤中獲取
        var bitmap: Bitmap? = DoubleCacheManager.get(bitmapRequest.getUriMd5())
        log("緩存讀取成功高职? ${bitmap != null} ")
        if (bitmap != null) {
            return bitmap
        } else {
            //從網(wǎng)絡中獲取
            bitmap = downloadImage(bitmapRequest.getImageUrl())
            return bitmap
        }
    }

    /**
     * 網(wǎng)絡下載緩存
     */
    private fun downloadImage(uri: String?): Bitmap? {
        if (uri == null || uri.isEmpty()) {
            return null
        }
        var inputStream: InputStream? = null
        var bitmap: Bitmap? = null
        try {
            val url = URL(uri)
            val conn: URLConnection = url.openConnection()
            inputStream = conn.getInputStream()
            bitmap = BitmapFactory.decodeStream(inputStream)
            //9痴А!怔锌!將圖片放入緩存
            if (bitmap != null) {
                val key: String = MD5Utils.toMD5(uri)
                log("key = $key 存入緩存")
                DoubleCacheManager.put(key, bitmap)
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        } finally {
            try {
                inputStream?.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        return bitmap
    }

    /**
     * 在ui線程中展示圖片
     */
    private fun showImageInUiThread(request: BitmapRequest, bitmap: Bitmap?) {
        //需要判斷圖片是否為空 因為是軟應用
        //bitmap是否為空
        //tag是否匹配
        handler.post {
            val imageView: ImageView? = request.getImageView()
            val errorRes: Int? = request.getError()
            if (imageView != null && imageView.tag == request.getUriMd5()) {
                if (bitmap != null) {
                    imageView.setImageBitmap(bitmap)
                } else {
                    //bitmap為空 說明加載失敗 顯示錯誤圖片
                    errorRes?.let {
                        imageView.setImageResource(it)
                    }
                }
            }
        }

        request.getRequestListener()?.let {
            if (bitmap != null) {
                handler.post {
                    it.onResourceReady(bitmap)
                }
            } else {
                handler.post {
                    it.onException()
                }
            }
        }
    }

}

生命周期

那么到這里圖片的加載和緩存就已經(jīng)完成了寥粹,接下來就是重點的生命周期的綁定,其實生命周期的綁定很簡單埃元,就往簡單了想排作,不要去想的復雜,思路:
1.通過傳入的上下文創(chuàng)建一個Fragment
2.先自定義一個HashMap<Int, ArrayList<String>>亚情,內(nèi)部參數(shù)<Activity的HashCode,ArrayList<內(nèi)存緩存中bitmap的key,就是地址的MD5>>
獲取到BitmapRequest的上下文activity哈雏,獲取activity的HashCode
獲取通過地址得到三級緩存中提供的bitmap之后楞件,將key(BitmapRequest的uriMd5)放入上方HashMap的HashCode對應的列表中
3.在創(chuàng)建的Fragment被銷毀的時候,移除內(nèi)存中的bitmap并且回收裳瘪,調(diào)用gc

LifeCycleObservable
class LifeCycleObservable private constructor() {

    private val activityMap: HashMap<Int, ArrayList<String>> by lazy { hashMapOf<Int, ArrayList<String>>() }
    private val addCode: StringBuilder by lazy { StringBuilder() }
    private val removeCode: StringBuilder by lazy { StringBuilder() }

    companion object {
        val instance: LifeCycleObservable by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            LifeCycleObservable()
        }
    }

    /**
     * 將ActivityCode 和 對應的bitmap的imageUrlMd5集合相對應
     */
    fun add(activityCode: Int, imageUrlMd5: String) {
        addCode.append("activityCode = $activityCode  imageUrlMd5 = $imageUrlMd5 \n")
        var urlMd5List: ArrayList<String>? = activityMap[activityCode]
        if (urlMd5List == null) {
            urlMd5List = arrayListOf()
        }
        if (!urlMd5List.contains(imageUrlMd5)) {
            urlMd5List.add(imageUrlMd5)
        }
        activityMap[activityCode] = urlMd5List
        log("add  activityCode = $activityCode  imageUrlMd5 = $imageUrlMd5 urlMd5List = ${urlMd5List.size}")
    }


    fun remove(activityCode: Int) {
        val urlMd5List: ArrayList<String>? = activityMap[activityCode]
        urlMd5List?.let {
            for (imageUrlMd5 in it) {
                //只是從內(nèi)存中將所有的緩存獲取
                val bitmap = DoubleCacheManager.get(imageUrlMd5, DoubleCacheManager.LRU)
                DoubleCacheManager.remove(imageUrlMd5, DoubleCacheManager.LRU)
                //回收
                if (bitmap != null && !bitmap.isRecycled) {
                    bitmap.recycle()
                }
                removeCode.append("activityCode = $activityCode  imageUrlMd5 = $imageUrlMd5 \n")
            }
            //系統(tǒng)回收  有些手機會執(zhí)行 有些手機不會執(zhí)行 可以自己手動GC查看內(nèi)存
            System.gc()
            //目前添加的必刪除的多
            log("\nremoveCode \n$removeCode")
        }
        activityMap.clear()

    }
    
}
RequestDispatcher的run()中土浸,添加
override fun run() {
        //死循環(huán) 由于是阻塞式 在子線程中 所以不會發(fā)生界面卡頓
        while (!isInterrupted) {
            //獲取當前的任務
            try {
                val bitmapRequest: BitmapRequest = queue.take()
                showPlaceHolder(bitmapRequest)
                //1、獲取bitmap 先從緩存中獲取 沒有再從網(wǎng)絡獲取
                val bitmap: Bitmap? = getBitmap(bitmapRequest)
                //2彭羹、通過Handler到ui線程展示圖片
                showImageInUiThread(bitmapRequest, bitmap)
                //生命周期
                //3黄伊、此時已經(jīng)把緩存放入 那么把緩存的key uriMd5和Activity結(jié)合
                val uriMd5 = bitmapRequest.getUriMd5()
                uriMd5?.let {
                    LifeCycleObservable.instance.add(bitmapRequest.getActivity().hashCode(), uriMd5)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
RequestManagerFragment 銷毀

class RequestManagerFragment : Fragment() {

    private val activityCode: Int by lazy { activity!!.hashCode() }

    override fun onDetach() {
        super.onDetach()
        LifeCycleObservable.instance.remove(activityCode)
    }
}

三、內(nèi)存變化效果圖

首先是進入首頁派殷,添加存儲權(quán)限还最,點擊跳轉(zhuǎn)到另一個界面,點擊加載30多張圖片毡惜,不進行復用拓轻,內(nèi)存會暴漲到300M,然后點擊退出经伙,將當前界面綁定的bitmap全部回收扶叉,并且調(diào)用gc,內(nèi)存恢復到初始狀態(tài)

內(nèi)存變化.GIF

四、源碼地址

?? 自定義Glide來理解Glide設(shè)計模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末枣氧,一起剝皮案震驚了整個濱河市溢十,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌达吞,老刑警劉巖张弛,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宗挥,居然都是意外死亡乌庶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門契耿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞒大,“玉大人,你說我怎么就攤上這事搪桂⊥傅校” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵踢械,是天一觀的道長酗电。 經(jīng)常有香客問我,道長内列,這世上最難降的妖魔是什么撵术? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮话瞧,結(jié)果婚禮上嫩与,老公的妹妹穿的比我還像新娘。我一直安慰自己交排,他們只是感情好划滋,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著埃篓,像睡著了一般处坪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上架专,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天同窘,我揣著相機與錄音,去河邊找鬼胶征。 笑死塞椎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的睛低。 我是一名探鬼主播案狠,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼服傍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骂铁?” 一聲冷哼從身側(cè)響起吹零,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拉庵,沒想到半個月后灿椅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡钞支,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年茫蛹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烁挟。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡婴洼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撼嗓,到底是詐尸還是另有隱情柬采,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布且警,位于F島的核電站粉捻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斑芜。R本人自食惡果不足惜肩刃,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杏头。 院中可真熱鬧树酪,春花似錦、人聲如沸大州。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厦画。三九已至,卻和暖如春滥朱,著一層夾襖步出監(jiān)牢的瞬間根暑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工徙邻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留排嫌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓缰犁,卻偏偏與公主長得像淳地,于是被迫代替她去往敵國和親怖糊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 7.1 壓縮圖片 一颇象、基礎(chǔ)知識 1伍伤、圖片的格式 jpg:最常見的圖片格式。色彩還原度比較好遣钳,可以支持適當壓縮后保持...
    AndroidMaster閱讀 2,519評論 0 13
  • Glide筆記 一扰魂、簡介 在泰國舉行的谷歌開發(fā)者論壇上,谷歌為我們介紹了一個名叫Glide的圖片加載庫蕴茴,作者是bu...
    AndroidMaster閱讀 3,907評論 0 27
  • 一劝评、簡介 在泰國舉行的谷歌開發(fā)者論壇上,谷歌為我們介紹了一個名叫Glide的圖片加載庫倦淀,作者是bumptech蒋畜。這...
    天天大保建閱讀 7,477評論 2 28
  • 版權(quán)聲明:本賬號發(fā)布文章均來自公眾號百侧,承香墨影(cxmyDev),版權(quán)歸承香墨影所有能扒。未經(jīng)允許佣渴,不得轉(zhuǎn)載。 一初斑、前...
    承香墨影閱讀 759評論 1 6
  • 學習來源:郭霖大師博客地址 1辛润、圖片加載框架挺多,如Volley见秤、Glide砂竖、Picasso、Fresco鹃答、本次是...
    子謙寶寶閱讀 1,756評論 0 6