IT_Programming/Android_Java

[펌] Play Store 앱 등록시 Multiple APK 지원

JJun ™ 2015. 8. 6. 12:49



 [출처]

  : http://thdev.net/422

  : http://aroundck.tistory.com/2300

  : http://androiddevcorner.blogspot.kr/2014/10/building-multiple-apks-inside-android.html

  : http://tiii.tistory.com/8

  : http://stackoverflow.com/a/27543477


 [참조]

  : https://developer.android.com/google/play/publishing/multiple-apks.html

  : https://developer.android.com/training/multiple-apks/index.html

 https://www.youtube.com/watch?v=FTlhWySJkYM




안드로이드 Multiple APK를 지원한다고 합니다. 지원하게 되면 어떤식으로 적용이 가능한지 이를 통해 변경된 사항이 무엇인지 작성하려고 합니다. 
이 글은 안드로이드 개발 홈페이지에 작성되어 있는 글을 참고하여 작성하였습니다.


Multiple AKP Support 메뉴얼

  https://developer.android.com/google/play/publishing/multiple-apks.html




Multiple APK 적용가능한 범위


같은 이름의 패키지 명을 사용해야 하는데 APK 1개로 처리하기엔 이미지 사이즈도 크고, 

그로 인해 적용되어야 하는 폰이 아닌 경우에도 모두 1개의 APK로 처리해야 하여 그만큼의 용량을 비효율적으로 사용하게 되는 경우 등이 있습니다.
멀티 APK 지원을 통해 이를 해결할 수 있도록 변경되었습니다.


여러개의 APK를 둬야 하는 아래 사항에 대하여 Multiple APK 적용이 가능한 경우들 입니다.


 - CPU마다 최적화를 처리해야 하는 경우 (NDK 빌드 시)

 - 이미지 사이즈를 각각 적용해야하는 경우 (DPI 순으로)

 - 플렛폼 버전별로 별도의 앱을 구현해야 할 경우

 - OpenGL Texture 지원 포멧에 따라서


이렇게 4가지 방식의 경우 Multiple APK를 통해 동일한 Package 이름을 사용하여 1개의 앱을 마켓에 등록하고,
위의 사항에 따라서 다운받는 안드로이드 종류에 따라서 모두 다르게 적용이 가능하도록 지원하게 됩니다.


이미지를 사이즈별로 각각 적용해야 하는 경우에는 지원하지 않는 사이즈를 가진 폰에서까지 APK에 포함되어 있는 

모든 이미지를 가지게 되는 사항을 말합니다. 내폰은 XHDPI를 적용하는 폰인데 HDPI, MDPI, LDPI의 사이즈 이미지까지 모두 가지고 있어야 하는 경우입니다.
이들의 이미지 때문에 APK 파일도 커지는 경우에 적용하시면 됩니다. 이미지 1~2개를 가지고 분리하는것은 .. 의미가 없겠죠?


위와 같은 이유로 분리해야 할 경우가 있다면 Multiple APK 지원을 하시면 됩니다.



Multiple APK Rules


응용 프로그램을 Multiple APK를 지원하는 고급모드를 사용하기 전에 지켜야할 내용입니다.


 - 제가 작성하는 내용은 아래 사이트를 참고하여 중요해 보이는 내용을 작성하였습니다.
   모든 내용이 포함되어 있지 않으므로 자세한 내용은 아래 사이트를 참고해주세요.

    https://developer.android.com/google/play/publishing/multiple-apks.html#Rules


 - 꼭 동일한 패키지명을 가져야 하며 같은 사이닝을 해야 합니다.

 - 버전 코드는 서로 다르게 가지고 있어야 합니다. 아래 Multiple APK 생성하기 부분을 참고하세요.

 - 위의 분리 조건을 제외하고 지원되는 내용이 일치해야 합니다.


위에서 작성한 Multiple APK 분리 가능 조건의 내용과 일치해야 합니다.
그 외 완전히 다르게 처리하는 경우가 발생해서는 안된다는 이야기입니다.
이 경우는 전혀 다른 앱이 되는것이겠죠? 


 - 버전 코드가 기존에는 0400이고 새로 적용하는 APK가 0300이라면 업데이트가 불가능하게 됩니다.
   버전 코드에 주의하세요.


