Android studio plugin cover

안드로이드 스튜디오 플러그인 직접 만들어보기

안드로이드 개발자들을 위한 수준 있는 독립 컨퍼런스인 Droid Knights에서 “안드로이드 스튜디오 플러그인 만들어보기”라는 주제로 발표해주신 차영호님의 강연입니다.


소개

Gnome이라는 오픈 소스 데스크탑 소프트웨어의 번역을 한 경험이 있고 Python 기반의 임베디드 기기 OS를 만들었고 최근에는 안드로이드를 차량에 올리는 개발을 하고 있습니다. 앱보다는 OS 쪽을 주로 수정하는 작업을 하고 있어서 주로 터미널 환경에서 일하므로 IDE 환경을 쓰는 것이 오랜 소망이었습니다. 물론 커맨드 라인에서 작업할 수 있도록 구글이 만든 스크립트가 있기 때문에 grep 등의 스크립트를 잘 사용하면 생각보다 큰 어려움이 없지만 대규모로 리팩토링을 하는 등의 작업을 하는 것에는 애로사항이 있습니다.

idegen.sh를 사용하면 IntelliJ 등 IDE 상에서 소스코드를 부를 수는 있지만, 일반적인 안드로이드 코드가 아닌 Java 코드로 인식하므로 큰 도움은 되지 않습니다. 또한, 디버깅도 어려우므로 IntelliJ에서 안드로이드를 빌드할 수 있도록 플러그인을 만들기 시작했고, 그 과정에서의 노하우와 경험을 공유하고자 합니다.

API 찾기

개발을 시작할 때 주로 API 참고 문서를 보는 일이 많은데, 아쉽게도 IntelliJ의 플러그인을 위한 문서는 따로 없습니다. 대신 안드로이드 스튜디오나 IntelliJ의 소스 코드를 직접 참고하면서 API를 하나하나 찾아야 합니다. IntelliJ IDEA Community EditionAndroid Studio에서 소스를 볼 수 있습니다. 하지만 안드로이드의 Git 리파지토리를 클론하고 나면 1기가 정도 되므로 너무 방대합니다. 이 중 Java 소스 코드만 5만 개 정도 되고요. 하지만 이 모든 것을 다 볼 필요는 없고 이 중 openapi 패키지 하위 API들을 주의 깊게 보면 되며, 약 3천 개 정도 됩니다.

android-studio-api

약 3천 개의 API 중에서도 다시 끝에 -api로 명명된 인터페이스를 자세히 보게 됩니다. 그러면 다시 1,200개 정도로 살펴볼 대상이 줄어들죠. 사실 예전에는 문서가 있었지만 Jetbrain에서 IntelliJ를 오픈 소스로 전환하면서 문서 배포가 중단됐습니다.

플러그인 개발 설정

이제 본격적으로 개발을 시작하기 위해 설정 방법을 알아보겠습니다. 퀵 가이드에서 플러그인 프로젝트를 만들 때 간단한 소개가 나오긴 하지만 이것을 참고하다가 낭패를 볼 수 있으니, Gradle 프로젝트를 어떻게 만드는지 알려주는 튜토리얼을 찾는 것이 좋습니다. 다만 이 튜토리얼도 업데이트가 4년 전이므로 잘 걸러 보는 것이 필요합니다.

이런 개발 뉴스를 더 만나보세요

android-studio-build-gradle1

Gradle로 프로젝트로 만들기 위해 제가 사용한 스크립트를 예제로 보여드리겠습니다. build.gradle 파일 내용이고, 먼저 Jetbrain에서 별도로 gradle 플러그인을 호스팅하는 maven maven 리파지토리를 등록합니다. 제일 아랫줄의 Java plugin은 등록하지 않아도 프로젝트를 인식하는 것을 최근 확인했습니다. 이렇게 등록한 이후 일반적인 Java 애플리케이션을 만드는 것처럼 Gradle 스크립트에 정의하면 됩니다.

android-studio-build-gradle2

단, 일반적인 Java 애플리케이션 등록 이외에 추가로 해당 플러그인에 어느 버전에서 빌드할 것인지, 다른 플러그인은 어떤 것을 설치할지, 어떤 이름으로 플러그인을 배포할지 추가로 설치해야 합니다. IC-2016.1처럼 IC라는 버전은 IntelliJ의 Community Edition 약자이고 Ultimate Edition에서 빌드하려면 IU로 바꿔주면 됩니다. patchPluginXml는 플러그인 동작을 정의하는 Plugin.xml을 빌드할 때 수정할 수 있도록 프로퍼티를 제공합니다. 여기서 IntelliJ의 플러그인이 동작할 버전을 지정해줄 수 있습니다. 예제의 145는 2016년 첫 번째 버전을 의미합니다. 각 버전별 빌드 숫자는 Jetbrain 홈페이지를 참조하세요. 마지막으로 publishPlugin은 플러그인을 퍼블리시할 때 사용할 정보입니다.

이제 빌드에 필요한 정보를 추가하는 방법을 알아봤으니 IntelliJ에서 Gradle 프로젝트를 임포트하고, 제대로 설정된 경우 Tasks 폴더 아래에 IntelliJ 폴더가 추가됩니다. 이 중 플러그인 빌드 작업을 하는 buildPlugin과 IDE 안에서 플러그인을 디버깅하거나 동작을 확인할 수 있도록 플러그인만 동작하는 IDE를 새로 띄워주는 runIde를 주로 사용하게 됩니다.

plugin.xml

android-studio-plugin-xml1

앞서 봤던 plugin.xml에서는 플러그인이 동작하는 여러 속성과 값 등을 정의합니다. 위 그림처럼 ‘resources’ > ‘META-INF’ 디렉터리 안에 위치합니다. id는 해당 플러그인의 id를 임의로 지정하면 됩니다. name은 플러그인을 설치할 때 사용자가 확인하는 이름입니다. version은 gradle 스크립트에서 버전 프로퍼티를 지정하면 오버라이드되므로 임의 값을 넣어도 됩니다. 제작자 정보도 기입합니다. 플러그인 설치될 때 설명은 description에 넣습니다. idea-version은 플러그인이 동작할 IDE 버전을 뜻하는데 앞서 지정한 버전이 오버라이드됩니다. Jetbrain의 특정 제품군에서만 동작하도록 한정하려면 depends에서 네임스페이스를 지정하면 됩니다. 예제에서는 IntelliJ와 안드로이드 스튜디오에서만 동작하도록 했습니다.

플러그인 동작 지정

이제 플러그인이 어떤 식으로 동작할지 지정하겠습니다. 보통 C나 Java는 프로그램이 런타임에 main 함수를 찾은 다음 원하는 동작을 수행하는데, 플러그인인 경우에는 이미 동작이 돌아가는 상황에서 추가로 기능을 넣어야 합니다. 이런 엔트리 포인트는 다음과 같습니다.

  • Action
    • Menu
    • Toolbar
  • Component
    • Application
    • Project
    • Module

메뉴의 아이템을 선택하거나 툴바의 아이콘을 누를 때 특정 동작을 하게 하는 경우에는 액션 스타일로, IntelliJ가 동작할 때 항상 기능하도록 하려면 컴퍼넌트 스타일로 만듭니다.

plugin.xml - 액션

먼저 메뉴에다가 기능을 추가하는 액션 스타일을 살펴보겠습니다.

android-studio-plugin-action1

Build 메뉴에 make와 mm이라는 아이템을 추가하기 위해 정의했습니다. 오른쪽처럼 빌드 메뉴에 Android Make와 Android mm 항목이 추가됩니다. action 밑에 id와 수행할 class를 정의하고 단축키도 추가할 수 있습니다. group-id로 어느 메뉴 그룹에 속할지도 정의할 수 있습니다. 해당 메뉴 항목이 동작할 코드도 작성해 볼까요?

android-studio-plugin-action2

실행할 클래스는 IntelliJ가 지원하는 AnAction을 상속하고 actionPerformed를 구현해야 합니다. 예제는 toolbar 서비스 인스턴스를 얻어와서 동작하도록 했습니다.

