본문 바로가기

TOOL/Common

Mobile Build 과정(Dalvik / ART / dex file / OAT file / NDK / JRE / JDK / JNI / Clang / LLVM / Library)

Dalvik


롤리팝 이전부터 사용되었던 VM(virtual machine)


ART(Android Run Time)


롤리팝버전부터 사용되는 VM(virtual machine)


dex file


virtual machine에서 bite code로 사용하는 file


OAT(Optimized Ahead of Time) File


Application이 처음 설치될때 생성되는 file, dex file을 dex2opt program을 통해 odex(Optimized dex)라는 최적화된 dex file로 바꿔서 사용한다.

이와 비슷하게 dex2oat program은 dex file을 바아 oat file을 만든다.

dex2oat program은 android OS 내부에 설치되어 있으며 처음 apk file을 설치될때 실행된다.


oat file은 'ELF(Excutable and Linkable Format)' file이다. 즉, CPU에 의해 실행 가능하고 따라서

VM위에서 실행되는것에 비해 훨신 빠른 속도를 보여주고 훌륭한 memory 할당과 GC 기능을 가지고 있다.


NDK(Native Development Kit)


C++ Code가 Library가 사용될 CPU에서 실행 가능한 assembly어로 compile하는것

NDK로 build하면 native code가 원하는 CPU Architecture의 Instruction Set으로 compile되는것을 믿고 사용할 수 있으며

Android측의 여러 system들을 좀더 편리하게 사용할 수있게 하기위한 library들이 포함된다.


JRE(Java Runtime Environment)


JVM을 포함하여 program실행과정에 필요한 여러 library들을 포함하고 있다.



JDK(Java Development Kit)


JRE와 동시에 Java Language Compile을 위한 compiler 및 여러 tool들도 포함한 package



JNI(Java Native Interface)


C / C++로 작성된 native code에서 JVM을 생성, JVM에서 실행된 java program에서 Shared Library loading을 할수있게 해준다.

(Java에서 할수있는 일을 거의 전부 다 할수있게 해준다.)


NDK와 JNI를 이용하여 native까지 사용하는 이유는 속도때문이다.

Native code가 직접 CPU에 의해 실행되기 때문에 실행속도가 더 빠르다.

Memory 관리 차원에서도 Java의 memroy관리보다 native code에서 'malloc', 'free', 'new', 'delete'와 같은 명령어를

사용하는게 더 효과적이다.

(JVM이 아닌 OS를 통해 직접 관리하기 때문이다.)


Shared Library의 native code에서는 hardware 가속을 받아 열심히 rendering하여 최종 결과물인 bitmap을 만든다.

만들어진 bitmap을 application의 view에 입히면 우리가 보는 game화면이 완성된다.


ANT / Gradle (Build를 돕는 build tool(build 배포 도구))


Android Studio(IDE)와 build system이 서로 독립적이기 때문에 사용한다.

즉 android studio는 code 편집만 담당하고 build는 Gradle을 통해 모두 수행된다.

'gradlew assembleDebug'를 실행하면 gradle을 이용하여 미리 명시된대로 작업을 한 뒤 debug용 apk  file이 만들어진다.

(Java source compling, dex file 생성 aapt로 apk생성, zipalign으로 aligning, apksigner로 sining등의 작업을 수행한다.)



Unity는 Internal build system을 사용하는데 특정순서대로 android SDK utility를 호출하여 APK를 생산한다.


1. Unity Asset 준비 및 build

2. Script Compiling

3. Plugin 처리

4. Split Application Binary option이 선택된 경우 resource를 apk와 OBB(확장 파일)에 속하는 부분으로 나누기

5. AAPT(Android Asset Packing Tool) Utility를 사용하여 Android resource build

6. Android Manifest를 생성하고 library manifest를 이에 병합

7. Java code를 Dalvik Executable format(DEX)로 compiling

8. IL2CPP Scripting Backend가 선택된 경우 IL2CPP Libraray build

9. APK 및 OBB Package build 및 최적화



AndroidManifest.xml


System에게 application의 몇가지 꼭 필요한 정보를 제공하기 위해 필요한 file

Application에서 사용하는 기능에 대한 권한, build하는데 사용된 SDK version, 

Code에 존재하는 APP Components(Activity, Service, Broadcast Receiver, Content Provider등)



1. Android OS에서 application 실행

2. VM Process 키고 bite code들(dex, oat file 등)도 memory에 올린다.

3. Java에서 MainClass의 Main method를 entry 함수로 지정, 어디서부터 code를 실행할지 VM에게 알려줘야한다.

   (이런 알려줘야할 정보들을 AndroidManifest.xml file에 기재해놓음, 'activity' element내의 'intent-filter'에서 그 정보를 제공한다.)


