Android Build Process (1)

2025. 1. 4. 18:33·mobile/android

Task :app:preBuild UP-TO-DATE
Task :app:preDebugBuild UP-TO-DATE
Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
Task :app:generateDebugResValues
Task :app:mapDebugSourceSetPaths
Task :app:checkDebugAarMetadata
Task :app:generateDebugResources
Task :app:packageDebugResources
Task :app:createDebugCompatibleScreenManifests
Task :app:extractDeepLinksDebug
Task :app:parseDebugLocalResources
Task :app:processDebugMainManifest
Task :app:processDebugManifest
Task :app:javaPreCompileDebug
Task :app:mergeDebugShaders
Task :app:compileDebugShaders NO-SOURCE
Task :app:generateDebugAssets UP-TO-DATE
Task :app:mergeDebugAssets
Task :app:compressDebugAssets
Task :app:desugarDebugFileDependencies
Task :app:mergeDebugJniLibFolders
Task :app:mergeDebugNativeLibs NO-SOURCE
Task :app:stripDebugDebugSymbols NO-SOURCE
Task :app:validateSigningDebug
Task :app:writeDebugAppMetadata
Task :app:writeDebugSigningConfigVersions
Task :app:preDebugUnitTestBuild UP-TO-DATE
Task :app:processDebugManifestForPackage
Task :app:preDebugAndroidTestBuild SKIPPED
Task :app:javaPreCompileDebugUnitTest
Task :app:checkDebugDuplicateClasses
Task :app:checkDebugAndroidTestAarMetadata
Task :app:generateDebugAndroidTestResValues
Task :app:mergeLibDexDebug
Task :app:mapDebugAndroidTestSourceSetPaths
Task :app:generateDebugAndroidTestResources
Task :app:mergeDebugResources
Task :app:mergeDebugAndroidTestResources
Task :app:processDebugAndroidTestManifest
Task :app:javaPreCompileDebugAndroidTest
Task :app:mergeDebugAndroidTestShaders
Task :app:compileDebugAndroidTestShaders NO-SOURCE
Task :app:generateDebugAndroidTestAssets UP-TO-DATE
Task :app:mergeDebugAndroidTestAssets
Task :app:compressDebugAndroidTestAssets
Task :app:desugarDebugAndroidTestFileDependencies
Task :app:checkDebugAndroidTestDuplicateClasses
Task :app:mergeDebugAndroidTestJniLibFolders
Task :app:mergeDebugAndroidTestNativeLibs NO-SOURCE
Task :app:stripDebugAndroidTestDebugSymbols NO-SOURCE
Task :app:mergeLibDexDebugAndroidTest
Task :app:validateSigningDebugAndroidTest
Task :app:writeDebugAndroidTestSigningConfigVersions
Task :app:processDebugAndroidTestResources
Task :app:mergeExtDexDebugAndroidTest
Task :app:processDebugResources
Task :app:compileDebugKotlin
Task :app:compileDebugJavaWithJavac NO-SOURCE
Task :app:processDebugJavaRes
Task :app:mergeExtDexDebug
Task :app:dexBuilderDebug
Task :app:bundleDebugClassesToCompileJar
Task :app:mergeDebugJavaResource
Task :app:mergeProjectDexDebug
Task :app:packageDebug
Task :app:createDebugApkListingFileRedirect
Task :app:assembleDebug
Task :app:compileDebugUnitTestKotlin
Task :app:compileDebugUnitTestJavaWithJavac NO-SOURCE
Task :app:assembleDebugUnitTest
Task :app:compileDebugAndroidTestKotlin
Task :app:compileDebugAndroidTestJavaWithJavac NO-SOURCE
Task :app:processDebugAndroidTestJavaRes
Task :app:dexBuilderDebugAndroidTest
Task :app:mergeProjectDexDebugAndroidTest
Task :app:mergeDebugAndroidTestJavaResource
Task :app:packageDebugAndroidTest
Task :app:createDebugAndroidTestApkListingFileRedirect
Task :app:assembleDebugAndroidTest

 

안드로이드 새 프로젝트를 생성하여 Build하면 위와 같이 Gradle Task들이 실행되는 걸 확인할 수 있다. 위에서 실행한 작업들을 기준으로 Build 과정을 요약하면 아래와 같다. 위에서도 확인할 수 있듯이 각각의 Task는 필요한 종속성에 따라 실행 순서가 달라지기 때문에 무조건 아래의 순서로 Build 과정이 수행되는 것은 아니다.

  • Pre-Build 단계: 빌드 설정 준비 및 종속성 확인.
  • 리소스 처리: 리소스 파일 병합 및 패키징.
  • 소스 코드 컴파일: Kotlin/Java 파일을 바이트 코드로 변환.
  • DEX 변환: 바이트 코드를 DEX 형식으로 변환 후 병합.
  • APK 생성: 모든 파일을 통합해 APK 파일 생성.

 

