Android Platform Version : Android 5.1 (수정된 롤리팝 버전입니다~)
전혀 연관성이 없습니다. 참고로 API Level은 항상 양의 정수 값입니다.
2. minSdkVersion
안드로이드 앱이 실행될 수 있는 최소한의 API 레벨을 의미합니다. 만약에 앱이 선언해 놓은 버전보다 디바이스의 버전이 낮을 경우에는 앱이 인스톨되지 않습니다.
앱 개발시 필수적으로 선언해야 하는 필드이며 만약에 선언을 하지 않았을 경우 ‘1’이 디폴트로 선정됩니다.
3. targetSdkVersion
안드로이드 앱의 타겟 API 레벨을 의미합니다. 만약 선언이 되어 있지 않다면 minSdkVersion과 동일한 값으로 간주됩니다. ‘타겟 API’의 개념은 해당 앱이 개발시에 테스트 되어진 API 레벨을 의미합니다. 당연히 targetSdkVersion은 minSdkVersion 보다 같거나 높을 수 밖에 없습니다. 하지만 targetSdkVersion이 높다고 하더라도 앱은 minSdkVersion에서도 인스톨 및 실행할 수 있습니다.
그럼 targetSdkVersion은 언제 사용이 될까요?
당연하겠지만 안드로이드 API 버전이 올라가면서 플랫폼이 조금씩 바뀌게 됩니다. 아래의 상황을 가정해봅시다.
이 상황에서는 당연히 앱을 인스톨하고 실행하는데에 전혀 무리가 없습니다. 하지만 만약 안드로이드 API가 7버전에서 10버전으로 오면서 앱 실행과 관련된 API에 변경이 생겼다면 어떻게 될까요? 이 경우 앱의 실행이 개발자의 예상과 달라질 가능성이 있습니다. 때문에 안드로이드 플랫폼에서는 하위 호환성 관련 기능을 활성화를 시키고 앱을 실행시켜야 합니다. 즉, 최초 릴리즈시의 동작성을 최대한 보장하기 위해서 안드로이드 플랫폼에게 정보를 알려주는 역할을 담당하는 필드입니다.
앱을 유지보수 하는 입장에 대해서 이야기를 해봅시다. 신규 기능을 추가하지 않는 이상 minSdkVersion이 높아질 가능성은 없습니다. 하지만 버그 수정이나 로직 수정 등의 이유로 앱을 버전업 할 때, targetSdkVersion은 항상 업데이트 하는 게 좋습니다.
4. maxSdkVersion
안드로이드 앱이 실행될 수 있는 최대한의 API Level을 의미합니다. 지금까지의 지식을 바탕으로 앱은 [minSdkVersion ~ maxSdkVersion] 사이의 디바이스에 인스톨 및 실행이 가능하다는 거죠. 조금 더 알아봅시다.
아마도 API Level이 높아지면서 기존의 API들이 사라지거나 수정되는 것을 감안한 필드였을 것으로 생각됩니다. 하지만 이 필드는 아래의 문제점 및 제약사항을 가지고 있습니다.
향후 API가 어떻게 바뀔지 예측할 수 없으므로 앱을 개발하는 시점에서는 maxSdkVersion을 알 수 없습니다.
‘하위 호환성’은 유지하지 않는다는 주장과 동일합니다.
플랫폼이 안정화가 되면 하위 호환성을 유지하는 형태로 발전합니다. 따라서 이 필드는 일시적으로 사용될 가능성이 높습니다.
개발자 문서에서도 더이상 이 필드를 사용하지 말라고 합니다.
기존의 안드로이드 버전들에서는 (1.5, 1.6, 2.0, 2.0.1) 이 값을 앱 인스톨시에 사용했다고 합니다. 만약 플랫폼의 API Level이 maxSdkVersion 보다 높을 경우 인스톨이 거부되었습니다. 만약 소프트웨어 업데이트로 플랫폼의 버전을 높일 경우에도 이미 인스톨되어 있던 앱들을 뒤져서 maxSdkVersion이 낮은 앱을 제거 했었습니다. (생각해보면 인스톨 된 앱을 지워버리는 정책은 어이없는 것 같네요. 소프트웨어 업데이트를 했더니 잘 쓰고 있던 SNS가 지워진 상황이라니!)
하지만 최신의 안드로이드 버전에서는 (2.0.1 이후) 더이상 maxSdkVersion을 체크하지 않는다고 합니다. 심지어 maxSdkVersion이 설정되어 있으면 컴파일도 실패한다고 하네요. 하지만 이미 포함되어서 앱스토어에 올라가 있는 앱의 경우에는 구글 플레이 앱스토어에서 필터링에 사용된다고 합니다.
Unity Application.persistentDataPath의 경로는 Android의 버전과 Write Access에 따라 달라짐
Android에서 Application.persistentDataPath가 돌려주는 값이 Write Access에 따라 달라지기에 조사해봤습니다. Unity 5.3.6에서 돌려주는 경로에 사양변경이 있었으므로, Unity 5.3.6 이전 버전에서 작성한 애플리케이션을 Unity 5.3.6 이후로 버전업할 때 주의를 기울이지 않으면 데이터가 사라질 위험이 있습니다. 갖고 있는 Android기기와 Unity 4.7.2f1, 5.3.6f1에서 검증했습니다.
외부 저장소라고 불리는 영역이지만 SD 카드가 아니라 빼낼 수 없게 내장된 저장소를 참조하는 기기도 있음. Android 4.4 이후에는 권한을 요구하지 않고 외부 저장소에 읽고 쓰기가 가능.
Windows PC를 예로 들면 오래된 Android 기기 C드라이브: 내장 드라이브 (Android 내부 저장소) D드라이브: USB로 접속된 외장 하드디스크 (Android 외부 저장소) 최신 Android 기기 C드라이브: 내장 드라이브 파티션 0 (Android 내부 저장소) D드라이브: 내장 드라이브 파티션 1 (Android 외부 저장소) E드라이브: USB로 접속된 외장 하드디스크 (Android 외부 저장소)
WRITE_EXTERNAL_STORAGE를 추가한 경우, Android 6 이후에는 Permissions Storage의 물음에서 ALLOW를 선택할 때까지는 기동할 때마다 물어봄. ALLOW를 선택한 후에도 Application의 설정에서 Permissions Storage 허가의 변경은 가능. 4.1.2 (200SH): Write Access: External (SDCard) 만이 SD 카드의 경로를 돌려줌. SD 카드를 뽑았을 경우에는 Internal Only와 같은 경로를 돌려줌. 그 외에는 내부 저장소를 참조. Unity 4.7.2 Android 6.0.1는 Permissions Storage의 허가 유무로 돌려주는 경로가 달라짐. Unity 5.3.6 Android 4.4 이후는 Permissions Storage의 허가 유무와 상관없이 돌려주는 값이 같음.
Unity 4.7.2에서 작성한 애플리케이션에서 Application.persistentDataPath에 저장한 데이터를 Unity 5.3.6에서 작성한 애플리케이션에서 덮어씌웠을 경우 읽어들일 수 있을까?
UnauthorizedAccessException: Access to the path "/storage/sdcard0/Android/data/{package name}/files/{file name}" is denied. external-storage를 internal-storage에 복사하려고 해서 실패?
UnauthorizedAccessException: Access to the path "/storage/sdcard0/Android/data/{package name}/files/{file name}" is denied. external-storage를 internal-storage에 복사하려고 해서 실패?
Internal Only, External (Permissions Storage: DENY)일 때에는 internal-storage를 external-storage에 복사한 후에 읽어들이도록 되어 있음. external-storage로 변경해도 internal-storage는 복사 시점 그대로 변경은 되지 않음.
정리
Internal Only -> External (SDCard)로의 변경은 데이터를 이어받지 못하는 경우가 있음. 그 경우, 수동으로 /data/data/{package name}/files/ 를 복사하는 등의 대응이 필요. External (SDCard) -> Internal Only로의 변경은 오래된 Android 기기기에서 정상으로 처리되지 않을 가능성이 있음. Android 6 이후에는 Unity 4.7.2의 Write Access가 어느쪽이든 Unity 5.3.6의 Write Access: Internal Only로 해두면 데이터를 이어받아 읽고 쓰기가 가능.
2020-02-18 01:09:43.567 4919-11220/? E/SignInAuthenticator: **** **** APP NOT CORRECTLY CONFIGURED TO USE GOOGLE PLAY GAME SERVICES **** This is usually caused by one of these reasons: **** (1) Your package name and certificate fingerprint do not match **** the client ID you registered in Developer Console. **** (2) Your App ID was incorrectly entered. **** (3) Your game settings have not been published and you are **** trying to log in with an account that is not listed as **** a test account. **** **** To help you debug, here is the information about this app **** Package name : 모자이크 **** Cert SHA1 fingerprint: 모자이크 **** App ID from manifest : 모자이크 **** **** Check that the above information matches your setup in **** Developer Console. Also, check that you're logging in with the **** right account (it should be listed in the Testers section if **** your project is not yet published). **** **** For more information, refer to the troubleshooting guide: **** http://developers.google.com/games/services/android/troubleshooting ****
- AAB는 유저 다운로드 기준 150mb가 최대이기 때문에 AAB는 165mb 정도가 최대라고 보면 된다고 함.
(유저가 다운 받을 때 15mb정도 줄어든다고 함)
- OBB는 제한을 걸었다가 다시 풀어서 글 쓰는 현재 기준으로는 기존(APK 100mb + OBB)처럼 사용가능 하다고 함.
Android App Bundle 빌드하기
먼저Unity 2018.3 베타에서 프로젝트를 엽니다. APK 대신에 Android App Bundle을 빌드하려면Build Settings창(메뉴:File>Build Settings)을 열고Build App Bundle체크박스를 선택합니다(Build System은 기본 옵션인Gradle로유지).
Build를 선택하면 Google Play에 직접 퍼블리시할 수 있는 AAB 파일을 생성합니다.
Build and Run을 선택하면 AAB 파일을 생성한 다음 연결된 기기별 임시 APK 파일을 생성하여 기기에 설치하고 애플리케이션을 실행합니다.
Build를 선택하고 앱을 기기에 수동으로 설치하려면 Google에서 제공하는bundletool utility(Unity 설치 위치의 Editor/Data/PlaybackEngines/AndroidPlayer/Tools 디렉토리에 있음)를 사용할 수 있습니다. bundletool 플래그와 옵션에 대한 자세한 정보는여기에서 확인해 보세요.
참고로, 생성된 앱 번들에는 모든 지원 대상의 라이브러리가 포함되어야 하므로 앱 번들을 빌드할 때는Split APKs by target architecture(Edit>Setting>Player)옵션이 비활성화되어 있습니다.
APK 파일에는DEX(Dalvik Executable)파일 형식의 실행 가능한 바이트코드 파일이 포함됩니다.
단일 DEX 파일 내에서 참조할 수 있는 메서드의 총 개수를 65,636으로 제한하며 이 DEX 파일에는 프레임워크 메서드, 라이브러리 메서드, 본인 앱에서 정의한 메서드가 모두 포함됩니다. 즉 앱 내의 모든 메서드가 65,536개를 넘어서 이러한 에러가 발생한 것입니다. 64 * 1024의 값과 동일하며 이 제한을 64K 참조 제한이라고 합니다.
안드로이드 L (롤리팝, 5.0 API 21 ) 미만의 플랫폼 버전에서는 앱 코드 실행을 위해 Dalvik 런타임을 사용합니다. APK당 하나의 classes.dex 바이트코드 파일로 앱을 제한합니다. 이러한 제한을 해결하기 위해multidex지원 라이브러리를 사용할 수 있습니다.
안드로이드 L (롤리팝 5.0 API 21) 이상에서는 Dalvik이 아닌 ART (Android Runtime)이라는 런타임을 사용합니다. 이 런타임은 APK 파일로부터 여러 개의 DEX 파일을 로드하는 것을 지원합니다. ART는 앱 설치 시에 사전 컴파일 수행하여classesN.dex파일들을 스캔하고, 안드로이드 기기가 실행할 수 있도록.oat파일로 컴파일 합니다.
그래서 minSdkVersion21이상이라면multidex지원 라이브러리가 필요없습니다. build.gradle에 간단하게 multiDexEnabled true만 추가하면 됩니다.
multidex 속도가 느린 이유?
각 DEX 파일들을 빌드할 때 빌드 툴(buildToolsVersion)은 주 DEX 파일에 어떤 클래스들을 포함할지 고르는 아주 복잡한 의사결정을 수행하게 됩니다. 이러한 과정이 없다면 앱 실행에 필요한 클래스들이 주 DEX에 포함되어있지않아 Crash가 나기때문입니다. 그래서 이러한 과정을 거치게되는데 시간이 상당히 오래걸립니다.
네이티브 코드가 주 dex에 포함이 안되는 경우?
네이티브 코드를 사용하는 라이브러리를 포함한다고 생각해 봅시다. 그 라이브러리에서 네이티브(JNI) 코드를 사용하게되면 의사결정 과정에서 라이브러리가 구동되기위해 필요한 클래스들이 주 DEX 파일에 포함되지 않을 수 있습니다. 그럴 경우에는 multiDexKeepFile,multiDexKeepProguard를 사용해서 주 DEX에 포함시키도록 해야합니다.
빌드 최적화
위에도 써놨듯이multidex는 복잡한 의사결정을 수행하면서 빌드 시간이 상당히 커질 수 밖에 없습니다. 빌드 시간을 줄이기 위해서 빌드 사이에 multidex를 재사용하는pre-dexing을 사용할 수도 있습니다. 하지만Android 5.0 롤리팝이상인ART환경에서만 가능합니다.
Android Studio 2.3이상인 경우는IDE에서 자동으로pre-dexing을 사용하기 때문에 별도로 작성할 것은 없습니다.android studio와gradle plugin은 최신버전으로 업데이트하면 빌드 속도를 최적화하는 기능들이 추가적으로 들어있기 때문에 항상 최신버전으로 유지하는 것이 좋습니다.
multidex의 알려진 문제들.
- 단말기에 DEX 파일들을 설치할 때, 두번째 DEX 파일이 클 경우에ANR(Android Not Responding)이 발생할 수 있습니다. 이를 막기 위해서ProGuard에서 코드축소(Code Shrinking)을 해야 합니다.
- Android 5.0 롤리팝 미만에서 실행할 경우 linearalloc limit (issue 78035)이슈를 완전히 막을 수 없습니다.
READ_PHONE_STATE Permission isn’t in my Manifest or Plugins!
Unity automatically adds theREAD_PHONE_STATEpermission into builds when either:
Your scriptscontain code which require the permission.
Thetarget SDK version isn’t set(or set below 4)which causes the manifest merger to assume the SDK version is lower than 4 and the system will implicitly grant the READ_PHONE_STATE permission to the app.(in later versions of Unity 5 and Unity 2017 the target SDK version is now set in the editor making managing it much easier)
You have aplugin in your project which has its own manifest filerequesting the permission(the manifest can be contained within your jar or aar files, they’re not always simply in your project) –Note that if a plugin is requesting a permission then it’s probably required and may cause issues if removed, check the plugin documentation if you’re unsure!
Code which causes Unity to automatically add the permission
In the case of your scripts containing code which need the permission. Unity automatically adds the permission when using functions which require it.
SomeSystemInfoproperties such asSystemInfo.deviceUniqueIdentifierrequire the READ_PHONE_STATE permission so referencing it in a script will force Unity to add it.
As an alternative to SystemInfo.deviceUniqueIdentifier when needing a unique device identifier and it’s not important to keep it the same between wiping save data. Consider generating a unique value based onSystem.DateTime.Now.Ticksand storing in the playerprefs instead.
Why am I asked to allow/deny the permission at app launch?
Starting with Android 6.0 the user was given more control over permissions apps were allowed to use at runtime; rather than a blanket list of confusing permission being included with the app at installation. Permissions which are prompted for the user to allow are classified asdangerous permissionsas they can allow the app to access sensitive data.(in this case READ_PHONE_STATE can allow reading of the phone number, call statuses or listphone accountsregistered on the device)
If you want to manually control when the permissions are requested at runtime rather than all dangerous permissions just being prompted at startup then you can add: <meta-data android:name=”unityplayer.SkipPermissionsDialog” android:value=”true” /> Between the <application> tags of your manifest file.
With the permission dialog skipped all dangerous permissions will remain defaulted as denied! But this allows you to request permissions manually when you need them, rather than all at once at app launch.(However note that you’ll either need to write a Java plugin yourself to control this or find an already built plugin from the store such asAndroid Buddywhich fits this exact purpose!)
Learn more about the READ_PHONE_STATE android permission!