Droidkaigi yusuke konishi header

안드로이드 리소스 리팩토링

소개

저는 Quipper Limited에서 일하는 Yusuke Konishi입니다. 종종 개인 프로젝트를 만들곤 하는데, 이번에는 본 컨퍼런스인 DroidKaigi 2017 공식 컨퍼런스 앱을 만들었습니다. 이 앱을 이번 강연의 예제로 사용하겠습니다.

왜 리팩토링을 해야 할까요?

작년 6월에 Quipper Limited에 입사한 후 8개월 동안 두 번 디자인 변경을 도왔습니다. 리소스 관리 원칙이 전혀 없어서 정말 힘든 작업이었죠. 몇몇 텍스트는 직접 작성된 경우도 있고 컬러와 스타일도 너무 많았습니다. 또한 앱 구조상 리소스가 더 복잡해졌죠.

우리 회사에는 일본 마켓을 위한 StudySapuri와 인도네시아, 필리핀, 멕시코 등 글로벌 마켓을 위한 Quipper, 두 가지 제품이 있었습니다. 코드는 같지만 성향에 따라 구분된 제품들로, 기능 일부에만 차이가 있고 겉보기에는 거의 유사합니다. Quipper에 입사할 당시에는 유사하지만 다른 스타일 XML과 복잡한 컬러 XML이 사용되고 있었기 때문에 리팩토링하기 정말 어려웠습니다. 여러 번의 시행착오를 거친 후 리소스를 관리하는 최적의 방법을 찾을 수 있었고, 이번 강연에서 공유하고자 합니다.

끝까지 함께 하시면 자신의 앱에서 리소스를 리팩토링해야 할지 말지를 판단하고 리소스를 어떻게 리팩토링할 지 알 수 있을 겁니다.

복잡한 리소스 문제

우리 앱의 경우 비슷한 테이블과 스타일이 많이 있었고 네이밍 규칙이 없는 데다 컬러와 디멘션이 직접 쓰이기도 했습니다. 이렇게 리소스가 복잡한 경우 개발 속도가 느려지고 디자인을 통일시키기가 어렵습니다. 리소스를 깔끔하게 정리하는 편이 좋겠죠.

리팩토링 단계

먼저 안드로이드의 기본 자원인 colors.xmldimens.xml 부터 고치기 시작했습니다. 나중에 깨달은 일이지만 스타일과 드로어블도 여기에 의존하죠. 다음으로 styles.xml에서 themes.xml을 분리하고 새 styles.xml을 만들었습니다. 마지막으로 드로어블 이름을 아이콘, 이미지, 셀렉터 등으로 변경했습니다.

컬러와 디멘션에 의존적인 스타일

스타일과 컬러, 디멘션 사이에는 의존성이 있습니다.

konishi-styles

예를 들어 이 텍스트 캡션 스타일은 colors.xml의 grey 컬러와 dimens.xml의 1.3 라인 스페이싱, 12dp에 의존성을 갖습니다. 리소스를 리팩토링할 때는 먼저 색상과 크기(디멘션)을 수정하는 것이 좋습니다.

1. 컬러

개인적으로 colors.xml는 앱에 사용하는 모든 색을 정의하므로 컬러 팔레트처럼 사용할 수 있어야 합니다. 디자이너가 있는 팀이라면 색 디자인은 함께 하는 것이 좋겠죠.

현재 colors.xml입니다. GreyscalesQuipper colors 두 가지 섹션으로 나눴는데 너무 많은 섹션을 만드는 것보다는 아래처럼 간단하게 나누는 것으로도 충분합니다.

konishi-colors

그레이 스케일 섹션에는 흰색, 검은색, 회색을 정의했는데, 네이밍 규칙을 잘 만드는 것이 중요합니다. 위 경우에는 디자이너와 함께 “grey1”, “grey2”, “grey3” 처럼 색이름을 정했습니다. “grey darker”, “grey lighter”처럼 정하지 않은 이유는 다양한 회색 조 색상을 쓰는 경우가 생기는데 “grey white”, “grey whiter”, “grey whitest”로는 이해하기가 어렵기 때문입니다. 같은 맥락으로 투명한 색상의 경우 “color transparent” 보다는 “color alpha percent“라고 짓는 것이 좋습니다. 회색 조와 유사하게 여러 투명색상이 있을 수 있기 때문입니다.

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

두 번째 섹션에서는 앱에서 사용하는 특정 색상을 정의했습니다. 팀에 디자이너가 없는 경우 어떤 이름이라도 괜찮습니다. 예제인 DroidKaigi 앱에서는 blue, red, green처럼 간단한 색이름을 사용했습니다. 디자이너가 있는 경우 엔지니어와 디자이너가 공통 팔레트로 사용할 수 있으므로 함께 색상을 결정하는 것이 좋겠죠.

디자인 가이드 라인에 다음처럼 색상을 정의했습니다.

konishi-designer-colors

colors.xml에도 “warning”, “danger” 등 같은 이름을 사용했습니다. 이렇게 하면 디자인으로 새 레이아웃을 만들때 이해하기 쉬워집니다.

리팩토링 리뷰

colors.xml 리팩토링 단계를 먼저 보여 드렸습니다. 먼저 이상적인 색상을 정의하는 colors_new.xml를 만들고 새 XML 파일 생성 풀 리퀘스트를 보냈습니다. 각 단계에서 작은 PR을 보내서 충돌을 방지하고 코드 리뷰하는 사람의 노력을 줄여 주세요.

다음 리팩토링 단계는 레이아웃에서 직접 쓰인 컬러를 교체하는 것입니다. 슬프게도 우리 회사의 경우 이런 것이 많았죠.

세번째로 기존 컬러를 새 컬러로 단계적으로 바꿔 나갔습니다. 이 단계에서는 작은 PR을 계속 보내는 것이 중요합니다. 처음에는 검은색만 바꾸고 PR을 보냈고, 다음으로는 “grey1” 컬러를 바꾸고 PR을 보내는 식으로 계속 진행했습니다.

안드로이드 스튜디오에서는 색상을 쉽게 Grep 으로 검색할 수 있습니다. 경로 대화 상자에서 파일을 열고 표현 식과 함께 Grep 한 다음 각 표현 식을 검색해서 교체하면 됩니다. 썩 훌륭한 방법은 아니지만 잘 작동합니다.

마지막으로 모든 기존 colors.xml을 제거한 다음 colors_new.xml으로 이름을 바꿨습니다. 어려운 작업은 아니지만 여러 엔지니어가 있는 팀이라면 다시 한번 작은 PR을 보내라고 조언하고 싶네요.

2. dimens.xml

컬러를 수정했다면 다음 타깃은 dimens 입니다. 앞서 말했지만 컬러와 디멘션은 기본 리소스입니다. dimens_base.xmldimens.xml 두 파일을 정의하는 것이 좋습니다. dimens_base.xml은 아이콘, 텍스트, 버튼 등 기본 구성요소의 기본 크기를 정의합니다. 한편 dimens.xml은 특정 페이지의 크기를 정의하죠.

우리 앱의 dimens_base.xml입니다.

konishi-dimens base

구성 요소별로 섹션을 나눴습니다. 네임 스페이스 기능을 리소스에 적용하고 싶었지만, 안드로이드에는 그런 기능이 없으므로 단순한 구성 요소별로 섹션을 나누고 프리픽스 형식으로 이름을 지정했습니다.

space, radius, elevation, 등으로 섹션을 만들었습니다. 엄격한 네이밍 규칙은 없고 단지 space_16dptext_14dp 정도로 프리픽스를 붙였습니다. 이런 구체적인 사이즈를 디멘션 이름에 포함하면 안 된다고 하는 사람들도 있는데, 이렇게 이름 지을 경우 나중에 디자인 변경이 있을 경우 고칠 부분이 많기 때문입니다. 하지만 “space medium”이나 “space large”와 같이 상대적인 이름을 붙일 경우 dimens.xml만 변경하면 됩니다.