1. Pre-Build 과정

  • 캐시 초기화, 파일 경로 생성, 임시 디렉토리 준비 등 빌드 환경을 준비.
  • Gradle 플러그인의 설정을 읽어들여 빌드에 필요한 종속성을 확인하고, 작업 그래프(Task Graph)를 생성
  • 빌드 타입(Debug, Release)에 따라 필요한 설정을 적용
    • 디버그 빌드: 디버그 키스토어를 설정.
    • 릴리스 빌드: 릴리스 키스토어 및 프로가드(ProGuard/R8) 설정 확인.
  • 앱에서 사용하는 AAR 종속성의 메타데이터를 확인, 버전이나 호환성 문제가 있는지 검증 등등

 

2. 리소스 컴파일

출처:  https://shishirthedev.medium.com/build-process-in-android-8c955d6467b8

 

위 이미지는 컴파일 부터 빌드까지의 과정을 도식화한 것인데, 그 중 빨간색 네모 박스에 해당하는 부분이 리소스 컴파일 단계이다.

 

  • AAPT2를 통해 AndroidMainfest.xml 포함 모든 리소스 파일들을 각각 바이너리 형식으로 컴파일하여 .flat 파일로 변환
// R.java 파일 예시 
public final class R { 
	public static final class drawable { 
    	public static final int ic_launcher = 0x7f080000; 
    } 
    
    public static final class layout { 
    	public static final int activity_main = 0x7f030000; 
    } 
}
  • 리소스 파일을 컴파일 한 것과는 별개로, AAPT2를 통해 /res 폴더 하위 모든 리소스 파일에 참조할 수 있는 고유한 ID를 부여하고 병합하여 R.java 파일을 생성.
    • 우리가 소스 상에서 리소스를 참조할 때 R.layout.activity_main 과 같이 코드를 작성하는데, 이 때 R 클래스를 통해 리소스를 가져오는 것도 이때문이다.
  • 생성한 .flat 파일들을 병합하여 모든 리소스 파일의 정보를 담은 리소스 테이블 파일(resoureces.arsc) 생성. 추후 APK 파일을 생성할 때 apkBuilder를 통해 APK에 포함됨.
    • 런타임에서 R 클래스로 특정 리소스에 접근하면 resourcees.arsc 파일에서 해당 리소스 파일을 찾아 메모리에 올림.
💡 AAPT(Android Asset Packaging Tool)란?
- Android SDK 내 포함된 안드로이드 빌드 툴 중 하나로, 리소스 파일을 컴파일하고 패키징할 때 사용하는 도구.
- AAPT를 통해 리소스 파일을 바이너리 형식으로 파싱하고 컴파일 함. 패키징 단계를 ‘컴파일’과 ‘링크’ 단계로 나눈 것이 특징.

 

3. 소스코드 컴파일

출처:  https://androidpangyo.tistory.com/217

 

  • Java(.java)/Kotlin(.kt) 소스 파일이 컴파일러(javac, kotlinc)를 통해 소스코드가 중간코드인 바이트 코드 (.class)로 변환 됨.
    • ex) MainActivity.java → 컴파일러(javac) → MainActivity.class
    • ex) HomeFragment.kt → 컴파일러(kotlinc) → HomeFragment.class
💡 컴파일? 컴파일러?
- 컴파일이란 인간이 이해할 수 있는 언어로 작성한 내용을 기계가 이해할 수 있는 언어로 바꿔주는 과정.
- 컴파일러란 인간이 이해할 수 있는 고급 프로그래밍 언어(Java, Kotlin)를 기계가 이해할 수 있는
   저급 프로그래밍 언어(어셈블리 언어, 기계어)로 바꾸는 도구.
💡 바이트 코드는 기계어다?
- 바이트 코드는 기계어가 아닌, 고급 언어와 기계어 사이에 있는 중간 코드로, JVM에서 실행되는 코드이다.

 

 

3-1) 바이트 코드로 변환하는 이유

바이트 코드는 운영체제나 하드웨어(CPU)에 상관없이 JVM 위에서 실행할 수 있기 때문. 따라서 운영체제 별로 소스코드를 빌드할 필요 없이 한번 바이트 코드로 컴파일 하면 여러 운영체제에서 빌드가 가능하다. (흔히 WORA, “Write Once, Run Anywhere”라고 표현함.)

 

3-2) Java/Kotlin 컴파일러의 원리

컴파일러의 동작 원리와 과정을 이해하는 글이 아니기 때문에 간략하게만 짚고 넘어가겠다.

  • 소스코드 분석
    • 컴파일러는 소스 코드(.java/.kt)를 읽고 구문 트리(Syntax Tree)를 생성.
    • 구문 트리는 소스 코드의 구조를 나타내며, 각 문법 요소(클래스, 메서드, 변수 등)를 노드로 표현.
  • 타입 체크 (Type Checking)
    • 컴파일러는 구문 트리에서 각 요소의 타입을 검사하여 정적 타입 검증.
    • ex) Kotlin의 널 안전성 검사(?, !!) 확인 등
  • 바이트 코드 생성
    • 구문 트리를 기반으로, 컴파일러는 중간 코드(IR)를 생성.
      • IR(Intermediate Representation): 소스 코드와 바이트 코드 사이의 추상화된 코드 표현.
    • 컴파일러는 IR을 변환하여 JVM이 이해할 수 있는 바이트 코드(.class 파일)를 생성.

 