ex) 처음 시작될 Activity에 대한 전형적인 선언

1. 나의 package에 MainActivity라는 class가 존재한다.

2. 이 MainActivity는 Main Activitiy니까 application이 처음 시작될 때 MainActivity의 instance를 만들어서 실행해라.

3. Launcher를 통해 application을 노출시킬때 이 Activity를 Entry Point로 생각해라.


이런식으로 App Component인 Activity, Service, Broadcast Receiver, Content Provider에 대한 정보들을 명시적으로 제공하는 것이 

안드로이드의 구조다.

특히 Activity에 대한 부분은 Engine들로부터 만들어지는 Intermediate source들을 보기위해 꼭 필요한 정보이다.



Android에서 APK Build 과정


1. Java Code 작성

2. Java Code를 compile하여 class file 생성

3. class file을 dex file로 변환

4. dex file과 사용중인 resource들을 apkbuilder로 apk file 생성

5. zipalign으로 apk file binary aligning

6. apksigner로 apk file signing

7. adb 혹은 store를 통헤 apk download

8. apk의 sign check

9. application이 실행될 때 VM을 키고 자신의 code 실행





Unreal Build과정


Unreal에서는 UAT(Unreal Automation Tool)이라는 것으로 이 과정들이 모두 숨겨져있다.

UAT를 이용하여 resource cooking, binary field, 기기배포 등을 모두 할수있도록 만들었다.

unreal editor에서 packaging, preview launching할때도 내부적으로 UAT를 통해 build하고 기기에 배포한다.


Project directory의 Intermediate file들을 보면 전형적인 android project의 directory구조를 가지고 있으며

UAT 명령어 실행 Log들을 보면 도중에 ANT명령어 실행과 log들을 확인할 수 있다.

Android Project의 AndroidManifest.xml file을 보면 Main Activity로 'GameActivity' 라는 Unreal Engine측의 class를 사용하는것을 확인할 수 있다.


당연히 project에 생성한 C++ class들에서 JNI를 사용할 수 있기 때문에 직접 Java의 code들을 실행할 수 있다.

이를 이용하여 Tapjoy와 같은 native 기능을 사용하는 SDK를 사용할 수 있다.

Project의 C++ code들은 build를 통해 SharedLibrary 'libUE4.so'로 만들어진다.

그리고 'GameActivity'에서는 'System.loadLibrary'로 'libUE4.so'를 load함으로 native code들이 실행 가능해진다.


Third party Java class들을 사용하기 위해


1. 사용하려는 Java class를 compile하여 jar file로 만든다.

2. jar file이 함께 packaging되도록 해야한다.

2-1. jar file을 engine directory의 android template project의 'lib' directory에 넣어버리는 방법

     (이 방식의 문제점은 해당 engine을 사용하는 모든 project에 영향을 끼치는 것이다.)

2-2. 4.13 version까지 기준 Plugin Language기능을 이용하는 방법

     (Plugin Language에 명세로 어떤 file들을 Intermediate project에 copy하거나, 추가권한획득이나 App Component 추가를 위한

      AndroidManifest.xml수정, GameActivity의 몇몇 life cycle method에 code추가 등이 가능하나 제한적이다.)





Unity Build 과정


Unity에서도 editor를 통해 build하는것이 일반적이다.

Android build를 하면 project의 'Temp/StagingArea' directory에 Android build 관련 file들이 생성된다.

이를 똑같이 ANT나 Gradle로 build하면 APK를 만들게 되는것이다.


'AndroidManifest.xml'을 확인하면 'UnityPlayerActivity'라는 Activity로 시작하게 되어있다.

'libs/armeabi-v7a' directory에 'libmain.so', 'libmono.so', 'libunity.so' 라는 file이 생성되어 있는데

'UnityPlayerActivity'는 'UnityPlayer'를 실행하도록 하고, 'UnityPlayer'는 'libmain.so'를 load하게 되어있다.

(아마 'libmono.so', 'libunity.so'들도 어딘가에서 load해서 사용하고 있을 것 이다.)


Unity에서는 C#으로 coding하기 때문에 C / C++ 로 된 JNI를 직접 사용하는 경우는 드물다.

'AndroidJavaClass', 'AndroidJavaObject'등 Class로 Wrapping된 것을 사용하는데 결국 이는 내부적으로 JNI를 사용하는 것 이다.


Third party Java Class를 사용하기 위해


1. 사용하려는 Java class들을 compile하여 jar file로 만든다.