“padding medium”이나 “margin large”처럼 상대적인 이름은 사용하기가 어렵습니다. 디자인이 구체적인 사이즈를 특정하면 이 사이즈를 기억해야만 합니다. 하지만 팀원들은 종종 이 이름을 잊어버리고 dimens.xml를 보고 구체적인 사이즈를 다시 확인하곤 했습니다.

dimens.xml은 다음과 같습니다.

konishi-dimens-xml

dimens.xml은 각 포매팅 함수나 페이지별로 특정 크기를 정의합니다. 새로운 것이 자주 추가되며 점점 커지게 되므로 dimens_base.xmldimens.xml을 분리했습니다.

예를 들어 앱의 로그인 페이지는 다음과 같습니다.

konishi-login

로그인 페이지에 특정 로그인 이메일과 폼 사이즈를 사용합니다. 이 사이즈는 dimens.xml에서 정의됩니다. 사이즈가 한 군데에서만 사용되면 dimens.xml에 넣기 때문이죠. 개인적으로는 모든 디멘션을 정의하는 것이 더 간단한 것 같습니다.

디멘션을 리팩토링하면서 먼저 dimens_base.xml을 만들고 각 섹션 안에 있는 모든 디멘션을 교체했습니다. 처음에는 space 사이즈만 교체했는데, 앞서 말씀드린 컬러와 마찬가지로 grep 을 사용해서 교체하고 작은 PR을 보냈습니다. 종종 새 dimens_base.xml에 정의되지 않은 이상한 사이즈도 발견했는데 그 경우 디자이너에게 물어봤습니다. 사실 디자인도 바꿨기 때문에 엄밀한 의미의 리팩토링은 아니었죠.

이 역시 어려운 작업은 아니었고 테마와 스타일 XML 리팩토링을 위한 준비 단계 정도였습니다.

3. themes.xml

컬러와 디멘션을 리팩토링한 후 themesstyles 을 리팩토링하기로 했습니다. 많은 부분에 영향을 미치기 때문에 조심해서 리팩토링해야 합니다.

애플리케이션과 액티비티의 테마는 AndroidManifest.xml에서 설정할 수 있습니다. 테마를 사용해서 각 컴퍼넌트의 윈도우 백그라운드, 컬러, 사이즈 등 공통 모양을 적용할 수 있습니다. 예를 들어 스플래시 액티비티는 투명한 테마를 사용해서 배경색을 투명하게 했습니다.

테마를 사용해서 각 레이아웃의 배경색을 없앨 수 있습니다. 리팩토링 전에는 어떤 배경색은 각 액티비티나 프래그먼트가 설정돼 있었는데 대부분은 이 백그라운드를 없앨 수 있습니다. 윈도우 백그라운드를 나중에 설정할 수 있으므로 각 액티비티나 프래그먼트에서 설정할 필요가 없고 성능 면에서도 이편이 나을 겁니다.

윈도우 백그라운드 이외에도 테마로 여러 공통 스타일을 설정할 수 있습니다. 예를 들어 colorAccent, colorPrimary, colorPrimaryDark를 설정하면 아래와 같은 색이 적용됩니다.

konishi-theme1 konishi-theme2

스테이터스 바에는 colorPrimaryDark, 체크된 체크 박스에는 colorPrimary, 체크되지 않은 체크 박스에는 textColorSecondary가 적용됐습니다. 이 테마를 레이아웃 안의 각 구성 요소에 적용할 수도 있습니다. 어떤 테마 속성이 구성 요소 색상에 적용되는지 이해하는 것이 좀 어렵지만 테마를 잘 사용하면 각 레이아웃에서 불필요한 스타일을 줄일 수 있습니다.

