目錄
我的學習手冊 - 熱更新了解了一下下
我的學習手冊 - Glide了解了一下下
我的學習手冊 - 進程保活了解了一下下
我的學習手冊 - EventBus了解了一下下
我的學習手冊 - ARouter了解了一下下
Glide
概況
首先為什么要用Glide呢,最主要的原因就是緩存機制和它的生命周期綁定
一、緩存機制
所有的圖片緩存機制都是使用的三級緩存,即內(nèi)存緩存览效,磁盤緩存,服務器存儲。
除此之外澡绩,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)