Android Studio/java

03. [안드로이드/java] 안드로이드 인 앱 업데이트 하기

drizzle0925 2021. 6. 30. 13:19
728x90

In App Update가 필요한 이유

개발자는 사용자가 애플리케이션을 최신 버전으로 빠르게 업데이트하여 모든 사람이 업데이트에 포함된 최신 기능을 사용하기를 바랍니다. 구글은 특정 앱에 대한 업데이트가 있을 때마다 안드로이드 사용자에게 알림을 보내지만 이것은 auto-update 기능을 활성화한 사람들에 한정됩니다.. 그래서 앱 내부에서 사용자가에게 업데이트를 알리는 인 앱 업데이트를 해보겠습니다.

 

전제조건

1. 구글 플레이 콘솔계정을 가지고 있을 것

2. Google Play 스토어에 서비스하고 있는 앱이 있을 것

 

요구사항

1. Android 5.0 (API 21) 이상의 기기

2. Google Play Core Library 버전 1.5.0 이상

 

 

프로젝트를 생성합니다.

Empty Activity > Next

 

 

프로젝트 설정값을 입력합니다.

Language는 Java를 선택 > Minimum SDK 버전은 API 21을 선택합니다 > Finish

 

안드로이드 5 (API 21) 이상에서만 작동하는 API입니다. 설정이 제대로 되었는지 확인합니다.

Gradle Scripts > bulid.gradle(Module) > android > defaultConfig > minSdkVersion 확인

 

 

아래 코드를 build.gradle(Module)에 추가합니다.

implementation 'com.google.android.play:core:1.7.3'

코드를 추가했다면 상단에 Sync Now를 클릭합니다.

 

 

MainActivity 클릭하여 파일을 엽니다.

다음과 같이 전역 변수를 선언합니다.

private AppUpdateManager mAppUpdateManager;
private static final int RC_APP_UPDATE = 100;

 

onCreate 안에 다음과 같이 입력

mAppUpdateManager = AppUpdateManagerFactory.create(this);
mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
    @Override
    public void onSuccess(AppUpdateInfo result) {
        if(result.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
               && result.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
           try {
                mAppUpdateManager.startUpdateFlowForResult(result, AppUpdateType.FLEXIBLE, MainActivity.this, RC_APP_UPDATE);
           } catch (IntentSender.SendIntentException e) {
              e.printStackTrace();
           }
       }
   }
});
mAppUpdateManager.registerListener(installStateUpdatedListener);

 

메서드 추가

private InstallStateUpdatedListener installStateUpdatedListener = new InstallStateUpdatedListener(){
        @Override
        public void onStateUpdate(InstallState state){
            if(state.installStatus() == InstallStatus.DOWNLOADED){
                showCompletedUpdate();
            }
        }
    };

    @Override
    protected void onStop() {
        if(mAppUpdateManager != null) mAppUpdateManager.unregisterListener(installStateUpdatedListener);
        super.onStop();
    }

    // 업데이트가 완료되면 호출되는 메서드
    private void showCompletedUpdate() {
        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),"New app is ready!",Snackbar.LENGTH_INDEFINITE);
        snackbar.setAction("Install", new View.OnClickListener(){
            @Override
            public void onClick(View view){
                mAppUpdateManager.completeUpdate();
            }
        });
        snackbar.show();
    }

    // 닫기 대화상자를 클릭하면 호출되는 메서드
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(requestCode == RC_APP_UPDATE && resultCode != RESULT_OK){
            Toast.makeText(this, "취소", Toast.LENGTH_SHORT).show();
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
    
    // 앱을 포그라운드로 가져올때마다 설치 대기중인 앱이 있는지 확인
    @Override
    protected void onResume() {
        super.onResume();
        mAppUpdateManager
            .getAppUpdateInfo()
            .addOnSuccessListener(appUpdateInfo -> {
                // If the update is downloaded but not installed,
                // notify the user to complete the update.
                if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                    showCompletedUpdate();
                }
            });
    }

 

 

최종 완성 코드

package com.drizzle.appupdatetest;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

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;
import com.google.android.play.core.tasks.OnSuccessListener;

public class MainActivity extends AppCompatActivity {
    private AppUpdateManager mAppUpdateManager;
    private static final int RC_APP_UPDATE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAppUpdateManager = AppUpdateManagerFactory.create(this);
        mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
            @Override
            public void onSuccess(AppUpdateInfo result) {
                if(result.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                        && result.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
                    try {
                        mAppUpdateManager.startUpdateFlowForResult(result, AppUpdateType.FLEXIBLE, MainActivity.this, RC_APP_UPDATE);
                    } catch (IntentSender.SendIntentException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        mAppUpdateManager.registerListener(installStateUpdatedListener);
    }
    
    private InstallStateUpdatedListener installStateUpdatedListener = new InstallStateUpdatedListener(){
        @Override
        public void onStateUpdate(InstallState state){
            if(state.installStatus() == InstallStatus.DOWNLOADED){
                showCompletedUpdate();
            }
        }
    };

    @Override
    protected void onStop() {
        if(mAppUpdateManager != null) mAppUpdateManager.unregisterListener(installStateUpdatedListener);
        super.onStop();
    }

    // 업데이트가 완료되면 호출되는 메서드
    private void showCompletedUpdate() {
        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),"New app is ready!",Snackbar.LENGTH_INDEFINITE);
        snackbar.setAction("Install", new View.OnClickListener(){
            @Override
            public void onClick(View view){
                mAppUpdateManager.completeUpdate();
            }
        });
        snackbar.show();
    }

