DEV Community

Sewon Ann
Sewon Ann

Posted on

2 1

Android용 Fat AAR gradle plugin 개발을 포기했다.

발단

요즘 안드로이드 앱을 개발할 땐 내부적으로 여러개의 gradle 모듈을 조합해서 구성하는 방식을 많이 적용한다. 잘 구성을 하면 아키텍처 제약사항을 확실하게 적용할 수 있고, 빌드 시간도 줄일 수 있다.

하지만 앱이 아닌 외부 배포용 라이브러리의 경우엔 모듈을 쪼갤경우, 의존 모듈도 모두 배포를 해야 하는 부담이 생긴다. 한두개라면 까짓거 하지 뭐! 라고 생각할 수 있지만, 잘개 쪼개서 한자릿 수를 넘어간다면 대책을 세워야 한다. 모두 배포하던지, 어떻게든 내부 모듈들을 잘 묶어서 한 통의 aar 로 만들어 배포하던지.

java 세상에서 jar로 배포할 경우에도 다들 이런 고민을 했기 때문에 fat jar 관련 내용은 굉장히 많고, gradle 상에서 fat jar를 만드는 노하우도 쉽게 찾을 수 있다. 하지만 aar 조립은 jar 조립보다 훨씬 어렵다.

어쨌든 해내야 할 업무라서 1주일 넘게 씨름을 했지만 너무 어려워 거의 포기한 상태이다. 지금까지 삽질의 기록을 간단히 남겨본다. 참고로 실험은 이미 배포된 aar을 묶는 것으로 했다.

전개

무에서 시작할 순 없으니 기존에 남들이 시도해 본 내용을 찾아봤다. 최근에도 개발이 이뤄지고 있는 fat-aar-android 를 시작점으로 잡았다.

이 gradle plugin 은 embed 라는 custom config를 사용해 의존 관계를 선언한다. 그리고 embed로 선언한 모듈의 내용이 aar에 함께 포함되는 방식이다. 멋져보이지만 testImplementation 과 같은 형태를 지원하지 않기 때문에 제약도 있다. 내 경우엔 aar에 함께 배포할 대상은 local module 이므로 굳이 저렇게 어렵게 가지 않아도 되어, plugin 을 가져다 수정해보기 시작했다.

flavor 처리

먼저 의존 module이 flavor를 가지는데, 배포 module이 그 flavor를 가지지 않았을 때 빌드 에러가 난다. 이 부분은 missingDimensionStrategy 를 이용해서 결정하도록 plugin 기능을 확장했다. 이 작업도 쉽진 않았다.

databinding

의존 module이 databinding을 쓸 경우, aar에는 특별한 디렉터리가 생긴다. databinding v1 에선 /data-binding 만 생기고, v2 에선 /data-binding/data-binding-base-class-log 가 생긴다. 문제는 저 디렉터리 내용물이 json 또는 bin 이라 대체 하위 라이브러리들의 내용들을 어떻게 조합할 지 감도 잡히지 않았다. 그런데 다행히 기계적으로 하위 aar들의 디렉터리 내용을 긁어다 조합하니 잘 돌아갔다. 그리고 이 작업을 위해선 배포 module 은 자신이 databinding을 쓰지 않더라도 무조건 databinding enabled를 true로 선언해 줘야 했다.

maven pom dependency

aar 자체는 의존 라이브러리 정보를 가지지 않는다. maven에 배포될 때 만들어지는 pom 파일을 통해 의존 라이브러리 정보를 표현한다. 따라서 여러 aar 들을 묶기 위해선 의존 aar 들의 의존 정보를 모두 가져다가, 배포 module의 pom 에 기록을 해야 한다. 자연스럽게 처리하려면 의존 module 의 dependency 를 싹 가져다 배포 module 의 dependency 에 넣어주면 되는데, 이 처리가 쉽지 않았다. 의존 module 의 dependency를 알아내려면 resolve 과정을 거쳐야 하는데, 이미 resolve 까지 다 한 이후엔 dependency 를 건드리지 못하는 것으로 추정된다. 결국 배포하려면 maven publish plugin 을 사용할 거라는 가정하에, maven publish extension 의 pom 생성 정보를 조작해서 pom 생성 시 내가 의도한 dependency 를 기록하도록 강제했다. 다만 배포 시 pom 이외에 gradle module metadata 도 배포될 수 있는데, 이 metadata 가 존재할 경우 의존관계는 pom 보다 gradle module metadata가 우선시되는 것 같다. 이 쪽을 건드리긴 쉽지 않아 그냥 아래와 같이 gradle module metadata 배포를 막아버렸다.

