본문으로 바로가기
반응형

photo by unsplash

App bundle을 이용하여 분할된 APK들을 직접 단말에 설치해 볼 수 있습니다.

App bundle 자체로는 설치가 불가능 하기 때문에 App bundle file인 aab(Android App Bunlde)을 apk로 변경하고 설치하는 방법에 대해서 알아봅니다.

App Bundle 생성

Android studio를 이용하면 간단하게 app bundle을 생성할 수 있습니다. 표시된 그림의 하단 Menu인 Generate Signed Bundle을 이용하면 signing 된 app bundle 생성이 가능하며, 이를 바로 Play store에 업로드하면 됩니다.

빌드가 완료되면 project가 설치된 폴더의 app/build/outputs/bundle/... 아래에서 생성된 aab 파일을 확인할 수 있습니다. 경로는 환경에 따라 다를수 있습니다.

command 창에서 빌드하려면 bundle tool이 필요하므로 https://github.com/google/bundletool/releases 에서 다운로드합니다. 기본적인 사용은 방법은 아래의 command로 jar를 수행시키면 됩니다.

java -jar bundletool-all-1.4.0.jar

편의를 위해서 아래와 같은 bat 파일로 생성하고 윈도우 환경 변수의 path에 해당 bat 파일 경로를 추가해 주면 쉽게 호출하여 사용할 수 있습니다.

// bundletool.bat 파일 생성
java -jar (경로)\bundletool-all-1.4.0.jar %*
c:\> bundletool version
1.4.0

만약 맥이라면 ~/.zshrc에 아래 한 줄을 추가하도록 합니다.

alias bundletool='java -jar (경로)/bundletool-all-1.4.0.jar'

사실 command로 빌드하려면 추가적인 작업이 더 필요합니다. 따라서 Android studio를 사용하는 방법을 권장하며, 빌드 서버가 따로 존재하여 빌드를 진행하는 경우 AAPT2를 이용하여 앱의 리소스를 컴파일한 후 여러 리소스를 단일 APK에 연결하도록 합니다. 자세한 방법을 여기서 언급하지 않고 android developer 사이트의 링크로 대체합니다. https://developer.android.com/studio/build/building-cmdline?hl=ko

App Bundle의 설치

App bundle을 단말에 설치 가능한 apk로 만들기 위해서는 먼저 분할된 apk의 집합(apks)으로 변경해야 합니다.

bundletool build-apks --bundle=app-debug.aab --output=./app-debug.apks

이는 signing없이 apks를 생성하게 되므로 아래와 같이 debug로 signing 된다는 문구가 표시됩니다.

INFO: The APKs will be signed with the debug keystore found at 'c:\(경로).android\debug.keystore'.

만약 signing key가 존재한다면 아래와 같이 keystore와 password 정보를 같이 넣어주도록 합니다.

bundletool build-apks --bundle=app-debug.aab --output=/MyApp/app-debug.apks
--ks=/MyApp/keystore.jks
--ks-pass=file:/MyApp/keystore.pwd
--ks-key-alias=MyKeyAlias
--key-pass=file:/MyApp/key.pwd

 

apks가 생성되면 install-apk 옵션으로 연결된 기기에 설치합니다.

bundletool install-apks --apks=./app-debug.apks

 

단말에 따른 apk 수동 생성

install-apks 명령어를 통하여 연결된 단말에 분할되어 최적화된 apk를 설치할 수 있습니다. 하지만 단말 설정 파일을 생성하여, 해당 조합에 따라 apk만 따로 생성이 가능합니다. 즉 단말의 정보 파일만 있으면 필요한 apk를 미리 생성할 수 있으며, 필요에 따라서 분할된 apk에 탑재되는 구성 apk들을 조정할 수 있습니다.

Device의 정보 추출

먼저 device를 연결하여 해당 정보를 추출합니다.

bundletool get-device-spec --output=./v40-spec.json

제가 개발할 때 사용하는 단말은 LG V40 단말입니다.

생성된 json 파일은 아래와 같습니다.