액션 바 컬러를 변경하려면 각각의 액티비티나 모든 컴퍼넌트의 테마에서 세팅하는 것이 아니라 액션 바를 위한 다른 테마를 설정해야 합니다. 백 버튼이나 텍스트처럼 공통 스타일을 변경하고 싶다면 먼저 themes.xml에서 각 테마를 설정하는 것이 좋습니다.

konishi-action-bar

styles.xml 파일로부터 themes.xml을 분리했을까요? themes.xmlstyles.xml과 구조가 거의 같지만 styles.xml이 좀더 빈번하게 업데이트될 가능성이 크며, 리소스 파일은 업데이트 빈도에 따라 나누는 것이 좋다고 생각하기 때문에 이 둘을 분리했습니다. 우리 앱의 경우 스타일과 테마가 복잡하고 제품 성향도 다릅니다.

우리는 같은 코드를 사용하지만, 제품 성향에 따라 분리된 두 개의 앱을 관리하고 있습니다. 일본 시장을 위한 “Sapuri”와 글로벌 시장을 위한 “Quipper”죠. 외관은 거의 같지만 스타일이 두 개 사용하고 있었습니다. 또한, 각각의 styles.xml에 테마와 스타일이 있었습니다. 각 styles.xml의 거의 모든 테마가 같으므로 통합하는 것이 필요했습니다. 먼저 styles.xml에서 일부 테마를 추출했습니다. 스타일을 동시에 리팩토링하고 싶었지만, 단계별로 리팩토링해야 하므로 일단 참았습니다.

Sapuri 버전의 themes.xml은 메인 themes.xml와 같고 단지 컬러만 달랐으므로 제거했습니다. colors.xml을 적절히 나눴다면 themes.xml을 통합할 수 있겠죠? 다음으로 테마를 하나하나 살펴보면서 Sapuri 버전의 themes.xml로부터 제거했습니다. 모든 테마를 통합하고 난 후 마침내 Sapuri 버전에서 themes.xml를 제거할 수 있었습니다.

테마 리팩토링 단계는 이걸로 끝이지만 테마 변경으로 많은 곳이 영향을 받고 테마의 동작은 API 레벨별로 달라지기 때문에 레이아웃을 교차하는 것이 두려웠습니다. 그래서 GitHub PR 템플릿을 수정해서 점검했습니다.

풀 리퀘스트 템플릿

기본으로 사용하던 GitHub PR 템플릿은 섹션이 몇 개 없었습니다. 섹션이 많으면 채우기 어렵기 때문이죠. 필요 없는 섹션의 경우 없애도 무방합니다.

처음 “개요” 섹션에 간단한 설명을 작성합니다. 다음 섹션인 “알려진 이슈”에서는 이미 알고 있는 이슈에 대해 작성합니다. “디자인 리뷰” 섹션에서는 디자이너에게 레이아웃 점검을 요청합니다. 기본적으로는 디자이너 검토 후 PR을 만들었죠. 마지막으로 “스크린샷” 섹션에는 변경 전후의 스크린샷이나 각 API 레벨별 스크린샷을 올렸습니다.

themes.xml을 추출하고 윈도우 배경색을 적용했을 때의 PR입니다. 디자인 가이드라인에 정의된 대로 배경색을 추가하고 styles.xml로부터 themes.xml를 추출했다고 요약했습니다. “알려진 이슈” 섹션에는 이 이슈로 발생하는 레이아웃 이슈들에 대해 언급했습니다. 예를 들어 툴바에 이상한 프로그레스 아이콘 색이 보였는데 다른 PR에서 프로그레스 바를 위한 다른 테마를 만들어서 해결했습니다.

konishi-pr-exmaple.png

마지막으로 “스크린샷” 섹션에는 themes.xml 동작이 API 레벨에 따라 바뀌기 때문에 API 16과 API 20의 스크린샷을 추가했습니다. UI 테스트를 하고 스크린샷을 찍는다면 점검이 훨씬 쉬워질 겁니다.

4. styles.xml

