Regular Motion

개발자가 상팔자

Category: android (page 1 of 23)

Common Design Patterns for Android

이 글은 https://www.raywenderlich.com에 소개된 글을 저자의 동의를 얻고 번역한 것임을 알려드립니다.

개발자로서 일하며 행복을 유지하기 위해서는 고객과 사장님도 만족시켜야 하지만, 미래의 나 또한 만족시켜야 한다.  (행복한 닭이 더 맛있는 달걀을 낳는다고 하지 않던가)

미래의 나는 과거의 내가 작성한 코드를 이어받게 될 것이고, 아마 왜 이렇게 코드를 작성했는지 궁금해 할 것이다.  헷갈리는 주석을 왕창 달아 놓는 것보다, 많이 사용되는 디자인 패턴을 적용하여 코드를 읽기 쉽고, 구조화 시켜 놓는게 미래의 나를 위해 훨씬 나은 방법일 것이다.

이 글은 안드로이드 개발을 하며 많이 사용하는 디자인 패턴을 소개 할 것이다. 디자인 패턴이란 일반적인 소프트웨어 개발시 마주 할 수 있는 문제들을 해결 할 수 있는 재사용 가능한 해결책이라고 할 수 있다. 이 글에서는 많은 디자인 패턴을 소개하지는 않을 예정이지만, 시작점으로 좋은 글이 될 것이라고 생각한다.

Getting Started

“동일한 수정을 여러 곳에 적용해야 할 부분이 이 프로젝트에 있나요?” – Future You

미래의 당신은 아마 추측하거나 추론하며 프로젝트의 의존성을 찾는 시간을 최소화하고 싶을 것이다. 그러기 위해 프로젝트는 최대한 재사용 가능하며, 읽기도 쉽고, 이해하기 쉬워야 할 것이다.  하나의 클래스부터 전체 프로젝트에 이르기까지 위 목표를 달성하기 위해, 패턴을 다음의 카테고리로 나눌 수 있다.

  • Creational patterns: how you create objects.
  • Structural patterns: how you compose objects.
  • Behavioral patterns: how you coordinate object interactions.

당신은 아마 각각의 패턴을 지칭하는 이름을 모른채 몇 종류의 패턴들은 사용하고 있을지도 모른다. 이제 각각의 카테고리에 어떤 패턴이 있으며 실제로 Android에 어떻게 구현/적용 할 수 있는지 알아보자.

Creational

  • Builder
  • Dependency Injection
  • Singleton

Structural

  • Adapter
  • Facade

Behavioral

  • Command
  • Observer
  • Model View Controller
  • Model View ViewModel

Creational Patterns

“복잡한 오브젝트가 여러곳에서 필요할 때는 어떻게 하죠?” – Future You

미래의 당신은 위 물음에 “동일한 코드가 필요할 때마다 Copy and Paste하면 되잖아요?” 라는 말을 듣고 싶지는 않을 것이다.  대신 Creational Pattern으로 오브젝트의 생성을 단순하게 유지하고 쉽게 반복 가능하도록 만들 수 있습니다.

Builder

단골 샌드위치 가게의 주문서에 빵의 종류와 재료 그리고 양념을 펜으로 선택한다. 주문서의 이름이 “Build my own”이지만 실제로 샌드위치를 직접 만들지는 않고 입맛에 맞게 각각의 재료들을 선택한 뒤 직원에게 넘겨 줄 뿐이다.

이와 비슷하게 Builder pattern은 Object의 복잡한 생성과정(빵을 익히고 자르고, 재료를 넣고, … )을 Object(샌드위치)에서 분리한 것이다. 이와 같은 방법을 통해 동일한 프로세스로 다른 Object(샌드위치)를 쉽게 만들어 낼 수 있다.

Android의 AlertDialog.Builder와 같은 오브젝트에 Builder pattern이 사용된다.

위의 AlertDialog를 Build하는 과정은 단계적으로 진행되며, AlertDialog의 요소 중 수정하고 싶은 부분만 수정 할 수 있도록 해준다.  AlertDialog.Builder 문서를 보면, Alert을 만들 때 매우 제한적인 요소만 설정 할 수 있다는 것을 알 수 있다.

위 코드는 아래와 같은 Alert을 생성한다.

please_use_the_spicy_mustard

Dependency Injection

Dependency Injection은 빌트인 가구가 갖춰진 아파트로 이사하는 것과 비슷하다. 필요한게 이미 있기 때문에; IKEA 홈페이지에 접속해서 가구를 주문하고 배달을 할 필요가 없다.