tasks.withType(GenerateModuleMetadata) {
    enabled = false
}

R

난 여기서 포기했다. 안드로이드 앱이나 라이브러리는 R 클래스를 가진다. 라이브러리의 R 클래스는 aar 에 존재하지 않고, 자신을 사용하는 쪽에서 생성한다. 즉 앱을 빌드하면 앱의 빌드과정에서 자신이 사용하는 라이브러리의 R 클래스들이 쫙 만들어진다. 이 내용은 build/generated/not_namespaced_r_class_sources 쪽에서 확인할 수 있다.

문제는 묶어서 배포하게 되면 배포 aar 에 묶인 하위 aar 들의 R 들이 생기지 않는다. 앱 입장에선 자신이 만들어야 할 R 대상은 배포 aar 하나 뿐이기 때문이다. 이를 위해 fat aar 플러그인에선 각 aar 들의 R 클래스를 직접 만들어서 aar 안에 집어넣었다. 내용은 이런 식이다.

class R {

  static final int my_resource_id = com.배포모듈.R.my_resouce_id;
}

즉 자신의 값을 나중에 만들어질 배포 모듈의 R 값으로 선언하는 식이다. 캬! 똑똑하다.

하지만 이 방식은 2가지 문제가 있다.

  1. 최종 R에 필드가 누락될 경우, 런타임에서 클래스 로딩 시점에 크래시가 발생한다.
  2. databinding v1 의 경우 switch 문이 다수 등장하는데, 여기서 constant expression 이 아니라고 하면서 빌드가 실패한다.

미리 만든 R과 최종 R의 필드가 일치하거나 최종 R이 더 필드 수가 많으면 문제가 없고, databinding v2 를 써도 문제가 없다. 하지만 R의 내용이 어떻게 될 지 모르는 상태에서 1번 문제를 낙관적으로 잘 되겠지 하고 넘길 순 없다.

물론, 아래와 같이 극단적으로 R 을 만들어버리면 실행은 되겠지만 (정말 필요한 R 필드는 당연히 생성된다) 퍼포먼스에 문제가 생긴다. 이런 필드가 천개도 넘게 생길 수 있기 때문이다.

class R {

  static final int my_resource_id;

  try {
       my_resource_id = com.배포모듈.R.my_resouce_id;
  } catch(Throwable t) { }
}

이 문제를 가장 아름답게 해결하려면, 결국은 배포 aar 에 포함된 하위 라이브러리의 R도 빌드 과정에서 자연스럽게 생성되어야 한다. 몇 가지 실행방안은 떠오르지만 하나같이 어려워보인다.

첫째는 LinkApplicationAndroidResourcesTask 를 속여서, 마치 추가로 R을 생성해야 할 라이브러리가 있는 척 하는 것이다. 그런데 내부적으로 보면 이를 위해 symbol file도 만들어야 하고, 이래저래 속이기 위해 해야 할 일이 많고, 또 쉽지 않아 보인다.

둘째는, 만들어진 R을 쓱 가져다 조작해서 만들어내는 것이다. 즉 com.배포모듈.R 이 만들어지면 이걸 가져다 똑같은 필드를 가진 com.의존모듈.R 을 만든다. 근데 이 작업이 잘 되려면 com.배포모듈.R 이 생성되고 추가적인 빌드 진행이 되기 전의 알맞는 타이밍에 실행되어야 하고, 클래스를 읽어다 같은 필드를 가지면서 패키지만 다른 클래스를 어떻게 만들어내는지도 잘 모르겠다. 런타임에 reflection을 쓰는 것도 아니고 class 자체를 만들어내야 해서.

그래서 현재는 R 클래스 생성에 막혀서 잠시 포기 상태이다. ㅠㅠ

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →