본문 바로가기
개발/안드로이드

[안드로이드] 앱 업데이트를 관리하는 방법 #1

by 핸디(Handy) 2020. 6. 2.

안드로이드 앱을 업데이트 하는 방법은 2가지로 나뉩니다.
1. 유저가 알아서 언젠가 하길 기다리기
2. 개발자가 앱 실행시 판단해서 강제로 하기

여기서 우리는 2번째에 해당하는 경우에 대해 2가지 방법으로 알아보려고 합니다.

하지만 제가 설명하는 것이 최고의 솔루션은 아니고 개발하면서 배웠던 2가지 방법이라 혹시 더 나은 방법이 있다면 알려주세요.

<방식>
1. Firebase의 Remote Config 를 이용해서 앱 버젼 비교후 다이얼로그로 업데이트 알려주기
2. 안드로이드의 기능, In App update 이용하기

이렇게 2가지 방법이 있습니다.

오늘 포스트에서는 1번에 해당하는 코드와 구현 방법에 대해 설명해드리겠습니다.

FireBase의 Remote Config는 이름 그대로 원격 설정? 의 느낌입니다. 앱에서 원하는 타이밍에 FireBase에 올려놓은 값을 불러서 쓸수가 있으며, ConSole 상에서 값을 바꾸게 되면 3초 이내로 바로 설정값이 변경되어 관리하기 편한것이 장점입니다.

FireBase Remote Config를 설정하는 방법은 추후에 업데이트하기로 하고 사전 준비가 되어있다는 가정아래에 글을 이어가겠습니다.

안드로이드의 Gradle Scripts의 build.gradle을 보게 되면 android 안에 

다음과 같은 정보가 있습니다. 
앱 패키지명인 applicationId 부터 정보가 나열되어있습니다.
여기서 우리가 눈여겨볼 것은 versionCode와 versionName 입니다. 

일반적으로 versionCode의 경우 여러가지 정보를 섞어서 쓰는 듯합니다.
저의 경우 오늘이 6월 2일, 업데이트 처음이라면 1  = > 200601 + 1 = 2006011 이라는 버젼 코드를 써줍니다. 만약 오늘업데이트를 했는데 바로 오류가 발견됬다면 2006012 로 판단하여 적어줍니다.

versionName의 경우는 약간 다른데 X.Y.Z 의 형식을 가져왔습니다. 이런 방식을 지칭하는 용어가 있었는데 그건 기억이 잘 나지 않네요. 다시 설명으로 돌아가보자면
처음 X의 경,우 큰 틀에서의 변화입니다. UI 가 갈아엎어지고 주요 기능이 전부 바꼈을 때 하나씩 업그레이드 해주고

Y의 경우, 기능 추가 또는 가시적인 변화가 잔잔하게 있을때 업데이트 해줍니다.

마지막 Z의 경우, 오류 수정 또는 긴급 업데이트 사항등을 적용할때 업데이트 해주는 방식을 사용합니다.

그래서 저의 앱 경우 큰 변화가 2번 있었기 때문에 3.Y.Z이고 기능이 중간에 한번 추가됬고 오류로 긴급업데이트를 했기때문에 3.1.1 버젼을 사용하고 있는 것입니다.

참고로 versionCode, versionName의 경우 안드로이드 콘솔상에서 앱 버젼을 판별하는 수단임으로 같은 값으로 업데이트 할 수 없습니다. 따라서 한번에 하나씩 올려줘야하고 최신 버젼이 어떤 것인지 판별가능해야합니다. 그래서 일반적으로 값을 올려주는 식으로 합니다.


이제 코드로 가보겠습니다. 일반적으로 앱 로딩시에 로딩 페이지가 따로 있는 경우가 대다수 입니다.  따라서 앱 업데이트를 판별하는 코드는 여기에 작성하여 메인페이지로 넘어갈것인지 구글스토어로 업데이트 보낼것인지 결정하겠습니다.

<전체적인 flow> 
앱 클릭 -> IntroAcitivity 시작 -> RemoteConfig에서 값을 가져와 버젼비교 -> 업데이트 필요시 업데이트다이얼로그 출력 -> 설치페이지 