    // 닫기 대화상자를 클릭하면 호출되는 메서드
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(requestCode == RC_APP_UPDATE && resultCode != RESULT_OK){
            Toast.makeText(this, "취소", Toast.LENGTH_SHORT).show();
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
    
    // 앱을 포그라운드로 가져올때마다 설치 대기중인 앱이 있는지 확인
    @Override
    protected void onResume() {
        super.onResume();
        mAppUpdateManager
            .getAppUpdateInfo()
            .addOnSuccessListener(appUpdateInfo -> {
                // If the update is downloaded but not installed,
                // notify the user to complete the update.
                if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                    showCompletedUpdate();
                }
            });
    }
}

 

 

코드 작업이 완료되었으면 구글 플레이 콘솔에 등록된 인증서 키로 APK 파일 생성합니다.

 

내부 앱 공유를 통해 생성한 APK을 업로드하고 내부 테스터들에게 배부합니다.

https://play.google.com/console/internal-app-sharing

 

Google Play Developer Console

하나의 계정으로 모든 Google 서비스를 Google Play Developer Console로 이동하려면 로그인하세요.

accounts.google.com

 

 

결과물

업데이트를 선택한 경우

 

 

거부를 선택한 경우


앱 강제 업데이트

완성본 코드

package com.drizzle.appupdatetest;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

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;
import com.google.android.play.core.tasks.OnSuccessListener;

public class MainActivity extends AppCompatActivity {
    private AppUpdateManager mAppUpdateManager;
    private static final int RC_APP_UPDATE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAppUpdateManager = AppUpdateManagerFactory.create(this);
        mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
            @Override
            public void onSuccess(AppUpdateInfo result) {
                if(result.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                        && result.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)){
                    try {
                        mAppUpdateManager.startUpdateFlowForResult(result, AppUpdateType.IMMEDIATE, MainActivity.this, RC_APP_UPDATE);
                    } catch (IntentSender.SendIntentException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // mAppUpdateManager.registerListener(installStateUpdatedListener);
    }

    private InstallStateUpdatedListener installStateUpdatedListener = new InstallStateUpdatedListener(){
        @Override
        public void onStateUpdate(InstallState state){
            if(state.installStatus() == InstallStatus.DOWNLOADED){
                showCompletedUpdate();
            }
        }
    };

    @Override
    protected void onStop() {
        // if(mAppUpdateManager != null) mAppUpdateManager.unregisterListener(installStateUpdatedListener);
        super.onStop();
    }

    // 업데이트가 완료되면 호출되는 메서드
    private void showCompletedUpdate() {
        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),"New app is ready!",Snackbar.LENGTH_INDEFINITE);
        snackbar.setAction("Install", new View.OnClickListener(){
            @Override
            public void onClick(View view){
                mAppUpdateManager.completeUpdate();
            }
        });
        snackbar.show();
    }

    // 닫기 대화상자를 클릭하면 호출되는 메서드
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(requestCode == RC_APP_UPDATE && resultCode != RESULT_OK){
            Toast.makeText(this, "취소", Toast.LENGTH_SHORT).show();
            finish();
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
            @Override
            public void onSuccess(AppUpdateInfo result) {
                if(result.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS){
                    try {
                        mAppUpdateManager.startUpdateFlowForResult(result, AppUpdateType.IMMEDIATE, MainActivity.this, RC_APP_UPDATE);
                    } catch (IntentSender.SendIntentException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

 

결과물

업데이트를 하지 않을 경우

 

 

업데이트를 선택했을 경우


참고사이트

https://developer.android.com/guide/playcore/in-app-updates/kotlin-java?hl=ko 

 

인앱 업데이트 지원(Kotlin 또는 자바)  |  Android 개발자  |  Android Developers

이 가이드에서는 Kotlin이나 자바를 사용하여 앱에서 인앱 업데이트를 지원하는 방법을 설명합니다. 네이티브 코드(C/C++)를 사용하는 구현과 Unity를 사용하는 구현의 경우 별도의 가이드를 참고하

developer.android.com

https://www.youtube.com/watch?v=Hks5_STaOSo&t=583s 

 

728x90