plugin.xml - 컴퍼넌트

android-studio-plugin-component1

컴퍼넌트의 경우 plugin.xmlproject-components에 정의합니다. 예제의 경우 ProjectMonitor라고 정의했습니다.

android-studio-components-compare

컴퍼넌트는 ApplicationComponent, ProjectComponent, ModuleComponent, 세 가지 종류가 있습니다. ApplicationComponent는 일반적으로 IDE 생명 주기와 일치합니다. 따라서 initComponent()disposeComponent()에서 시작/종료 시의 기능을 추가할 수 있습니다. ProjectComponent는 새로운 프로젝트를 생성하거나 기존 프로젝트를 오픈하는 경우 projectOpened(), 닫히는 경우 projectClosed()를 제공해서 프로젝트 별로 구현할 수 있도록 합니다. 마지막으로 ModuleComponent는 프로젝트 하나에 모듈이 하나 이상 로딩될 때마다 호출되는 moduleAdded() 인터페이스 메서드를 제공합니다. 이 메서드 하나만 제공하므로 아직 다 개발이 되지 않은 것처럼 보여서 다른 방식을 설명하겠습니다.

android-studio-message

먼저 IntelliJ IDE에서 사용하는 메시지 인프라스트럭처를 이해해야 합니다. 안드로이드의 인텐트 브로드캐스트와 사용이 유사합니다. 인텐트를 토픽, 브로드캐스트 리시버를 리스너클래스로 생각하시면 좋습니다. 토픽과 관련해서는 PROJECT_ROOTS, MODULES, VFS_CHANGES 주로 세 가지가 있는데, 모듈의 루트 디렉터리가 바뀌는 경우 PROJECT_ROOTS를 사용하며, MODULES라는 토픽은 해당 모듈의 추가/삭제 등의 이벤트를 받아줍니다. VFS_CHANGES의 경우 프로젝트의 파일 추가/삭제/이름 변경 등의 이벤트를 받아올 수 있습니다.

android-studio-module-listener1

Kotlin 코드라서 어려울 수 있지만 Plugin.xml에서 등록한 ProjectMonitor라는 ProjectComponent와 ModuleLister 두 개의 인터페이스가 상속받습니다. 생성자에서는 MODULES라는 토픽에 대한 이벤트를 받기 위해 messageBus.connect()한 후 모듈 리스너에서 제공하는 콜백을 이용해 수신합니다.

android-studio-module-listener2

moduleAdded에서는 안드로이드 모듈이 맞는지 확인한 후 특정 동작을 하는 식의 구현을 했습니다.

plugin.xml - 서비스

android-studio-service1

서비스는 생명 주기와 관계없이 필요할 때 생성할 수 있습니다. 서비스의 종류에 따라 싱글톤으로 동작하는데, 예제의 경우 projectServiceapplicationService를 등록했습니다. 전자의 경우 프로젝트별로 동작하고 후자의 경우 애플리케이션에서 하나만 동작합니다. 예제에서는 DeviceManager를 등록해서 디바이스를 동작하게 하는 것이라 applicationService로 등록했습니다.

android-studio-service2

applicationServiceprojectService의 경우에는 ServiceManager, moduleService의 경우에는 ModuleServiceManager를 통해 사용할 수 있습니다. 예제는 앞서와 같이 액션을 서비스로 구현했는데, 중간에 ServiceManager.getService로 지정한 클래스를 인자로 넣어서 인스턴스를 얻어오고 있습니다. 해당 인스턴스를 다 사용한 다음에는 disposable 인터페이스를 상속받아서 dispose를 호출해서 소멸자처럼 사용할 수 있습니다.

사용자 인터페이스

이제부터는 사용자의 눈에 직접 보이는 인터페이스 부분에 대해서 설명하겠습니다. 대부분의 UI 컴퍼넌트는 Swing으로 작성돼 있고 팝업 창이나 노티피케이션, 툴 윈도우 등 특정 방법으로 제공하고 있습니다. 이 중 툴 윈도우 사용법을 중심으로 말씀드리겠습니다. 또한 GUI Builder로 쉽게 UI를 만드는 팁도 알려드리겠습니다.

