bazel 모바일 설치

문제 신고 소스 보기

Android를 위한 빠른 반복 개발

이 페이지에서는 bazel mobile-install가 Android의 반복 개발을 훨씬 더 빠르게 만드는 방법을 설명합니다. 이 접근 방식의 이점과 기존 앱 설치 방법의 어려움에 대해 설명합니다.

요약

Android 앱의 소규모 변경사항을 매우 빠르게 설치하려면 다음 단계를 따르세요.

  1. 설치하려는 앱의 android_binary 규칙을 찾습니다.
  2. proguard_specs 속성을 삭제하여 Proguard를 사용 중지합니다.
  3. multidex 속성을 native으로 설정합니다.
  4. dex_shards 속성을 10으로 설정합니다.
  5. Dalvik이 아닌 ART를 실행하는 기기를 USB를 통해 연결하고 USB 디버깅을 사용 설정합니다.
  6. bazel mobile-install :your_target을 실행합니다. 앱 시작이 평소보다 약간 느립니다.
  7. 코드 또는 Android 리소스를 수정합니다.
  8. bazel mobile-install --incremental :your_target을 실행합니다.
  9. 오래 기다릴 필요가 없습니다.

유용한 Bazel 명령줄 옵션은 다음과 같습니다.

  • --adb는 사용할 adb 바이너리를 Bazel에 알립니다.
  • --adb_arg를 사용하여 adb의 명령줄에 인수를 추가할 수 있습니다. 워크스테이션에 여러 기기가 연결되어 있는 경우 설치할 기기를 선택할 수 있습니다.bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app에서 앱을 자동으로 시작

확실하지 않은 경우 예시를 살펴보거나 Google에 문의하세요.

소개

개발자 도구 모음의 가장 중요한 속성 중 하나는 속도입니다. 코드를 변경하여 1초 이내에 실행되는 것을 확인하는 것과 변경사항이 예상대로 작동하는지에 관한 피드백을 받기까지 몇 분 또는 몇 시간 동안 기다려야 하는 것에는 큰 차이가 있습니다.

안타깝게도 .apk 빌드를 위한 기존의 Android 도구 모음에는 수많은 모놀리식 순차적 단계가 수반되며 Android 앱을 빌드하려면 이 모든 단계를 수행해야 합니다. Google 지도와 같은 대규모 프로젝트에서는 한 줄 변경을 빌드하기 위해 5분을 기다려야 했습니다.

bazel mobile-install를 사용하면 앱 코드를 전혀 변경하지 않고도 변경 프루닝, 작업 샤딩, Android 내부 요소의 현명한 조작을 조합하여 Android 반복 개발을 훨씬 빠르게 수행할 수 있습니다.

기존 앱 설치 관련 문제

Android 앱을 빌드하려면 다음과 같은 문제가 있습니다.

  • 덱싱. 기본적으로 'dx'는 빌드에서 정확히 한 번 호출되며 이전 빌드의 작업을 재사용하는 방법은 알 수 없습니다. 즉, 메서드가 하나만 변경되었더라도 모든 메서드를 다시 덱싱합니다.

  • 기기에 데이터 업로드 중. adb는 USB 2.0 연결의 전체 대역폭을 사용하지 않으며, 대용량 앱을 업로드하는 데 시간이 오래 걸릴 수 있습니다. 리소스 또는 단일 메서드 등의 작은 부분만 변경되더라도 전체 앱이 업로드되므로 이로 인해 큰 병목 현상이 발생할 수 있습니다.

  • 네이티브 코드로 컴파일합니다. Android L에서는 앱을 Dalvik처럼 적시에 컴파일하는 대신 미리 컴파일하는 새로운 Android 런타임인 ART를 도입했습니다. 이렇게 하면 설치 시간이 길어지는 대신 앱의 속도는 훨씬 빨라집니다. 이 경우 일반적으로 앱을 한 번 설치한 다음 여러 번 사용하지만, 앱이 여러 번 설치되고 각 버전이 최소 몇 번 실행되는 경우 개발 속도가 느려지므로 사용자는 이러한 점을 감당할 수 있습니다.

bazel mobile-install의 접근 방식

