This commit is contained in:
2025-10-13 18:57:22 +09:00
parent 197d704e27
commit e9b287cf81
4 changed files with 207 additions and 48 deletions
@@ -23,6 +23,7 @@ import android.content.ComponentCallbacks2
import android.database.sqlite.SQLiteDatabase
import bums.lunatic.launcher.helpers.HourlyLogWriter
import bums.lunatic.launcher.helpers.PrefHelper
import bums.lunatic.launcher.home.Base64ImageCache
import bums.lunatic.launcher.home.Base64RequestHandler
import bums.lunatic.launcher.utils.Blog
import com.squareup.picasso.OkHttp3Downloader
@@ -45,6 +46,7 @@ internal class LunaticLauncher : Application() {
appContext = this
// Base.initialize(this)
PrefHelper.initialize(this)
Base64ImageCache.init(this)
val dir = File("/storage/emulated/0/bums_ob/BUM'S PACED /scraped/logs")
///BUM'S PACED/pdfs
if (!dir.exists()) {
@@ -1,15 +1,25 @@
package bums.lunatic.launcher.home
import android.content.Context
import android.graphics.BitmapFactory
import android.util.Base64
import androidx.collection.LruCache
import com.squareup.picasso.Picasso
import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.File
import java.io.IOException
import java.math.BigInteger
import java.security.MessageDigest
/**
* Base64 데이터를 URL 키와 함께 저장하는 메모리 캐시 (Singleton 또는 DI로 관리)
* Base64 데이터를 URL 키와 함께 저장하는 2단계 캐시 (메모리 + 파일)
* 1. 메모리 캐시(LruCache)를 먼저 확인하여 빠른 접근을 제공합니다.
* 2. 메모리에 없는 경우 파일 캐시를 확인하여 디스크 영속성을 지원합니다.
* OOM(메모리 부족)을 피하기 위해 비트맵 자체가 아닌 Base64 문자열을 저장합니다.
*/
object Base64ImageCache {
@@ -17,22 +27,83 @@ object Base64ImageCache {
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
private val cacheSize = maxMemory / 8
// Key: Image URL, Value: Base64 Data String
// L1 Cache: Memory (Key: Image URL, Value: Base64 Data String)
private val lru: LruCache<String, String> = LruCache(cacheSize)
// L2 Cache: File System
private var cacheDir: File? = null
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
/**
* 캐시를 사용하기 전에 반드시 Application 클래스 등에서 초기화해야 합니다.
* @param context 애플리케이션 컨텍스트
*/
fun init(context: Context) {
// 앱의 캐시 디렉토리 내에 이미지 캐시 전용 폴더 생성
cacheDir = File(context.cacheDir, "base64_image_cache").apply { mkdirs() }
}
/**
* 메모리 캐시와 파일 캐시에 Base64 데이터를 저장합니다.
* 파일 저장은 백그라운드에서 비동기적으로 수행됩니다.
*/
fun put(url: String, base64Data: String) {
// 1. 메모리 캐시에 저장
if (lru.get(url) == null) {
lru.put(url, base64Data)
}
// 2. 파일 캐시에 비동기적으로 저장
coroutineScope.launch {
cacheDir?.let {
try {
val file = File(it, url.toMd5())
file.writeText(base64Data, Charsets.UTF_8)
} catch (e: Exception) {
e.printStackTrace() // 실제 앱에서는 로깅 라이브러리 사용 권장
}
}
}
}
/**
* 메모리 또는 파일 캐시에서 Base64 데이터를 가져옵니다.
* 파일 캐시에서 가져온 경우, 메모리 캐시에도 추가합니다.
*/
fun get(url: String): String? {
return lru.get(url)
// 1. 메모리 캐시에서 먼저 조회
lru.get(url)?.let { return it }
// 2. 메모리에 없으면 파일 캐시에서 조회
cacheDir?.let {
try {
val file = File(it, url.toMd5())
if (file.exists()) {
val base64Data = file.readText(Charsets.UTF_8)
// 파일에서 읽은 데이터를 메모리에 올려서 다음 접근 속도를 높임
lru.put(url, base64Data)
return base64Data
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return null
}
/**
* URL을 MD5 해시로 변환하여 안전한 파일 이름으로 사용합니다.
*/
private fun String.toMd5(): String {
val md = MessageDigest.getInstance("MD5")
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
}
}
/**
* Picasso가 요청한 URL이 Base64ImageCache에 있는지 확인하는 커스텀 핸들러
* (이 클래스는 변경할 필요가 없습니다)
*/
class Base64RequestHandler : RequestHandler() {
@@ -44,7 +115,7 @@ class Base64RequestHandler : RequestHandler() {
*/
override fun canHandleRequest(data: Request): Boolean {
val url = data.uri.toString()
// data: 스킴이 아니고, 우리 캐시에 URL 키가 존재할 때만 true 반환
// data: 스킴이 아니고, 우리 캐시(메모리 또는 파일)에 URL 키가 존재할 때만 true 반환
return !url.startsWith(DATA_SCHEME) && Base64ImageCache.get(url) != null
}
@@ -947,6 +947,7 @@ class GeckoWeb : BWebview {
"WebtoonContents"-> {
}
"MSG" -> {
context.toast("Received Msg privates form ${lPortMessage.msg}")
}
"SHOWVIEWER" -> {
}