2. 'Asset/Plugins/Android' directory에 해당 jar file을 두면 build 과정에서 함께 packaging이 된다.

    (Java 측에서 바로 이용하든, JNI로 찾아와서 이용하든 편한대로 사용하면 된다.)





IOS Build 과정


1. Source file compiling - ARM Architecture의 목적 file 생성

2. 각 목적파일 linking

3. Story board를 비롯한 각종 resource들 처리

4. Application Packaging

5. Package내에서 bundle resources copy

6. Package내에서 프로비전 file 내장

7. Package내 file들 기반으로 singning


목적파일 :  컴파일러나 어셈블러가 소스 코드 파일을 컴파일 또는 어셈블해서 생성하는 파일

프로비저닝 : 사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포해 두었다가 필요 시 시스템을 즉시 사용할 수 있는 상태로 미리 준비해 두는 것을 말한다. 


IOS에서 application은 완벽하게 native application으로 실행된다.


Xcode Build directory
XCode에서 build하면 'Build' directory가 생긴다.

Intermediate Directory


중간 file들을 모아두는 directory, compile 과정에서 생겨나는 목적file, debugger를 위한 file들,

후처리 된 resource들 등이 이 directory내에 생성된다.


Products Directory


Build 최종 결과물이 위치하게 되는 directory

XCode에서 build 대상으로 선택한 기기와 Debug, Release 여부에 따라 이름이 정해진다.

IOS에 한정하여 보면 다음과 같다.


Debug-iphoneos : General iOS Devices 선택 상태에서 Debug 빌드 결과물들이 위치

Debug-iphonesimulator : Simulator 선택 상태에서 Debug 빌드 결과물들이 위치

Release-iphoneos : General iOS Devices 선택 상태에서 Release 빌드 결과물들이 위치

Release-iphonesimulator : Simulator 선택 상태에서 Release 빌드 결과물들이 위치


그렇기 때문에 

'Debug-iphonesimulator' directory에 생성된 static library file에 대한 architecture information을 보면

x86_64와 같은 Intel architectgure, 

'Debug-iphoneos' directory 에 생성된 static library file 에 대한 architecture information을 보면

arm64와 같은 ARM architecture로 나타난다.






LLVM(Low Level Virtual Machine)


Compiler 기반 구조, program을 compile time, link time, run time상황에서 program의 작성 language에 상관없이 최적화를

쉽게 구현할 수 있도록 구성되어 있다.


clang


문법분석, 의미분석등을 위한 frontend project

C, C++, Object-C, Object-C++ program language를 위한 compiler 

frontend, backend로 LLVM을 사용한다.


Instruction Set


명령어 집합, 명령어 집합구조는 micro processor가 인식해서 기능을 이해하고 실행할 수 있는 기계어 명령어



LLVM build 과정과 결과물


IOS의 application은 VM같은것이 없는 native application이다.

어찌됫건 C / C++류의 language를 compile하여 어떤 실행 가능한 program을 얻기 위해서는 compiler가 꼭 필요하다.


LLVM build 과정


1. clang으로 compile하여 bitcode를 만든다.

2. 만들어진 bitcode를 Optimizer로 최적화한다.

3. 최적화된 bitcode를 원하는 CPU architecture의 instruction set으로 변환한다.


사실 intermediate file을 만들고 그것을 이용하여 최종 instruction set을 assembly로 만든다는 점이

기존의 compiler구조와 큰 차이는 없다.



Linking


나누어진 목적파일을 link하는 작업, dynamic linking / static linking으로 나누어진다.


Static Linking


'gcc -o output file1.o file2.o'와 같이 여러 목적파일을 linking할때 linking 을 compile time에 하는것이다.

static linking을 할때 static library도 함께 linking할 수 있다.

'gcc -o output main.c -lsome.a'처럼 명령어를 실행하면 'main.c' source file과 함께 'libsome.a'라는 static library를 함께 linking하여

output이라는 실행 file을 만들겠다는 뜻이다.

('-l' option뒤에는 library의 filename에서 'lib'을 땐 이름이 온다.)

static library는 보통 '*a', '*.lib' file로 잘 알려져 있다.


Dynamic Linking(dll)


실행 file을 먼저 실행한 뒤 linking되는것이다.

dynamic linking은 실행 file에 static library가 포함되어야 하기 때문에

실행file 용량도 커지고 memory에 올려둬야할 code 영역도 너무 커진다.

dll는 여러 process가 동일한 code 영역을 공유할 수 있도록 함으로서 용량, memory를 줄여준다.


linux에서 '/user/lib' directory에 각종 system 공용 library가 있듯이

IOS에서도 해당 directory에 존재하며 apple에서는 dll을 'dylib'라는 확장자로 표시한다.

