꿈꾸는 시스템 디자이너

Flutter 강좌 - 앱 배포하기 1/2 | 배포용 앱 APK 빌드하기 본문

Development/Flutter

Flutter 강좌 - 앱 배포하기 1/2 | 배포용 앱 APK 빌드하기

독행소년 2019. 10. 10. 13:28

 

Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com


Flutter 강좌 시즌2 목록 : https://here4you.tistory.com/149

 

이번 강좌에서는 개발한 앱을 Google Player에 등록하는 방법에 대해서 정리한다.

 

1. 런처 아이콘 생성

앱을 실행할 때 사용할 런처 아이콘은 Android Asset Studio를 이용해서 생성할 수 있다.

https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html

 

Android Asset Studio - Launcher icon generator

 

romannurik.github.io

 

아이콘으로 사용할 이미지를 작업한 후에 Android Asset Studio의 Launcher icon generator를 통해 안드로이드의 규격에 맞는 아이콘으로 생성한다. 원하는 효과를 설정한 후 화면 우측 상단의 다운로드 아이콘을 클릭하면 아이콘 파일들이 ic_launcher.zip 압축 파일로 다운로드 된다.

 

압출 파일의 내용을 확인하면 web용 아이콘 1개와 앱에서 사용할 해상도별 아이콘들이 res 폴더에 저장되어 있다.

web용 파일은 구글 콘솔에서 앱을 등록할 때 사용되고, 나머지는 프로젝트에 추가해서 사용한다.

 

우선 res 폴더 내부의 모든 파일들을 배포할 프로젝트의 android/app/src/main/res 폴더에 복사한다. 참고로 이 폴더에는 Flutter 아이콘이 같은 이름으로 저장되어 있으므로 작업한 파일들로 오버라이트한다.

복사후 에뮬레이터를 이용해서 앱을 실행해보면 아이콘이 변경된 것을 확인할 수 있다.

 

 

2. 앱 서명하기

배포할 앱의 개발자의 정보를 앱에 주입하기 위해서 앱에 서명을 해야한다. 개발자를 인증하기 위한 일종의 인증서라고 이해하면 된다. 터미널에서 다음과 같이 입력하여 keystore 서명서를 생성한다.

keytool -genkey -v -keystore c:/Users/parkc/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

명령어를 대충 눈대중으로 살펴보면(본인도 잘 모른다.)

Java에서 제공하는 keytool을 이용해서 keystore(앱 서명서)를 생성하는데,

서명서(키파일: key.jks)은 내 문서(c:/User/parkc)에 저장하고,

서명서를 암호화하는 알고리즘은 RSA를 사용하며

암호화되는 키의 사이즈는 2048 크기로 하고

서명서의 유효기간은 10000일로 한다

는 뜻인 것 같다.

상세하게 알 필요는 없는 내용이지만 key.jks 파일이 저장되는 위치(내 문서)에 들어가 사용자명(윈도우 계정명)은 각자의 계정명으로 대입해서 생성한다.

 

3. 앱으로부터 keysotre 참조하기

배포용 앱을 빌드할 때 참조하기 위해 프로젝트의 android/ 폴더에 key.properties 파일을 생성한 후 다음과 같이 작성한다.

storePassword=<키생성시 입력한 암호>
keyPassword=<키생성시 입력한 암호>
keyAlias=key
storeFile=<키파일의 경로와 파일명>

 

4. Gradle에서 서명 구성하기

Gradle 빌드시 key.properties 파일을 참조하도록 android 블럭 상단에 아래의 내용을 추가한다.

// start of Gradle 서명 구성
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
// end of Gradle 서명 구성

android {
...
}

 

앱을 배포본(release)으로 빌드하기 위해 android 블럭 내부의 buildType을 release 속성으로 변경하고 그 블럭 (buildType) 상단에 singnigConfigs 블럭을 추가한다.

android {
...

    // start of signingConfigs
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }
    // end of signingConfigs
    buildTypes {
        release {
            // release 속성으로 변경
            signingConfig signingConfigs.release
        }
    }
}

이제 앱을 배포용으로 빌드하면 서명키가 자동으로 주입된다.

 

5. Proguard 사용

Proguard는 배포할 앱의 소스코드를 난독화하는 설정이다. APK 파일의 크기를 줄이고 코드를 디컴파일하여도 소스코드의 내용을 이해할 수 없도록 난독화할 수 있다.

Proguard Rule을 구성하기 위해 /android/app/proguard-rules.pro 파일을 생성하고 다음과 규칙을 추가한다.

## Flutter wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
-dontwarn io.flutter.embedding.**

 

Gradle 빌드시 proguard-rules.pro 파일을 참조하여 코드 난독화와 사이즈를 축소할 수 있도록 /android/app/build.gradle 파일의 buildTypes 블럭안에 다음의 내용을 추가한다.

android {
    ...
    
    buildTypes {
        release {
            // release 속성으로 변경
            signingConfig signingConfigs.release

            // start of 코드난독화 및 사이즈 축소
            minifyEnabled true
            useProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // end of 코드난독화 및 사이즈 축소
        }
    }
}

 

