diff --git a/app/src/main/kotlin/bums/lunatic/launcher/BookmarkUploader.kt b/app/src/main/kotlin/bums/lunatic/launcher/BookmarkUploader.kt index 16af24c2..db6afad6 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/BookmarkUploader.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/BookmarkUploader.kt @@ -25,15 +25,19 @@ import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.logging.HttpLoggingInterceptor object OkHttpClientInstance { - val client: OkHttpClient by lazy { - // 네트워크 로그를 보기 위한 인터셉터 설정 + // [수정] 누수 발생 시 어디서 할당되었는지 추적하기 위해 FINE 레벨 설정 제안을 반영 + java.util.logging.Logger.getLogger(OkHttpClient::class.java.name).level = java.util.logging.Level.FINE + val loggingInterceptor = HttpLoggingInterceptor().apply { + // 개발 중에는 BASIC이나 HEADERS로 두면 연결 상태를 더 잘 볼 수 있습니다. level = HttpLoggingInterceptor.Level.NONE } OkHttpClient.Builder() .addInterceptor(loggingInterceptor) + // [추가] 연결 풀 설정을 명시적으로 관리하여 누수 가능성을 줄입니다. + .connectionPool(okhttp3.ConnectionPool(5, 5, java.util.concurrent.TimeUnit.MINUTES)) .build() } } @@ -373,7 +377,21 @@ class BookmarkApiService { private val bookmarkLikeUrl = "$baseUrl/bookmarks/{bookmarkId}/like" private val bookmarkUnlikeUrl = "$baseUrl/bookmarks/{bookmarkId}/unlike" + val client: OkHttpClient by lazy { + // [수정] 누수 발생 시 어디서 할당되었는지 추적하기 위해 FINE 레벨 설정 제안을 반영 + java.util.logging.Logger.getLogger(OkHttpClient::class.java.name).level = java.util.logging.Level.FINE + val loggingInterceptor = HttpLoggingInterceptor().apply { + // 개발 중에는 BASIC이나 HEADERS로 두면 연결 상태를 더 잘 볼 수 있습니다. + level = HttpLoggingInterceptor.Level.NONE + } + + OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + // [추가] 연결 풀 설정을 명시적으로 관리하여 누수 가능성을 줄입니다. + .connectionPool(okhttp3.ConnectionPool(5, 5, java.util.concurrent.TimeUnit.MINUTES)) + .build() + } /** * 서버에서 북마크 목록을 가져오는 suspend 함수 @@ -393,7 +411,7 @@ class BookmarkApiService { .get() .build() - val responseJson = OkHttpClientInstance.client.newCall(request).execute().use { response -> + val responseJson = client.newCall(request).execute().use { response -> if (!response.isSuccessful) { println("❌ 북마크 API 호출 실패: ${response.code} ${response.message}") return@withContext null @@ -430,7 +448,7 @@ class BookmarkApiService { .post(requestBody) .build() - val responseJson = OkHttpClientInstance.client.newCall(request).execute().use { response -> + val responseJson = client.newCall(request).execute().use { response -> if (!response.isSuccessful) return@withContext null response.body?.string() } @@ -455,7 +473,7 @@ class BookmarkApiService { .post(requestBody) .build() - val responseJson = OkHttpClientInstance.client.newCall(request).execute().use { response -> + val responseJson = client.newCall(request).execute().use { response -> if (!response.isSuccessful) return@withContext null response.body?.string() } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt b/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt index 62d7a821..43311dfe 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt @@ -25,12 +25,16 @@ 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.home.NeoRssActivity.Companion.lActivity import bums.lunatic.launcher.utils.Blog import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.Picasso import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import org.mozilla.geckoview.GeckoRuntime +import org.mozilla.geckoview.GeckoRuntimeSettings +import org.mozilla.geckoview.GeckoRuntimeSettings.ALLOW_ALL import java.io.File import java.util.concurrent.TimeUnit @@ -39,6 +43,47 @@ internal class LunaticLauncher : Application() { companion object { var appContext : LunaticLauncher? = null var mHourlyLogWriter : HourlyLogWriter? = null + private var sRuntime: GeckoRuntime? = null + fun getRuntime() : GeckoRuntime? { + appContext?.initGeckoRuntime() + return sRuntime + } + + } + + private fun initGeckoRuntime() { + if (sRuntime == null) { + try { + val profileDir = File(this.getExternalFilesDir("file"), "geckoview_profile") + Blog.LOGE("profileDir >>> ${profileDir.absolutePath} ${profileDir.exists()}") + if (!profileDir.exists()) profileDir.mkdirs() + + val configFile = File(profileDir, "geckoview-config.yaml") + configFile.writeText(""" +args: ["--profile", "${profileDir.absolutePath}"] +prefs: + network.cookie.lifetimePolicy: 0 + network.cookie.maxNumber: 3000 + network.cookie.thirdparty.sessionOnly: false +""".trimIndent()) + + sRuntime = GeckoRuntime.create(this, GeckoRuntimeSettings.Builder() + .arguments(arrayOf("--profile", profileDir.absolutePath)) + .configFilePath(File(profileDir, "geckoview-config.yaml").absolutePath) // 추가! + .aboutConfigEnabled(true) + .allowInsecureConnections(ALLOW_ALL) + .javaScriptEnabled(true) + .extensionsProcessEnabled(true) + .extensionsWebAPIEnabled(true) + .loginAutofillEnabled(true) + .debugLogging(false) + .consoleOutput(false) + .remoteDebuggingEnabled(true).build()) + } catch (e : Exception) { + e.printStackTrace() + + } + } } override fun onCreate() { @@ -56,7 +101,7 @@ internal class LunaticLauncher : Application() { } mHourlyLogWriter = HourlyLogWriter(dir) val logging = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.HEADERS + level = HttpLoggingInterceptor.Level.NONE } val cacheSize = 1024L * 1024 * 1024 * 6 // 60MB val cache = Cache(File(this.filesDir, "picasso-cache"), cacheSize) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt index e408d668..5e22480a 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt @@ -31,11 +31,11 @@ import androidx.appcompat.app.AlertDialog import androidx.core.net.toUri import androidx.core.view.isVisible import bums.lunatic.launcher.BookmarkUploader +import bums.lunatic.launcher.LunaticLauncher.Companion.getRuntime import bums.lunatic.launcher.R import bums.lunatic.launcher.helpers.ForeGroundService import bums.lunatic.launcher.helpers.ForeGroundService.Companion.ACTION_VIDEO_DOWNLOAD import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_TARGET_URL -import bums.lunatic.launcher.home.NeoRssActivity.Companion.getRuntime import bums.lunatic.launcher.home.tokiz.PortMessage import bums.lunatic.launcher.model.Dotax import bums.lunatic.launcher.model.DotaxArticles @@ -61,6 +61,7 @@ import org.jsoup.Jsoup import org.mozilla.gecko.util.ThreadUtils import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.GeckoSessionSettings import org.mozilla.geckoview.GeckoView import org.mozilla.geckoview.MediaSession import org.mozilla.geckoview.WebExtension @@ -101,7 +102,7 @@ open class GeckoWeb @JvmOverloads constructor( } } - // 3. 저장된 세션 상태 복구 메서드 +// // 3. 저장된 세션 상태 복구 메서드 fun restoreSessionState(session: GeckoSession) { val stateJson = context.getSharedPreferences("GeckoPrefs", Context.MODE_PRIVATE) .getString("gecko_session_state", null) @@ -109,7 +110,9 @@ open class GeckoWeb @JvmOverloads constructor( if (!stateJson.isNullOrEmpty()) { // 문자열에서 SessionState 객체 생성 val state = GeckoSession.SessionState.fromString(stateJson) + Blog.LOGE("Restored state >>> ${state}") if (state != null) { + session.restoreState(state) Log.d("GeckoWeb", "Session State Restored") } @@ -357,8 +360,9 @@ open class GeckoWeb @JvmOverloads constructor( onPageStopCallback?.invoke(success) saveCurrentSessionState() } - override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) { + override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) { + lastSessionState = sessionState onSessionStateChangeCallback?.invoke(sessionState) } } @@ -421,7 +425,16 @@ open class GeckoWeb @JvmOverloads constructor( private fun buildWeb() { getRuntime()?.let { runtime -> - val session = GeckoSession() + val sessionSettings = GeckoSessionSettings.Builder() + // 1. 뷰포트 모드를 모바일로 강제 + .userAgentOverride("Mozilla/5.0 (Android 14; Mobile; rv:120.0) Gecko/120.0 Firefox/120.0") + .viewportMode(GeckoSessionSettings.VIEWPORT_MODE_MOBILE) + .allowJavascript(true) + .contextId("JUST_ONE") + .usePrivateMode(false) + .build() + val session = GeckoSession(sessionSettings) + restoreSessionState(session) session.open(runtime) this.setSession(session) @@ -433,6 +446,7 @@ open class GeckoWeb @JvmOverloads constructor( session.mediaDelegate = mediaDelegate session.promptDelegate = promptDelegate session.mediaSessionDelegate = mediaSessionDelegate + runtime.settings.loginAutofillEnabled = true runtime.webExtensionController.setAddonManagerDelegate(addonManagerDelegate) runtime.webExtensionController diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt index aa09b07d..3998428d 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt @@ -58,6 +58,7 @@ import org.mozilla.geckoview.ExperimentDelegate import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntimeSettings +import org.mozilla.geckoview.GeckoRuntimeSettings.ALLOW_ALL import java.io.File @@ -67,11 +68,7 @@ open class NeoRssActivity : CommonActivity() { private lateinit var binding: RssActivityBinding companion object { - private var sRuntime: GeckoRuntime? = null - fun getRuntime() : GeckoRuntime? { - lActivity?.initGeckoRuntime() - return sRuntime - } + var isOpendFold = false @@ -450,8 +447,8 @@ open class NeoRssActivity : CommonActivity() { binding.controllPanel.visibility = View.GONE binding.floatingActionMenu.visibility = View.GONE } - sRuntime?.shutdown() - sRuntime = null +// sRuntime?.shutdown() +// sRuntime = null finish() } else -> {} @@ -459,27 +456,7 @@ open class NeoRssActivity : CommonActivity() { binding.floatingActionMenu.close(false) } - private fun initGeckoRuntime() { - if (sRuntime == null) { - try { - val profileDir = File(this.filesDir, "geckoview_profile") - if (!profileDir.exists()) profileDir.mkdirs() - sRuntime = GeckoRuntime.create(this, GeckoRuntimeSettings.Builder() - .configFilePath(profileDir.absolutePath) - .aboutConfigEnabled(true) - .javaScriptEnabled(true) - .extensionsProcessEnabled(true) - .extensionsWebAPIEnabled(true) - .experimentDelegate(experimentDelegate) - .debugLogging(false) - .remoteDebuggingEnabled(true).build()) - } catch (e : Exception) { - e.printStackTrace() - - } - } - } override fun onTouchEvent(event: MotionEvent?): Boolean { return super.onTouchEvent(event) @@ -495,8 +472,8 @@ open class NeoRssActivity : CommonActivity() { override fun onDestroy() { try { - sRuntime?.shutdown() - sRuntime = null +// sRuntime?.shutdown() +// sRuntime = null } catch (e: Exception) { e.printStackTrace() } super.onDestroy() @@ -552,33 +529,33 @@ open class NeoRssActivity : CommonActivity() { }) } - val experimentDelegate = object : ExperimentDelegate { - override fun onGetExperimentFeature(feature: String): GeckoResult { - Blog.LOGE("onGetExperimentFeature $feature") - return super.onGetExperimentFeature(feature) - } - - override fun onRecordExposureEvent(feature: String): GeckoResult { - Blog.LOGE("onRecordExposureEvent $feature") - return super.onRecordExposureEvent(feature) - } - - override fun onRecordExperimentExposureEvent( - feature: String, - slug: String - ): GeckoResult { - Blog.LOGE("onRecordExperimentExposureEvent $feature , $slug") - return super.onRecordExperimentExposureEvent(feature, slug) - } - - override fun onRecordMalformedConfigurationEvent( - feature: String, - part: String - ): GeckoResult { - Blog.LOGE("onRecordMalformedConfigurationEvent $feature , $part") - return super.onRecordMalformedConfigurationEvent(feature, part) - } - } +// val experimentDelegate = object : ExperimentDelegate { +// override fun onGetExperimentFeature(feature: String): GeckoResult { +// Blog.LOGE("onGetExperimentFeature $feature") +// return super.onGetExperimentFeature(feature) +// } +// +// override fun onRecordExposureEvent(feature: String): GeckoResult { +// Blog.LOGE("onRecordExposureEvent $feature") +// return super.onRecordExposureEvent(feature) +// } +// +// override fun onRecordExperimentExposureEvent( +// feature: String, +// slug: String +// ): GeckoResult { +// Blog.LOGE("onRecordExperimentExposureEvent $feature , $slug") +// return super.onRecordExperimentExposureEvent(feature, slug) +// } +// +// override fun onRecordMalformedConfigurationEvent( +// feature: String, +// part: String +// ): GeckoResult { +// Blog.LOGE("onRecordMalformedConfigurationEvent $feature , $part") +// return super.onRecordMalformedConfigurationEvent(feature, part) +// } +// } val callBackHandler = Handler(Looper.getMainLooper()) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt index ff92f6c1..15184375 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt @@ -915,26 +915,26 @@ internal class RssHome : Fragment() { } fun randomOrNull() : RssData? = lasted.randomOrNull() - fun rett(imageView: ImageView,imageUrl: String){ - // OkHttp로 직접 이미지 다운로드 후 - val request = Request.Builder().url(imageUrl).build() - val client = OkHttpClient() - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - // 실패 시 기본 이미지 처리 or 로깅 - - } - - override fun onResponse(call: Call, response: Response) { - response.body?.byteStream()?.let { inputStream -> - val bitmap = BitmapFactory.decodeStream(inputStream) - mainHandler.post({ - imageView.setImageBitmap(bitmap) - }) - } - } - }) - } +// fun rett(imageView: ImageView,imageUrl: String){ +// // OkHttp로 직접 이미지 다운로드 후 +// val request = Request.Builder().url(imageUrl).build() +// val client = OkHttpClient() +// client.newCall(request).enqueue(object : Callback { +// override fun onFailure(call: Call, e: IOException) { +// // 실패 시 기본 이미지 처리 or 로깅 +// +// } +// +// override fun onResponse(call: Call, response: Response) { +// response.body?.byteStream()?.let { inputStream -> +// val bitmap = BitmapFactory.decodeStream(inputStream) +// mainHandler.post({ +// imageView.setImageBitmap(bitmap) +// }) +// } +// } +// }) +// } } var toast: Toast? = null fun Context.toast(string: String) { diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/TokiFragment.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/TokiFragment.kt index 4c0701aa..63325816 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/TokiFragment.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/TokiFragment.kt @@ -35,7 +35,6 @@ import bums.lunatic.launcher.databinding.BooktokiBinding import bums.lunatic.launcher.home.GeckoWeb import bums.lunatic.launcher.home.GeckoWeb.JxEvent import bums.lunatic.launcher.home.NeoRssActivity -import bums.lunatic.launcher.home.NeoRssActivity.Companion.getRuntime import bums.lunatic.launcher.home.toast import bums.lunatic.launcher.home.tokiz.view.PagedTextLayout import bums.lunatic.launcher.home.tokiz.view.PagedTextViewInterface diff --git a/app/src/main/kotlin/bums/lunatic/launcher/utils/CustomOkHttpGlideModule.kt b/app/src/main/kotlin/bums/lunatic/launcher/utils/CustomOkHttpGlideModule.kt index 4db8bf9d..0d36a33f 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/utils/CustomOkHttpGlideModule.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/utils/CustomOkHttpGlideModule.kt @@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit class CustomOkHttpGlideModule : AppGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { val logging = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.HEADERS + level = HttpLoggingInterceptor.Level.NONE } val cacheSize = 1024L * 1024 * 1024 * 6 // 60MB val cache = Cache(File(context.filesDir, "clide-cache"), cacheSize)