소프트웨어 측면에서 보자면, Dependency Injection은 새로운 Object를 생성하는 시점에 필요한 Object들을 제공해준다. 따라서 새로 생성되는 Object는 필요한 Object들을 만들 필요가 없다.

안드로이드에서는 앱의 다양한 Activity 또는 Fragment에서 Network Client나 Image Loader, SharedPreferences와 같은 다양한 Object들에 접근 할 필요가 있다. Dependency Injection을 활용하면 이러한 Object들을 Activity나 Fragment들에 Inject 한 뒤, 필요한 시점에 바로 사용 할 수 있도록 해준다.

Dagger 2는 Google과 Square가 협업하여 개발한 Framework으로, Android에서 가장 유명한 Dependency Injection Framework이다. 클래스에 @Module 어노테이션을 추가한 뒤, @Provides 어노테이션으로 필요한 모듈을 제공하도록 할 수 있다.

위 모듈은 Object 생성과 필요한 설정을 수행한다. 규모가 있는 앱을 위한 Best practice로는, 여러 모듈을 함수로 구분하여 생성 할 수도 있다. Component interface를 만든 뒤, Inject가 필요한 모듈과 클래스를 나열한다.

Component는 dependency들이 어디서 와야(module coming from) 되고 어디로 가야(injection point) 되는지를 판단하여 묶어 주는 역할을 한다.

마지막으로 @Inject 어노테이션을 사용해서 Dependency가 필요한 곳에 삽입 할 수 있다.

* Dagger에 대한 간소화된 개요이기 때문에, 자세한 내용은 Dagger 문서를 참조하기 바란다.

Singleton

Singleton 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 해준다. 모델링하는 Object가 실제로 단 하나의 인스턴스만 갖을 때, Singleton 패턴은 잘 동작한다.

위 ExampleSingleton 클래스는 객체의 생성자를 static 함수인 getInstance() 함수 안으로 숨겨서, 객체가 하나만 생성되도록 보장한다. Singleton 객체에 접근이 필요할 때는 아래와 같이 getInstance() 함수로 접근 할 수 있다.

Singleton은 이해하기 가장 쉬운 패턴 중 하나지만, 오/남용되기 가장 쉬운 패턴이기도 하다.  복수의 객체에서 동일한 Singleton 인스턴스에 접근 할 수 있기 때문에 예상치 못한 Side Effect가 발생하기 쉽고, 원인을 파악하기 어려운 경우도 많다.  따라서 사용하기 전에 패턴을 정확히 이해하고 사용하는 것이 중요하다.

 

Structural Patterns

미래의 당신은 전형적인 작업(typical task)을 수행하는 클래스와 오브젝트들을 익숙한 형태로 정리 할 수 있도록 도와준 Structural Pattern을 의심의 여지없이 고마워 할 것이다. AdapterFacade는 안드로이드에서 가장 많이 사용되는 패턴이다.

Adapter

Apollo 13에서 엔지니어들이 네모난 마개를 ‘Round Hole’에 맞추는 유명한 장면이 있다. 이 장면은 Adapter의 역할을 은유적으로 보여준다. Adapter 패턴은 Class의 Interface를 변환하여 사용자가 예상하는 다른 Class의 Interface에 맞출 수 있도록 해준다.
(By converting the interface of a class into another interface the client expects).

당신 앱의 비즈니스 로직은 제품이 될수도, 사용자 또는 건조틀이 될 수도 있다. 그게 뭐가됐든, RecyclerView는 모든 Android App에서 공통으로 사용되는 ‘Round Hole’ 이다.

RecyclerView는 Star Trek의 에피소드를 하나도 본적이 없기 때문에 Tribble이 뭔지 모른다.  하지만 데이터를 처리하고, ViewHolder에 데이터를 올바르게 전달해주는 Adapter의 역할을 한다.

Facade

Facade 패턴은 인터페이스들의 모음을 사용하기 쉽게 하기 위한 Higher-level 인터페이스를 제공한다. 아래 다이어그램은 이를 더 자세히 설명해준다.

만약 Activity에서 도서 목록이 필요하다면, 내부 저장소나 캐시 그리고 API 클라이언트의 내부 로직에 대한 이해 없이도 요청 할 수 있어야 한다.  이는 Activity와 Fragment의 코드를 깨끗하고 간결하게 유지하는 것을 넘어 Activity에 영향을 주지 않고, API 구현에 대한 변경사항을 적용 할 수 있도록 해준다.

SquareOne의 Retrofit은 Facade 패턴을 구현할 수 있도록 도와주는 Open Source Android 라이브러리다. API data를 제공하는 Interface를 아래와 같이 생성하면,

Client에서는 listBooks()을 호출하는 것 만으로 도서 목록을 얻어 올 수 있다.  RequestInterceptor 또는 OkClient의 설정을 수정(customize)하는 것 만으로 Client의 변경없이 캐시 동작 방식을 제어 할 수 있다.

Behavioral Patterns

Behavioral 패턴은 앱의 기능에 책임을 부여 할 수 있도록 해준다. Behavioral 패턴을 통해 프로젝트의 구조나 설계를 파악하는데 도움을 얻을 수 있다. 이 패턴의 Scope는 두 객체간의 관계에서 부터 전체 어플리케이션의 구조에 이르기까지 다양하게 나뉜다. 다양한 Behavioral 패턴이 하나의 앱에 사용되는게 일반적이다.

Command

인도 음식점에서 ‘Saag Paneer’를 주문했을 때, 음식이 만들어지는 과정은 알 필요가 없다. 종업원에게 주문을 알려주기만 하면 된다.

이와 비슷하게 Command 패턴은 Request를 수신하는 곳에 대한 정보없이 발행 할 수 있도록 해준다.

Greenrobot의 EventBus는 Command 패턴이 아래와 같이 동작할 수 있도록 해주는 Android Framework다.

EventBus-Publish-Subscribe

EventBus의 실제 동작방식은 링크를 참고하기 바란다.

Observer

Observer 패턴은 Objects간의 1 대 多 의존성을 정의 한 뒤, Object의 상태가 변경됐을 때, 이에 의존적인 Object들에게 내용이 전달되고, 자동으로 갱신된다.

Observer 패턴은 활용도가 높아, API Call이나 사용자 입력에 대한 반응과 같이 종료시간을 예측하기 어려운 작업에는 모두 적용 할 수 있다.

RxAndroid Framework은 아래와 같은 방식으로 Observer 패턴을 앱에 적용 할 수 있게 해준다.

 

[Android] 악화가 양화를 구축한다.

‘악화가 양화를 구축한다’는 말은 그레샴이 멀리 500년 후를 내다보고 지금의 Android를 빚대어 한 말인 것 같다.

대다수의 안드로이드 개발자들은 표준을 준수하는 코드를 작성하고 싶어 하지만 삼성의 높은 시장 점유율과 반복되는 버그 리포트는 표준만 준수해서는 답이 없다는 뼈아픈 가르침과 인실좆을 반복적으로 알려준다.

삼성을, 정확히는 갤럭시를 지원하기 위한 코드들은 마치 표준인양 인터넷을 떠돌아다니고 표준은 설 곳을 잃는다.

Android Support Library의 눈부신 활약으로 요즘은 상황이 많이 좋아졌지만 카메라나 미디어 코덱과 같이 하드웨어와의 연동이 필요한 부분은 현재 진행형이다.

범용적이거나 호환성이 높은 제품을 만들 때 필요한 자질은 보이지 않는 단점들을 하나 하나 덮어 줄 수 있는 인내심과 따뜻한 마음 그리고 구글링 인 것 같다.

[Android] SecurityException when Notification is being registered with Vibration

안드로이드 개발을 하다보면 재현이 되지 않는 버그들이 참 많다.  어떤 버그들은 특정 기기에서만 발생하고, 또 어떤 버그는 특정 OS 버전에서만 발생하기도 한다.  따라서 상용 서비스는 반드시 CrashliticsSplunk Mint와 같은 버그 리포트 서비스를 연동 후 배포하기를 권장한다.

실 사용자가 10만명 정도되는 앱에 버그 리포트 서비스를 연동해보면 별 회개망칙한 버그들을 다 볼 수 있다.

오늘 수정한 버그는 앱에서 Notification을 등록 할 때 발생하는 Vibrate로 인해 SecurityException이 발생 할 수 있는 버그다.

앱에서 Notification을 등록 할 때, setVibrate() 함수 또는 setDefaults() 함수로 진동을 줄 수 있는데 이때 사용자 기기의 OS 버전이 4.2.1 미만이고 AndroidManifest.xml에  Vibrate 권한을 설정하지 않았다면 SecurityException이 발생 할 수 있다. (‘발생한다’가 아니라 ‘발생 할 수 있다’고 표현하는 이유는 테스트 결과 일부 기기는 4.2 미만에서도 SecurityException이 발생하지 않는다.)