* Proguard Rule 파일을 이용해서 위의 내용과 같이 build.gradel 파일을 설정한 후 배포용 앱을 빌드할 때 간혹 에러가 발생할 수 있다. 이는 Flutter 앱을 개발하면서 사용한 외부 패키지들의 난독화도중 에러가 발생하는 것으로 보여지는데 필자도 아직까지 해결하지 못하고 있다. 이경우 우선은 위의 코드난독화 및 사이즈 축소 설정을 주석으로 처리해서 빌드시 발생하는 에러를 회피할 수 있다.

 

6. 앱 매니페스트 파일 검토

/android/app/src/main/AndroidManifest.xml 파일을 열어 앱의 최종 이름과 사용할 퍼미션을 설정한다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.h4u.flutter_example">

    <application
        android:name="io.flutter.app.FlutterApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="Flutter Code Examples"> // 앱의 최종 이름
        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
        </activity>
    </application>
    <!--퍼미션 설정-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    <uses-permission android:name="android.permission.ADD_VOICEMAIL" />
    <uses-permission android:name="android.permission.USE_SIP" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission android:name="android.permission.BODY_SENSORS" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
    <uses-permission android:name="android.permission.RECEIVE_MMS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

대부분의 경우 퍼미션 설정은 앱 개발 단계에서 이미 설정되었을 것이다. 다만 android:label은 프로젝트 생성시 입력한 프로젝트 명이 기본으로 기술 되어 있는데 이 이름을 최종적으로 결정하여 부여한다. 이 labe 값은 앱이 설치되고 실행될 때 표시되는 앱의 이름에 해당한다.

 

7. 빌드 구성 검토하기

/android/app/build.gradle 파일을 열어 빌드 구성을 검토한다.

  • 버전명과 버전코드 설정
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

 

  • applicationId 설정
android {
    compileSdkVersion 28

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        applicationId "com.h4u.flutter_examples" //application의 고유 ID 설정
        minSdkVersion 16
        targetSdkVersion 28
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
//        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    ...
}

 

applicationId는 프로젝트를 생성할 때 설정된 기본 패키지의 경로가 자동으로 기술된다. 하지만 프로젝트 생성 이후부터는 실제 앱의 기본 패키지와 applicationId 값은 서로 관련이 없어지므로 applicationId 값이 변경되어도 소스코드의 패키지에는 영향을 주지 않으므로 원하는 applicationId값을 설정하면된다. 또한 한번 Id값을 결정하여 마켓에 등록하면 Id값을 변경할 수 없으므로 신중하게 설정해야 한다.

 

8. 앱 번들 빌드

프로젝트의 루트 경로에서 빌드를 시작한다. 안드로이드 스튜디어의 경우 화면 하단의 터미널창에서 입력할 수 있다.

명령어

flutter build appbundle

 

빌드 결과

D:\workspace\Flutter\mywork\flutter_example>flutter build appbundle
Initializing gradle...                                              0.9s
Resolving dependencies...                                           4.3s
Running Gradle task 'bundleRelease'...
Running Gradle task 'bundleRelease'... Done                        14.6s
Built build\app\outputs\bundle\release\app.aab (15.5MB).

빌드한 앱 번들은 번들 도구와 구글 플레이를 통해 테스트가 가능하다고 하나 본인은 생략했다.

또한 번들의 경우 arm과 x86용 코드가 모두 컴파일되어 APK파일이 무거워진다고 한다. 위의 코드에서도 15.5MB로 표기되고 있다.

 

9. APK 빌드

역시 프로젝트 루트 경로에서 빌드를 시작한다.

명령어

flutter build apk --split-per-abi

 

빌드 결과

D:\workspace\Flutter\mywork\flutter_example>flutter build apk --split-per-abi
Initializing gradle...                                              0.9s
Resolving dependencies...                                           3.9s
Running Gradle task 'assembleRelease'...
Running Gradle task 'assembleRelease'... Done                      11.4s
Built build\app\outputs\apk\release\app-armeabi-v7a-release.apk (9.1MB).
Built build\app\outputs\apk\release\app-arm64-v8a-release.apk (9.4MB).

번들 빌들때와는 달리 타겟별도 따로 빌드되었고 결과물의 크기도 줄어든 것을 확인할 수 있다.

 

10. 기기를 통해 APK 설치 및 테스트

빌드한 APK 파일을 테스트용 폰에 설치해서 정상 동작 여부를 확인한다.

명령어

flutter install

 

설치 결과

D:\workspace\Flutter\mywork\flutter_example>flutter install
More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.

LM V409N                  • LMV409Na0e251e9 • android-arm64 • Android 9 (API 28)
Android SDK built for x86 • emulator-5554   • android-x86   • Android 9 (API 28) (emulator)
No target device found

D:\workspace\Flutter\mywork\flutter_example>flutter install -d LMV409Na0e251e9
Initializing gradle...                                              0.9s
Resolving dependencies...                                           3.9s
Installing app.apk to LM V409N...
Uninstalling old version...
Installing build\app\outputs\apk\app.apk...                         5.3s

D:\workspace\Flutter\mywork\flutter_example>

기본 명령어로 설치를 시도하자 연결 가능한 기기가 2종인 관계로 설치할 기기를 결정하라는 안내가 나온다.

-d 옵션을 이용해서 실제 설치할 기기(혹은 에뮬레이터)를 설정한 후 다시 설치를 시도한다.

앱이 정상 동작하는지 확인한다. 디버그 모드의 경우 앱의 우측 상단에 표시되던 디버크 마크도 사라진 것을 확인할 수 있다.

Comments