Android大圖監(jiān)測(cè)系統(tǒng)的三種實(shí)現(xiàn)方式
原理解析
- 內(nèi)存占用計(jì)算
首先,我們需要了解如何計(jì)算一張圖片在內(nèi)存中的占用大小。Android中,圖片占用的內(nèi)存主要由其寬、高和每個(gè)像素的位數(shù)決定。我們可以使用以下公式計(jì)算:
[ 內(nèi)存占用大小 = 寬 \times 高 \times 像素位數(shù) / 8 ]
- 大圖判定標(biāo)準(zhǔn)
一般情況下,大圖的定義是指超過(guò)一定閾值的圖片。這個(gè)閾值可以根據(jù)應(yīng)用的實(shí)際需求來(lái)設(shè)定,通常建議根據(jù)設(shè)備的內(nèi)存情況和應(yīng)用場(chǎng)景動(dòng)態(tài)調(diào)整。
- 監(jiān)測(cè)策略
大圖監(jiān)測(cè)一般采用兩種策略:主動(dòng)監(jiān)測(cè)和被動(dòng)監(jiān)測(cè)。主動(dòng)監(jiān)測(cè)通過(guò)周期性地掃描內(nèi)存中的圖片資源,識(shí)別大圖,進(jìn)行處理。而被動(dòng)監(jiān)測(cè)則是在圖片加載過(guò)程中實(shí)時(shí)判斷是否為大圖。
主動(dòng)監(jiān)測(cè)
主動(dòng)監(jiān)測(cè)只要獲取到內(nèi)存中的圖片資源,通過(guò)掃描判斷是否超過(guò)設(shè)置的閾值即可。
class LargeImageScanner {
fun scanLargeImages() {
// 遍歷內(nèi)存中的圖片資源
for (image in MemoryManager.getAllImages()) {
val imageSize = calculateImageSize(image)
// 判斷是否為大圖
if (imageSize > LARGE_IMAGE_THRESHOLD) {
// 進(jìn)行處理,如壓縮、裁剪或異步加載
handleLargeImage(image)
}
}
}
private fun calculateImageSize(image: Bitmap): Int {
// 計(jì)算圖片占用的內(nèi)存大小
return image.width * image.height * (image.config.bitsPerPixel / 8)
}
private fun handleLargeImage(image: Bitmap) {
// 實(shí)現(xiàn)大圖的處理邏輯,例如壓縮、裁剪或異步加載
// ...
}
}
被動(dòng)監(jiān)測(cè)
被動(dòng)監(jiān)測(cè)的目的是,讓圖在加載的過(guò)程中,自動(dòng)獲取到加載圖片的大小。所以切入的時(shí)機(jī)就非常重要。
在第三方圖片加載庫(kù)回調(diào)中進(jìn)行大圖監(jiān)測(cè)
如果你使用的是第三方圖片加載庫(kù)Glide,最簡(jiǎn)單的直接的是在圖片加載的成功的時(shí)機(jī)進(jìn)行監(jiān)測(cè)。
class GlideImageLoader {
fun loadWithLargeImageCheck(context: Context, url: String, target: ImageView) {
Glide.with(context)
.asBitmap()
.load(url)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
// 圖片加載失敗處理
// ...
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
// 圖片加載成功,檢查是否為大圖
resource?.let {
val imageSize = calculateImageSize(it)
if (imageSize > LARGE_IMAGE_THRESHOLD) {
// 處理大圖邏輯,如壓縮、裁剪或異步加載
handleLargeImage(it)
}
}
return false
}
})
.into(target)
}
private fun calculateImageSize(image: Bitmap): Int {
// 計(jì)算圖片占用的內(nèi)存大小
return image.width * image.height * (image.config.bitsPerPixel / 8)
}
private fun handleLargeImage(image: Bitmap) {
// 實(shí)現(xiàn)大圖的處理邏輯,例如壓縮、裁剪或異步加載
// ...
}
}
但上面這種方式存在幾個(gè)弊端
- 適用性低,強(qiáng)制要求所以圖片加載都要調(diào)用
loadWithLargeImageCheck方法,如果是一個(gè)現(xiàn)有的大項(xiàng)目,將無(wú)法改造。 - 強(qiáng)依賴于第三方加載庫(kù)
Glide,后續(xù)換庫(kù)也不兼容
所以為了解決上面的這幾個(gè)問(wèn)題,我們要想的是,能否不依賴于第三方圖片加載庫(kù)呢?
于是就有了下面這種方式
在網(wǎng)絡(luò)加載圖片時(shí)進(jìn)行大圖監(jiān)測(cè)
現(xiàn)在使用網(wǎng)絡(luò)請(qǐng)求基本都是使用Okhttp,在這種情況下,你可以考慮使用攔截器(Interceptor)來(lái)實(shí)現(xiàn)通用的大圖監(jiān)測(cè)邏輯。攔截器是OkHttp 中的一種強(qiáng)大的機(jī)制,可以在請(qǐng)求發(fā)起和響應(yīng)返回的過(guò)程中進(jìn)行攔截、修改和監(jiān)測(cè)。
以下是一個(gè)使用OkHttp攔截器進(jìn)行大圖監(jiān)測(cè)的示例:
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException
class LargeImageInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 發(fā)起請(qǐng)求前的處理,可以在這里記錄請(qǐng)求時(shí)間等信息
val response = chain.proceed(request)
// 請(qǐng)求返回后的處理
if (response.isSuccessful) {
val contentType = response.body()?.contentType()?.toString()
// 檢查是否為圖片資源
if (contentType?.startsWith("image/") == true) {
// 獲取圖片大小并進(jìn)行大圖監(jiān)測(cè)
val imageSize = calculateImageSize(response.body()?.byteStream())
if (imageSize > LARGE_IMAGE_THRESHOLD) {
// 處理大圖邏輯,如壓縮、裁剪或異步加載
handleLargeImage()
}
}
}
return response
}
private fun calculateImageSize(inputStream: InputStream?): Int {
// 通過(guò)輸入流計(jì)算圖片占用的內(nèi)存大小
// ...
}
private fun handleLargeImage() {
// 實(shí)現(xiàn)大圖的處理邏輯,例如壓縮、裁剪或異步加載
// ...
}
}
然后,在創(chuàng)建OkHttpClient時(shí),添加這個(gè)攔截器:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(LargeImageInterceptor())
.build()
通過(guò)這種方式,你只需要在OkHttp中添加一次攔截器,即可在每個(gè)圖片請(qǐng)求中進(jìn)行通用的大圖監(jiān)測(cè)處理,而不用在每個(gè)請(qǐng)求的響應(yīng)回調(diào)中添加監(jiān)測(cè)代碼。這樣使得代碼更加清晰、易于維護(hù)。
可能又有人會(huì)說(shuō),我網(wǎng)絡(luò)加載庫(kù)換了,那不是一樣無(wú)法兼容嗎?
確實(shí),雖然概率比直接換第三方圖片加載庫(kù)還低,但既然有可能,就要盡可能的解決。
于是就是了下面的這種終極方法。
使用ASM插樁進(jìn)行大圖監(jiān)控
這就升級(jí)到圖片加載的本質(zhì)了,任何圖片加載最終都是要填充到ImageView上。而在這過(guò)程中自然避免不了使用ImageView的方法進(jìn)行填充圖片。
例如:setImageDrawable等等。
當(dāng)然也可以直接hook整個(gè)ImageView,全局將其替換成HookImageView,再到其內(nèi)部實(shí)現(xiàn)大圖監(jiān)測(cè)。 這兩種都是通過(guò)ASM,只是對(duì)象不一樣,但原理都基本一致。
以下是一個(gè)簡(jiǎn)單的示例,使用ASM對(duì)Android中的 ImageView 的 setImageDrawable 方法進(jìn)行攔截:
import org.objectweb.asm.*;
public class ImageViewInterceptor implements ClassVisitor {
private final ClassVisitor cv;
public ImageViewInterceptor(ClassVisitor cv) {
this.cv = cv;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("setImageDrawable") && desc.equals("(Landroid/graphics/drawable/Drawable;)V")) {
return new ImageViewMethodVisitor(mv);
}
return mv;
}
// 其他方法省略,你可以根據(jù)需要實(shí)現(xiàn)其他 visitX 方法
}
class ImageViewMethodVisitor extends MethodVisitor {
public ImageViewMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitCode() {
super.visitCode();
// 在方法開(kāi)頭插入大圖監(jiān)測(cè)邏輯的字節(jié)碼
// ...
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN) {
// 在 RETURN 指令前插入大圖監(jiān)測(cè)邏輯的字節(jié)碼
// ...
}
super.visitInsn(opcode);
}
}
// 在某處,使用 ASM 進(jìn)行字節(jié)碼修改
ClassReader cr = new ClassReader("android/widget/ImageView");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ImageViewInterceptor interceptor = new ImageViewInterceptor(cw);
cr.accept(interceptor, 0);
....
這個(gè)示例中,ImageViewInterceptor 對(duì) ImageView 的 setImageDrawable 方法進(jìn)行了攔截,ImageViewMethodVisitor 中插入了大圖監(jiān)測(cè)邏輯的字節(jié)碼。
需要注意的是。在實(shí)際應(yīng)用中,需謹(jǐn)慎考慮因字節(jié)碼操作而引起的潛在問(wèn)題和兼容性風(fēng)險(xiǎn)。
注意事項(xiàng)與優(yōu)化技巧
在實(shí)現(xiàn)大圖監(jiān)測(cè)時(shí),我們需要注意以下事項(xiàng):
- 靈活設(shè)置閾值: 根據(jù)不同設(shè)備和應(yīng)用場(chǎng)景,動(dòng)態(tài)調(diào)整大圖的閾值,以保證監(jiān)測(cè)的準(zhǔn)確性和及時(shí)性。
- 合理選擇處理方式: 對(duì)于大圖,可以選擇合適的處理方式,如壓縮、裁剪或異步加載,以降低內(nèi)存占用。
- 異步處理: 將大圖的處理放在異步線程中,避免阻塞主線程,提高應(yīng)用的響應(yīng)性。
總結(jié)
通過(guò)本文的學(xué)習(xí),相信你已經(jīng)對(duì)Android大圖監(jiān)測(cè)有了深入的理解,并可以在實(shí)際項(xiàng)目中應(yīng)用這些知識(shí),提升應(yīng)用的性能和用戶體驗(yàn)。
以上就是Android大圖監(jiān)測(cè)系統(tǒng)的三種實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于Android大圖監(jiān)測(cè)系統(tǒng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android進(jìn)程間通信(IPC)機(jī)制Binder簡(jiǎn)要介紹
本文主要介紹 Android進(jìn)程間通信(IPC)機(jī)制Binder簡(jiǎn)要介紹, 這里介紹了Binder機(jī)制如何實(shí)現(xiàn)進(jìn)程通信機(jī)制,有研究Android源碼的朋友可以看下2016-08-08
簡(jiǎn)單實(shí)現(xiàn)Android滾動(dòng)公告欄
這篇文章主要為大家詳細(xì)介紹了如何簡(jiǎn)單實(shí)現(xiàn)Android滾動(dòng)公告欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
解決Android調(diào)用系統(tǒng)分享給微信,出現(xiàn)分享失敗,分享多文件必須為圖片格式的問(wèn)題
這篇文章主要介紹了解決Android調(diào)用系統(tǒng)分享給微信,出現(xiàn)分享失敗,分享多文件必須為圖片格式的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
WAC啟動(dòng)Android模擬器 transfer error: Read-only file system錯(cuò)誤解決方法
這篇文章主要為大家分享下WAC啟動(dòng)Android模擬器時(shí)出現(xiàn)transfer error: Read-only file system 問(wèn)題的解決方法2013-10-10
Android Flutter實(shí)現(xiàn)興趣標(biāo)簽選擇功能
我們?cè)谑状问褂脙?nèi)容類 App 的時(shí)候,不少都會(huì)讓我們選擇個(gè)人偏好,通過(guò)這些標(biāo)簽選擇可以預(yù)先知道用戶的偏好信息。我們本篇就來(lái)看看 Flutter 如何實(shí)現(xiàn)興趣標(biāo)簽的選擇,需要的可以參考一下2022-11-11
Android短信發(fā)送器實(shí)現(xiàn)方法
這篇文章主要介紹了Android短信發(fā)送器實(shí)現(xiàn)方法,以實(shí)例形式較為詳細(xì)的分析了Android短信發(fā)送器從界面布局到功能實(shí)現(xiàn)的完整步驟與相關(guān)技巧,需要的朋友可以參考下2015-09-09
Android?app啟動(dòng)節(jié)點(diǎn)與上報(bào)啟動(dòng)實(shí)例詳解
系統(tǒng)的啟動(dòng)過(guò)程非常復(fù)雜,下面這篇文章主要給大家介紹了關(guān)于Android?app啟動(dòng)節(jié)點(diǎn)與上報(bào)啟動(dòng)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
Android IPC進(jìn)程間通信詳解最新AndroidStudio的AIDL操作)
這篇文章主要介紹了Android IPC進(jìn)程間通信的相關(guān)資料,需要的朋友可以參考下2016-09-09