(현재는 dylib를 포함시켜 build하는 application은 배포할 수 없으며 대신 framework 확장자를 가진 package들이

기존 dll file 역활을 수행한다.)


IOS package내 file들을 보면 frameworks라는 directory 내에 SimpleFramework.framework가 추가되었으며

이를 통해 application을 실행할 때 frameworks directory 내의 framework들을 모두 dynamic linking해주는것을 알수있다.

(window에서 program을 배포할 때 사용중인 dll file을 함께 packaging하여 배포하는것과 같은 맥락이다.)


'ipa' file은 application package를 그냥 압축한것이다.



tbd(Text-Based stub libraries) file


개발자들에게 노출되는 library, 예를들어 Foundation framework같은 library들은 직접적으로는 tbd file을 가지고 있기만 한것으로 바뀐다.

즉, tbd file은 library를 설명하는 file이다.

내용물을 보면 어떤 architecture에 대해 어떤 symbol들이 존재하고, 또 어떤 dylib를 참조하고 있는지 등에 대한 내용이다.

여러 symbol들과 build된 binary의 CPU architecture, 공유 library 위치등의 정보를 제공해준다.


이 file을 참조하여 compile할 떄 symbol들이 존재하지 않으면 symbol이 존재하지 않는다는 error를 띄울 수 있고

application process를 띄운 뒤 dynamic linking을 할때는 어떤 경로에 공유 library가 있는지 찾아 load한 뒤 linking할 수 있다.





Packaging 


필요한 file들을 정해진 규칙에 맞게 directory구조로 만드는 작업이다.

실행file과 Info.plist file, 각종 image, storyboard resource등을 적절한 directory에 둔다.

필요한 경우 Frameworks directory내에 사용중인 framework들을 둔다.


Provisioning


여기에 provision file이 embedded(내장)되는데 개발자 등록을 하고 apple 개발자 console에서 만든 provision file을 

package안에 둠으로써 누가 만든 application인지 알도록 하는것이다.

간단하게 provision file을 copy하여 package 내에 locate 시키는 것 이다.


Signing


마지막으로 'codesign'으로 signing작업을 한다.

package내에 있는 framework들 까지 포함한 모든 file들에 대해 특정 key로 연산한 결과값을 listing한다.

이로서 누군가 package내에 file을 변조한 뒤 다시 배포하는 경우 값 대조를 통해 변조 여부를 알수있게 된다.






Unreal에서 IOS Build


Unreal에서의 IOS build 역시 Automation Tool 로 build과정이 감싸져 있다.

Animation Tool, Build Tool, Header Tool 등이 mono로 실행된다.

각각의 tool은 C#으로 짜여져 있어 .NET Framework기반이기 때문에 OSX에서도 mono로 실행가능하기 때문이다.

그리고 blue print project가 아닌 C++ native project는 OSX에서만 IOS packaging이 가능하다.

BluePrint project는 blueprint 관련 resource만 추가 binary로 update하고 signing만 하면 되지만

C++ native code들은 build를 위해 IOS SDK와 Xcode build toolchain이 꼭 필요하기 때문이다.


clang은 C / C++ 문법과 Objective-C문법을 섞어 code를 짜도 compile이 된다.

이 덕분에 C++로 짜여진 module은 compile할 수 있다.

홈페이지에서 engine을 받으면 IOS관련 engine source들이 이미 IOS SDK기반으로 static library로 build된 상태이기 때문에

project의 code들만 build를 한 뒤 linking하여 최종 static library를 생성한다.

만약 source 기반으로 build를 진행한 다면 engine source를 build하는것 부터 시작한다.

Engine source를 build할 때 IOS SDK기반으로 compile 되는 것 이다.


만들어진 static library는 그냥 사용해도 된다.

IOS native application에서 build된 static library의 method들은 그냥 호출하여 사용이 가능하며 해당 library는

IOS native code로 compile된 static library이기 때문이다.





Unity에서 IOS Build


Unity에서는 IOS build시 Xcode Project와 file들을 만들도록 되어있다.

IOS build를 하면 지정한 directory에 XCode project가 생긴다.

Unity Engine이 작동하기 위해 Mono를 비록한 여러가지 module들이 한 static library로 제공된다.


Unity에서 작성한 C# Code들은 C++ source로 변환되면서 원래 C#에서 호출하려 했던 함수를 C++에서 호출하도록 

Interpet되는게 Unity의 작동원리이다.










참고


https://toughrogrammer.tistory.com/240





'TOOL > Common' 카테고리의 다른 글

LLVM / clang  (0) 2019.03.06