Android 4.2.1에 이 문제와 관련해서 패치가 있었다. 읽어보면 기기가 진동모드여서 진동이 불가피한 경우거나 setDefaults()의 인자로 Vibrate를 설정하는 경우는 더 이상 AndroidManifest.xml에 Vibrate 권한을 추가 할 필요가 없다. 다만 setVibrate() 함수를 통해 Custom Vibration pattern을 등록하는 경우는 여전히 권한 추가가 필요하다.

따라서 우리는 AndroidManifest.xml에 아래와 같이 권한을 추가하여 꼭 필요한 기기에 한해서만 권한을 요청하도록 설정 할 수 있다.

 

아마 내부 테스트 중에는 Vibrate와 관련한 권한 문제가 없었는데, 버그 리포트로 처음 접한다면 위에서 언급한 문제일 확률이 매우 높다.

 

[Android] Doubt till your Bug Tracking tool.

AndroidStudio의 Monitors로 memory monitoring 중 기이한 현상을 발견했다.

앱에서 다운로드를 시작하면 장판처럼 고요하던 메모리가 요동을 치기 시작하는 것이다.

실제로 3~5MB 정도의 메모리가 매우 빠른 속도로 할당/해제를 반복한다. 저사양 단말에서 파일 다운로드시 앱이 굉장히 느려지는 현상도 왠지 요동치는 그래프와 깊은 관련이 있을 것 같다.

다운로드 중 Monitors 툴에 나타나는 그래프는 아래와 같다.

요동치는 메모리

17초부터 다운로드가 시작됐고, 이후 요동치는 메모리를 보시라. 길에서 탕웨이를 우연히 만나면 내 심장도 저렇게 요동 치겠지.

처음에는 다운로드 중 Notification과 View를  갱신하기 때문에 메모리가 요동치는 줄 알았는데, 해당 작업을 주석처리 후 테스트 해봤지만 요동치는 메모리는 돌아오지 않았다.

범인을 잡기 위해, Allocation Tracking을 해보니 용의선상에 없던 놈이 범인이었다. 범인은 앱에서 사용하던 버그 트래킹 툴(Splunk Mint)인데,  툴에서 Network IO를 Monitoring 하는 로직이 BufferedInputStream의 read시마다 실행되도록 구현되어 있어 속도가 빠른 네트워크를 통해 대용량 파일을 다운로드 할 때 해당 로직이 매우 빈번하게 실행된다.

15초간 Allocation Tracking한 결과는 아래와 같다. 눈을 크게 뜨고 보시라.

절대 현혹되지 마라

 

다행히 Network Monitoring을 비활성화 할 수 있도록 SDK가 구현되어 있어 비활성화 후 테스트하니 메모리는 다시 고요해졌다.

이렇게…

백투장판

 

 

금일의 교훈

  1. 몸에 좋은 약도 알고 먹자.
  2. Android Studio의 Monitoring을 통해 Profiling을 가끔해보자. 버그 같지 않은 버그를 잡을 수 있다.

[Android] Unit Test

태초에 사람이 완벽하기는 커녕, 전혀 믿을게 되지 못하기 때문에 우리는 테스트가 반드시 필요하다.

 

Android에는 크게 2가지의 테스트가 존재한다. 1. Unit Test, 2. Instrumentation Test.

* Unit Tests는 Development machine의 JVM에서 실행되는 테스트를 의미하고,

* Instrumentation Tests는 실제 기기에서 수행되는 테스트를 의미한다.

Unit Tests는 Android API 사용에 제한이 있는 단점이 존재하지만, Development machine에서 각각의 기능을 빠르게 테스트를 수행 할 수 있다는 장점이 있다.

 

Android Studio에서 Unit Tests를 작성하기 위해서는 먼저 build.gradle에 아래 2개의 dependencies를 추가해줘야 한다.

 

2개의 dependency를 추가한 뒤, Build Variants의 Test Artifact를 Unit Tests로 변경한다.

 

이제 필요한 테스트 함수를 src/test/java 밑에 추가한 뒤, 구현한 Business Logic들이 정상적으로 동작하는지 검증하면된다.

추가로 각각의 테스트 함수는 상호 의존성 없이 작성되어야 한다.

 

아래는 AES Encryption/Decryption이 정상적으로 동작하는지 검증하기 위해 작성한 함수다.

Unit Tests를 통해 정상적인 경우는 물론, 예상할 수 있는 예외상황이 미리 정의된 대로 처리가 되는지 검증 할 수 있다.

 

태초에 인간은 완벽하지 않다. 쏟아지는 버그 리포트에 당황하지 말고 미리미리 대비하자.

 

* References

http://tools.android.com/tech-docs/unit-testing-support

 

Older posts

© 2017 Regular Motion

Theme by Anders NorenUp ↑