이 정도로 정리는 했지만 더 디테일한 내용이 많이 있습니다. 대부분 버전코드에 대한 내용입니다.
안드로이드 마켓에서는 버전 코드가 기존에 작성한 코드가 1.0.0 이라면 다음 코드는 1.0.0 보다 커야 합니다.
이를 Multiple APK 룰에 따라서 적용 받으시는게 가장 안전하다는 이야기가 됩니다.
그리고 이미지 사이즈의 경우 XHDPI를 지원하는 APK가 있고, HDPI를 지원하는 APK가 있습니다.
하지만 실제로 2개 모두 XHDPI가 지원되는 형태를 가지고 있을 수 있습니다. 이럴 경우에는 오류를 발생시키게 됩니다.
모두 버전과 이미지 사이즈 등 겹치지 않게 설계해야 Multiple APK를 지원 할 수 있다는 이야기가 되겠습니다.




Multiple APK 생성하기


Multiple APK를 지원하기 위해서는 별도의 프로젝트가 필요하게 됩니다. 이렇게 별도의 프로젝트 폴더를 가지고 있어야 한다면
당연히 소스코드도 변경되어야 합니다. 그렇다고 버전에 따라서 소스코드까지 다르게 처리하기에는 너무 많은 코드를 가지게 되는 단점이 있습니다.
이럴 때는 적적하게 작성된 중복코드를 라이브러리 형태의 코드로 처리하는 것이 가장 좋은 방법이라고 합니다.
이미지만 다르게 처리하거나, 버전만 다르게 처리하거나, OpenGL Texture 지원 포멧방식만 다르게 하거나 등이있습니다.
모두 라이브러리를 통해 적절하게 처리해주면 중복 코드를 최소화 할 수 있는 방법이라고 합니다.



- Multiple APK의 버전 코드명 작성법


싱글 APK의 경우 1개의 APK 파일에 버그 패치를 하더라도 모든 폰에 적용이 되게 됩니다.
하지만 Multiple APK를 지원한 이후에는 수많은 APK에 모두 버그 패치를 적용할 필요는 없습니다.
내가 버그 패치할 1개의 파일에 대해서만 업로드하면 됩니다.

기존 싱글 APK에서는 버전 코드이름이 1.0.0 의 순서를 사용하여 버그 패치라면 1.0.1 등의 숫자가 적용됩니다.
하지만 Multiple APK는 7자리 이상의 숫자를 사용할 것을 권장한다고 합니다.
바로 아래와 같습니다.





그림의 설명에도 있지만 API Level 2자리 코드, 가운데 2자리는 스크린 사이즈이거나 OpenGL texture 지원 포멧 형식에 따른 번호,
안드로이드 앱의 현재 앱 버전코드 3자리로 구분되어 있습니다. 위에서 제공하는 버전코드는 API 4 버전에서 동작하며 스크린사이즈가
small-normal을 지원하는 3.1 버전의 앱을 의미하게 됩니다.

버전별로 스크린사이즈를 세분화 화고 싶을 경우 위와 같이 작성합니다.
위에 작성된 코드는 하나의 예일 뿐 개발자가 직접 정의하여 사용하시면 됩니다.
난 API만 구분하고 싶어의 경우라면 2.3.3을 지원하는 버전과 그 이상의 버전을 지원하는 버전 2개로 구분하는 번호를 작성하시면 됩니다.


17100 이라는 숫자를 예로 들면 17 버전의 API를 사용하고, 현재 앱 버전은 1.0.0 이라는 의미가 됩니다.
minSdkVersion만 구분하시면 2.3.3 을 지원하는 앱과 구분이 가능하게 됩니다. 2.3.3 을 지원하는 경우는 10100 이 되겠죠?





 이 글은 APK Support에는 없지만 참고하면 좋을것 같아추가해보았습니다.



최근 안드로이드의 해상도, 버전이 많이 추가되고 있습니다. 안드로이드에서 지원하는 해상도는 LDPI부터 XXHDPI까지 지원합니다.
(XXHDPI는 최근에 추가되었지만 기존 XHDPI를 통해서도 지금은 10인치까지 지원이 되더군요) 최근에 조사한 자료에 따르면
아래 표와 같은 점유율을 가지고 있습니다. 아직 2.3.3~2.3.7 버전을 사용하는 사용자가 전체 1위에 해당됩니다.
40%가 넘게 3.0 미만의 버전을 사용하는 셈이죠. (2013.05.18)


표는 아래 사이트에서 가져왔습니다.

