본문 바로가기
안드로이드 코틀린

[Kotlin][Android] 인앱 업데이트

by teamnova 2022. 10. 19.

안녕하세요.

Google Play Core 라이브러리의 인앱 업데이트 기능을 사용하여 앱을 기기에서 최신상태로 유지할 수 있는 방법을 알아보겠습니다. 

Android 5.0(API 레벨 21) 이상을 실행하는 기기는 인앱 업데이트 기능과 호환됩니다.

 

다음은 앱 내에서 업데이트를 표시하는 두 가지 방법입니다.\

 

업데이트 종류

Flexible(유연한)

사용자가 앱을 업데이트하려는 경우 팝업 창이 표시됩니다. 

수락과 거부는 모두 선택 사항입니다. 

동의하면 업데이트가 백그라운드에서 다운로드되기 시작합니다. 

업데이트가 약간의 UI 조정 또는 성능 업그레이드 를 제공하는 경우 이를 활용하는 것을 권장합니다. 

 

Immediate(즉각적인)

앱을 사용하려면 무조건 업데이트해야 합니다. 

보안 패치와 같은 중요한 업데이트 가 있는 경우 이를 활용할 수 있습니다 .

 

업데이트를 해야한다고 알릴 수 있는 두가지 신호가 있습니다. 

 

1. Priority(우선순위) : 0에서 5 사이의 정수를 제공하여 각 릴리스에서 업데이트의 중요도를 지정합니다(5가 가장 높은 우선 순위). 앱을 업데이트하기 위해 적절한 업데이트 흐름(Immediate 또는 Flexible)이 시작됩니다.

Google Play Console 을 통해 업데이트의 우선순위 를 설정할 수 있는 방법은 없고 Google Play 개발자 API 를 사용해야 합니다 .

  • 5: 즉시 표시(중요 업데이트에 권장)
  • 4: 5일 후 즉시 표시 및 3일 후 유연성 표시.
  • 3: 30일 후 즉시 표시, 15일 후 유연 표시(성능 업데이트 권장)
  • 2: 90일 후 즉시 표시 및 30일 후 유연성 표시(마이너 업데이트 권장)
  • 1: 항상 유연성 표시
  • 0: 업데이트에 영향 없음 흐름.

 

2. Staleness : 장치가 업데이트를 사용할 수 있음을 인식한 시간을 지정합니다. 이는 적절한 흐름을 설정하는 데 도움이 됩니다. 예를 들어, 사용자가 업데이트 릴리스 후 이전 30일 동안 앱을 업데이트하지 않은 경우 Flexible이 트리거되고, 90일보다 긴 경우 Immediate이 트리거됩니다.

 

Android에서 인앱 업데이트를 구현하는 방법

모듈 수준 build.gradle 파일에 다음 종속성을 추가합니다.

dependencies {
    implementation 'com.google.android.play:core:1.7.0'
}

 

InAppUpdate.kt 라는 새 파일을 만듭니다.

 

import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallState
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability

class InAppUpdate(activity: Activity) : InstallStateUpdatedListener {

    private var appUpdateManager: AppUpdateManager
    private val MY_REQUEST_CODE = 500
    private var parentActivity: Activity = activity

    private var currentType = AppUpdateType.FLEXIBLE

