출처: http://leipiel.tistory.com/8

개발 협업을 위한 안드로이드 디자인 가이드 #02

저번 포스팅에서 디자이너가 제작 전에 이해하거나 알아둬야 할 기본적인 안드로이드 UI 정보에 대해 정리해 보았다. 앞선 내용 전부 디자인 제작 시 반영이 잘 되었다면 개발파트에서 적용하는데 큰 문제는 없을 것이다.

이번에는 실제로 개발자와 협업할 때 필요한 UI 가이드 문서 제작 방법을 정리하려고 한다. 전에도 언급했다시피 UI는 결국 개발 파트를 거쳐 완성되므로 개발 파트와 디자인 파트 간의 밀도 있는 소통이 이루어져야 하며 그 중심에는 UI 가이드 문서가 있다. 정리하고자 하는 가이드 방식은 개인 경험을 통해 축적된 것이기 때문에 내 방식을 주장하거나 강요하는 것은 절대로 아니다. '그냥 이런 방식도 있구나.' 정도로 읽으시면 되겠다. (제 방식에 오류나 더 나은 방법은 알려주세요.)


1. Layout

레이아웃은 UI 구조 및 구성 의도를 잘 전달하기 위해 표시한다.
가상의 프레임들을 시각적으로 제공해 줌으로써 개발자는 버튼의 위치 및 정렬, 각 view의 부모, 형제, 자식 관계를 쉽게 이해할 수 있다. 나의 경우는 이 프레임들을 투명도가 있는 컬러 박스로 구분하되, 필요에 따라 선으로 구분하여 표시한다. 이 표시는 각 구성요소의 위치나 관계성을 이해하기 쉽게 하는 용도다. 개발 관점의 view 구성 가이드가 아니므로 개발자가 더 효율적인 방법으로 구성하면 된다.

layout_01


2. Images

이미지는 기본적으로 사이즈가 고려된 분할 이미지 파일 형태로 제공되기 때문에 사이즈를 기재해줄 필요는 없다. 40dp x 40dp로 제작된 이미지는 40dp x 40dp로 표출되는 것은 너무 당연한 것이기 때문. (코드 상에서도 이미지 사이즈를 입력하지 않아도 리소스 사이즈 그대로 표시된다.)

이미지는 파일명 정보가 가장 중요하다.
물론 이미지 파일들을 보면 어디에 쓰인 것인지 알 수는 있겠지만 리소스가 많고 투명 값이 많은 이미지들의 경우 탐색기의 썸네일로만 구분하기 어렵기 때문에 가이드에 이미지 파일명이 기재되는 것이 바람직하다. 또 버튼과 같이 이벤트에 따라 여러 개의 이미지 리소스를 사용하는 경우는 미리 약속한 규칙으로 표현해주면 된다. 우리의 경우 파일명 접미사 규칙을 이용하여 구분하고 있다.

images_01_n은 normal, _p는 press를 의미함


3. Weight

전 포스팅에서 전체 디스플레이의 가변성을 고려하여 디자인해야 한다고 언급한 적이 있다. 전체 영역 중 어떤 view가 가변성을 가질 것인지 표시해 주는 것이다. 나는 개발자가 사용하는 명칭 그대로 weight라고 약속하여 가이드에 표시하고 있다. 이런 명칭을 규정하기 나름이므로 각자 환경에 맞게 규정하여 표시하면 된다. 또 연속성을 갖는 여러 개의 가변 영역일 경우 해당 비율을 기재해주면 된다.

img_weight_01


 

4. Spacing

모든 레이아웃 구성요소들의 간격은 기본 dp단위로 기재한다.
굳이 margin, padding 구분하여 세세히 기재해줄 필요는 없다. 부모 객체에 padding 값을 적용하는 것과 자식에 margin 값을 적용하는 것은 특별한 경우를 제외하고는 차이가 없다. 그러므로 간격 값만 표시하고 그것을 어느 객체에 margin / padding으로 처리할 것인지는 개발자의 몫으로 남기자.

img_spacing_01Header에 left-padding:8 을 할지, btn_prev에 left-margin:8 을 할지는 개발자의 선택!

5. Align

안드로이드에서 구성요소의 위치를 좌표로 지정하여 사용하는 경우는 매우 드물다. 그렇기 때문에 정렬(Align)과 간격(Spacing)을 이용하여 위치 값을 가이드 해야 한다. 정렬 값은 가로 기준, 세로 기준 두 개의 값을 표시하며 이 또한 개발자와 사전에 약속한 규칙으로 기재하면 된다.
나의 경우, 안드로이드에서 사용하는 명칭은 익숙하지 않아 웹상의 용어로 규정하여 사용했었다. (align, v-align) 요즘은 가이드 내에서 최대한 텍스트를 줄이기 위해 선으로 표시하는 방법을 사용한다.

img_align_01Prev 버튼이 Header 기준으로 align=left, v-align=center에 위치함을 점선으로 표시

6. Text

기본 서체 단위는 SP를 사용하며 이의 환산 방식은 DP와 동일하다. 다만 SP를 사용하는 이유는 OS나 어플리케이션 상에서 서체 크기를 변경하여 사용할 때 적용되는 단위가 SP라고 한다. 그러므로 서체는 SP 단위로 기재한다.(편의에 따라 단위는 생략하자.) 색상은 일반적으로 Hex 값으로 입력하니 hex 값을 기재해주면 된다.
행간은 조절 가능하나 자간은 불가능한 것으로 알고 있다.(문자의 가로 비율을 조절할 수 있으나 자간이나 커닝은 조절할 수 없는 것으로 알고 있음) 이런 부분은 OS의 버전업에 따라 변동이 있으므로 체크하여 표현성에 자유로움을 더하자.

img_text_01굵기, 크기, 색상으로 표기되어 있다.
때에 따라 TextView가 고정 크기일 수도, 문자길이에 따른 가변너비일 수도 있다.

디자이너 관점에서 안드로이드 개발자와 협업하기 위한 GUI 개발 가이드를 작성하는 규칙(?)을 끄적여봤다.
사실 위에 쓰여진대로 app GUI 가이드 문서를 만든다는 것은 여간 번거로운 작업이 아닐 수 없다. 그래픽툴로 디자인하는 시간만큼 GUI 개발 가이드를 작성하는 시간이 소요되는 것이 나의 경험이다. 물론 시간을 줄일 수 있는 방법이 있고, 그런 방법을 계속 고민하고 테스트해 보지만 그럼에도 불구하고 GUI개발 가이드 작업은 참 재미없고 지루한 작업이다.
여기서 간과하지 말아야 할 것은 GUI 개발 가이드는 디자이너-개발자의 소통을 돕기 위한 미리 합의한 규칙 정도 되는 것이다. 소통을 위한 도구일 뿐이지 목적은 아니다. 그러므로 처한 상황과 여건에 맞게 최소한의 기재만 해도 GUI 개발하는데 무리가 없게 개발자와 많은 대화와 고민을 하는 것이 가장 중요하다.

  1. 1465979746 2016.06.15 17:35

    제 블로그도 놀러오세요~

출처: http://leipiel.tistory.com/7


개발 협업을 위한 안드로이드 디자인 가이드 #01

디자인의 특성상 디자이너의 작업물이 독립적인 결과물로 산출되는 경우는 거의 없다.
편집, 인쇄 디자이너는 자신의 작업물이 온전하게 결과물로 나오기 위해 인쇄 과정을 이해하는 노력이 필요하다. 직접 인쇄소를 들락날락하며 필름교정과 인쇄교정을 직접하는 것은 인쇄과정이 자신의 결과물에 미치는 영향을 잘 알기 때문이다.

UI 디자인은 어떨까? 모바일은 각 플랫폼 클라이언트 개발자를 통해 구현되고 웹은 퍼블리셔나 웹개발자를 통해 구현된다. (영세한 회사는 웹퍼블리셔가 없는 경우도 많다.) 내가 아무리 포토샵으로 미려하게 디자인을 한다 하더라도 개발에 적용될 수 없는 화면 구성이거나 개발자가 전혀 이해할 수 없는 방식으로 전달하게 된다면 결과물은 형편 없을 것이 뻔하다. 결국 UI 디자인의 최종 결과물은 실제 구동되는 프로그램으로 구현되는 것이기 때문에 개발에 대한 이해도가 꼭 필요하다고 생각한다. (물론 개발을 배우자라는 말은 아니다.)

이미 iOS도 아이폰6와 6+ 덕분에 플렉서블한 디자인 설계를 해야되지만 안드로이드는 이미 태생부터 멀티 디스플레이를 고려하는 디자인을 해야만 한다. 어떻게하면 다양한 End-User의 모바일 디바이스에 디자인 의도를 동일하게 전달할 수 있을까? 라는 질문에서 시작했고 결국 회사의 안드로이드 개발팀을 끈질기게 괴롭히고 닥달한 끝에 회사 내부에서 [디자인/개발 협업을 위한 가이드 규칙]을 정의할 수 있었다. 2013년, 시간을 들여 정의한 덕분에 지금까지 개발간 특별한 불통없이 UI협업이 적절하게 이루어졌다고 생각한다. 그 가이드 규칙과 내가 경험한 노하우를 끄적여 보고자 한다. 물론 이것이 틀린 부분도 있을 것이고(나나 우리 개발동료들이나 우리 수준에 맞는 정리일 뿐 모든 기술적 오류나 정보를 알고 한 것이 아니기 때문) 정답도 아니다. 우연히 들리신 분들은 그냥 참고용으로 보시면 되겠다. 오류 사항은 '이 녀석들, 이런 상식도 몰라'하고 웃어주시면 된다. (욕..욕만은 반사!)


1. 크기의 단위 (Dimension)

DIP (Device Independent Pixels) or DP

안드로이드에서 사용하는 독립적 단위 수치. 이론 상 어떠한 해상도에서도 같은 크기를 보여주는 것이 핵심이라고 한다. (같은 크기를 보여주는 것은 구글의 가이드라인을 잘 지키지 않으므로 큰 의미가 없어져 버렸다.)
안드로이드 초기에는 디자이너들이 dp라는 개념을 전혀 몰랐고 개발 또한 px수치로 개발하는 개발자도 더러 보았다. 하지만 요즘은 왠만한 UI 디자이너들은 한번씩은 들어봤고 물어봤을만한 수치가 아닐까? 개발자에게 전달되는 GUI 개발 가이드 문서에 dp로 기재되었느냐 아니냐에 따라 단편적으로 디자이너의 이해수준을 가늠하는 척도가 되기도 하는 그 dp, 이제는 다 아는 것이지만 안드로이드 UI 디자인을 정리할 때 빠질 수 없는 부분이기에 간단히 정리하고 넘어가자.


변환식 : px = dp x (dpi/160)


1dp 값

 LDPI (120dpi)

 MDPI (160dpi)

 HDPI (240dpi)

 XHDPI (320dpi)

 XXHDPI (480dpi)

 XXXHDPI (640dpi)

 0.75px

 1px

 1.5px 

 2px

 3px

 4px

※ MDPI는 1dp=1px이 되는 기준 dpi


변환식이 있지만 이것은 px 에서 dp 치환을 이해하는 것으로 족하다. 그냥 외우자. 주입식 교육에 찌들어버린 머리로는 외우는 것 이상의 좋은 방법이 떠오르지 않는다.


SP(Scale Independent Pixels)

스케일에 따른 독립 픽셀 단위. 기본적으로 치환 방식은 dp와 동일한 값이다. 다만 sp수치는 OS설정이나 app 설정에서 단계별로 설정한 값으로 변경하면 함께 변하는 유동적인 값이다. 그렇기 때문에 sp는 글꼴 크기를 지정할 때 사용을 권장한다. (특별한 경우 다르게 쓰일 수 있다. 하지만 거의 드물고 적합한 구성은 아니라고 생각한다. 그냥 서체 크기는 sp라고 알고 있으면 된다.)


디자인 제작 시 DP, SP 적용

dp의 개념을 어렴풋이 알지만 실제 포토샵에서 디자인 작업을 할 때 이것을 어떻게 적용해야 하는지 헷갈릴 수 있다. 어찌보면 간단하다. 1dp, 정수 단위로 작업하면 된다. 즉, 1080 x 1920(xxhdpi)의 픽셀 해상도를 기준으로 작업하게 된다면 모든 GUI 구성요소(가이드 시 수치로 전달되거나 조각낸 이미지 리소스 사이즈 모두 다)들이 3px의 배수로 제작하면 된다.

하지만 난 여기서 더 나아가 2dp(xxhdpi 기준, 6px) 단위로 작업하라고 말하고 싶다. 이유는 간단하다. 1dp 단위로 작업할 경우 홀수는 hdpi일 때 픽셀 환산 소수점이 나타나게 된다. 예를 들면 3dp가 xxhdpi에서 픽셀 환산 시 9px이지만 hdpi에서는 4.5px이다. 하지만 물리적 픽셀은 소수점이 존재하지 않는다. 구성요소 픽셀과 디스플레이의 픽셀이 1:1매칭해야 가장 깔끔하고 뚜렷하게 보이게 되는데 그런 점에서 볼 때 디자인 제작 시 2dp의 배수로 제작을 염두하고 만드는 것이 결과적으로 좋다. (hdpi를 사용하는 단말이 거의 존재하지 않을 때는 고려하지 않아도 좋다. 슬슬 고려하지 않아도 되는 추세일지도 모르겠다.)


2. Density & Screen Size

screen ranges

구글은 해상도 및 화면크기가 다른 디바이스들을 범용으로 지원하기 위해 밀도(Density)와 화면크기(Screen size)로 분류하여 기준을 제시하고 있다.


스크린 사이즈 분류

  • small (least 426dp x 320dp)
  • normal (least 470dp x 320dp)
  • large (least 640dp x 480dp)
  • xlarge (least 960dp x 720dp)
밀도(dpi) 분류
  • ldpi (120dpi)
  • mdpi (160dpi)
  • hdpi (240dpi)
  • xhdpi (320dpi)
  • xxdpi (480dpi)
  • xxxhdpi (640dpi)

복잡한 것 같지만 간단하다. 현재 출시된 폰으로 비교해 보자.

 

 디스플레이 크기

 해상도(px)

 스크린 분류

 밀도 분류

 해상도(dp)

 갤럭시 노트 2

 5.55 인치

 1280 x 720

 normal

 xhdpi

 640 x 360

 갤럭시 메가

 6.3 인치

 1280 x 720

 large  hdpi 853.3 x 480

노란색으로 강조한 것을 비교해 보면, 두 단말의 픽셀 해상도는 동일하지만 dp 해상도는 달라지게 된다. 이것은 같은 픽셀 해상도지만 스크린의 차이 때문에 밀도 분류가 다르게 적용되면서 1dp가 갖는 픽셀 수가 달라지기 때문이다.
포토샵으로 동일한 px 도큐멘트에서 디자인 작업을 했다 하더라도 스크린/밀도에 따라 가이드에 적히는 dp는 변할 수 있다는 점 기억하자. 또 스크린 사이즈 몇 부터 몇은 normal, 디스플레이 ppi 몇 부터 몇은 xhdpi와 같이 구글에서 기준을 제시하고 있으나 이것을 지킬 제조사들이 아니다. 특이한 단말은 확보하여 개발의 도움으로 확인하는 절차를 거쳐야 한다.



국내 주요 Display dp 해상도 비교 (phone)

국내 안드로이드 디스플레이 dp 크기 비교

현재 출시된 1440 x 2560px의 QHD 폰들은 정리가 되진 않았지만 dp환산으로는 360 x 640dp이기 때문에 고려하고 보면 될 것이다. 물론 이 중 현재는 거의 사용하지 않는 크기의 디스플레이도 있겠지만 전체적인 이해를 위한 것이니 이 점은 염두하자. 실제 적용할 때는 app의 적용 대상 단말이라든가, 디바이스별 점유율 등을 고려해야 하겠다. (현재는 320 x 480px를 가진 단말을 지원하는 것이 절대적인 비효율일 것이다.)


디자인 제작 시 영역의 가변성 고려

위의 비교표처럼 각기 다른 크기의 디스플레이를 다 만족시킬 수 있는 UI가 되려면 디자이너가 그래픽 작업을 시작하기 전에 가변성을 고려한 유연한 화면 설계를 미리하고 진행해야 한다.
위의 모든 디스플레이를 고려한다는 전제로,
width : 최소 320dp ~ 최대 400dp (차이 값 80dp)
height : 최소 480dp ~ 최대 640dp (차이 값 160dp)

가변성 예시가변적 화면구성 예시. 각 화면의 주요 구성요소들이 전체 view의 변화에도 잘 대응한다.

높이만 예시로 보자. 전체 view 에 있는 모든 구성요소가 최대 높이 640dp, 최소 높이 480dp에도 모두 담길 수 있게 디자인해야 한다. 만약 구성요소들의 높이가 480dp를 넘었다면 640dp에서는 정상적으로 나오나 480dp에서는 찌그러져 나올 것이다. 또한 전체 view 크기의 변화에도 구성요소들이 자연스러운 위치로 변화하는 것도 고려해야 한다. 위의 예시를 보면 140dp 높이를 가진 view는 헤더 바로 밑에 고정 간격 값, 하단 인증 버튼은 bottom에 간격 값으로 위치한다. 가운데 텍스트 view 영역 중앙에 위치하면서 가변영역을 구성한다. (이에 대한 자세한 설명은 다음 포스팅에 상세 설명할 예정이다.)
이런 가변성을 미리 고려하고 디자인 작업을 진행을 하게되면 개발파트로 넘어가 app에 적용된 후 멀티 디바이스의 문제로 다시 재작업하는 일은 없어질 것이다.


3. 리소스 폴더 구조

화면들을 포토샵으로 제작하는 작업이 완료되면 각각의 구성요소 리소스를 분할하고 가이드를 제작하는 과정을 거친다. 최종적으로 이렇게 잘라낸 이미지 리소스로 내가 공들여 만든 GUI를 표현해 주고 있다. 그럼 과연 그 리소스들이 어떤 구조로 쓰이는지 이해하고 제작하고 있는 것일까?
디자이너가 완벽하게 이해할 이유는 없겠지만 drawable 폴더의 구조와 관계성을 어느정도 이해하는 것은 필요하고 생각한다.

밀도 분류

  • drawable-nodpi
  • drawable-ldpi
  • drawable-mdpi
  • drawable-xhdpi
  • drawable-xxhdpi

(tvdpi나 xxxhdpi가 있지만 우선 제외하자.) 폴더명에서 잘 드러난다. 각 dpi별로 이미지 리소스를 구분하여 넣는다. 해당 dpi에 알맞는 이미지 리소스를 넣게 되면 단말별로 자신에게 맞는 리소스만을 불러 사용하기 때문에 이미지 최적화나 메모리 관리 등 여러 장점들이 존재한다. 다만 모든 리소스들 갖게 되면 app 용량이 늘어날 수 있으니 app의 목적이나 여러 환경을 고려하여 판단해야 하겠다. 

아마 대부분 가장 큰 사이즈만 작업을 한다. 어차피 단말에서 알아서 리사이징을 하기 때문이다. 이미지 리소스가 xxhdpi 폴더에만 있는 app을 xhdpi 단말에서 구동하게 되면 자체적으로 xxhdpi에 있는 리소스를 66.6% 다운 스케일링하여 표출한다. 그렇기 때문에 큰 사이즈 리소스만 갖고 있어도 표현의 문제는 없다고 보여진다. 다만 불필요하게 큰 이미지를 축소해 보여지므로 메모리 관리는 허술할 여지가 있다. (요즘 폰들은 메모리 걱정할 이유가 없나?)

또 하나 기억할 것은 drawable 폴더를 읽는 순서가 있다. 보통 자신에게 맞는 dpi 폴더를 먼저 읽고 해당 리소스가 없으면 가장 큰 dpi폴더에서부터 작은 폴더 순으로, 가장 마지막에 nodpi로 읽는다. 단말별로 조금씩 다르다는 어느 개발자 분의 블로그에서 읽은 적이 있었는데 거의 대부분은 언급한 순서로 결과가 나왔다.
이런 성질을 이용한 유용한 Tip이 있다. 경험 상 큰 리소스를 낮은 dpi 단말에서 표현해 줄 때 큰 문제는 없으나 작은 이미지, 얇은 표현의 이미지(블릿 아이콘이나 light 서체로 만든 BI) 등 미세한 다운스케일링이 필요한 이미지들은 깨짐 현상이 발생한다. 이 현상은 iOS에서도 마찬가지로 나타나는데, 내 판단에는 실시간으로 소프트웨어로 이미지 스케일링 프로세스의 한계라고 보여진다. 이런 것은 각 사이즈별로 작업하여 갖고 있는 것이 좋다. 따라서 낮은 dpi 단말에서 깨짐이 우려되는 리소스들만 작업하여 각 dpi 폴더에 넣어준다. 그러면 우선순위로 그 리소스들은 dpi에 맞게 표현이 되고 나머지 리소스는 가장 큰 리소스를 사용하게 된다.

nodpi 폴더에 있는 리소스는 dpi에 상관없이 100% 사이즈로 표현한다. 즉 dpi와 관계없이 이미지 픽셀을 1:1매칭해서 표현한다. 활용방법은 드물지만 필요에 따라 유용하게 사용할 수 있으니 기억할 수 있도록 하자.

  • drawable-normal-xhdpi
  • drawable-large-xhdpi
  • drawable-normal-land-xxhdpi
폴더명만 봐도 어떤 상태에서 해당 폴더의 리소스를 사용하는지 알 수 있다. 이렇게 drawable 폴더는 디바이스의 상태에 따라 다양하게 분류하여 알맞게 사용할 수 있음을 기억하고 활용하자.


1.안드로이드의 주요 단위

용어 및 단위설명
Pixel화면상의 픽셀
Resolution픽셀 단위의 화면 크기.
예를 들어, 갤럭시노트 10.1의 해상도는 1280*800인데, 이는 픽셀이 1280개 및 800개임을 의미한다.
DPI (Dots Per Inch : 밀도)

물리적인 1 인치 당 포함되는 픽셀 개수. 예를 들어, 160 DPI는 1인당 픽셀이 160개 포함된다는 것을 의미한다.
주요 DPI는 다음과 같다.

  • - ldpi (low) : 120 DPI
  • - mdpi (medium / baseline) : 160 DPI
  • - TVDPI : 213 DPI
  • - hdpi (high) : 240 DPI
  • - xhdpi (extra high) : 320 DPI
  • - xxhdpi (double extra high) : 480 DPI

mdpi에서 1DPI = 1PX

DIP (Density Independent Pixels) 또는 DP밀도 독립 단위로, 장치의 밀도에 상관없이 물리적으로 (거의) 동일한 크기를 갖는다.
스크린 크기물리적인 크기의 종류를 나타낸다. 다음의 4종류가 존재한다
  • - X-Large: 주로 10.1 인치 이상의 디바이스
  • - Large: 주로 5인치 이상의 디바이스
  • - Normal: 3인치에서 5인치 미만의 사이의 디바이스
  • - Small: 3인치 미만의 디바이스
SP (Scale Independent Pixels)스케일 독립 픽셀 단위로 , dip와 유사하며, 글꼴 크기를 지정할 때 주로 사용된다.

디바이스별 테스트

갤럭시노트 10.1옵LTE 2넥서스7옵Q
해상도 (PX 단위)800 x 1280720 x 1280800 x 1280480 x 800
해상도 (DP 단위)800 x 1280360 x 640600 x 961320 x 533
DPI160 DPI320 DPI213 DPI240 DPI
스크린 크기X-LargeNormalLargeNormal
밀도 비율 (DPI / 160)121.3312501.5
Keypoint
- 안드로이드의 기준 DPI는 중간 수준인 160 DPI이다.
- 160 DPI를 기준으로 DPI가 크면 밀도가 높아지고, DPI가 작으면 밀도가 낮아진다.
- 또한, 160 DPI인 경우 밀도독립 단위인 DP(DIP)와 픽셀이 같은 크기를 갖는다.
즉, 160 DPI에서 1 DP는 1 PX이 된다.

2.DIP (Density Independent Pixels) 또는 DP

DP 단위는 독립적인 크기를 나타낼 수 있도록 하는 단위이다. 이론상 어떠한 해상도에서도 같은 크기를 보여주는 것이 핵심 개념이다.

표준이 되는 화면크기를 중심으로 보다 큰 화면에서는 지정된 배율로 크기를 늘려주고, 작은 화면에서는 지정된 배율로 크기를 줄여준다.