public class IntroActivity extends AppCompatActivity {
    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intro);

        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
      
        final FirebaseRemoteConfig firebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
        FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
                .build();
        firebaseRemoteConfig.setConfigSettingsAsync(configSettings);
        firebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_default);
        firebaseRemoteConfig.fetch()
                .addOnCompleteListener(this, task -> {
                    String installed_version ="";
                    String firebase_version ="0@0@0";
                    boolean versionPass = true;

                    if(task.isSuccessful()){
                        firebaseRemoteConfig.activate();
                        installed_version = BuildConfig.VERSION_NAME.replace(".","@");
                        if(firebaseRemoteConfig.getString("android_version").length()>0){
                            firebase_version = firebaseRemoteConfig.getString("android_version").replace(".","@");
                        }
                        if(Integer.parseInt(installed_version.split("@")[0]) > Integer.parseInt(firebase_version.split("@")[0])){
                            versionPass = true;
                        } else if( Integer.parseInt(installed_version.split("@")[0]) == Integer.parseInt(firebase_version.split("@")[0]) ) {
                            if (Integer.parseInt(installed_version.split("@")[1]) > Integer.parseInt(firebase_version.split("@")[1])) {
                                versionPass = true;
                            } else if(Integer.parseInt(installed_version.split("@")[1]) == Integer.parseInt(firebase_version.split("@")[1])) {
                                if (Integer.parseInt(installed_version.split("@")[2]) > Integer.parseInt(firebase_version.split("@")[2])) {
                                    versionPass = true;
                                } else if(Integer.parseInt(installed_version.split("@")[2]) == Integer.parseInt(firebase_version.split("@")[2])) {
                                    versionPass = true;
                                } else{
                                    versionPass = false;
                                }
                            }
                            else {
                                versionPass = false;
                            }
                        } else {
                            versionPass = false;
                        }



                        if(!versionPass){
                            Log.d("version_check_fail ", installed_version +" | "+ firebase_version);
                            AppUpdatecheck();
                        }else{
                            Log.d("version_check_success ", installed_version +" | "+ firebase_version);
                            beforeIntro();
                        }

                    } else{
                        Log.d("remoteconfig", "failed!");
                      }



                });


    }

    // 인트로 화면
    private void beforeIntro() {
        // 약 2초간 인트로 화면을 출력.
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(IntroActivity.this, MainActivity.class);
                intent.putExtra("intro", false);
                startActivity(intent);
                finish();
            }
        }, 1000);
    }

    public void AppUpdatecheck() {

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("업데이트 안내");
        builder.setMessage("앱 버젼이 다릅니다. 보다 좋은 서비스를 위해 업데이트 해주세요.");
        builder.setPositiveButton("업데이트",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        String url2 = "https://play.google.com/store/apps/details?id=앱 패키지명";
                        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url2));
                        startActivity(i);
                        System.runFinalization();
                        System.exit(0);
                    }
                });
        builder.setNegativeButton("나가기",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        System.runFinalization();
                        System.exit(0);
                    }
                });
        builder.show();

    }

}

 <코드 설명>

firebaseRemoteConfig의 getInstance와 builder를 통해 기본 틀을 정해주고 그다음에 데이터를 받아옵니다.

코드 중간에

  firebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_default);

이 부분의 경우 값을 제대로 받아오지 못했을때 default값이 있습니다.

res/xml 안에 새로운 xml 파일을 만들어주고 값을 넣어주시면 됩니다. 하지만 앱 버젼의 경우 default값보단 실제 기기에 설치되어있는 versionName를 사용하기 때문에 쓸일은 없습니다만 구현해달라고 하니..

 

우선 설치되있는 앱의 버젼네임을 가져옵시다.

   installed_version = BuildConfig.VERSION_NAME.replace(".","@");

firebaseRemoteConfig.fetch().addOnCompleteListener로 들어가서 값을 비교하기 시작합니다.

   firebase_version = firebaseRemoteConfig.getString("android_version").replace(".","@");

이 부분이 RemoteConfig에 있는 값을 받아오는 부분입니다. 여기에 지금 리펙토링 할 부분이 있는데 처음에 특수문자 .를 split하는 방법이 안먹혀서 @로 바꾼후에 split를 진행하였습니다.( 물론 제 실수였지만요,,)

FireBase RemoteConfig의 android_version 값

보시면 제대로 통신이 완료됫으면 RemoteConfig에 설정해놓은 3.0.1를 가져옵니다.

그다음에 비교 로직을 통해 이전 버젼인지 다음버젼인지 판단하는 과정을 거칩니다.

내 비교로직을 보시면 개판입니다. 처음엔 3.0.1 -> 301로 변경해서 값 비교를 했는데 어느 날 업데이트를 12번한날이 있었습니다... 그 때 문제가 생겨서 일단 임시방편으로 수정을 생각한 것이 X,Y,Z 를 각각 비교하는 것이었습니다. 그래서 코드가 저런식으로 난잡하게 되어있습니다. 시간날때 다시 리펙토링해야하는데 잘 돌아가니 놔두고 있습니다. 하지만 다음 포스터에서 설명할 In app update 방식을 도입할 것이기 때문에 바로 삭제를 할 것같지만요.

다시 내용으로 돌아가보겟습니다. 비교 로직을 돌아가 versionPass가 False가 나오면 버젼업데이트가 필요하다는 것입니다.

어찌보면 RemoteConfig에 있는 것이 이용가능한 최소 버젼이라고 판단되는군요. Remote Config보다 작으면 강제 업데이트니깐요.  아무튼 False 가 나오게 되면 AppUpdateCheck() 로 넘어가 구글플레이스토어로 화면을 넘겨줍니다.

이때 다이얼로그를 써줘서 값을 표현하였습니다. 

             System.runFinalization();
             System.exit(0);

클릭 이벤트에 이 부분을 넣은 이유는 업데이트가 진행될때 앱이 실행중이면 간혹 업데이트가 꼬인다고 합니다. 그래서 어차피 스토어창으로 넘어가면 재설치가 됨으로 강제로 앱을 꺼주는 코드입니다. 또는 나가기 버튼을 클릭했을때 앱을 꺼주는 코드이기도 합니다.

<최종 결과물>

이로써 업데이트를 컨트롤 하는 기능 구현이 완료되었습니다. 

일반적으로 앱 업데이트가 나오면 일주일안에 유저의  95%가 업데이트를 완료한다고 합니다. 하지만 긴급하게 앱을 강제로 업데이트 하거나 더이상 이번 버젼에서 기능을 제공하지 않아 업데이트가 필요할 때 유용한 기능입니다.

다음 글에서는 In App update를 통한 업데이트 버젼 관리에 대해 적어보도록 하겠습니다.

댓글