http://developer.android.com/about/dashboards/index.html?buffer_share=0df75&utm_source=buffer&utm_medium=twitter&utm_campaign=Buffer%253A%252Bkwang82%252Bon%252Btwitter


그 다음으로 JellyBean(4.1, 4.2), ICS 27.5%, Honeycomb가 0.1% 입니다.





이번엔 적용가능한 DPI를 살펴보겠습니다.
아래와 같이 4가지 DPI 사이즈를 가지고 있습니다.
거기에 최근에 추가된 XXHDPI도 있습니다.
(이 XXHDPI는 새로운 어플리케이션 생성시에 확인가능합니다.)







마무리


수많은 플렛폼과 수많은 해상도를 지원하기 위해서 단일 APK를 통해 지원할 수있는 앱이 일반적입니다.
그렇지만 게임과 같이 고해상도의 이미지가 많은 경우에는 Multiple APK를 작성하여 구분하면 됩니다.
이렇게 Multiple APK를 지원하고 싶다면 위에서 작성한 코드네임을 주의하여 작성해야 한다고 합니다.
싱글 APK의 경우는 상관 없습니다. 테블릿과 스마트폰앱을 따로 올릴 필요가 없다는 설명을.. 최종적으로 하고^^;; 마치겠습니다.
부족한 내용이 많으니 댓글로 잘못된 부분 지적 부탁드립니다.











 안드로이드, Multiple APK Support

 




단적인 예로 현재 마켓에 등록 가능한 APK 파일은 50MB 로 제한되어 있다. 

이 경우 화면 해상도에 따른 여러셋의 리소스를 한 APK 에 포함할 수 없는 경우가 발생하기도 한다.


구글 플레이에서는 하나 이상의 APK 파일을 동일한 이름을 갖는 하나의 어플리케이션으로 등록할 수 있도록 지원된다.




멀티플 APK 지원을 위한 조건


다음 세 가지 형태의 메니페스트의 필터를 기반으로 구분된다.


1. OpenGL 텍스쳐 압축 포맷.

<support-gl-texture>


2. Screen Configuration

<supports-screens>, <compatible-screens> 로 표현


3. 플랫폼 버전

<uses-sdk>. 하위 호환성을 최소화하며 유지는 가져가는 데 필요하다.




UX 는?


여러개의 APK 가 있어도, 조건에 최적화된 하나의 APK 만 마켓에 표시된다. 그리고 별점과 댓글 등은 하나의 앱으로 관리된다.




결론


하나의 앱을 여러 APK 로 분리하여 배포하는 것은 권장사항은 아니지만, 제약조건이 있을 때는 더 좋은 조건이 될 수도 있다. 

하나의 APK 는 배포 과정도 단순하고, 코드 유지 보수에도 장점이 많다.








Building multiple APKs inside Android Studio


Build variants are a huge feature for Android development. The use cases for such a feature
are many. It can be as simple as having a free version of your app and a paid version.
Or maybe you are making generic software that can be sold to many companies that all want they're own branding.


Whatever your use case may be, Gradle comes to the rescue.
Not only can you configure your variants but you can also select which ones actually get built. You are probably already aware of the standard way to do this in Android Studio which is
to use the "Build Variants" tool window and select the variant that you want to build.






This builds one variant at a time and is very practical while you are developing as it allows you to quickly switch from one variant to the another.

Once development is done, you will want to build all of your release variants for final testing and distribution. You can do this in Android Studio as well. Simply open the "Gradle Tasks" tool window, which is usually on the right. You will see many tasks that start with 'assemble', double click on one of those and your APKs will be created.




For example, double clicking on 'assembleRelease' will create all your release apks.

From the docs:
Building and Tasks
We previously saw that each Build Type creates its own assemble task
, but that Build Variants are a combination of Build Type and Product Flavor.
When Product Flavors are used, more assemble-type tasks are created. These are:
1) assemble[Variant Name]
2) assemble[Build Type Name]
3) assemble[Product Flavor Name]
1) allows directly building a single variant. For instance assembleFlavor1Debug.
2) allows building all APKs for a given Build Type. For instance assembleDebug will build both Flavor1Debug and Flavor2Debug variants.
3) allows building all APKs for a given flavor. For instance assembleFlavor1 will build both Flavor1Debug and Flavor1Release variants.
The task assemble will build all possible variants.
Note the "Recent tasks" section which allows you to quickly execute previously run tasks.