GUI Builder

android-studio-gui-builder

IntelliJ에서 GUI Builder를 열어서 수정하는 화면에서는 Java와 Kotlin 언어가 섞여 있습니다. 좌측의 AndroidBuilder.form은 디렉터리와 유사한 구조로 보이지만 실제 디렉터리가 아니라 안드로이드 스튜디오에서 사용하는 폼을 그룹화한 것입니다. 중간의 Component Tree에서는 Swing 코드와 실제 컴퍼넌트의 바인딩을 볼 수 있습니다. 화면에서는 JPanel이라는 가장 최상위의 컴퍼넌트를 AndroidBuilderForm 클래스의 mAndroidBuilderContent 멤버와 바인딩했습니다. 이후에 플러그인 안에서 해당 변수로 Swing 컴퍼넌트에 접근할 수 있게 됩니다.

android-studio-compile

좌측 위는 실제 작성한 부분으로 하단은 IntelliJ에서 플러그인을 컴파일한 이후의 바이트 코드를 디컴파일한 것입니다. 실제 제가 추가하지 않은 부분이 추가된 것을 볼 수 있습니다. Java로 작성하면 이를 수정할 수 있지만 Kotlin으로 작성한 경우 불가능해서 코드 안에서 Swing 코드를 초기화하는 부분이 호출되지 않아 UI 부분이 무용지물이 된 경우가 있으므로 바인딩하는 것은 Java로 작성하는 것을 추천합니다.

android-studio-toolwindow

작성한 Swing 컴퍼넌트를 툴 윈도우에 적용하는 코드입니다. 앞서 GUI Builder에서 바인딩한 멤버를 컨텐트로 매핑한 다음 mToolWindow에 적용했습니다. 툴 윈도우를 만들 때 registerToolWindow를 true로 하지 않으면 프로젝트 로딩 상태와 상관없이 동작할 수 없으므로 주의하기 바랍니다.

배포

보여드린 것 외에도 다양한 API가 있지만 말씀드린 부분부터 시작하면 안드로이드 플러그인 작성을 쉽게 시작할 수 있을 것으로 생각합니다. 이후 퍼블리시를 위해서는 다양한 방법이 있는데 Gradle 플러그인을 Jetbrain이 제공하는 publishPlugin을 사용하려고 했지만 알려진 이슈가 고쳐지지 않고 있으므로, Gradle 퍼블리시보다는 Jetbrain의 Plugins 페이지에서 직접 업로드하는 것이 좋습니다. 퍼블리시 이후에도 업데이트할 수 있으며, 배포는 Jetbrain의 심사를 거쳐 이뤄집니다. 제 경우 일주일 정도의 시간이 걸렸으므로 여유를 가지고 기다리세요.

결론

개발 환경이 열악한 편이고 문서를 직접 참고할 수 없이 소스를 직접 확인해야 합니다. 하지만 IntelliJ 소스도 오픈 소스이고 여러 오픈 소스인 플러그인이 많으므로 참고하시면 많은 학습이 될 것으로 생각합니다.


본 영상과 글은 Droid Knights의 비디오 스폰서인 Realm에서 제공합니다. 모바일 개발자가 더 나은 앱을 더 빠르게 만들도록 돕는 Realm 모바일 데이터베이스Realm 모바일 플랫폼을 통해 핵심 로직에 집중하고 개발 효율을 높여 보세요! 공식 문서에서 단 몇 분 만에 시작할 수 있습니다. 또한 Realm 홈페이지에서는 모바일 개발자를 위한 다양한 최신 기술 뉴스와 튜토리얼을 제공하고 있으니 즐겨찾기하고 자주 들러 주세요!

다음: Realm Java의 새로운 기능을 만나 보세요!

General link arrow white

컨텐츠에 대하여

2017년 3월에 진행한 Droid Knights 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.

차영호

안드로이드 OS 개발자입니다. 근데 개발보다는 개발환경 개발에 관심이 더 많습니다.

4 design patterns for a RESTless mobile integration »

close