스타일은 레이아웃 속성을 정의하고 레이아웃을 좀 더 단순하게 만들 수 있습니다. 하지만 styles.xml은 좀 더 복잡하죠.

여러 번의 시행착오를 거친 후 저는 styles.xml를 파일 몇 개로 분리했습니다. styles_login.xml이나 styles_messages.xml처럼 특정 페이지를 위한 다른 스타일을 만들었습니다. styles.xml은 아이콘, 텍스트, 버튼처럼 기본 스타일을 정의했습니다. 이 파일은 일반적으로 자주 바뀌지 않죠. 한편, 각 페이지를 위한 다른 스타일 XML 파일을 만들었는데 엄격한 규칙은 없지만 파일 이름에 서픽스를 유지하고 속성 이름의 프리픽스 정도만 네이밍했습니다. 필요하다면 네이밍 규칙은 바꿔도 됩니다.

styles.xml로 레이아웃을 간단하게 만들 수 있습니다.

konishi-styles

“버튼”에 대한 예시로, 모든 버튼은 공통 디자인을 가지므로 스타일을 만들어서 디자인을 통일시키면 좋겠죠. 아래와 같은 버튼 스타일입니다.

konishi-button-style

몇몇 레이아웃은 정의되지 않습니다. 마지막 속성은 버튼 그림자를 없애기 위한 상태 리스트 애니메이터로, 우리 디자이너의 요구사항이었습니다. 이처럼 버튼 스타일을 적용하지 않으면 모든 버튼 스타일을 변경하기 어렵겠지만, 이 경우 상태 리스트 애니메이터 속성을 버튼 스타일에 넣어서 쉽게 해결했습니다.

“점”으로 연결해서 스타일을 상속시킬 수 있습니다. 다른 컬러의 버튼 스타일이 필요하면 Button.Primary를 만들고 백그라운드와 텍스트 속성만 정의하면 됩니다. 안드로이드 스튜디오의 자동 완성을 사용해서 스타일을 쉽게 사용할 수 있으므로 편리합니다. Button.만 적어도 안드로이드 스튜디오가 Button.Primary를 제시해주죠.

layout_height이나 layout_width와 같은 레이아웃 속성이 styles.xml에 들어가면 안된다고 지적하는 사람들도 있습니다. 하지만 여태까지 우리 경험에 따르면 layout_margin과 같은 속성과 달리 styles.xmllayout_height이나 layout_width를 넣는 것은 큰 문제가 안 됩니다.

툴바 스타일

툴바 스타일 예제를 보겠습니다.

konishi-toolbar-style

이 테마는 부모 속성을 사용해서 앱 연락처 툴바 스타일을 상속합니다. 툴바 스타일 외에도 Widget.AppCompat은 텍스트나 체크 박스와 같은 머티리얼 디자인 구성 요소 스타일을 갖습니다. 새로 스타일을 만들기 전에 기존 스타일이 있나 확인해보는 것이 좋습니다.

한편, 설정 페이지에서만 사용하는 헤더 텍스트 스타일을 정의하는 styles_settings.xml처럼 특정 페이지를 위한 스타일 파일을 만들었습니다. 여러 페이지에서 사용되는 스타일이라면 styles.xml에서 정의하는 편이 좋겠죠. 특별한 텍스트 타이틀이라서 styles_settings.xml이라는 새 파일로 만들었습니다.

스타일 파일을 분리해서 styles.xml를 깔끔하고 작게 유지하고 다른 스타일을 쉽게 찾을 수 있게 됐습니다.

5. drawables

마지막으로 드로어블을 리팩토링했습니다. 일반적으로 앱에 너무 많은 드로어블이 있으므로 관리하기 어려운 경우가 많습니다.