[참고]


 Android Studio / Gradle

 build.gradle을 아래와 같이 수정하세요:


 android {

     // 앱 로직의 남은 부분

     splits {

          abi {

               enable true

              reset()

              include 'x86', 'x86_64', 'arm64-v8a', 'armeabi-v7a', 'armeabi'

              universalApk false

          }

     }

 }



 CPU종류에 따라 더 자세한 것을 보려면 Android Gradle 문서 확인하세요.




You'll also note that the tasks that you run are added to your Configurations dropdown.
This allows you to quickly access them by hitting Shift-F10.






And that's it! I hope that this helps you out.








안드로이드 앱을 개발하면서 네이티브코드가 포함된 라이브러리를 사용할 때 
통합버전의 APK를 만들게 되면 모든 프로세스 별로 라이브러리가 추가 되면서 용량이 늘어나게 됩니다.
이럴 때 필요한 것이 Multiple APK기능인데 아래와 같은 것들은 지원합니다.

  • 각각의 APK에 다른 OpenGL texture 압축 포멧 지원
  • 각각의 APK에 다른 스크린 사이즈와 density를 설정
  • 각각의 APK에 다른 디바이스에 대한 특성 설정
  • 각각의 APK에 다른 플렛폼 버전 설정
  • 각각의 APK에 다른 CPU 아키텍처 설정 (ARM, x86, and MIPS)


Multiple APK를 만들기 위해서는 몇가지 필수 제약사항이 있습니다.



Multiple APK의 버전 코드 규칙

필수는 아니지만 구글에서 권장하는 버전 규칙입니다. 
앞 두자리는 API 버전 그다음 두자리는 화면 사이즈 마지막 세자리는 앱버전 입니다.
굳이 지킬 필요는 없지만 버전코드를 통해 쉽게 지원 API 및 화면 크기를 알 수 있습니다.

Example

예제는 CPU 별로 나눠서 APK를 생성하는 예제 입니다.버전코드는 각 CPU 별 코드 값 * 1000000 + versionCode 입니다.


build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
android {
    ...
    splits {        
    abi {
            enable true
            reset()
            include 'x86', 'x86_64', 'arm64-v8a', 'armeabi-v7a', 'armeabi' //분리할 CPU
            universalApk false //통합apk생성여부
        }
    }
 
}
 
// map for the version code
ext.versionCodes = ['x86':1, 'x86_64':2, 'arm64-v8a':3, 'armeabi-v7a':4, 'armeabi':5 ]
 
import com.android.build.OutputFile
 
android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        output.versionCodeOverride =
                project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
    }
}
cs



자세한 문서는 

http://developer.android.com/intl/ko/google/play/publishing/multiple-apks.html
http://developer.android.com/intl/ko/tools/building/plugin-for-gradle.html
http://geeks.everything.me/2015/06/10/taking-the-ks-off-your-apks-part-1/  (사이즈별 cpu 별 apk 분리하는 법)

더보기


Taking the Ks off your APKs (part 1)

EverythingMe had a long time running bug related to orientation change, caused by the (crazy fact in 3…2…1…) fact that View will not get the Activity’s onConfigurationChange() when it’s not attached. The bug manifested in phones, although they were programmatically configured to dump any orientation change events, some did slip in as the launcher activity started while phone is in landscape mode, causing the views to not receive the programatic forced portrait configuration change, and keep the landscape resources loaded when in portrait mode.

The original solution

While looking for a solution, we determined that the best solution for us would be totally eliminating configuration changes on phones (while keeping everything operational for tablets). Gradle’s apk splits seemed like the perfect solution. Moreover, it will shrink our user-facing APK size and lower our resource count (no tablet resources on the phone builds), making it much faster to install with Android’s package installer.

Android Tools Project have released abi splits around v0.14 of com.android.tools.build:gradle, and density splits around v1.0 (?) (I couldn’t find the actual version where it was released), but at the time of writing these lines, there still is no built-in solution for phone-tablet apk splits.

I came across this thread in google groups. Douglas Ferguson and Matthew Townsend came up with a solution (a bit hacky, but hey, it works!). The solution involves manipulation on the AndroidManifest.xml files at build time. Gradle creates different versions the project’s resources in intermediates directory, just before they are wrapped into the final APKs.But this solution stopped working after gradle 0.6.3, and after digging into changes in gradle’s source code I came back with these findings:

  1. There was a gradle api change: variant.processManifest.doLast has been moved deeper into output.processManifest.doLast
  2. intermediate manifest files have been moved frombuild/filtered_manifested/${variant.name}/AndroidManifest.xml to${buildDir}/intermediates/manifests/full/${screenSize}/${density}/${abi}/${buildType}/AndroidManifest.xml

Let’s see how the revised solution works:

Background

First, we’ll need to take a look at the gradle configuration of a density splitted apk:

density {
    enable project.splitApks
    exclude "ldpi", "tvdpi", "xxxhdpi"
    compatibleScreens 'small', 'normal', 'large', 'xlarge'
}

When we add the compatibleScreens tag in density splits configuration, gradle will add the following lines in each splitted manifest file (this example is taken for the xhdpi manifest):

XHTML

<compatible-screens>
   <screen android:screenDensity="xhdpi" android:screenSize="small"/>
   <screen android:screenDensity="xhdpi" android:screenSize="normal"/>
   <screen android:screenDensity="xhdpi" android:screenSize="large"/>
   <screen android:screenDensity="xhdpi" android:screenSize="xlarge"/>
</compatible-screens>

Splitting time

In order to split the apks to phones and tablets we thought that removing the android:screenSize=”xlarge” line from phone builds, we’d be able to easily tell google play to distribute this build only to phones. Taking a closer look showed us that Android largeScreens refers both to 5” devices and 7” devices, therefore in here google notes:

NOTE: Android 3.2 introduces new attributes: android:requiresSmallestWidthDp, android:compatibleWidthLimitDp, and android:largestWidthLimitDp. If you’re developing your application for Android 3.2 and higher, you should use these attributes to declare your screen size support, instead of the attributes based on generalized screen sizes.So by using both requiresSmallestWidthDp and xScreens google play developer console will both distribute the correct APK to big screen phones and tablets, and show what screen layout the specific APK supports (this example is taken for the same phone-xhdpi manifest).

XHTML

<supports-screens 
   android:anyDensity="true" 
   android:largeScreens="true" 
   android:normalScreens="true" 
   android:smallScreens="true" 
   android:xlargeScreens="false"
   android:requiresSmallestWidthDp="240" />

We also removed the android:screenSize=”xlarge” from every phone build:

XHTML

<compatible-screens>
   <screen android:screenDensity="xhdpi" android:screenSize="small"/>
   <screen android:screenDensity="xhdpi" android:screenSize="normal"/>
   <screen android:screenDensity="xhdpi" android:screenSize="large"/>
</compatible-screens>

and the corresponding xhdpi tablet manifest:

XHTML

<supports-screens 
   android:anyDensity="false" 
   android:largeScreens="false" 
   android:normalScreens="false" 
   android:smallScreens="false" 
   android:xlargeScreens="true"
   android:requiresSmallestWidthDp="600" />

XHTML

<compatible-screens>
   <screen android:screenDensity="xhdpi" android:screenSize="large"/>
   <screen android:screenDensity="xhdpi" android:screenSize="xlarge"/>
</compatible-screens>

Configuration

The splitting was done using two (three actually) different productFlavors, each configuring the following manifestPlaceholders

build.gradle

productFlavors {
   if (!project.splitApks) {
       // a universal build for all device types
       universal {
           manifestPlaceholders = [
                   configChanges: "keyboardHidden|orientation|screenSize",
                   xlargeScreens: "true",
                   largeScreens : "true",
                   normalScreens: "true",
                   smallScreens : "true",
                   requiresSmallestWidthDp : "240"
           ]
       }
   }
   else {
       phone {
           manifestPlaceholders = [
               // phones will receive no onConfigurationChanged events!
                configChanges: "",
                xlargeScreens: "false",
                largeScreens : "true",
                normalScreens: "true",
                smallScreens : "true",
                requiresSmallestWidthDp : "240"
           ]
       }
       tablet {
           manifestPlaceholders = [
                   configChanges: "keyboardHidden|orientation|screenSize",
                   xlargeScreens: "true",
                   largeScreens : "true",
                   normalScreens: "false",
                   smallScreens : "false",
                   // makes sore only tablets get this apk on Google Play
                   requiresSmallestWidthDp : "600"
           ]
       }
   }
}

Corresponding AndroidManifest.xml lines:

XHTML

<supports-screens
    android:xlargeScreens="${xlargeScreens}"
    android:largeScreens="${largeScreens}"
    android:normalScreens="${normalScreens}"
    android:smallScreens="${smallScreens}"
    android:requiresSmallestWidthDp="${requiresSmallestWidthDp}"
    android:anyDensity="true" >
</supports-screens>

Back to build.gradle , this is a basic apk split setup:

splits {
    abi {
        enable project.splitApks
        reset()
        include  'armeabi', 'armeabi-v7a','x86'
        universalApk true
    }
    density {
        enable project.splitApks
        exclude "ldpi", "tvdpi", "xxxhdpi"
        compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
}

This is where all the magic happens:

Java

// map for the version code, you can do whatever calculation you want here, as long as it applies 
// to google's documentation in http://developer.android.com/google/play/publishing/multiple-apks.html 
def versionCodesAbi = ['armeabi': 1, 'armeabi-v7a': 2, 'x86': 3]
def versionCodesDensity = [all: 0, mdpi: 1, hdpi: 2, xhdpi: 3, xxhdpi: 4]
def versionCodesScreenSize = [phone: 0, tablet: 1]
// these groups represent which screen size the script should exclude in every flavour
def screenSizeExcludeGroups = [tablet: ["small", "normal"], phone: ["xlarge"]]
task getVersionCode() {
    if (project.splitApks) {
       android.applicationVariants.all  { variant ->
           // assign different version code for each output
           println "Preparing outputs for " + variant
           variant.outputs.each { output ->
               def abiOffset = 0
               def abi = output.getFilter(com.android.build.OutputFile.ABI)
               if (abi != null) {
                   abiOffset = versionCodesAbi.get(abi)
               }
               def densityOffset = 0
               def density = output.getFilter(com.android.build.OutputFile.DENSITY)
               if (density != null) {
                   densityOffset = versionCodesDensity.get(density) * 4 //shift left by 2
               }
               def screenSize = 'phone'
               def screenSizeOffset = 0
               if (output.name.startsWith("tablet")) {
                   screenSize = 'tablet'
                   screenSizeOffset = 32 //turn on 6th bit
               }
               output.versionCodeOverride = screenSizeOffset + 
                                            abiOffset + 
                                            densityOffset + 
                                            (android.defaultConfig.versionCode)
               println "versionCode= " + output.versionCodeOverride + "    name= " + output.name
               def buildType = output.name.endsWith("Release") ? "release" : "debug"
               println "buildType " + buildType
               if (abi == null) { abi = ""}
               if (density == null) { density = ""}
               if (project.splitApks) {
                   def manifestFile = 
                   "${buildDir}/intermediates/manifests/full/${screenSize}/${density}/${abi}/${buildType}/" + 
                   "AndroidManifest.xml"
                   // These lines were automatically added in densitySplits by using compatibleScreens,
                   // this removes unnecessary screenSize declerations.
                   output.processManifest.doLast {
                       for (String key : screenSizeExcludeGroups.get(screenSize)) {
                           replace_in_file(manifestFile, 
                           "<screen\\s*android:screenDensity=\".*\"\\s*android:screenSize=\"${key}\" />", "")
                      }
                  }
               }
           }
       }
   }
}
/*
 * Replaces a string in a file
 */
def replace_in_file(filePath, fromString, toString) {
    def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
    new File(manifestFile).write(updatedContent, 'UTF-8')
}

Bottom line

We managed to create 40 different APKs:

2 productFlavors (phone/tablet)

4 abi splits (armeabi/armeabi-v7/x86/universal)

5 densities (mdpi/hdpi/xhdpi/xxhdpi/universal)

We stopped the dreaded bug, and shrunk user facing APKs by over 2MBs!

Let’s take a glimpse at our dev console…


을 참고하면 도움이 됩니다.




Android Gradle 1.0 Computing Version code in multi-flavor setup



From Google user guide

Multi-flavor variants

In some case, one may want to create several versions of the same apps based on more than one criteria. For instance, multi-apk support in Google Play supports 4 different filters. Creating different APKs split on each filter requires being able to use more than one dimension of Product Flavors.

Consider the example of a game that has a demo and a paid version and wants to use the ABI filter in the multi-apk support. With 3 ABIs and two versions of the application, 6 APKs needs to be generated (not counting the variants introduced by the different Build Types). However, the code of the paid version is the same for all three ABIs, so creating simply 6 flavors is not the way to go. Instead, there are two dimensions of flavors, and variants should automatically build all possible combinations.

This feature is implemented using Flavor Dimensions. Flavors are assigned to a specific dimension android { ...

flavorDimensions "abi", "version"
productFlavors {
    freeapp {
        flavorDimension "version"
        ...
    }
    x86 {
        flavorDimension "abi"
        ...
    }
} }

flavorGroups was replaced by flavorDimensions, so you need to use next code at build.gradle

   // 2 dimensions of flavors. API is more important than ABI.
flavorDimensions "api", "abi"
productFlavors {
    gingerbread {
        flavorDimension "api"
        minSdkVersion 10
        versionCode = 1
    }
    icecreamSandwich {
        flavorDimension "api"
        minSdkVersion 14
        // this must be higher than the gingerbread version to ensure update of the
        // app when the device gets a system update from GB to ICS
        versionCode = 2
    }
    x86 {
        flavorDimension "abi"
        ndk.abiFilter "x86"
        // this is the flavor part of the version code.
        // It must be higher than the arm one for devices supporting
        // both, as x86 is preferred.
        versionCode = 3
    }
    arm {
        flavorDimension "abi"
        ndk.abiFilter "armeabi-v7a"
        versionCode = 1
    }
    mips {
        flavorDimension "abi"
        // It must be higher than the arm one for devices supporting
        // both, as mips is preferred.
        ndk.abiFilter "mips"
        versionCode = 2
    }
    fat {
        flavorDimension "abi"
        // fat binary, lowest version code to be
        // the last option
        versionCode = 0
    }
}
// make per-variant version code
applicationVariants.all { variant ->
    // get the version code of each flavor
    def apiVersion = variant.productFlavors.get(0).versionCode
    def abiVersion = variant.productFlavors.get(1).versionCode
    // set the composite code
    variant.mergedFlavor.versionCode = apiVersion * 1000000 + abiVersion * 100000 + defaultConfig.versionCode
}



Update:

Add these lines to be able see versionCode at generated names of apk

    applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def apk = output.outputFile;
        def newName =  "${output.name}-${variant.mergedFlavor.versionCode}"
        if (variant.buildType.versionNameSuffix) {
            newName += "-${variant.buildType.versionNameSuffix}"
        }
        if (output.zipAlign) {
            output.zipAlign.outputFile = new File((File) apk.parentFile, newName + '-aligned.apk');
        }
        output.packageApplication.outputFile = new File((File) apk.parentFile, newName + ".apk")
    }
}