{
  "supportedAbis": ["arm64-v8a", "armeabi-v7a", "armeabi"],
  "supportedLocales": ["ko-KR", "en-US"],
  "deviceFeatures": ["reqGlEsVersion=0x30002", "android.hardware.audio.output", "android.hardware.bluetooth", "android.hardware.bluetooth_le", "android.hardware.camera", "android.hardware.camera.any", "android.hardware.camera.autofocus", "android.hardware.camera.capability.manual_post_processing", "android.hardware.camera.capability.manual_sensor", "android.hardware.camera.capability.raw", "android.hardware.camera.flash", "android.hardware.camera.front", "android.hardware.camera.level.full", "android.hardware.faketouch", "android.hardware.fingerprint", "android.hardware.location", "android.hardware.location.gps", "android.hardware.location.network", "android.hardware.microphone", "android.hardware.nfc", "android.hardware.nfc.any", "android.hardware.nfc.hce", "android.hardware.nfc.hcef", "android.hardware.opengles.aep", "android.hardware.ram.normal", "android.hardware.screen.landscape", "android.hardware.screen.portrait", "android.hardware.sensor.accelerometer", "android.hardware.sensor.barometer", "android.hardware.sensor.compass", "android.hardware.sensor.gyroscope", "android.hardware.sensor.hifi_sensors", "android.hardware.sensor.light", "android.hardware.sensor.proximity", "android.hardware.sensor.stepcounter", "android.hardware.sensor.stepdetector", "android.hardware.telephony", "android.hardware.telephony.gsm", "android.hardware.touchscreen", "android.hardware.touchscreen.multitouch", "android.hardware.touchscreen.multitouch.distinct", "android.hardware.touchscreen.multitouch.jazzhand", "android.hardware.usb.accessory", "android.hardware.usb.host", "android.hardware.vulkan.compute", "android.hardware.vulkan.level", "android.hardware.vulkan.version=4194307", "android.hardware.wifi", "android.hardware.wifi.direct", "android.hardware.wifi.passpoint", "android.software.activities_on_secondary_displays", "android.software.app_widgets", "android.software.autofill", "android.software.backup", "android.software.companion_device_setup", "android.software.connectionservice", "android.software.cts", "android.software.device_admin", "android.software.home_screen", "android.software.input_methods", "android.software.live_wallpaper", "android.software.managed_users", "android.software.midi", "android.software.picture_in_picture", "android.software.print", "android.software.verified_boot", "android.software.voice_recognizers", "android.software.webview", "com.google.android.apps.dialer.SUPPORTED", "com.google.android.feature.ZERO_TOUCH", "com.lge.fido.fingerex", "com.lge.hardware.display.camera_notch", "com.lge.hifirecorder.hifirecording", "com.lge.hifirecorder.trim", "com.lge.software.cliptray", "com.lge.software.drm", "com.lge.software.gallery_memories", "com.lge.software.integrity", "com.lge.software.leccp", "com.lge.software.lgthings", "com.lge.software.mme", "com.lge.software.sdencryption", "com.lge.software.talkbackquickaccess", "com.lge.software.volumevibrator", "com.lge.software.wfdService", "com.lge.wfds.asp", "com.lge.wifi.lgp2p", "com.nxp.mifare"],
  "glExtensions": ["GL_OES_EGL_image", "GL_OES_EGL_image_external", "GL_OES_EGL_sync", "GL_OES_vertex_half_float", "GL_OES_framebuffer_object", "GL_OES_rgb8_rgba8", "GL_OES_compressed_ETC1_RGB8_texture", "GL_AMD_compressed_ATC_texture", "GL_KHR_texture_compression_astc_ldr", "GL_KHR_texture_compression_astc_hdr", "GL_OES_texture_compression_astc", "GL_OES_texture_npot", "GL_EXT_texture_filter_anisotropic", "GL_EXT_texture_format_BGRA8888", "GL_OES_texture_3D", "GL_EXT_color_buffer_float", "GL_EXT_color_buffer_half_float", "GL_QCOM_alpha_test", "GL_OES_depth24", "GL_OES_packed_depth_stencil", "GL_OES_depth_texture", "GL_OES_depth_texture_cube_map", "GL_EXT_sRGB", "GL_OES_texture_float", "GL_OES_texture_float_linear", "GL_OES_texture_half_float", "GL_OES_texture_half_float_linear", "GL_EXT_texture_type_2_10_10_10_REV", "GL_EXT_texture_sRGB_decode", "GL_OES_element_index_uint", "GL_EXT_copy_image", "GL_EXT_geometry_shader", "GL_EXT_tessellation_shader", "GL_OES_texture_stencil8", "GL_EXT_shader_io_blocks", "GL_OES_shader_image_atomic", "GL_OES_sample_variables", "GL_EXT_texture_border_clamp", "GL_EXT_multisampled_render_to_texture", "GL_EXT_multisampled_render_to_texture2", "GL_OES_shader_multisample_interpolation", "GL_EXT_texture_cube_map_array", "GL_EXT_draw_buffers_indexed", "GL_EXT_gpu_shader5", "GL_EXT_robustness", "GL_EXT_texture_buffer", "GL_EXT_shader_framebuffer_fetch", "GL_ARM_shader_framebuffer_fetch_depth_stencil", "GL_OES_texture_storage_multisample_2d_array", "GL_OES_sample_shading", "GL_OES_get_program_binary", "GL_EXT_debug_label", "GL_KHR_blend_equation_advanced", "GL_KHR_blend_equation_advanced_coherent", "GL_QCOM_tiled_rendering", "GL_ANDROID_extension_pack_es31a", "GL_EXT_primitive_bounding_box", "GL_OES_standard_derivatives", "GL_OES_vertex_array_object", "GL_EXT_disjoint_timer_query", "GL_KHR_debug", "GL_EXT_YUV_target", "GL_EXT_sRGB_write_control", "GL_EXT_texture_norm16", "GL_EXT_discard_framebuffer", "GL_OES_surfaceless_context", "GL_OVR_multiview", "GL_OVR_multiview2", "GL_EXT_texture_sRGB_R8", "GL_KHR_no_error", "GL_EXT_debug_marker", "GL_OES_EGL_image_external_essl3", "GL_OVR_multiview_multisampled_render_to_texture", "GL_EXT_buffer_storage", "GL_EXT_external_buffer", "GL_EXT_blit_framebuffer_params", "GL_EXT_clip_cull_distance", "GL_EXT_protected_textures", "GL_EXT_shader_non_constant_global_initializers", "GL_QCOM_texture_foveated", "GL_QCOM_shader_framebuffer_fetch_noncoherent", "GL_EXT_memory_object", "GL_EXT_memory_object_fd", "GL_EXT_EGL_image_array", "GL_NV_shader_noperspective_interpolation", "GL_KHR_robust_buffer_access_behavior"],
  "screenDensity": 560,
  "sdkVersion": 27
}

단말은 Native library를 arm 기반 32bit, 64bit를 지원하며, 현재 단말에 설정된 언어는 한국어와 영어 두 가지입니다.

지원하는 open GL의 종류가 표기되며, 화면의 density는 560이고 sdkVersion은 27(O OS)라고 표기됩니다.

Device에 해당하는 apk 추출

먼저 생성해 놓은 전체 apks에서 해당 단말에 해당하는 분할된 apk를 추출해 보겠습니다.

bundletool extract-apks
 --apks=./app-debug.apks
 --output-dir=./
 --device-spec=./v40-spec.json

제가 생성한 aab에는 Feature module이 존재하지 않습니다. 따라서 필요한 apk는 전부 base에 관련된 것들만 존재합니다.

먼저 기본 동작을 담고 있는 base-master.apk가 포함됩니다. base와 관련된 configuration apks로는 단말에서 지원하는 언어인 base-en.apk, base-ko.apk와 화면 해상도에 맞는 base-xxxhdpi.apk가 포함되었습니다.

설치 시 예상되는 apk 크기

설치 전에 실제 설치 시 예상되는 apk의 크기를 확인해 볼 수 있습니다.

bundletool get-size total --apks=./app-debug.apks

-- 결과
MIN,MAX
10438392,10472471

 

실제 설치된 상태

Android Studio의 device explore를 통해 확인하면 분리된 채로 apk가 설치되어 있는걸 확인할 수 있습니다.

정리

App bundle은 android studio에서 쉽게 생성할 수 있습니다. signing 또한 손쉽게 android studio에서 제공하여 play store에 바로 올릴 수 있는 aab 파일을 생성해 줍니다. 다만 배포해야 하는 대상이 play store가 아닐 때 bundle tool을 이용하여 여러 가지 결과물들을 만들어 낼 수 있습니다. 

App bundle의  기본적인 개념과 사용방법을 알아봤습니다. apk 배포에서 app bundle로 전환만으로 얻을 수 있는 용량 감소라는 장점은 App bundle을 적용해야 하는 하나의 큰 이유가 될 수 있습니다.

단일 APK가 분할된 멀티 APKs로 표현되어 플랫폼에서 동작하면서, play store에서 분할/재조합의 과정이 발생하고, 자동 생성되는 분할 apks 이외에도 개발자가 의도적으로 기능을 분할하여 선택 탑재 가능한  Dynamic feature module이라는 개념이 발생하면서 용량을 더 많이 감소시킬 수 있는 장점을 제공합니다. 다음번 포스팅에서는 Dynmic feature module의 생성과 동작 그리고 주의해야 하는 side issue들에 대해서 좀 더 면밀히 기재하고자 합니다.

References

[1] developer.android.com/studio/command-line/bundletool

[2] developer.android.com/studio/build/building-cmdline

 

반응형