..
This commit is contained in:
parent
4f389dea45
commit
98d4d3e463
@ -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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<JSONObject?> {
|
||||
Blog.LOGE("onGetExperimentFeature $feature")
|
||||
return super.onGetExperimentFeature(feature)
|
||||
}
|
||||
|
||||
override fun onRecordExposureEvent(feature: String): GeckoResult<Void?> {
|
||||
Blog.LOGE("onRecordExposureEvent $feature")
|
||||
return super.onRecordExposureEvent(feature)
|
||||
}
|
||||
|
||||
override fun onRecordExperimentExposureEvent(
|
||||
feature: String,
|
||||
slug: String
|
||||
): GeckoResult<Void?> {
|
||||
Blog.LOGE("onRecordExperimentExposureEvent $feature , $slug")
|
||||
return super.onRecordExperimentExposureEvent(feature, slug)
|
||||
}
|
||||
|
||||
override fun onRecordMalformedConfigurationEvent(
|
||||
feature: String,
|
||||
part: String
|
||||
): GeckoResult<Void?> {
|
||||
Blog.LOGE("onRecordMalformedConfigurationEvent $feature , $part")
|
||||
return super.onRecordMalformedConfigurationEvent(feature, part)
|
||||
}
|
||||
}
|
||||
// val experimentDelegate = object : ExperimentDelegate {
|
||||
// override fun onGetExperimentFeature(feature: String): GeckoResult<JSONObject?> {
|
||||
// Blog.LOGE("onGetExperimentFeature $feature")
|
||||
// return super.onGetExperimentFeature(feature)
|
||||
// }
|
||||
//
|
||||
// override fun onRecordExposureEvent(feature: String): GeckoResult<Void?> {
|
||||
// Blog.LOGE("onRecordExposureEvent $feature")
|
||||
// return super.onRecordExposureEvent(feature)
|
||||
// }
|
||||
//
|
||||
// override fun onRecordExperimentExposureEvent(
|
||||
// feature: String,
|
||||
// slug: String
|
||||
// ): GeckoResult<Void?> {
|
||||
// Blog.LOGE("onRecordExperimentExposureEvent $feature , $slug")
|
||||
// return super.onRecordExperimentExposureEvent(feature, slug)
|
||||
// }
|
||||
//
|
||||
// override fun onRecordMalformedConfigurationEvent(
|
||||
// feature: String,
|
||||
// part: String
|
||||
// ): GeckoResult<Void?> {
|
||||
// Blog.LOGE("onRecordMalformedConfigurationEvent $feature , $part")
|
||||
// return super.onRecordMalformedConfigurationEvent(feature, part)
|
||||
// }
|
||||
// }
|
||||
val callBackHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user