fix: 업데이트 설치 버그 수정 및 Android 12+ 호환성 개선 (v1.11.4)

- BroadcastReceiver에 RECEIVER_NOT_EXPORTED 플래그 추가 (Android 12+)
- 다운로드 완료 후 리시버 자동 해제로 메모리 누수 방지
- FileProvider 경로 수정으로 APK 설치 실패 문제 해결
- 버전 1.11.4로 업데이트
This commit is contained in:
sanjeok77
2026-03-13 07:25:41 +09:00
parent 1c64245ca2
commit 4d44d8fb7a
6 changed files with 72 additions and 42 deletions

View File

@@ -24,8 +24,8 @@ android {
applicationId = "com.hotdeal.alarm"
minSdk = 31
targetSdk = 35
versionCode = 20
versionName = "1.11.3"
versionCode = 21
versionName = "1.11.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -172,10 +172,7 @@ class MainActivity : ComponentActivity() {
override fun onDestroy() {
super.onDestroy()
// 리시버 해제
downloadReceiver?.let {
ApkDownloadManager.unregisterDownloadCompleteReceiver(this, it)
}
ApkDownloadManager.unregisterDownloadCompleteReceiver(this)
}
// 비저빌리티 콜백 - 백그라운드에서 포어그라운드 전환 시 새로고침

View File

@@ -477,22 +477,20 @@ private fun MoreTab(viewModel: MainViewModel) {
}
}
private fun downloadAndInstallApk(context: Context, updateInfo: com.hotdeal.alarm.util.UpdateInfo) {
// ApkDownloadManager를 사용하여 다운로드 시작 및 자동 설치
val downloadId = ApkDownloadManager.downloadApk(context, updateInfo)
// 다운로드 완료 리시버 등록 - 완료 시 자동 설치
ApkDownloadManager.registerDownloadCompleteReceiver(
context = context,
downloadId = downloadId,
onComplete = {
ApkDownloadManager.installApk(context)
},
onFailed = {
Toast.makeText(context, "다운로드 실패. 다시 시도해주세요.", Toast.LENGTH_SHORT).show()
}
)
}
private fun downloadAndInstallApk(context: Context, updateInfo: com.hotdeal.alarm.util.UpdateInfo) {
val downloadId = ApkDownloadManager.downloadApk(context, updateInfo)
ApkDownloadManager.registerDownloadCompleteReceiver(
context = context,
downloadId = downloadId,
onComplete = {
ApkDownloadManager.installApk(context)
},
onFailed = {
Toast.makeText(context, "다운로드 실패. 다시 시도해주세요.", Toast.LENGTH_SHORT).show()
}
)
}
// ==================== 공통 컴포넌트 ====================

View File

@@ -6,7 +6,9 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import android.widget.Toast
import androidx.core.content.FileProvider
import kotlinx.coroutines.Dispatchers
@@ -19,8 +21,12 @@ import java.io.File
*/
object ApkDownloadManager {
private const val TAG = "ApkDownloadManager"
private const val APK_FILE_NAME = "hotdeal-alarm-update.apk"
// 등록된 리시버 추적 (메모리 누수 방지)
private var registeredReceiver: BroadcastReceiver? = null
/**
* APK 다운로드 시작
*/
@@ -65,6 +71,9 @@ object ApkDownloadManager {
onComplete: () -> Unit,
onFailed: () -> Unit
): BroadcastReceiver {
// 기존 리시버가 있으면 먼저 해제
unregisterDownloadCompleteReceiver(context)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1
@@ -72,15 +81,25 @@ object ApkDownloadManager {
val downloadManager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager
val query = DownloadManager.Query().setFilterById(downloadId)
val cursor = downloadManager?.query(query)
cursor?.use {
if (it.moveToFirst()) {
val statusIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
val status = it.getInt(statusIndex)
when (status) {
DownloadManager.STATUS_SUCCESSFUL -> onComplete()
DownloadManager.STATUS_FAILED -> onFailed()
DownloadManager.STATUS_SUCCESSFUL -> {
Log.d(TAG, "다운로드 완료, 설치 시작")
onComplete()
// 설치 후 리시버 해제
unregisterDownloadCompleteReceiver(context)
}
DownloadManager.STATUS_FAILED -> {
Log.e(TAG, "다운로드 실패")
onFailed()
// 실패 시 리시버 해제
unregisterDownloadCompleteReceiver(context)
}
}
}
}
@@ -88,10 +107,22 @@ object ApkDownloadManager {
}
}
context.registerReceiver(
receiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
// Android 12+ 에서는 RECEIVER_NOT_EXPORTED 플래그 필요
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(
receiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
Context.RECEIVER_NOT_EXPORTED
)
} else {
context.registerReceiver(
receiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
}
registeredReceiver = receiver
Log.d(TAG, "다운로드 리시버 등록됨, downloadId=$downloadId")
return receiver
}
@@ -99,11 +130,15 @@ object ApkDownloadManager {
/**
* 다운로드 완료 리시버 해제
*/
fun unregisterDownloadCompleteReceiver(context: Context, receiver: BroadcastReceiver) {
try {
context.unregisterReceiver(receiver)
} catch (e: Exception) {
// 이미 해제된 경우 무시
fun unregisterDownloadCompleteReceiver(context: Context) {
registeredReceiver?.let { receiver ->
try {
context.unregisterReceiver(receiver)
Log.d(TAG, "다운로드 리시버 해제됨")
} catch (e: Exception) {
Log.w(TAG, "리시버 해제 실패 (이미 해제됨): ${e.message}")
}
registeredReceiver = null
}
}

View File

@@ -2,7 +2,7 @@
<paths>
<external-files-path
name="downloads"
path="Download" />
path="." />
<cache-path
name="cache"
path="." />

View File

@@ -1,10 +1,10 @@
{
"version": "1.11.3",
"versionCode": 20,
"updateUrl": "https://git.webpluss.net/sanjeok77/hotdeal_alarm/releases/download/v1.11.3/app-release.apk",
"version": "1.11.4",
"versionCode": 21,
"updateUrl": "https://git.webpluss.net/sanjeok77/hotdeal_alarm/releases/download/v1.11.4/app-release.apk",
"changelog": [
"뽐뿌 인기 게시물 뱃지 표시",
"인기 게시물 감지 기능 추가",
"DB 마이그레이션 (v4->v5)"
"업데이트 설치 버그 수정",
"Android 12+ 호환성 개선",
"다운로드 리시버 생명주기 관리 개선"
]
}