3-3) 클래스 파일 구조

먼저 구조를 보기 전에 예시를 들기 위해 기본적인 MainActivity 소스를 클래스 파일로 컴파일 해보겠다.

package com.briel.huum.ui.activity;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView textView = new TextView(this);
        textView.setText("Hello, Android!");
        textView.setTextSize(20);

        setContentView(textView);
    }
}

평범한 MainActivity 이다.

 

javac -classpath ~/Library/Android/sdk/platforms/android-33/android.jar -d out MainActivity.java

Java 소스 파일을 수동으로 컴파일 하려면 위와 같이 javac 명령어를 사용하면 된다. 몇가지 옵션을 사용했는데,

  • -classpath: 클래스 패스를 지정하는 옵션으로, 컴파일 시 참고할 클래스 파일이나 jar 경로를 입력한다. 순수하게 java로만 작성한 코드라면 사용할 필요가 없으나, 위 소스에선 “android.app.Activity”와 같이 Android 플랫폼 API를 사용하기 때문에 해당 API를 가지고 있는 Android.jar를 지정해줘야 한다.
  • -d out: 컴파일 된 클래스 파일의 저장 경로와 이름을 지정하는 옵션이다.

 

Hex Friend로 열어 본 MainActivity.class 파일

명령어를 실행하면 지정한 디렉토리에 MainActivity.class 파일이 생성된걸 확인할 수 있다. 생성 된 클래스 파일은 그냥 열 순 없고, Hex Editor 툴을 이용해서 열어볼 수 있다. 본인은 Hex Friend라는 툴을 이용했다. Hex Friend로 클래스 파일을 열어보면 위와 같이 16진수가 무질서하게 적혀있는거 같지만 사실 아래의 구조로 코드가 이루어져있다.

 

출처:  https://blog.lse.epita.fr/2014/04/28/0xcafebabe-java-class-file-format-an-overview.html

 

  • 마법 숫자 (Magic Number)
    • .class 파일의 시작 부분에 위치하며, 파일이 JVM 바이트 코드임을 나타냄
    • 예시 값: 0xCAFEBABE.
  • 버전 정보
    • .class 파일이 지원하는 JVM 마이너/메이저 버전을 나타냄
    • 예시 값: 마이너 0000(0), 메이저 003D(61→ Java 17)
  • 상수 풀 (Constant Pool)
    • 클래스에서 사용하는 문자열, 메서드 이름, 필드 이름, 리터럴 등의 정보를 저장
    • 인덱스 기반으로 참조되어 클래스 및 메서드의 동작을 정의
  • 클래스 정보
    • 클래스 이름, 부모 클래스 이름, 인터페이스 정보를 포함
  • 필드 정보
    • 클래스가 포함하는 필드(변수)의 이름, 타입, 접근 제어자(public, private 등)를 정의
  • 메서드 정보
    • 메서드의 이름, 반환 타입, 매개변수 타입, 바이트 코드 명령어를 포함
    • 메서드 바이트 코드에는 JVM 명령어(예: aload, iload, invokevirtual)가 포함
  • 부가 정보 (Additional Information)
    • 디버깅 정보, 주석, 지역 변수 테이블 등을 포함
💡 바이트 코드는 바이너리 코드인가요?
- 바이트 코드는 바이너리 코드가 아니다. 바이트 코드는 가상머신(JVM, ART)이 이해하는 중간 코드이고 바이너리 코드는 기계가 이해하는 기계어이다.

 

 

 

 

참고


AAPT2

Android Build Process Step by Step

JVM, DVM, ART 이해하기

안드로이드 빌드 과정(Android APK build process)를 이해하자!

안드로이드, 어디까지 아세요 [1] - Build process

자바 컴파일 과정: 소스코드부터 실행까지

저작자표시 비영리 변경금지 (새창열림)

'mobile > android' 카테고리의 다른 글

Android 정렬 (Feat. zipalign)  (0) 2025.03.23
APK 파일 구조  (3) 2025.01.07
상용 앱 APK 추출해보기 (Feat. ADB)  (4) 2025.01.04
'mobile/android' 카테고리의 다른 글
  • Android 정렬 (Feat. zipalign)
  • APK 파일 구조
  • 상용 앱 APK 추출해보기 (Feat. ADB)
9a6riel
9a6riel
  • 9a6riel
    잡다한 공간
    9a6riel
  • 전체
    오늘
    어제
    • 분류 전체보기
      • algorithm
      • cs
        • network
        • os
      • mobile
        • android
        • test
      • language
        • kotlin
        • java
      • security
      • troubleshooting
      • etc.
        • ai
        • article review
        • project
  • 블로그 메뉴

    • 링크

    • 공지사항

      • read.me
    • 인기 글

    • 태그

      JVM
      codegpt
      foreach
      ChatGPT
      build
      zipalign
      Val
      AAB
      apk
      ADB
      llm
      Ollama
      Java
      align
      android
      AI
      var
      Kotlin
      CONTINUE
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.1
    9a6riel
    Android Build Process (1)
    상단으로

    티스토리툴바