See bellow result of build:

    gingerbreadArmDebug-1100123.apk
    gingerbreadArmDebug-1100123-aligned.apk
    gingerbreadFatDebug-1000123.apk
    gingerbreadFatDebug-1000123-aligned.apk
    gingerbreadMipsDebug-1200123.apk
    gingerbreadMipsDebug-1200123-aligned.apk
    gingerbreadX86Debug-1300123.apk
    gingerbreadX86Debug-1300123-aligned.apk
    icecreamSandwichArmDebug-2100123.apk
    icecreamSandwichArmDebug-2100123-aligned.apk
    icecreamSandwichFatDebug-2000123.apk
    icecreamSandwichFatDebug-2000123-aligned.apk
    icecreamSandwichMipsDebug-2200123.apk
    icecreamSandwichMipsDebug-2200123-aligned.apk
    icecreamSandwichX86Debug-2300123.apk
    icecreamSandwichX86Debug-2300123-aligned.apk
    gingerbreadArmRelease-1100123.apk
    gingerbreadFatRelease-1000123.apk
    gingerbreadMipsRelease-1200123.apk
    gingerbreadX86Release-1300123.apk
    icecreamSandwichArmRelease-2100123.apk
    icecreamSandwichFatRelease-2000123.apk
    icecreamSandwichMipsRelease-2200123.apk
    icecreamSandwichX86Release-2300123.apk


Info from one of them, extracted by apktool:

version: 2.0.0-RC3
apkFileName: gingerbreadArmDebug-1100123.apk
isFrameworkApk: false
usesFramework:
  ids:
  - 1
sdkInfo:
  minSdkVersion: '10'
  targetSdkVersion: '21'
packageInfo:
  forced-package-id: '127'
versionInfo:
  versionCode: '1100123'
  versionName: '1.0'
compressionType: false



Update 2:

Published my test project at GitHub

gradle-27508708-master.zip


gradle-27508708-master.zip
0.11MB