DP 계산법
px = dp + (dpi / 160)

    * dp 와 dip (Density Independent Pixel)

    말 그대로 실제 픽셀에 독립적인 단위로 안드로이드 폰의 다양한 해상도를 지원하기 위해 만든 단위이다. 큰 화면이든 작은 화면이든 같은 크기로 나타나게 되어있다. (그러나 적용해보면 미세하게 다르다..) 즉, 화면이 작은 폰에서 10원짜리 만하게 나타난다면 화면이 큰 폰에서도 10원짜리 만하게 나타나도록 되어있다. dp(dip)와 px간의 변환을 하는 방법은 아래와 같다.


    dp(dip)와 px간의 변환

    px = dp * (160 / dpi) = dp * density

    dp = px / (160 / dpi) = px / density


    여기서 density는 density = dpi / 160 계산 한다.

    ldpi : density = 0.75

    mdpi : density = 1

    hdpi : density = 1.5

    xdpi : density = 2


    ※ dpi와 density 구하는 방법


    DisplayMetrics outMetrics = new DisplayMetrics();

    getWindowManager().getDefaultDisplay().getMetrics(outMetrics);

    int dpi = outMetrics.densityDpi;

    float density =  outMetrics.density;


    ※ java코드에서 dp를 px로 바꾸는 방법

    java코드에서 density또는 dpi를 이용해서 dp를 px로 변환할 수도 있지만 TypedValue를 이용해서 다음과 같이 구할 수 있다.


    public int dpToPixel(int dp){

    int px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DP, context.getResources().getDisplayMetrics());

    }


    출처: http://i5on9i.blogspot.kr/2013/10/messenger.htmlhttp://rosaria1113.tistory.com/242

    Messenger 개념

    Handler 가 하나의 process 내에서 여러 Thread 들이 통신할 때 사용할 수 있다. 그런데 이 Handler 를 process 들끼리의 통신에 사용할 수는 없다. 이 때 사용하는 것이 Messenger 라는 녀석이다. 이 녀석을 이용해서 Handler 를 감싸면, 중간에 Marshaling 같은 부분들을 (Parcel 같은.) Messenger 가 해주고, 프로그래머 입장에서는 process 사이에서도 Handler 와 같은 interface 를 사용할 수 있게 돼서 프로그래밍이 좀 더 편하게 된다.

    그러니까 정리해서 얘기하면 process 사이에서 Handler 를 사용할 수 있게 해 주는 녀석 정도로 이해하면 될 듯 하다.

    아래 좀 다른 형태의 설명이 있으니 참고하세요.

    Messenger 는 특정 Handler 를 감싸는 클래스입니다. 가장 큰 특징은 바로, 이 Messenger 가 Parcelable 인터페이스를 구현하고 있다는 점 입니다. Handler 자체는 다른 프로세스로 넘겨 줄 수 없지만, 이를 Messenger 로 감싸면, 해당 Handler 로 원격에서 메세지를 전할 수 있는 Messenger 인스턴스를 생성할 수 있고, 이 Messenger 인스턴스는 한 프로세스에서 다른 프로세스로 이동 할 수 있습니다. 그래서, 복잡한 AIDL 을 정의하지 않고도 간편하게 Message 에 기반한 IPC 작업을 수행할 수 있습니다. 예제 코드를 살펴 보도록 하지요.

    출처 : 안드로이드 Handler 이야기 - Messenger Service - |작성자 휴우, 2011/08/18



    Messenger 예제

    간단한 예제는 아래에서 찾을 수 있습니다.
    [Android/안드로이드] Messenger 를 이용한 IPC.( Service binding ), 2012/05/21


    이 Messenger 를 이용해서 Service 와 Activity 가 통신하는 방법은 AIDL 에서 설명한 내용과 같다. 다시 정리하면 아래와 같다.

    • Service -> Activity
      Service 와 bind 되고 나서 Service 에 Activity 에서 생성된 Messenger() 를 Service에 넘겨줘서 Service 가 이것을 이용해서 Activity 에 원하는 Message 를 전달할 수 있게 해준다.
    • Activity -> Service
      Service 는 onBind() 에서 Message class type 을 넘겨준다(return). 이 Message 가 Bind 될 때 onServiceConnected 로 넘어오는 Binder 이다. 이녀석을 이용해서 Activity 는 Service 에 원하는 Message 를 전달하게 된다.
    • 코드
    Messenger 는 특정 Handler 인스턴스의 리퍼런스를 갖고 있으며, 이를 이용하여 해당 Handler 로 메세지를 보낼 수 있습니다. 이를 이용하여, 프로세스간 메세지 기반 커뮤니케이션을 수행할 때 활용될 수 있습니다. 

    [출처] 안드로이드 Handler 이야기 - Messenger Service - |작성자 휴우


    A 프로세스 -> B 프로세스로 Handler 인스턴스를 그냥 넘길수는 없다. Parcelable 인터페이스를 구현하고 있는 Messenger로 감싸면 전송이 가능하다.  (Parcelable 인터페이스 : 객체 직렬화를 위한 인터페이스, 생성된 객체를 파일이 저장하거나 스트림을 통해 보내는 것이 가능


    아래 예제는 2개의 Android Application을 생성하고 TestMessager2 어플에서 TestMessenger1에 있는 mHander로 메시지를 보내는 방법이다.

    mHandler(service)에서 Client(호출하는곳)으로 응답 메시지를 보내야 하는 경우 

      1. Client 에 핸들러를 생성, Messenger로 만든다.

      2. replyTo 필드를 사용해서 1번으로 만들어진 인스턴스를 추가

      3. Server는 수신된 Message의 replyTo 필드에서 Client Messener를 꺼내서 응답 메시지를 전달


    TestMessenger1

    MessengerService.java

    public class MessengerService extends Service {

    private static final String TAG = "MessengerService";

    private Handler mHandler = new Handler() {

    public void handleMessage(Message msg) {

    switch (msg.what) {

    case 1:

    Log.d(TAG, "mHandler.case 1");

    break;

    }

    }

    };

    public void onCreate() {

    super.onCreate();

    Log.d(TAG, "MessengerService onCreate()");

    }


    @Override

    public IBinder onBind(Intent arg0) {

    Log.d(TAG, "MessengerService onBind()");

    return new Messenger(mHandler).getBinder();

    }

    }


    AndroidManifest.xml

    <service 

    android:name="com.example.testmessenger1.MessengerService"

    android:process=":remote">

    <intent-filter >

    <action android:name="com.example.testmessenger1.MessengerService"/>

    </intent-filter>

    </service>

    AndroidManifest.xml을 위와 같이 수정해주지 않으면 TestMessenger2에서 

    "java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.example.testmessenger1/.MessengerService }" 이 뜬다.


    TestMessenger2

    MainActivity.java

    public class MainActivity extends Activity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    Button btn = (Button)findViewById(R.id.btn);

    btn.setOnClickListener(new OnClickListener() {

    @Override

    public void onClick(View v) {

    ComponentName cn = new ComponentName("com.example.testmessenger1",

        "com.example.testmessenger1.MessengerService");

    Intent intent = new Intent();

    intent.setComponent(cn);

                

    ServiceConnection conn = new ServiceConnection() {

    @Override

    public void onServiceDisconnected(ComponentName name) {}

    @Override

    public void onServiceConnected(ComponentName name, IBinder service) {

    Messenger messenger = new Messenger(service);

    Message msg = Message.obtain(null, 1);

    try {

    messenger.send(msg);

    } catch (RemoteException e) {

    }

    }

    };

    bindService(intent, conn, Context.BIND_AUTO_CREATE);

    }

    });

    }

    }

    Context.BIND_AUTO_CREATE flag를 사용해서인지 TestMessenger1이 실행되지 않아도 호출 된다.




    주의: AIDL 은 오직 Client 의 요청의 결과가 즉시 return 되어야 하기 때문에, Service 단에서 동시에 여러 스레드를 통해 개별 요청을 처리해야 하는 경우에만 필요합니다. 만일 그렇지 않은 경우에는 Messenger 클래스를 활용하세요.

    [출처] 안드로이드 Handler 이야기 - Messenger Service - |작성자 휴우


    Messenger 이외의 방법

    Service 와 Activity 간 통신을 하는 좋은 방법
    Android Example: Communication between Activity and Service using Messaging - Philipp's Tech Blog


    Broadcast 를 이용해 Service 에서 Activity 로 data 전달
    Android Coding: Pass data from Service to Activity


    AIDL
    위의 방법 외에 Service 에서 Activity 로 원하는 정보를 전달하는 방법은 aidl 을 사용할 수 있다. aidl 사용법 을 참고하자.



    Messenger 로 Object 전달

    Message.obj

    [see also. 1] 에 보면, seperate processes 에서는 Messenger 의 obj 전달이 불가능하다고 하는 듯 하다. 하지만 하나의 process 에 Service 와 Acitivity 가 같이 있는 경우라면 Message.obj 에 원하는 object 를 넣고 전달하는 것이 가능하다.

    Bundle

    [See Also. 1] 에 따르면, seperate process 라면 Message.obj 를 사용해서 원하는 object 를 넘기는 것은 불가능하고, 대신에 Bundle 을 사용해야 한다고 한다.

    Handler 에서 Bundle 은 마치 hashMap 처럼 사용할 수 있다. key 와 value 를 이용해서 값을 유연하게 넣을 수 있다. 그리고 이 녀석을 Message 에 실어서 Handler 로 보내면 된다.

    Bundle 관련 예제 : http://mobileorchard.com/android-app-developmentthreading-part-1-handlers/


    'Programing > Android' 카테고리의 다른 글

    개발 협업을 위한 안드로이드 디자인 가이드 #01  (0) 2016.05.08
    Resolution and DP (Density Independent Pixels)  (0) 2016.05.07
    Messenger  (0) 2016.05.06
    BroadcastReceiver  (0) 2016.05.06
    ANR (Application Not Responding)  (0) 2016.05.06
    Process and Thread  (0) 2016.05.04

    출처: http://cafe.daum.net/_c21_/bbs_search_read?grpid=1MWA2&fldid=aAfL&datanum=102&docid=1MWA2aAfL10220110915161750

    1. Broadcast Receiver에 대해서

     

    Android의 4가지 Component 중 가장 쉬운 것중 하나가 바로

    Broadcast Receiver이다.

    Broadcast Receiver는 각각의 Component들 간에 메시지를

    전달할 수 있는 방법을 제공한다.

     

     

    우선 여러 패키지에서는 귀를 기울일만한 방송에 대해 Receiver라는 녀석을 등록해 둔다.

    이 Receiver는 자신이 관심을 가지는 방송을 듣게되면 동작하게 되는 것이다.

     

    그림으로 보자면

    아래에 1번과 같이 특정 패키지 Component에서 sendBroadcast(메시지)를 통해서

    전역 방송을 날린다. 이때 방송은 "Test1"이라고 가정하자.

    그후 이미 등록된 Receiver 중 3번에 해당하는 receiver가 그 메시지에 관심을 가지고 있으므로 동작하게 될 것이다.

     

     

    여기서 방송이라는 표현을 썼는데 이 것이 바로 Intent이다.

    앞서 Activity를 설명하면서 Intent에 대해서 충분히 설명을 하였다.

    Andriod를 하면서 Intent는 아주 중요하다. (조금이라도 이해가 안되었다면 앞의 강좌를 다시보고 또 보자.)

    앞으로 계속 배우게 될 BroadcastReceiver, Service에서 계속 활용된다.

    Activity에서 설명한 Intent에 대해 이해하면 모두 같은 방식으로 사용되니 한번만 잘 이해하면 된다.

    (주변에 Intent에 대해 정확히 이해하는 사람이 많이 없어서 계속 말하는 것이다. ^^;)

     

    어쨌든 참 글로 쓰면 왜 쉬운 내용도 어렵게 전달이 될까? 짜증이 난다. ^^;

    카페에 누군가 동영상 강좌로 해 주면 안되겠냐는 글을 보았다.

    어쩔때는 정말 그러고 싶다. ㅋㅋㅋ

     

    자 계속해서 Receiver에는  두가지 종류가 있다.

    바로 정적 Receiver와 동적 Receiver이다.

    별로 어렵지 않으니 계속 강좌를 지켜보길 바란다.

     

     

    1.1 정적 Receiver

     

    정적 Receiver란 말 그대로 Receiver를 고정해서 등록해 놓고 원하는 방송에

    반응하는 Receiver를 말한다.

     

    역시 개발자는 소스를 보고 이해하는 것이 빠를 것이다.

    아래의 패키지로 이해해 보자.

    두 가지 패키지를 만들 것이다.

     

    우선 첫번째 패키지는 방송을 하는 패키지이다.

     

    아래에 1번과 같이 Intent를 하나 생성하고

    Intent에 방송할 내용을 담는다.

    방송할 내용 역시 Action이 사용된다. Activity와 똑같다. ㅎㅎㅎ

    setData()를 통해 Intent Filter에 매칭될 Scheme을 하나 넣어 보았다.  왜?  그냥.... -  _-;

    (BroadcastReceiver는 모든 패키지가 받을 수 있다. 그러므로 꼭 원하는 패키지 Receiver에게 전달하게 하려면

     적절한 Intent Filter 를 사용하는 것이 좋다.)

    1번에서 방송하게 될 Action을 기억해 두자. "android.intent.action.SUPERSK"

     

     

    자 이제 두번째 패키지 이다.

     

    당연히 이 패키지는 방송을 듣고 반응하는 리시버 이다.

     

    아래는 AndroidManifest.xml에 리시버를 하나 등록하고 있다.

    아래는 AndroidManifest.xml에 리시버를 하나 등록하고 있다.

    아래는 AndroidManifest.xml에 리시버를 하나 등록하고 있다.

    3번 반복한 것은 ^^ 오타가 아니다.

    왜냐하면 AndriodManifest.xml에 고정해서 리시버를 등록하기 때문에 정적 Receiver라고 말하는 것이다.

    이해가 되는가 안되면 다음에 나올 동적 Receiver까지 보면 이해가 될 것이다.

    일단 ^^/ 계속...

    위의 Intent Filter는 이전 Activity에서 Intent를 이해했다면 쉽게 볼 수 있을 것이다.

    2번을 보면 Action으로 첫번째 패키지가 보내는 방송에 귀를 기울이는 Action 일 것이다.

     

    계속해서 그 방송을 듣게 되면 처리되는 코드는 아래와 같다.

    해당 방송이 오면 Toast를 하나 띄울 것이다.

    위의 onReceive( Context context, Intent intent) 의 인자인

    intent로 방송을 보낼때 전달되었던 Intent가 그대로 전달받게 된다.

    바로 이 Intent로 원하는 데이터를 Intent Extra에 담아서 보내면 된다.

     

    자 이제 실행해 보자.

    첫번째 패키지에 버튼을 누르면

    2번과 같이 두번째 패키지가 방송을 듣고 Toast를 하나 보여 준다.

    쉽지 않은가?

     

    테스트 패키지 소스는 아래에 첨부한다.

     

    첨부파일 Broadcast1.zip

    첨부파일 Receiver1.zip

     

     

    1.2 동적 Receiver

     

    동적 Receiver는 AndroidManifest.xml에 Receiver를 등록하지 않는다.

    위에서 만든 패키지 중 방송을 보내는 패키지를 그대로 활용하고,

    아래의 패키지를 추가로 만들어 보자.

    위에서 왜 Activity를 하나 만들었을까?

    궁금증을 가지고 계속 아래를 보자.

     

    Activity의 내용이다.

    위에서 Activity의 생명 주기중 onCreate에서 Receiver를 등록하고 있다.

    차근차근 보자.

    1번에서 IntentFilter를 하나 생성하여 방송에 매칭될 Action과 Data의 Scheme을 등록하였다.

    2번에서 BroadcastReceiver 객체를 하나 생성한다. 객체 생성과 동시에 onReceiver() 함수를 구현해 주면 된다.

                차후 방송이 오게 되면 바로 onReceiver()함수가 호출될 것이다.

    3번에서 해당 BroadcastReceiver 객체를 등록한다. registerReceiver()함수는 바로 receiver 객체를 등록하게 해 준다.

               당연히 registerReceiver() 인자로 등록될 리시버 객체와 intentFilter를 넣어주면 된다.

     

    자 특정 Component에서 BroadcastReceiver 객체를 동적으로 생성하여 리시버를 등록 하였다.

    이 것이 바로 동적 Receiver라고 하는 이유다.

     

    자 잘 동작되는지 보자.

    1번과 같이 지금 만든 동적 리시버 패키지를 꼭 실행해 둬야 한다. (이유가 있다.)

    2번과 같이 꼭 Home Key를 눌러서 다시 홈으로 가자.

    3번에서 방송을 날릴 패키지를 실행하자.

    4번의 버튼을 눌러보면 동적 Receiver가 동작하고 Toast를 하나 띄울 것이다.

     

    자 다시 하나면 더 테스트를 해 보자.

    아래와 같은 시나리오로 해야한다.

    1번에서 동적 리시버 패키지를 실행하자

    2번과 같이 Back Key를 눌러 해당 패키지를 종료해 보자.

    3번과 같이 홈에서 방송을 보낼 패키지를 실행하고

    4번과 같이 방송 버튼을 눌러 Broadcast를 날려 보자.

    !!! Toast가 안뜨지 않는가?

    이것이 바로 동적 리시버의 특징이다.

    동적 리시버는 자신을 등록한 Component의 생명 주기가 끝나면 사라진다.

    위에서는 Activity에서 리시버를 등록 했으므로 Activity를 Back Key를 이용해서 종료하게 되면

    리시버도 사라지는 것이다.

     

    Activity의 생명 주기는 아래와 같다.

    즉 onDestroy()까지 타면 Receiver는 없어 진다.

    꼭 이해하도록 하자.

     

    동적 리시버에서 꼭 하나더 알아야 할 것이 있다.

    리시버를 등록할때 registerReceiver()라는 함수를 썼다.

    그렇다면 분면 unregisterReceiver()라는 함수도 있지 않겠는가 그렇다.

    registerReceiver()로 등록된 Receiver는 unregisterReceiver() 함수를 이용해서 등록해제 할 수 있다.

    즉 리시버를 더 이상 사용할 필요가 없을 때는 unregisterReceiver() 함수로 해제하면 된다.

    또한 한번 등록된 리시버를 중복해서 다시 등록하는 일이 없도록 하자.

    만일 두번 등록이 되면 중복된 리시버 모두 동작하기 때문이다.

    뿐만아니라 unregisterReceiver()를 통해 해제치 않으면

    메모리에 계속 잡고 있는 메모리 누수가 발생한다.

    꼭 해제해 주도록하자.

     

    적절한 위치에 등록과 해제 소스를 넣어 두자.

    예를 들어 onCreate()에 등록하고 onDestroy()에 해제 한다던가...

     

    테스트 패키지 소스는 아래에 첨부한다.

     

    첨부파일 Receiver2.zip

     

     

    1.3 동적 Receiver와 정적 Receiver는 어떤 차이가 있을까?

     

    android를 처음 접할때 이 점이 참 궁금했다.

    그 이유는 참 간단했다. 하지만 글로 설명해야 하는데 잠시 두렵기도 하다...  =_  =;;

    일단 정적 리시버는 한번 등록하고 계속 유지할 수 있다는 장점이 있지만

    해제가 어렵다는 단점이 있다.

    다른 단점을 이해하기 위해서는 동적 리시버의 장점을 설명해야 한다.

     

    동적 리시버는 등록한 component 생명 주기가 끝나면

    더이상 동작하지 않는다는 단점이 있다고 했다. 물론 계속 유지 하고 싶다면 정적 리시버를 쓰면 된다.

    동적 리시버는 내가 생각하는 아주 편리한 장점 두가지가 있다.

     

    첫번째는 바로 시스템에 큰 부하를 주지 않는 점이다.

     

    매분마다 발송되는 시스템 Action "android.intent.action.TIME_TICK" 이 있다

    이 것은 AlarmManagerService란 서비스가 매 분마다 발송한다.

    만일 정적 리시버로 해당 Action을 등록했다면 매분마다 계속 이벤트를 받게 되고,

    매 분마다 해당 이벤트에 대한 처리를 할 것이다.

    이는 시스템 측면에서 전체적이 성능을 저하시킬 것이다.

    하지만 동적리시버는 한 Component의 생명주기 내에서만 해당

    이벤트를 받으므로 큰 부하를 주지 않는 것이다.

     

    참고로  "android.intent.action.TIME_TICK" 이벤트는 정적 리시버로는 받을 수 없다.

    위에서 말한 이유 때문이다. 즉 Android 시스템에서 시스템에 부하를 주는

    경우를 당연히 두지 않는다.

    이렇게 동적 리시버에서만 Action을 받을 수 있도록 하는 Intent Flag는

    "FLAG_RECEIVER_REGISTERED_ONLY" 이다.

    ( FLAG_RECEIVER_REGISTERED_ONLY는

       "17. Broadcast Receiver에 대해서 - IntentFlag 정리"

      강좌에서 설명하니 넘어가도록 하자.)

     

    두번째는 다른 Component 내에 소스가 존재한다는 것이다.

    리시버를 등록할때를 생각해 보자.

     

    뭐 아래와 같다고 하자. 

     

        public class testActivity extends Activity

        {

            private   int        mCount = 0;

     

            public void onCreate(Bundle b)

            ....

     

            mReceiver = new BroadcastReceiver()

            {

                  pubilc void onReceiver(Context context, Intent intent)

                  {

                

                  }

             };

             registerReceiver( mReceiver , intentFilter);

     

            .....

        }

    위에서 Activity 내에 Receiver가 달려 있다.

    여기서 mCount 라는 멤버 변수로 접근해 보자.

     

     

        public class testActivity extends Activity

        {

            private   int        mCount = 0;

     

            public void onCreate(Bundle b)

            ....

     

            mReceiver = new BroadcastReceiver()

            {

                  pubilc void onReceiver(Context context, Intent intent)

                  {

                        mCount  = 20;

                  }

             };

             registerReceiver( mReceiver , intentFilter);

     

            .....

        }

    위와 같이 쉽게 mCount로 접근할 수 있다.

    너무 당연한 얘기 아닌가?

    즉 Activity 내에 모든 멤버 객체에 접근하고

    그 객체를 참조할 수 있는 것이다.

     

    정적 Receiver를 생각해 보자.

    정적 Receiver는 특정 Component에 포함되어 있지 않으므로

    Receiver에서 사용하고 싶은 특정 Component의 멤버변수를 전혀 접근 할 수 없다.

    (물론 Application 객체를 이용하여 접근할 수는 있다. -   _-; 혹시 궁금한 사람들은

     옆에 링크로 이동하여 Activity의 name 속성 부분을 보라.  여기로 링크  )

     

    이해 되었는가?

    이해 되지 않는 부분은 늘 질문하라...ㅎㅎㅎ

     

    1.4 Receiver 꼭 알아야 할 것

     

    Activity와 마찬가지로 Receiver 또한 ActivityManagerService가 처리한다.

    아래의 그림을 보자.

    1번과 같이 sendBroadcast() 호출하면

    2번과 같이 ActivityManagerService가 활성화 되어야 할 등록된 Receiver를 호출해 준다.

    만일 여러개의 리시버가 등록되어 있다면 순서대로 활성화 시킨다.

    3,4,5번을 보면 등록된 리시버가 3개이다.

    만일 Receiver1이 처리되는 시간이 3초라면 다음 처리될 Receiver2는 3초후에 활성화 될 것이다.

    또한 Receiver2이 6초라는 시간이 소모되면 Receiver3번은 총 9초후에 활성화 될 것이다.

     

    여기서 말하고자 하는 것은 리시버에 대한 처리는 최대한 빨리 끝내는 것이 바람직하다.

    ^^ 만일 처리시간이 오래 걸리게 되면 Activity와 마찬가지로 ANR이 발생된다.

     

    아래의 테스트 코드로 이해해 보자.

    리시버를 하나 등록했다.

    해당 리시버는 12초의 지연을 주 예이다.

    어떻게 될까?

     

    아래와 같이 실행해 보자.

    눈에 에러는 보이지 않으나 에러가 발생될 것이다.

     

    아래를 보고 이해해 보자.

    로그를 보자.

    5번을 보면 리시버가 timeout이 발생되고

    6번에서 ANR을 발생시켜 해당 리시버를 죽이기 위해 리시버가 포함된 Process를 죽여 버린다.

     

    즉 시스템에서도 리시버에 처리 지연을 두고 보지 않는 다는 말이고

    ActivityManagerService는 다른 작업을 하기 위해 절대 기다려 주지 않는 다는 것이다.

     

    아래에 Framework 소스를 잠시 보자.

    리시버의 Timeout은 정확히 10초임을 알 수 있다.

    그렇다고 10초 내에 끝내라는 것이라고 이해하면 안된다.

    최대한 간단한 작업만을 하라는 것이다.

     

    리시버는 Activirty 처럼 UI가 존재하지 않는다.

    그러므로 Background에서 돌아가는 component이다.

    다음에 배우겠지만 service component 역시 background에서 돌아간다.

    하지만 둘을 같이 보면 안된다.

    Receiver는 제약 사항이 많기 때문이다. 지금 그 제약 사항을 설명하는 것은 어렵다.

    Service를 배우지 않았기 때문이다. 차후에 다시 설명 하겠다.

    (참고로 몰라도 되지만 ... Receiver에서는 bindService가 허용되지 않는다.)

     

    어쨌든 기억하라. ~!!!!!

     

    리시버는 간단한 작업만 하도록 하자.

    예를 들어 다른 component를 활성화 한다던지,

    (startActivity or startService 등)

    DB에 값을 변경한다던지... 꼭 구구절절히 말하기는 힘들지만 어쨌든 onReceiver 함수 내에서만 처리될 수 있는

    간단한 작업만을 하자.

     

    간혹 리시버에 네트워크 작업등을 처리하려는 사람을 보았다.

    그러지 말자. ^^;;;

    'Programing > Android' 카테고리의 다른 글

    Resolution and DP (Density Independent Pixels)  (0) 2016.05.07
    Messenger  (0) 2016.05.06
    BroadcastReceiver  (0) 2016.05.06
    ANR (Application Not Responding)  (0) 2016.05.06
    Process and Thread  (0) 2016.05.04
    Processes and Application Life Cycle  (0) 2016.05.04

    출처: http://sunphiz.me/wp/archives/447

    ANR(Application Not Responding)

    ANR이란 무엇인가?

    Application Not Responding의 약자이다. 단어 그대로, 어플리케이션이 응답하지 않는 경우 안드로이드 시스템에서 보여주는 에러이다. 메인 스레드(일명, UI 스레드)가 일정시간 동안 잡혀 있으면 발생한다.

    언제 ANR이 발생하는가?

    안드로이드 운영체제는 홈페이지 정리가 매우 잘 되어 있다. ANR의 경우는 여기에 정리되어 있는데,

    • 터치를 통한 사용자 입력이 5초 내에 처리되지 않았을 때
    • 브로드캐스트가 10초 내에 처리되지 않았을 때

    가 나와있다. 하지만 여기에 나오지 않은 경우가 하나 더 있다.

    • 서비스가 20초 내로 처리되지 않을 때

    안드로이드는 UI가 없는 컴포넌트인 브로드캐스트 리시버와 서비스도 메인 스레드에서 돌아가기 때문에 당연히 서비스에서도 시간 소모성 작업을 하는 경우 ANR이 발생할 수 있다.

    왜 ANR이 발생하는가?

    위의 ANR이 발생하는 조건을 보면 알겠지만, 앱이 무한 루프에 빠지거나 OOM(Out Of Memory)가 나야 ANR이 발생하는 것이 아니다. 시간이 좀 걸리는 처리를 해도 ANR이 충분히 발생할 수 있다. 그래서, 나처럼 자바프로그래밍에서 안드로이드로 온 사람에게는 한참동안 이해가 안될 수도 있다.

    ANR이 필요한가?

    그럼 이해가 안되는 것이 굳이 있어야하는 이유가 무엇인가? 내 생각에 이 부분을 이해하기 위해서는 사용자가 어떤 경우 문제가 있다고 느끼는가에 대한 것을 먼저 생각해야한다. 지금 대부분의 어플리케이션에서 사용하고 있는 프로그레스바를 생각하면 쉽다. 사용자에게 ‘움직이고 있다.’라는 사인을 주면 사용자는 기다린다. 하지만, 일명 먹통이 되면 사용자는 문제가 있다고 판단하여 전원을 끈다던가 다른 자신이 할 수 있는 해결책을 시도하는 것이다. 모바일 OS는 화면이 작아, 대부분(테블릿은 화면이 크고, 안드로이드에서 지원하는 프레그먼트는 한 화면에 여러개의 액티비티를 동작하게 할 수도 있다.)은 전체 화면을 차지한다. 이 때 화면이 정지한다면 정상적으로 동작하고 있음에도 문제가 있다고 느낄 수 있는 것이다.

    그럼 어떻게 ANR을 관리해야 할까?

    안드로이드 개발자 사이트를 참조해야할 질문이다. 여기에 잘 정리되어 있다. 간단히 요약하면,

    • 시간이 오래걸리는 작업은 스레드를 통해 처리하도록 권장한다.
    • 사용자에게는 프로그레스바 등을 이용해 진행 과정을 안내해 기다리도록 한다.
    • 이를 위해, 안드로이드에서 상속받아 사용할 수 있는 다양한 방법을 제공하고 있다.
    • Main thread에서 실행되는 임의의 method는 최소한의 일을 해야 합니다.
      특히 onCreate(), onResume() 과 같은 핵심 생명주기 method에서 가능한 적은 일 수행하는 것이 좋습니다.
      네트워크나 데이터베이스 operation 같은 잠재적으로 길게 실행될 가능성이 있는 작업들이나, 비트맵 크기를 조정하는 것 같은 계산상 값비싼 연산 쓰레드를 새로 생성해서 처리하는 것이 좋습니다. 물론 쓰레드에서 처리가 완료되었을 때 결과값을 받도록 handler를 설치 및 전달하는 것도 중요하지요.

      BR이 오랜 작업을 수행해야 하는 경우라면, service를 만드는 것이 좋습니다.
      BR이 activity를 띄워야 한다면, notification manager를 사용해서 사용자에게 보여주는 것으로 변경해야 합니다.

    dumpstate에서 ANR의 원인을 분석하고 싶다면?

    SYSTEM LOG에서 ActivityManager가 ANR 메시지를 표시하지 않는지 살펴보자. “ANR in”으로 검색하면 된다.

    문제가 발생한 곳의 위치가 표시되고, 이유와 함께 CPU 사용량도 표시된다.

    SYSTEM LOG에서 “SIG: 9″를 검색해 보자.  이는 시스템에서 ANR을 발생시키는 프로세스를 종료하기 위해 SIGNAL_KILL을 호출되면서 남기는 로그일 수 있다. 그 부분의 전후 로그를 살펴보자. ANR이라고 판단한 이유가 로그로 남아있을 수 있다.

    EVENT LOG에서 “am_anr” 태그로 검색해 보자. anr이 발생한 어플리케이션 정보를 얻을 수 있다.

    문제가 발생한 패키지 명과 PID, 이유가 표시된다.

    DUMP OF SERVICE… 의 VM TRACES AT LAST ANR 부분에도 살펴보자. 문제가 없는 경우라면 다음 처럼 아무 내용이 없다.

    하지만, ANR이 발생한 경우에는 어플리케이션의 콜스택이 남아있다. 글의 초반에 설명했듯이 ANR의 원인이 메인 스레드(일명 UI 스레드)이 오랫동안 바쁜 경우이므로 “main” 스레드 정보를 먼저 확인해야 한다. 특히, TIMED_WAIT나 MONITOR 에 있는지 확인하자.

    그 밖에

    ANR이 Activity Manager나 Window Manager 같은 곳에서 발생한 경우는 “어플리케이션이 응답이 없습니다. 기다리시겠습니까?”와 같은 메시지가 표시되지 않을 수 있다.

    참조


    'Programing > Android' 카테고리의 다른 글

    Messenger  (0) 2016.05.06
    BroadcastReceiver  (0) 2016.05.06
    ANR (Application Not Responding)  (0) 2016.05.06
    Process and Thread  (0) 2016.05.04
    Processes and Application Life Cycle  (0) 2016.05.04
    Google I/O - Memory Management For Android  (0) 2016.05.03

    출처: http://developer.android.com/intl/ko/guide/components/processes-and-threads.html
    프로세스 및 스레드

    애플리케이션 구성 요소가 시작되고 애플리케이션에 실행 중인 다른 구성 요소가 없으면 Android 시스템은 하나의 실행 스레드로 애플리케이션의 Linux 프로세스를 시작합니다. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됩니다 ("기본" 스레드라고 합니다). 애플리케이션 구성 요소가 시작되고 (애플리케이션의 다른 구성 요소가 존재해서) 해당 애플리케이션의 프로세스가 이미 존재하면 해당 구성 요소는 프로세스 내에서 시작되고 같은 실행 스레드를 사용합니다. 하지만 애플리케이션 내의 여러 가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에든 추가 스레드를 만들 수 있습니다.

    이 문서는 프로세스와 스레드가 Android 애플리케이션에서 작동하는 방식을 설명합니다.

    프로세스


    기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고, 대부분의 애플리케이션은 이를 바꿔서는 안 됩니다. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 확인해야 할 경우 매니페스트 파일에서 확인할 수 있습니다.

    구성 요소 —<activity>와 <service><receiver> 및 <provider>—의 각 유형에 대한 매니페스트 항목은 구성 요소를 실행할 프로세스를 지정하는 android:process 속성을 지원합니다. 이러한 속성을 설정하여 각 구성 요소를 자체 프로세스에서 실행시키거나 다른 구성 요소를 제외한 일부 구성 요소만 프로세스를 공유하게 할 수 있습니다 또한, android:process를 설정하여 다른 애플리케이션의 구성 요소를 같은 프로세스에서 실행시킬 수 있습니다. 단, 이는 애플리케이션이 같은 Linux 사용자 ID를 공유하고 같은 인증서에 서명되었을 경우에 한합니다.

    <application> 요소도 android:process 속성을 지원하여, 모든 구성 요소에 적용되는 기본값을 설정합니다.

    Android는 어느 시점엔가 프로세스를 종료하기로 결정할 수도 있습니다. 즉 메모리가 부족하거나, 사용자에게 더욱 즉각적인 서비스를 제공하는 다른 프로세스가 이 프로세스의 중단을 필요로 하는 경우 등입니다. 그러면 중단된 프로세스에서 실행되고 있던 애플리케이션 구성 요소도 따라서 소멸됩니다. 그와 같은 구성 요소가 할 작업이 다시 생기면 그에 대한 프로세스도 다시 시작됩니다.

    어느 프로세스를 삭제할지 결정할 때, Android 시스템은 사용자에 대한 이들의 상대적 중요성을 가늠합니다. 예를 들어, 눈에 보이는 액티비티를 호스팅하는 프로세스와 비교하여 화면에 보이지 않는 액티비티를 호스팅하는 프로세스를 쉽게 종료할 수 있습니다. 프로세스 종료 결정은 해당 프로세스에서 실행되는 구성 요소의 상태에 따라 달라집니다. 종료할 프로세스를 결정하는 데 사용하는 규칙은 아래에 설명되어 있습니다.

    프로세스 수명 주기

    Android 시스템은 최대한 오래 애플리케이션 프로세스를 유지하려고 시도하지만, 결국 오래된 프로세스를 제거하고 새 프로세스나 더 중요한 프로세스에 사용할 메모리를 확보해야 합니다. 유지할 프로세스와 종료할 프로세스를 결정하기 위해 시스템은 프로세스에서 실행되는 구성 요소와 해당 구성 요소의 상태에 기초하여 각 프로세스에 "중요 계층"을 부여합니다. 중요도가 낮은 프로세스가 먼저 제거되고, 그 다음으로 중요도가 낮은 프로세스를 제거하는 식으로 필요에 따라 시스템 리소스를 회복하는 것입니다.

    중요 계층에는 다섯 가지 단계가 있습니다. 다음 목록은 중요도 순서에 따른 프로세스 유형을 나타낸 것입니다(첫 번째 프로세스가 가장 중요하고 마지막으로 종료됩니다).

    1. 전경 프로세스

      사용자가 현재 진행하는 작업에 필요한 프로세스입니다. 다음 조건 중 하나가 참일 경우 프로세스가 전경에 있는 것으로 간주합니다.

      일반적으로, 주어진 시간에 존재하는 전경 프로세스는 몇 개밖에 되지 않습니다. 이들은 최후의 수단으로서만 종료됩니다. 즉, 메모리가 너무 부족해 계속 실행할 수 없는 경우를 말합니다. 일반적으로 그 시점이 되면 기기가 메모리 페이징 상태에 도달한 것이므로 전경 프로세스 몇 개를 중단해야만 사용자 인터페이스의 반응성을 유지할 수 있습니다.

    2. 가시적 프로세스

      전경 구성 요소는 없지만 사용자가 화면에서 보는 것에 영향을 미칠 수 있는 프로세스입니다. 다음 조건 중 하나가 참이면 가시적 프로세스로 간주합니다.

      • 전경에 있지는 않지만 사용자에게 보이는 Activity를 호스팅할 경우 (onPause() 메서드가 호출되었을 경우). 예를 들어 이것은 전경 액티비티가 대화를 시작하여 이전 액티비티가 그 뒤에 보일 경우 발생합니다.
      • 눈에 보이는(또는 전경) 액티비티에 바인딩된 Service를 호스팅할 경우.

      가시적인 프로세스는 매우 중요도가 높은 것으로 취급하고 모든 전경 프로세스를 실행하는 데 필요할 경우에만 종료됩니다.

    3. 서비스 프로세스

      startService() 메서드로 시작되었지만 두 개의 상위 계층 분류에 들어가지 않는 서비스를 실행하는 프로세스입니다. 서비스 프로세스는 사용자가 보는 것과 직접 연결되어 있지는 않지만, 일반적으로 사용자가 신경 쓰는 작업을 하므로(배경에서 음악 재생 또는 네트워크에서 데이터 다운로드) 시스템은 모든 전경 및 가시적 프로세스와 함께 실행할 만큼 메모리가 충분하지 않을 경우에만 프로세스를 중단합니다.

    4. 배경 프로세스

      현재 사용자에게 보이지 않는 액티비티를 보유한 프로세스입니다(액티비티의 onStop() 메서드가 호출되었습니다). 이와 같은 프로세스는 사용자 환경에 직접적 영향을 미치지 않고, 시스템은 언제든 이 프로세스를 중단시켜 전경, 가시적 또는 서비스 프로세스를 위한 메모리를 확보할 수 있습니다. 보통 한번에 실행 중인 배경 프로세스가 많은 편이므로 이들은 LRU(최저 사용 빈도) 목록에 보관하여 사용자가 가장 최근에 본 액티비티가 있는 프로세스가 가장 마지막에 중단되도록 합니다. 액티비티가 수명 주기 메서드를 올바르게 구현하고 자신의 현재 상태를 저장할 경우, 사용자가 액티비티로 다시 이동할 때 액티비티가 모든 가시적 상태를 복원하므로 프로세스를 중단시키더라도 사용자 환경에는 눈에 띄게 영향을 미치지 않습니다. 상태 저장과 복원에 관한 정보는 액티비티문서를 참조하십시오.

    5. 빈 프로세스

      활성 애플리케이션 구성 요소를 보유하지 않은 프로세스입니다. 이런 프로세스를 유지하는 유일한 이유는 다음에 내부 구성 요소를 실행할 때 시작 시간을 절약하기 위한 캐싱 때문입니다. 시스템은 프로세스 캐시와 기본 커널 캐시 사이에서 전반적인 시스템 리소스의 균형을 맞추기 위해 이 프로세스를 중단시키는 경우가 많습니다.

    Android는 프로세스에서 현재 활성 상태인 구성 요소의 중요도에 따라 프로세스에 가장 높은 수준을 부여합니다. 예를 들어, 프로세스가 서비스와 가시적 액티비티를 호스팅할 경우, 해당 프로세스는 서비스 프로세스가 아니라 가시적 프로세스 등급이 부여됩니다.

    또한, 프로세스의 등급은 다른 프로세스가 이에 의존할 경우 상승할 수 있습니다. 즉, 다른 프로세스에 서비스를 제공하는 프로세스가 서비스 제공 대상 프로세스보다 등급이 낮은 경우는 있을 수 없습니다. 예를 들어 프로세스 A의 콘텐츠 제공자가 프로세스 B의 클라이언트에 서비스를 제공하거나 프로세스 A의 서비스가 프로세스 B의 구성 요소에 바인딩되어 있을 경우 프로세스 A는 항상 중요도가 프로세스 B와 같거나 그보다 높습니다.

    서비스를 실행하는 프로세스가 배경 액티비티가 포함된 프로세스보다 높으므로, 장기 작업을 시작하는 액티비티는 작업자 스레드만 생성하기보다는 해당 작업에 대한 서비스를 시작하는 것이 좋습니다. 이는 특히 작업이 해당 액티비티보다 오래 지속될 경우 더욱 중요합니다. 예를 들어, 웹사이트에 사진을 업로드하는 액티비티가 업로드를 수행하는 서비스를 시작해야 사용자가 액티비티를 떠나더라도 배경에서 업로드를 지속할 수 있습니다. 서비스를 사용하면 액티비티에 어떤 일이 발생하든 해당 작업에 반드시 "서비스 프로세스" 우선 순위 이상이 부여됩니다. 이것이 브로드캐스트 수신기가 시간이 오래 걸리는 작업을 스레드에 넣기보다는 서비스를 사용해야 하는 것과 같은 이유입니다.

    스레드


    애플리케이션이 시작되면 시스템이 애플리케이션에 대한 실행의 스레드를 생성하며, 이를 "기본"이라고 합니다. 이 스레드는 드로어블 이벤트를 포함하여 적절한 사용자 인터페이스 위젯에 이벤트를 발송하는 역할을 맡기 때문에 중요합니다. 이것은 Android UI 도구 키트의 구성 요소(android.widget과 android.view 패키지의 구성 요소)와 개발자의 애플리케이션이 상호 작용하는 스레드이기도 합니다. 따라서 기본 스레드는 UI 스레드라고 불릴 때도 있습니다.

    시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않습니다. 같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 시작되고, 각 구성 요소를 호출하는 시스템이 해당 스레드에서 발송됩니다. 따라서 시스템 콜백에 응답하는 메서드(사용자 작업을 보고하는 onKeyDown() 또는 수명 주기 콜백 메서드)는 항상 프로세스의 UI 스레드에서 실행됩니다.

    예를 들어, 사용자가 화면의 버튼을 터치하면, 앱 UI 스레드가 위젯에 터치 이벤트를 발송하고, 위젯은 눌린 상태를 설정하고 이벤트 대기열에 무효화 요청을 게시합니다. UI 스레드가 이 요청을 대기열에서 해제하고 위젯에 스스로를 다시 그려야 한다고 알립니다.

    앱이 사용자 상호작용에 응답하여 집약적인 작업을 수행할 때는 이 단일 스레드 모델은 애플리케이션을 제대로 구현하지 않으면 낮은 성능을 보일 수 있습니다. 특히, 모든 것이 UI 스레드에서 발생하고 네트워크 액세스나 데이터 베이스 쿼리 등의 긴 작업을 수행하면 UI가 통째로 차단됩니다. 스레드가 차단되면 드로잉 이벤트를 포함하여 모든 이벤트가 발송되지 않습니다. 사용자가 보기에는 애플리케이션이 중단된 것처럼 보입니다. 더 나쁜 경우, UI 스레드가 몇 초 이상 차단되어 있으면 (현재 약 5초) 사용자에게 악명 높은 "애플리케이션이 응답하지 않습니다"(ANR) 대화가 표시됩니다. 그러면 사용자가 여러분의 애플리케이션을 종료 할 수도 있고, 불만족한 경우 앱을 제거할 수도 있습니다.

    또한, Andoid UI 도구 키트는 스레드로부터 안전하지 않습니다. 따라서 UI를 작업자 스레드에서 조작해서는 안 됩니다. 사용자 인터페이스 조작 작업은 모두 UI 스레드에서 해야만 합니다. 결론적으로, Android의 단일 스레드 모델에는 두 가지 단순한 규칙이 있습니다.

    1. UI 스레드를 차단하지 마십시오.
    2. Ui 스레드 외부에서 Android UI 도구 키트에 액세스하지 마십시오.

    작업자 스레드

    위에 설명한 단일 스레드 모델로 인해, 애플리케이션 UI의 반응성을 위해서는 UI 스레드를 차단하지 않는 것이 매우 중요합니다. 수행해야 할 작업이 있는데 이들이 즉각적인 조치를 요하지 않는 경우, 이런 작업은 반드시 별도의 스레드에서 수행해야 합니다("배경" 또는 "작업자" 스레드).

    예를 들어, 아래는 별도의 스레드에서 이미지를 다운로드하고 이를 ImageView에 표시하는 클릭 수신기에 대한 몇 가지 코드입니다.

    public void onClick(View v) {
       
    new Thread(new Runnable() {
           
    public void run() {
               
    Bitmap b = loadImageFromNetwork("http://example.com/image.png");
                mImageView
    .setImageBitmap(b);
           
    }
       
    }).start();
    }

    처음에는 네트워크 작업을 처리하기 위한 새 스레드를 생성하므로 아무 문제 없이 작동하는 것처럼 보입니다. 하지만, 이것은 단일 스레드된 모델의 두 번째 규칙 즉, 'UI 스레드 외부에서 Android UI 도구 키트에 액세스하지 마세요.'를 위반합니다. 이 샘플은 UI 스레드 대신 작업자 스레드에서 ImageView를 수정합니다. 이렇게 되면 정의되지 않고 예기치 못한 동작이 발생하는 결과를 초래할 수 있고, 이는 추적하기 어려워 시간도 오래 걸립니다.

    이 문제를 해결하기 위해 Android는 다른 스레드에서 UI 스레드에 액세스하는 여러 가지 방식을 제안합니다. 다음은 몇 가지 유용한 메서드 목록입니다.

    예를 들어 위의 코드를 수정하려면 View.post(Runnable) 메서드를 사용하면 됩니다.

    public void onClick(View v) {
       
    new Thread(new Runnable() {
           
    public void run() {
               
    final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
                mImageView
    .post(new Runnable() {
                   
    public void run() {
                        mImageView
    .setImageBitmap(bitmap);
                   
    }
               
    });
           
    }
       
    }).start();
    }

    이 구현은 이제 스레드로부터 안전합니다. 네트워크 작업은 별도의 스레드에서 수행된 반면 ImageView는 UI 스레드에서 조작되었기 때문입니다.

    그러나, 작업이 복잡해질수록 이런 종류의 코드가 더 복잡해질 수 있고 유지 관리하기 까다로워질 수 있습니다. 더 복잡한 상호 작용을 작업자 스레드로 처리하려면, 작업자 스레드에서 Handler를 사용하여 UI 스레드에서 전달 받은 메시지를 처리하는 방안을 고려해보십시오. 하지만 최선의 해결책은 AsyncTask 클래스를 확장하는 방법일 것입니다. 이것은 UI와 상호 작용해야 하는 작업자 스레드 작업의 실행을 단순화합니다.

    AsyncTask 사용

    AsyncTask를 사용하면 사용자 인터페이스에서 비동기식 작업을 수행할 수 있게 해줍니다. 이것은 작업자 스레드에서 차단 작업을 수행하고 그런 다음 그 결과를 UI 스레드에 게시하며, 개발자가 직접 스레드 및/또는 처리기를 처리할 필요가 없습니다.

    이를 사용하려면 우선 AsyncTask를 하위 클래스로 한 다음 doInBackground() 콜백 메서드를 구현해야 합니다. 이것은 여러 가지 배경 스레드에서 실행됩니다. UI를 업데이트하려면 onPostExecute()를 구현해야 합니다. 이는doInBackground()로부터의 결과를 전달하며 UI 스레드에서 실행되므로, 안전하게 UI를 업데이트할 수 있습니다. 그런 다음 UI 스레드에서 execute()를 호출하여 해당 작업을 실행하면 됩니다.

    예를 들어, 이런 방식으로 AsyncTask를 사용하여 이전의 예시를 구현할 수 있습니다.

    public void onClick(View v) {
       
    new DownloadImageTask().execute("http://example.com/image.png");
    }

    private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
       
    /** The system calls this to perform work in a worker thread and
          * delivers it the parameters given to AsyncTask.execute() */

       
    protected Bitmap doInBackground(String... urls) {
           
    return loadImageFromNetwork(urls[0]);
       
    }
       
       
    /** The system calls this to perform work in the UI thread and delivers
          * the result from doInBackground() */

       
    protected void onPostExecute(Bitmap result) {
            mImageView
    .setImageBitmap(result);
       
    }
    }

    이제 UI는 안전하고 코드는 더욱 단순해졌습니다. 작업을 작업자 스레드에서 수행되어야 하는 부분과 UI 스레드에서 수행되어야 하는 부분으로 구분하기 때문입니다.

    이 클래스를 사용하는 법을 완전히 숙지하려면 AsyncTask 참조를 읽어보시는 것이 좋습니다. 개괄적인 작동 방식은 아래에 간략히 소개해 놓았습니다.

    주의: 작업자 스레드를 사용할 때 마주칠 수 있는 또 한 가지 문제는 런타임 구성 변경으로 인해 액티비티가 예기치 못하게 다시 시작되는 것입니다 (예를 들어 사용자가 화면 방향을 바꾸는 경우). 이 경우 작업자 스레드를 소멸시킬 수 있습니다. 스레드가 재시작될 때 작업을 지속하는 방법과 액티비티가 제거되었을 때 작업을 적절히 취소하는 방법은 Shelves 샘플 애플리케이션의 소스 코드를 참조하십시오.

    스레드로부터 안전한 메서드

    어떤 경우에는 구현하는 메서드가 하나 이상의 스레드에서 호출되는 일도 있습니다. 따라서 이를 스레드로부터 안전하게 작성해야만 합니다.

    이것은 주로 원격으로 호출할 수 있는 메서드에 대해 참입니다. 예를 들어 바인딩된 서비스 내의 메서드 등이 해당됩니다. IBinder에서 구현된 메서드가 IBinder가 실행되는 프로세스에서 호출될 경우, 해당 메서드는 발신자의 스레드에서 실행됩니다. 그러나 호출이 다른 프로세스에서 발생하면, 해당 메서드는 시스템이 IBinder와 같은 프로세스에 유지하는 스레드 풀에서 선택된 스레드에서 실행됩니다(프로세스의 UI 스레드에서 실행되지 않습니다). 예를 들어, 어느 서비스의 onBind() 메서드는 해당 서비스 프로세스의 UI 스레드에서 호출되고, onBind()가 반환하는 객체에서 구현된 메서드는(예: RPC 메서드를 구현하는 하위 클래스) 해당 풀 안의 여러 스레드에서 호출되게 됩니다. 서비스에 클라이언트가 하나 이상 있을 수 있으므로, 하나 이상의 풀이 동시에 같은 IBinder 메서드에 참여할 수 있습니다. 그러므로 IBinder 메서드는 스레드로부터 안전하게 구현되어야 합니다.

    마찬가지로 콘텐츠 제공자는 다른 프로세스에서 발생한 데이터 요청을 수신할 수 있습니다. ContentResolverContentProvider 클래스가 세부적인 프로세스 간 통신의 관리 방식을 숨길 수는 있지만, 이러한 요청에 응답하는ContentProvider 메서드(—query()insert()delete()update() 및 getType() 메서드—)가 프로세스의 UI 스레드가 아니라 콘텐츠 제공자 프로세스의 스레드 풀에서 호출됩니다. 이러한 메서드가 동시에 몇 개의 스레드에서 호출될 수 있으므로, 스레드로부터 안전하게 구현되어야 합니다.

    프로세스 간 통신


    Android는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 메커니즘을 제공합니다. 여기서 메서드는 액티비티나 다른 애플리케이션 구성 요소에 호출되지만 원격으로 (또 다른 프로세스에서) 실행되고, 결과는 모두 발신자에게 되돌려 보냅니다. 메서드 호출과 메서드의 데이터는 운영 체제가 이해할 수 있는 수준으로 분해되고, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송된 다음 다시 결합되어 여기서 호출에 다시 응답합니다. 그런 다음 반환 값이 반대 방향으로 전송됩니다. Android가 이와 같은 IPC 트랜잭션을 수행하는 데 필요한 모든 코드를 제공하므로, 개발자는 그저 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데에만 집중하면 됩니다.

    IPC를 수행하려면 애플리케이션이 반드시 서비스에 바인딩되어야만 하며, 이때 bindService()를 사용해야 합니다. 자세한 정보는 서비스 개발자 가이드를 참조하십시오.


    'Programing > Android' 카테고리의 다른 글

    BroadcastReceiver  (0) 2016.05.06
    ANR (Application Not Responding)  (0) 2016.05.06
    Process and Thread  (0) 2016.05.04
    Processes and Application Life Cycle  (0) 2016.05.04
    Google I/O - Memory Management For Android  (0) 2016.05.03
    Avoiding Memory Leaks  (0) 2016.05.03
    • Processes

      출처: 
      http://namsieon.com/289http://developer.android.com/intl/ko/guide/topics/processes/process-lifecycle.html


      안드로이드 시스템은 메모리가 부족하여 실행되어야 할 프로세스가 실행되지 않는 것을 방지하기 위해 오래된 프로세스를 제거하는 방식을 사용합니다.



      여기에서 어떤 프로세스를 유지하고 어떤 프로세스를 죽일것인지는 무엇을 보고 판단하는 걸까요?

      바로 각각의 프로세스내에서 실행되는 컴포넌트 상태의 '중요성 계층구조(importance hierarchy)' 를 이용합니다.


      가장 낮은 중요성을 가진 프로세스가 가장 먼저 제거되고, 그 다음순으로 진행되지요.

       

      그 계층구조에는 다섯 단계가 존재합니다.



      1. 포그라운드 ( foreground ) 프로세스


      포그라운드 프로세스는 현재 사용자와 커뮤니케이션하는 프로세스 입니다.

      아래와 같은 조건을 가지고 있다면 포그라운드 프로세스로 간주합니다.


      ● 프로세스가 사용자와 상호작용하는 액티비티를 실행중
      ● 사용자와 상호작용하는 액티비티에 연결된 서비스를 포함하고 있음
      ● 생명주기 메소드 ( onCreate(), onStart(), onDestroy() ) 중 하나를 실행중인 서비스 객체를 가지고 있음
      ● onReceive() 메소드를 실행중인 브로드캐스트리시버 객체를 포함하고 있음


      2. 비지블(visible) 프로세스


      비지블 프로세스는 포그라운드 프로세스를 보유하지 않았지만 사용자에게는 보일 수 있는 프로세스 입니다.

      아래와 같은 조건을 가지고 있다면 비지블 프로세스로 간주합니다.


      ● 포그라운드가 아니지만 여전히 보여지는 액티비티를 보유 중
      ● 비지블 액티비티에 연결된 서비스를 포함하고 있음


      ☞  비지블 프로세스는 포그라운드 프로세스가 더 이상 실행 될 수 없을 정도의 상황이 아니라면 강제 종료 되진 않습니다.

      3. 서비스 ( service ) 프로세스


      서비스프로세스는 startService() 메소드를 사용해서 시작된 서비스를 실행중이면서, 포그라운드 프로세스와 비지블 프로세스에 속하지 않는프로세스 입니다.


      서비스 프로세스는 사용자에게 보이진 않지만, 사용자에게 영향을 줄 수 있는 ( mp3 재생 등 ) 작업을 하고 있다고 볼 수 있는데요, 따라서 그라운드 프로세스 및 비지블 프로세스를 유지할 메모리가 없지 않은 이상에는 강제종료 되진 않겠지요.



      4. 백그라운드 ( background ) 프로세스

      백그라운드 프로세스는 사용자의 눈에 보이지 않는 액티비티를 가진 프로세스 입니다. 이것은 사용자에게 직접적인 영향을 주지 않고, 포그라운드 -> 비지블 -> 서비스 프로세스 유지를 위해 언제든지 강제종료 될 수가 있습니다. 

      이런 이유로, 가장 최근에 사용자에게 보여진 액티비티를 가진 프로세스가 가장 나중에 종료될 수 있도록 LRU(Least Recently Used : 가장 최근에 적게 사용되는) 라는 목록으로 관리됩니다.

      이것은 오래된 보여지지 않는 액티비티의 강제종료가 사용자에게 별다른 영향을 주지 않는다고 생각하는 것이지요.


      5. empty 프로세스


      empty 프로세스는 활성화된 것들을 보유하지 않은 프로세스입니다.
      이 프로세스는 캐쉬 역할을 제외하면 하는것이 없기 때문에 강제종료 됩니다. 
      (즉, 중요성이 가장 낮습니다)



      추가적으로, 프로세스의 중요도는 다른 프로레스에 의해 높아질 수 있습니다. 

      다른 프로세스를 지원하는 프로세스는 지원하는 프로세스보다 낮은 중요도를 가지면 안되겠지요.

      예를들어 "A" 프로세스에 있는 서비스가 "B"에 연결되어 있다면 "A"와 "B"는 같은 중요도를 가져야 합니다.


      서비스 프로세스가 백그라운드 프로세스보다 중요도가 높기 때문에 오래걸리는 작업처리는 별도의 스레드에 할당하기 보단 서비스를 이용하여 처리하는것이 좋겠네요. 서비스를 이용하면 액티비티와는 무관하게 최소한 '서비스 프로세스' 우선 순위는 보장된다는 뜻이니까요. 

      때문에 오래걸리는 작업처리 ( 네트워크 다운/업로드 등 ) 는 스레드보다 서비스로 구현해야 하는 이유가 됩니다.

    • Application Life Cycle
      출처: 
      http://starkapin.tistory.com/134

      1. 수명 주기


      메모리를 자동으로 할당해주고 자동으로 비워준다는 것은 가상머신을 사용하는 우리들에게 있어 약일 수도 있지만 오히려 반대로 따지고 보면 당장 반환해야 할 순간에 손만 빨고 있을 지도 모른다는 소리다. 하지만 여지껏 많은 개발자들은 그런 환경에서 완성도 높은 애플리케이션을 개발해 왔으니 이러한 주기를 잘 파악해서 프로그램을 작성해야 한다.

      안드로이드 애플리케이션은 수명 주기를 제어하는 것을 제한 하고 있다. 

      빠른 반응 처리를 위해 달빅 가상 머신은 애플리케이션을 바로 죽이지 않고 가지고 있는다. 종료한 애플리케이션임에도 불구하고 사용자가 다시 누를수 있다는 가정을 하고 있는 것이다. 

      안드로이드는 우선 순위가 높은 것 부터 낮은 순으로 관리해서 높은 순위에 있는 애플리케이션에게 메모리를 우선적으로 할당해준다. 제한된 메모리 관리를 효율적으로 운용하기 위해 낮은 순위에 있는 애플리케이션은 우선 순위가 높은 애플리케이션이 더 많은 메모리가 필요할 때 종료되고 메모리를 반환하게 되어있다.


      2. 프로세스 상태

      안드로이드 애플리케이션은 단일 프로세스로서 하나의 달빅 인스턴스 위에서 베타적으로 실행된다. 

      2-1. 활성 프로세스(Active processes)
      중요한 우선 순위, foreground 상태로 현재 실행중인 애플리케이션을 뜻한다. 

      2-2. 화면에 보이는 프로세스(Visible processes)
      높은 우선 순위, 화면에 보이고는 있으나 비활성화된 프로세스들을 뜻한다.

      2-3. 시작된 서비스 프로세스(Stated Service processes)
      높은 우선 순위, 화면에 보이는 인터페이스 없이도 계속 되어야 하는 지속적인 처리를 지원

      2-4. 백그라운드 프로세스(Background processes)
      낮운 우선 순위, 화면에 보이는 액티비티를 가지고 있지 않으면서, 동시에 실행중인 서비스를 가지고 있지 않는 프로세스

      2-5. 빈 프로세스(Empty processes)
      낮은 우선 순위, 안드로이드 애플리케이션이 다시 띄워질 때 구동 시간을 향상시키기 위하여 이 캐시를 유지.


      3. 애플리케이션 수명 주기 이벤트 

      □ onCreate : 애플리케이션 생성시 호출, 모든 상태 변수와 공유 리소스를 초기화 

      □ onTerminate : 애플리케이션 객체가 종료될 때 호출(항상 호출 된다는 보장을 할 수 없음)
      리소스 회수를 위한 목적으로 커널에 의해 종료되는 경우 이 함수는 호출되지 않는다.

      ★ 애플리케이션 생명주기와 프로세스 생명주기는 일치하지 않는다!

      □ onLowMemory : 시스템 리소스가 부족할 때 애플리케이션이 추가로 메모리를 해제하는 기회를 준다. 모든 백그라운드 프로세스가 종료되었는데도 메모리가 부족하면 호출됨

      □ onConfigurationChanged : 애플리케이션 차원에서 구성 변경을 다룰 필요가 있을 때

      import android.app.Application;
      import android.content.res.Configuration;
      
      public class MyApplication extends Application{
      	
      	private static MyApplication singleton;
      	
      	public static MyApplication getInstance() {
      		return singleton;
      	}
      	
      	@Override
      	public final void onCreate() {
      		super.onCreate();
      		singleton = this;
      	}
      	
      	@Override
      	public final void onTerminate() {
      		super.onTerminate();
      	}
      	
      	@Override
      	public final void onLowMemory() {
      		super.onLowMemory();
      	}
      	
      	@Override
      	public final void onConfigurationChanged(Configuration newConfig) {
      		super.onConfigurationChanged(newConfig);
      	}
      }


    'Programing > Android' 카테고리의 다른 글

    ANR (Application Not Responding)  (0) 2016.05.06
    Process and Thread  (0) 2016.05.04
    Processes and Application Life Cycle  (0) 2016.05.04
    Google I/O - Memory Management For Android  (0) 2016.05.03
    Avoiding Memory Leaks  (0) 2016.05.03
    Activity, Fragment Lifecycles  (0) 2016.05.03

    Heap Size Limit


    안드로이드 앱에는 Heap Size 의 Limit 가 존재합니다. Heap Size 의 Limit 은 Device dependent합니다. 단말마다 다른  heap size limit 을 가지고 있다는 것이죠. 최초의 안드로이드 단말 G1 의 경우는 어플리케이션 하나가 가질 수 있는 heap memory 는 16MB 였습니다. 다시 말해, 어플리케이션 하나가 16MB 이상의 Heap 을 사용하는 경우 OutOfMemoryError 가 발생하며, Application Crash 현상이 발생한다는 것입니다. 모바일 단말의 하드웨어 스펙이 발달하면서, Heap Size Limit 은 증가하기 시작했습니다. Droid 의 경우 24M, Nexus One 의 경우 32M, Xoom 의 경우 48M 까지 증가했으며, 현재 High-end 급의 단말들은 더 높은 메모리 사이즈를 지원하겠죠.

    이 단말의 Heap Size Limit 은 API 코드로도 확인할 수 있습니다.

    int ActivityManager.getMemoryClass()
    // returns memory heap limit size in MB 





    더 큰 Heap Memory 를 사용할 순 없는가?


    Android SDK 3.0 HoneyComb 이상부터는 manifest 설정을 통해 기본 단말에서 제공하는 Heap memory size 보다 더 큰 용량을 사용할 수 있습니다. application tag 에 largerHeap = "true" 값을 넣어주면 됩니다.

    <application
            ....
            largerHeap="true"


    이 largerHeap 의 값도 device dependent 하다고 하니, ActivityManager.getMemoryClass() 를 통해서 사용 가능한 메모리 용량을 확인해 보시면 되겠습니다.


    largerHeap="true" 옵션은 주의해서 사용하셔야 합니다.
    먼저 Larger Heap Size 는 그만큼 GC 시간을 더 소비한다는 의미이며, 다른 App들을 죽일 수 있습니다.
    따라서 신중히 사용되어야 합니다. 





    Garbage Collection.


    2012/01/12 - [프로그래밍 놀이터/자바] - [Java] Garbage Collection ( GC ) 가 뭐 하는 녀석인지 한 번 해부해보자.

    Pre-GingerBread


    Android SDK 2.3 GingerBread 이전의 Garbage Collection 은 다음의 특징을 가집니다.

    - Stop-the-world
    - Full heap Collection
    - Pause times often > 100ms.


    From GingerBread


    GingerBread을 포함하여 이후의 버전들의 Garbage Collection 은 다음의 특징을 가집니다.

    - Concurrent ( mostly )
    - Partial collections.
    - Pause times usually < 5ms.


    GC 의 개선은 매우 혁신적이라고 할 수 있습니다.
    먼저 Stop-the-world 가 아닌 Concurrent 방식으로 바뀌었기 때문에, 반응성 측면이 상당히 좋아졌습니다.
    게다가 partial collection 형태로 GC 시간이 단축되어 pause time 이 보통 5ms 라고 하네요.
    이제 개발하면서, GC가 발생할까 걱정하는 시기는 지났다고 보여집니다.
    ( 물론 메모리 관리를 잘 해서 GC 는 가급적 불리지 않는 것이 가장 좋습니다. )







    Bitmap 에 대한 이야기 ( Native Memory )


    안드로이드 앱에서 Memory 를 가장 많이 소모하는 녀석이 바로 bitmap 이라고 할 수 있습니다.
    Phone 용 App 에서는 보통 50% 가 BItmap 이 소모하였으며,
    Tablet 과 같이 큰 화면에서는 더 많은 bitmap 을 display 하기 때문에 70% 이상을 소모한다고 하네요.

    Pre-HoneyComb


    Android SDK 3.0 HoneyComb 이전에는 Bitmap Object 를 실제 Bitmap 과 상관없이 Heap Memory 에 동일 사이즈로 할당을 합니다. 즉 Container 의 역할만 하는 것이지요. 그리고 실제 BItmap의 Pixel 값들은 Native 에 할당이 되어 있는 형태입니다.



    - freed via recycle() or finalizer
    - hard to debug
    - full, stop-the-world GCs


    From Honey Comb


    HoneyComb 을 포함한 이후 버전에서는 Bitmap Object 를 다른 방식으로 관리하고 있습니다. HoneyComb 이전 버전과 마찬가지로, Bitmap Object 는 동일하게 생성하지만, Native 에 할당하던 Pixel memory 를 Davik Heap 으로 위치를 옮긴 것이지요.



    - freed synchronously by GC
    - easier to debug
    - concurrent & partial GCs





    Log 를 통한 메모리 분석


    D/dalvikvm( 9050 ): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K paused 2ms+2ms.


    GC에 대한 로그정보를 통해 "개략적인" 메모리 사용 실태를 확인할 수 있습니다.


    Reason For GC


    위 로그의 "GC_CONCURRENT" 파트가 GC를 초래한 이유가 되겠습니다.
    GC_CONCURRENT 이외에도 다른 이유로 GC 가 초래되는데 다음과 같은 이유들이 있습죠.

    - GC_CONCURRENT
    - GC_FOR_MALLOC : GC가 수행 or 끝나기 전에 malloc 이 불려서 memory 가 full 이 된 경우
    - GC_EXTERNAL_ALLOC : external native memory alloc 이 발생하여 memory 가 full 이 된 경우
      ( HoneyComb 부터는 방식이 바뀌어, Bitmap 이나 NIO 의 ByteBuffer 류에는 이 GC 가 발생하지 않습니다. )
    - GC_HPROF_DUM_HEAP
    - GC_EXPLICIT


    Amount Freed


    "freed 2049K" 정보가 GC 를 통해 해제된 메모리 량입니다.
    약 2MB 정도가 해제되었네요.


    Heap Statistics


    "65% free 3571K/9991K" 이 부분이 Heap의 통계를 보여줍니다.
    위에서 Free 된 2049K 가 현재 사용하던 메모리 총 량의 65% 라는 것을 나타내며,
    GC 후 남아있는 사용하는 메모리가 3571K 이고, total memory size 가 9991K 라는 것을 나타냅니다.


    External Memory Statistics


    "external 4703K/5261k" 부분이 external memory statistics 이며,
    4703K 가 현재 alloc 된 memory, 5261K 가 soft limit 를 나타냅니다.


    Pause Time


    "paused 2ms + 2ms" 부분이 pause time을 나타냅니다.
    이렇게 2개의 숫자가 더해지는 경우는 Concurrent GC 일 때 발생을 하며, Stop-the-world GC의 경우 1개의 숫자만 표시됩니다. 첫 2ms 는 GC의 준비시간을 의미하며, 두번째 GC는 실제 GC의 collection 작업의 수행 시간을 말합니다. 합하여 4ms 로 매우 짧은데, Stop-the-world GC 의 경우 훨씬 긴 pause time 을 갖는다고 합니다. ( 100ms 를 훌쩍 넘는다고 하네요. )








    MAT ( Memory Analyzer ) 와 Heap Dump


    2012/02/24 - [프로그래밍 놀이터/안드로이드] - [Android/안드로이드] MAT ( Mermory Analyzer Tool ) Android, Eclipse 사용 방법.

    Android 에서는 Heap Memory 정보를 얻을 수 있는 Heap Dump 를 제공합니다.
    DDMS 상에서 Heap Dump 아이콘을 클릭하여 dump file 을 얻을 수도 있고, 
    코드상에서 Debug.dumpHprofData() 를 통해 추출해낼 수도 있습니다.

    이렇게 얻어진 heap dump file 은 heap memory information 을 가진 binary로, MAT 에서 읽기 위해서는 conversion이 필요합니다. command 창에서 다음과 같이 입력하면 되며, hprof 의 경우 android sdk 에 포함되어 있습니다.

    > hprof -conv orig.hprof converted.hprof



    Prerequisite


    MAT 을 사용하기에 앞서 다음과 같은 용어들과 개념들을 이해하셔야 합니다.

     Leak : 사용하지 않는 Object 를 참조하고 있어 GC 를 방해하는 것. ( C, C++ 의 leak 과는 조금 차이가 있습니다. )
    - Context 를 자체 생명주기보다 오래 참조하는 것은 leak 을 초래하는 지름길.
    - Activity, Conterxt, View, Drawable 등을 long-live reference 유지하는 것은 매우 위험한 일.
    - Non-static inner class와 Runnable 은 enclosing object 를 참조하기 때문에 생명주기와 synchronize 시키는 것이 중요.
    - 무분별한 Caches 는 OutOfMemory 와 Leak 을 초래하기 쉬움.

    Shallow heap : object 하나가 가지는 byte 용량을 나타냄.
    Retained heap : GC가 어떤 object 를 해지했을 때, 해지되는 총 메모리량으로, 한 object 가 참조하는 녀석들을 tree 형태로 따라가며 더한 값으로, 매우 중요한 정보를 갖는다.


    [특정 process id 정보만 log 보기]

    > adb logcat | grab [Process ID]




    MAT 사용하기.


    Histogram View


    사용하는 모든 메모리의 합을 object 별로 sum 하여 나타내준 graph 입니다. 
    사실 graph 라고는 하지만 list 형태로 나오죠. 

    Memory leak 을 추적할 때는 추적해보죠.

    ( 참고로, byte[] 가 최상단으로 올라오기 쉬운데, 이는 bitmap 이 이제 heap memory 에 assign 되기 때문입니다. )

    object를 우클릭 -> [List Object] -> [with incoming reference] 


    어떤 녀석이 해당 object 를 참조하고 있는지를 보여줍니다. with outgoing reference 는 해당 object 가 참조하는 녀석들이 나오겠죠?


    object 우클릭 -> [Path To GC Roots] -> [Exclude Weak References]


    Weak Reference 야 어차피 GC 와 크게 상관이 없으니, exclude 하고, 유용한 정보만 확인합니다. 이것을 통해 GC Root 를 찾아냄으로서, 의도하지 않은 Root 를 가지고 있지는 않은지 확인 할 수 있습니다.


    Filter 기능


    Activity나 Context 의 경우는 보통 하나를 유지합니다. 하지만, 경우에 따라 Leak 이 발생한 경우에는 Activity의 갯수가 2개 혹은 그 이상이 되는 경우를 발견할 수 있습니다. Activity 이름이나, 갯수를 명확히 알고 그것이 유지되어야 하는 경우에는 Histogram View의 상단에 Filter 기능을 사용하여 Filtering 하면, 해당 Object 의 갯수를 쉽게 파악할 수 있습니다.




    Dominator tree


    Dominator tree 는 어떤 object 가 어떤 object 를 참조하고 있는지를 보여주는 tree 입니다. 사실 이것이 실제 참조 그대로를 보여주는 것이 아니고, 살짝 fake 라고 할 수 있는 logic 으로 tree 를 구성하는데, 사실 이 fake 된 tree 가 memory leak 을 잡기에는 훨씬 수월합니다. 말로 설명은 어렵고, 아래 그림을 통해서 이해하시길 바랍니다.


    안드로이드의 경우 resource 를 많이 사용하기 때문에, Dominator tree 에서는 resource 가 보통 가장 위로 올라오게 됩니다. "항상" 은 아니지만 대부분 이 Resource object 자체는 크게 신경쓰지 않아도 됩니다.

    Object 우클릭 -> [Path To GC Roots] -> [exclude weak references]


    Histogram 과 마찬가지로, GC Roots 를 탐색하여, 참조의 root 를 찾을 수 있습니다.





    참고 사이트


    구글 Video Clip 에서는 친절하게도, 참조 사이트까지도 알려주고 있습니다.
    저도 아직 방문하지 않앗지만, 이 글을 포스팅 한 이후에 한번 방문해보려고 합니다.
    제목부터가 상당히 attractive 합니다.

    Markus Kohler's Java Performance Blog
    http://kohlerm.blogspot.com/


    출처 : http://aroundck.tistory.com/378

    'Programing > Android' 카테고리의 다른 글

    Process and Thread  (0) 2016.05.04
    Processes and Application Life Cycle  (0) 2016.05.04
    Google I/O - Memory Management For Android  (0) 2016.05.03
    Avoiding Memory Leaks  (0) 2016.05.03
    Activity, Fragment Lifecycles  (0) 2016.05.03
    Handler, Looper  (0) 2016.04.19

    안드로이드 개발사이트(http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html) 에 있는 내용으로,

    너무나도 중요하며 안드로이드 개발자라면 반드시 숙지해야 하는 부분이다.

    많은 안드로이드 개발자들이 메모리 문제로 고생하지 않길 바라는 마음에 번역하였다.


    메모리 릭 피하기

    안드로이드 어플리케이션들은 T-Mobile G1 모델을 포함하여 최소한 16MB 의 heap 용량을 갖는다. 이는 전화기에 사용하기에는 충분한 메모리이지만 몇몇 개발자들에게는 여전히 부족할 수도 있다. 심지어 이 메모리를 다 사용하려 하지 않는다 하더라도 한번쯤은 주의할 필요가 있다. 우리의 앱이 많은 메모리를 사용한다면 다른 앱이 실행될 때 우리의 앱을 죽일 수도 있기 때문이다. 즉, 안드로이드가 가능한 많은 앱을 메모리에 담을 수 있도록 하여, 앱의 빠른 실행전환을 돕는 것이 좋다.

    내가 맡은 일의 한 부분으로, 나는 안드로이드 어플리케이션의 메모리 릭 부분에 특히 집중하였으며 대부분이 같은 실수 때문인 것이라는 것을 알게 되었다. 실수란 바로 Context  참조를 오랫동안 갖고 있는 객체를 생성하는 것을 말한다.

    안드로이드에서는 대부분의 동작에 Context 를 사용하며 코드에서는 주로 리소스를 불러들이거나 접근하는 데 사용한다. 이러한 이유로 모든 안드로이드 widget(android.widget 패키지 내의 클래스들 - 역자 주) 들은 생성자에 Context 를 파라미터로 가진다. 일반적인 안드로이드 앱에서는 두 종류의 Context 를 사용하기 마련인데 바로 Activity 와 Application 이다. 개발자들은Context 가 필요한 코드에서, 전자를 사용하는 경우가 많은데 예를 들면 다음의 코드와 같다.

    @Override
    protected void onCreate(Bundle state) {
     
    super.onCreate(state);
     
     
    TextView label = new TextView(this);
      label
    .setText("Leaks are bad");
     
      setContentView
    (label);
    }

    이 코드를 살펴보자면 뷰들은 activity 전체에 대한 참조를 가진다. activity 뿐 아니라 activity 가 가진 모든 것들, 즉 View 전체의 계층구조 및 개별 View들이 사용하는 리소스들에 대한 참조를 가진단 뜻이다. 그러므로, Context 릭(leak) 이 발생한다는 것은 (릭이란 객체의 참조가 사라지지 않아 GC 대상에서 제외되는 객체를 만든다는 뜻임), 많은 메모리 낭비를 의미한다. 따라서, 조심하지 않으면 전체 Activity 에 대한 릭은 매우 빈번하게 발생한다.

    간단한 예를 들어보자. 화면 방향이 바뀔 경우, 시스템은 기본적으로 현재의 Activity 를 제거한 다음 그 상태를 보존한 채로 새로운 Activity 를 만든다. 이 과정 동안 안드로이드는 앱의 UI를 리소스부터 새로 읽어들인다. 

    그렇다면 매번 화면 방향을 바꿀 때 마다 그림을 읽어들이는 것을 원하지 않아, 다음과 같은 코드를 작성했다고 가정해 보자. 가장 쉬운 방법은 이미지를 static 참조로 바꾸어 그 상태를 확인함으로써 매번 불러들이지 않도록 하게 하는 것이다.

    private static Drawable sBackground;

    @Override
    protected void onCreate(Bundle state) {
     
    super.onCreate(state);
     
     
    TextView label = new TextView(this);
      label
    .setText("Leaks are bad");
     
     
    if (sBackground == null) {
        sBackground
    = getDrawable(R.drawable.large_bitmap);
     
    }
      label
    .setBackgroundDrawable(sBackground);
     
      setContentView
    (label);
    }

    이 코드는 매우 빠르며 또한 매우 잘못되었다. 위 코드대로라면 최초에 화면변환이 일어날 때 최초의 Activity 참조를 놓치게 된다(leaks). Drawable 이 View 에 붙을 때(attached) View 는 drawable 을 callback 으로 갖게 된다. 코드를 다시 살펴보면, drawable 은TextView 에 대한 참조를 가지며  TextView  는 activity(Context) 에 대한 참조를 다시 갖는 구조이다. 코드에 따라 다르긴 하겠지만 이는 즉, 하나의 drawable 이 TextView 뿐 아니라 더 많은 것들에 대한 참조를 가지게 될 수도 있다는 것이다.

    이 예제는 Context 릭이 발생하는 가장 간단하고 흔한 경우를 보여주며, 이를 막기 위해 안드로이드 개발팀이 홈 스크린의 소스코드 에서 어떤 노력을 하는지 확인할 수 있다. (unbindDrawables() 메소드를 살펴볼 것). 홈 스크린 소스 코드에서는 Activity 가  제거될 때, drawable 에 대한 callback 을 null 로 설정하여 이를 해결하고 있다. 흥미롭게도 놓친 context 들에 대한 참조들은 계속 이어질 수 있으며(GC되지 않았으므로 참조 가능함. 즉, 사라진 화면에 대한 참조조차도 가능하다는 뜻 - 역자 주), 또한 더욱 나쁘다. 메모리를 훨씬 빨리 고갈시킬 수 있기 때문이다.

    Context 에 의한 메모리 릭을 피할 수 있는 쉬운 두 가지의 방법이 있다.

    가장 명백한 첫번째 해결책은  Context 참조의 범위를 최소한으로 두라는 것이다. 위의 예제는 static 참조에 대한 예제이지만, 내부 클래스(inner class, http://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html 참조 - 역자 주) 와 외부 클래스의 암묵적인 참조 또한 위험하다.

    두 번째 해결책은 Application 의 Context를 사용하는 것이다. 이 Context는 우리의 앱이 살아있는 동안 계속 유효하며 activity 의 생명주기와는 전혀 무관하다. 만약 Context 가 필요한 장주기 객체(long-lived object)를 사용해야 할 경우  Context.getApplicationContext()또는 Activity.getApplication() 를 호출하여 획득한 Application 의 Context 사용을 고려하는 것도 좋다.

    결론짓자면, Context 참조와 관련한 메모리 릭을 피하려면 다음의 내용을 숙지해야 한다.

    • 장주기 객체의 activity 참조는 가능한 피한다. (activity를 참조하는 객체는 activity 가 죽을 경우 제거되어야 함 - 선언한 activity 이외의 곳에서는 GC 대상이 되도록 설계해야 한다. - 역자 주)
    • Activity 의  Context 대신  Application 의 Context 사용한다.
    • 객체 생명주기를 직접 제어하지 않는 경우  Activity 내에서 non-static 내부 클래스의 사용을 가급적 피하고 불가피하게 사용해야 하는 경우라면 참조 타입을 약한 참조로 두도록 한다. 이 문제는 ViewRoot 구현에서 했던 것 처럼  외부 클래스를WeakReference 참조로 둔 static 내부 클래스를 활용하는 것으로 해결할 수 있다.
    • 가비지 컬렉터는 메모리 릭 방지를 보장해 주지 않는다.


    'Programing > Android' 카테고리의 다른 글

    Processes and Application Life Cycle  (0) 2016.05.04
    Google I/O - Memory Management For Android  (0) 2016.05.03
    Avoiding Memory Leaks  (0) 2016.05.03
    Activity, Fragment Lifecycles  (0) 2016.05.03
    Handler, Looper  (0) 2016.04.19
    Parcelable vs. Serializable  (0) 2016.03.22

    + Recent posts