bazel mobile-install는 다음과 같이 개선됩니다.

  • 샤딩된 덱싱. 앱의 자바 코드를 빌드한 후 Bazel은 클래스 파일을 거의 동일한 크기의 부분으로 샤딩하고 dx을 별도로 호출합니다. dx는 마지막 빌드 이후 변경되지 않은 샤드에는 호출되지 않습니다.

  • 증분 파일 전송 Android 리소스, .dex 파일, 네이티브 라이브러리는 기본 .apk에서 삭제되고 별도의 모바일 설치 디렉터리에 저장됩니다. 따라서 전체 앱을 재설치하지 않고도 코드와 Android 리소스를 독립적으로 업데이트할 수 있습니다. 따라서 파일 전송에 소요되는 시간이 짧아지고 변경된 .dex 파일만 기기에서 다시 컴파일됩니다.

  • .apk 외부에서 앱의 일부 로드 작은 스텁 애플리케이션은 .apk에 삽입되어 기기 내 모바일 설치 디렉터리에서 Android 리소스, 자바 코드, 네이티브 코드를 로드한 다음 제어 권한을 실제 앱으로 전송합니다. 이는 아래 설명된 몇 가지 특수한 경우를 제외하고 앱에 투명합니다.

샤딩된 덱싱

샤딩된 덱싱은 상당히 간단합니다. .jar 파일이 빌드되면 도구가 거의 동일한 크기의 별도의 .jar 파일로 파일을 샤딩한 다음 이전 빌드 이후 변경된 파일에서 dx를 호출합니다. dex할 샤드를 결정하는 로직은 Android에만 국한되지 않습니다. Bazel의 일반 변경 프루닝 알고리즘을 사용할 뿐입니다.

첫 번째 버전의 샤딩 알고리즘에서는 단순히 .class 파일을 알파벳순으로 정렬한 후 목록을 같은 크기의 부분으로 자른 다음, 이 방법은 최적의 방법이 아닌 것으로 나타났습니다. 즉, 클래스가 추가되거나 삭제되면 (중첩 또는 익명 클래스 포함) 모든 클래스가 알파벳순으로 1씩 이동하게 되어 샤드가 다시 덱싱됩니다. 따라서 개별 클래스가 아닌 자바 패키지를 샤딩하기로 했습니다. 물론 이렇게 하면 새 패키지가 추가되거나 삭제되면 여전히 많은 샤드를 덱싱하지만, 단일 클래스를 추가하거나 삭제하는 것보다 빈도는 훨씬 적습니다.

샤드 수는 BUILD 파일에서 제어됩니다 (android_binary.dex_shards 속성 사용). 이상적인 환경에서는 Bazel이 가장 적합한 샤드 수를 자동으로 결정하지만 Bazel은 현재 샤드를 실행하기 전에 작업 집합 (예: 빌드 중에 실행할 명령어)을 알아야 합니다. 따라서 앱에 있는 Java 클래스가 몇 개가 될지 모르기 때문에 최적의 샤드 수를 결정할 수 없습니다. 일반적으로 앱이 더 느리게 작동하기 때문에 앱이 더 빠르게 작동하기 때문에 앱이 더 빠르게 작동하기 때문입니다. 최적 지점은 일반적으로 샤드가 10~50개 사이입니다.

증분 파일 전송

앱을 빌드한 후 다음 단계는 앱을 설치하는 것입니다. 가급적 적은 노력으로 앱을 설치하는 것이 좋습니다. 설치는 다음 단계로 구성됩니다.

  1. .apk 설치 (일반적으로 adb install 사용)
  2. .dex 파일, Android 리소스, 네이티브 라이브러리를 모바일 설치 디렉터리에 업로드

첫 번째 단계에서는 성과 증분이 많지 않습니다. 즉, 앱이 설치되거나 설치되지 않습니다. Bazel은 현재 필요한 경우 모든 경우에 결정할 수 없으므로 --incremental 명령줄 옵션을 통해 이 단계를 실행해야 하는지 여부를 사용자에게 표시합니다.

두 번째 단계에서는 빌드의 앱 파일을 기기에 있는 앱 파일과 체크섬을 나열하는 기기 매니페스트 파일과 비교합니다. 모든 새 파일이 기기에 업로드되고 변경된 파일이 업데이트되며 삭제된 모든 파일이 기기에서 삭제됩니다. 매니페스트가 없으면 모든 파일을 업로드해야 한다고 가정합니다.

기기의 파일을 변경하여 매니페스트의 체크섬을 변경하여 증분 설치 알고리즘을 속일 수 있습니다. 이는 기기에 있는 파일의 체크섬을 계산하여 보호할 수 있었지만 설치 시간을 늘릴 만한 가치가 없는 것으로 판단되었습니다.

Stub 애플리케이션

스텁 애플리케이션에서는 기기 내 mobile-install 디렉터리에서 dex, 네이티브 코드, Android 리소스를 로드하는 매직이 발생합니다.

실제 로드는 BaseDexClassLoader를 서브클래스로 분류하여 구현되며, 이는 충분히 문서화되어 있습니다. 이는 앱의 클래스가 로드되기 전에 발생하므로 APK에 있는 모든 애플리케이션 클래스를 adb install 없이 업데이트할 수 있도록 기기 내 mobile-install 디렉터리에 배치할 수 있습니다.

이 작업은 앱의 클래스가 로드되기 전에 발생해야 하므로 애플리케이션 클래스가 .apk에 있을 필요가 없습니다. 즉, 이러한 클래스를 변경하면 완전히 다시 설치해야 합니다.

이렇게 하려면 AndroidManifest.xml에 지정된 Application 클래스를 스텁 애플리케이션으로 바꿉니다. 이 API는 앱이 시작되는 시점을 제어하며, Android 프레임워크 내부에서 자바 리플렉션을 사용하여 가장 빠른 순간 (생성자) 클래스 로더와 리소스 관리자를 적절하게 조정합니다.

스텁 애플리케이션이 하는 또 다른 작업은 모바일 설치를 통해 설치된 네이티브 라이브러리를 다른 위치에 복사하는 것입니다. 이는 동적 링커가 파일에 X 비트를 설정해야 하므로 루트가 아닌 adb에서 액세스할 수 있는 위치에는 설정할 수 없기 때문입니다.

이러한 모든 작업이 완료되면 스텁 애플리케이션은 실제 Application 클래스를 인스턴스화하여 자체에 관한 모든 참조를 Android 프레임워크 내의 실제 애플리케이션으로 변경합니다.

결과

성능

일반적으로 bazel mobile-install를 사용하면 약간의 변경 후 대규모 앱의 빌드 및 설치 속도가 4~10배 향상됩니다.

다음은 일부 Google 제품에서 계산된 수치입니다.

물론 변경의 특성에 따라 다릅니다. 기본 라이브러리를 변경한 후 다시 컴파일하는 데는 더 많은 시간이 걸립니다.

제한사항

스텁 애플리케이션에서 사용하는 트릭이 모든 경우에 효과가 있는 것은 아닙니다. 다음은 예상대로 작동하지 않는 경우입니다.

  • ContextContentProvider#onCreate()Application 클래스로 전송되는 경우 이 메서드는 Application 클래스의 인스턴스를 교체하기 전에 애플리케이션이 시작되는 동안 호출되므로 ContentProvider는 여전히 실제 인스턴스 대신 스텁 애플리케이션을 참조합니다. 이와 같이 Context를 다운캐스트할 수 없으므로 버그가 아닙니다. 하지만 이 문제는 Google의 일부 앱에서 발생하는 것으로 보입니다.

  • bazel mobile-install에 의해 설치된 리소스는 앱 내에서만 사용할 수 있습니다. 다른 앱에서 PackageManager#getApplicationResources()를 통해 리소스에 액세스하는 경우 이러한 리소스는 마지막 비증분 설치에서 가져옵니다.

  • ART를 실행하지 않는 기기 스텁 애플리케이션이 Froyo 및 이후 버전에서 잘 작동하지만, Dalvik에는 코드가 특정 사례(예: 자바 주석이 특정 방식으로 사용되는 경우)에 여러 개의 .dex 파일에 배포되면 앱이 잘못되었다고 생각하는 버그가 있습니다. 앱이 이러한 버그를 간지럽히지 않는 한 Dalvik과도 호환되어야 합니다 (단, 이전 Android 버전 지원이 Google에서 중점을 두는 것은 아님).