    init {
        appUpdateManager = AppUpdateManagerFactory.create(parentActivity)
        appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
            // 업데이트를 해야하는 지 확인
            if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { // 업데이트 해야함
                if (info.updatePriority() == 5) { // Priority: 5 (Immediate update flow)- 즉각적으로 진행
                    if (info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
                        startUpdate(info, AppUpdateType.IMMEDIATE)
                    }
                } else if (info.updatePriority() == 4) { // Priority: 4
                    val clientVersionStalenessDays = info.clientVersionStalenessDays()
                    if (clientVersionStalenessDays != null && clientVersionStalenessDays >= 5 && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
                        // Trigger IMMEDIATE flow - 즉각적으로 진행
                        startUpdate(info, AppUpdateType.IMMEDIATE)
                    } else if (clientVersionStalenessDays != null && clientVersionStalenessDays >= 3 && info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                        // Trigger FLEXIBLE flow - 유연하게 진행
                        startUpdate(info, AppUpdateType.FLEXIBLE)
                    }
                } else if (info.updatePriority() == 3) { // Priority: 3
                    val clientVersionStalenessDays = info.clientVersionStalenessDays()
                    if (clientVersionStalenessDays != null && clientVersionStalenessDays >= 30 && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
                        // Trigger IMMEDIATE flow - 즉각적으로 진행
                        startUpdate(info, AppUpdateType.IMMEDIATE)
                    } else if (clientVersionStalenessDays != null && clientVersionStalenessDays >= 15 && info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                        // Trigger FLEXIBLE flow - 유연하게 진행
                        startUpdate(info, AppUpdateType.FLEXIBLE)
                    }
                } else if (info.updatePriority() == 2) { // Priority: 2
                    val clientVersionStalenessDays = info.clientVersionStalenessDays()
                    if (clientVersionStalenessDays != null && clientVersionStalenessDays >= 90 && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
                        // Trigger IMMEDIATE flow - 즉각적으로 진행
                        startUpdate(info, AppUpdateType.IMMEDIATE)
                    } else if (clientVersionStalenessDays != null && clientVersionStalenessDays >= 30 && info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                        // Trigger FLEXIBLE flow - 유연하게 진행
                        startUpdate(info, AppUpdateType.FLEXIBLE)
                    }
                } else if (info.updatePriority() == 1) { // Priority: 1
                    // Trigger FLEXIBLE flow - 유연하게 진행
                    startUpdate(info, AppUpdateType.FLEXIBLE)
                } else { // Priority: 0
                    // 인앱 업데이트 안 보여줌
                }
            } else {
                // 업데이트 필요 없음
            }
        }
        appUpdateManager.registerListener(this)
    }


    private fun startUpdate(info: AppUpdateInfo, type: Int) {
        appUpdateManager.startUpdateFlowForResult(info, type, parentActivity, MY_REQUEST_CODE)
        currentType = type
    }

    fun onResume() {
        appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
            if (currentType == AppUpdateType.FLEXIBLE) {
                // 업데이트가 다운로드되었지만 설치되지 않은 경우 사용자에게 업데이트를 완료하도록 알립니다.
                if (info.installStatus() == InstallStatus.DOWNLOADED)
                    flexibleUpdateDownloadCompleted()
            } else if (currentType == AppUpdateType.IMMEDIATE) {
                // IMMEDIATE(즉각적인)일때만 , 이미 업데이트 진행중인 상태입니다.
                if (info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                    startUpdate(info, AppUpdateType.IMMEDIATE)
                }
            }
        }
    }

    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == MY_REQUEST_CODE) {
            if (resultCode != AppCompatActivity.RESULT_OK) {
                // 업데이트가 취소되거나 실패한 경우 업데이트를 다시 시작하도록 요청할 수 있습니다.
                Log.e("ERROR", "Update flow failed! Result code: $resultCode")
            }
        }
    }

    private fun flexibleUpdateDownloadCompleted() {
        Snackbar.make(
                parentActivity.findViewById(R.id.activity_main_layout),
                "An update has just been downloaded.",
                Snackbar.LENGTH_INDEFINITE
        ).apply {
            setAction("RESTART") { appUpdateManager.completeUpdate() }
            setActionTextColor(Color.WHITE)
            show()
        }
    }

    fun onDestroy() {
        appUpdateManager.unregisterListener(this)
    }

    override fun onStateUpdate(state: InstallState) {
        if (state.installStatus() == InstallStatus.DOWNLOADED) {
            flexibleUpdateDownloadCompleted()
        }
    }

}

 

InAppUpdate.kt 클래스를 초기화하고 onResume 및 onActivityResults 메서드를 액티비티(예.MainActivity)에 추가합니다.

class MainActivity : AppCompatActivity() {

    private lateinit var inAppUpdate: InAppUpdate

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        inAppUpdate = InAppUpdate(this)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        inAppUpdate.onActivityResult(requestCode,resultCode, data)
    }

    override fun onResume() {
        super.onResume()
        inAppUpdate.onResume()
    }

    override fun onDestroy() {
        super.onDestroy()
        inAppUpdate.onDestroy()
    }
}

 

테스트

인앱 업데이트 솔루션을 테스트하려면 앱 을 내부테스트 트랙에 업로드하거나 내부 공유하기에 업로드하여 테스트할 수 있습니다.