쉽게 드로어블을 관리하는 방법은 이름 프리픽스를 결정하는 것입니다. 아이콘 이름은 “구성 요소”가 아니라 “모양”을 포함해야 합니다. 예를 들어 비디오 플레이 아이콘이 있다면 이 아이콘은 사운드 플레이로도 사용될 수 있으므로 ic_video_play 보다는 ic_play라고 이름 짓는 것이 좋습니다. 물론 비디오와 사운드 플레이 아이콘을 모두 만들 수도 있겠지만 드로어블 파일의 수를 줄이는 편을 선호하므로 저라면 같은 아이콘을 사용하겠습니다.


<ImageView
  style="@style/Icon.Small"
  android:tint="@color/grey2"
  app:srcCompat="@drawable/vec_ic_delete_22"/>

이름에 컬러를 포함하지 않은 이유는 틴트 를 사용하기 때문입니다. 이미지 뷰는 틴트 속성이 있습니다. 하지만 컬러 상태 리스트를 사용하면 이 틴트 속성은 잘 작동하지 않으므로 스태틱 메서드를 만들었습니다. 컬러 상태 리스트 를 사용한다면 layout.xml에 지정하기 위한 데이터 바인딩 어댑터를 만드는 것이 좋습니다.


public static void setImageTintCompat(@NonNull ImageView imageView, @ColorRes int ColorResId) {
  ColorStateList colorStateList = ContextCompat.getColorStateList(
    ImageView.getContext(), ColorResId);
  if (colorStateList != null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      imageView.setImageTintList(colorStateList);
    } else {
      Drawable src = imageView.getDrawable();
      if (src != null) {
        Drawable drawable = DrawableCompat.wrap(src);
        DrawableCompat.setTintList(drawble, colorStateList);
        imageView.setImageDrawable(drawble);
      }
    }
  }
}

konishi-naming-rules

또한, 아이콘뿐만 아니라 벡터 아이콘이나 이미지에 사용할 네이밍 규칙도 정했습니다.

일반적으로 드로어블을 리팩토링하는 것은 어렵기보다는 귀찮은 일에 가깝습니다. 먼저, 사용하지 않는 드로어블을 없앱니다. 물론 APK 파일이 사용하지 않는 드로어블을 포함하지 않도록 하는 “shrink” 옵션이 있긴 하지만 애초에 사용하지 않는 드로어블을 없애면 사용하지 않는 아이콘을 리네이밍하는 데 시간을 쓰지 않아도 됩니다. 사용하지 않는 드로어블부터 없애기 시작하세요.

안드로이드 스튜디오의 Analyze 메뉴에서 사용하지 않는 이미지를 확인할 수 있지만, 데이터 바인딩으로 이미지를 사용하는 경우에 아이콘을 삭제할 수 있으므로 주의하세요.

다음 단계는 모든 아이콘 이름을 리네이밍하는 것입니다. 아이콘 네임 리스트를 스프레드시트에 만들고 한 번에 변경했습니다. 우리 팀은 아이콘을 Sketch 파일에서 만들고 CI 프로세스에서 벡터 드로어블로 자동 변환하므로, Sketch 파일에서 리네이밍했습니다.

아이콘을 변경한 다음으로는 단계적인 리팩토링을 위해 이미지 이름을 변경했습니다. 마지막으로 다른 드로어블을 조금씩 리네이밍했습니다.

요약

1. 컬러와 디멘션부터 리팩토링하라. 리소스는 스타일과 컬러, 디멘션에 의존성을 가지므로 컬러와 디멘션을 먼저 리펙터링하는 것이 좋습니다.

2. 단계적으로 리팩토링하라. 충돌을 방지하고 리뷰 사용자의 시간을 아낄 수 있습니다.

3. 네이밍 규칙과 파일 분할 정책을 사용하라. 안드로이드 리소스 관리 시스템은 완벽하지 않습니다. 리소스 파일을 나누고 네이밍 규칙을 만들어서 사용하세요.

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

General link arrow white

컨텐츠에 대하여

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

Yusuke Konishi

Yusuke Konishi는 Quipper Limited의 엔지니어로 도라에몽의 팬입니다.

4 design patterns for a RESTless mobile integration »

close