|
슈퍼드로이드 카페의 안드로이드 강좌가 책으로 나왔습니다.
도서명 : 이것이 안드로이드다.
도서링크 : http://www.yes24.com//24/goods/13950202
================================================================================================
1. ANR(Application Not Responding)이란 무엇인가?
ANR은 해당 Application이 일정시간동안 응답이 없다는 것이다.
응답이 없다는 것은 무엇을 의미할까?
내부적으로 어떤 Thread가 처리가 너무 오래 걸려 UI가 반응을 하지 않는 상황이다.
예를 들어 화면에 두가지 버튼이 있다고 하자.
첫번째 버튼은 다운로드를 하는 버튼이고 두번째 버튼은 취소 버튼이다.
첫번째 버튼을 눌러서 다운로드를 내부적으로 진행하는데
문제는
다운로드가 너무 느려 다운로드를 할 동안은 아무런 처리를 할 수 없는 것이다.
이때 사용자가 취소버튼을 눌러도 전혀 반응을 하지 않는다.
사용자는 너무 답답해 할 것이다. 화가 날 것이다.
이에 대해 안드로이드에서는 Activity가 5초 동안 응답이 없으면
ANR이라는 다이얼로그를 구동하고 해당 Process를 강제로 종료해 버린다.
자 우리는 이전에 Main Thread를 배웠다.
Main Thread에서 대부분 화면에 그림을 그리거나
사용자의 이벤트(터치등의 동작)를 받아 들인다.
이 Thread가 바쁘면 화면에 그림을 그릴 수 없을 것이고
당연히 사용자 이벤트에 대한 반응을 보일 수 없다.
바로 Main Thread가 ANR에 대상이 되는 Thread라는 것이다.
이전 강좌에서 말했지만 아래와 같이 처리하면 Main Thread를 볼 수 있다.
그렇다면 ANR을 강제로 발생시켜 보는 패키지를 하나 만들어 보자.
패키지 구성은 아래와 같다.
아래와 같은 UI Layout을 가진다.
아래에서 ANR을 발생시키는 "ANR" 버튼과,
5초 동안 반응이 없음을 확인하는 "Button" 하나를 추가하였다.
아래와 같이 1번에서 ANR을 발생하기 위해 큰 부하를 주었다.
아래와 같이 실행해 보자.
중요한 것은 ANR을 누르고 2번째 과정과 같이 이어서 다른 버튼을 누르는 동작을 해야 한다.
ANR 버튼을 누른다고 ANR이 발생되지는 않기 때문이다.
Main Thread가 어떤 일을 하고 있는 중에
사용자가 Main Thread에 또 다른 일을 시켜야 한다.
그것이 바로 다른 버튼을 누르는 것이다. (물론 다양한 다른 이벤트를 발생해도 같다.)
다시 말하면 Main Thread가 바쁜 와중에 또 다른 작업을 요구한 시점 기준으로
요청에 대한 지연시간 5초의 Count가 되는 것이다.
위에서 두번째 과정인 "Button"을 누른 시점부터 5초 후에 ANR이 발생될 것이다.
대부분의 에러 발생시에는 Logcat에 에러에 대한 로그가 출력된다.
아래를 확인해 보자.
이번 경우의 ANR 발생 원인은 keyDispatchingTimedOut이다.
즉 사용자가 버튼을 누르는 동작을 취했는데 5초 동안 반응이 없었다는 것이다.
Activity에서 ANR이 발생한 경우의 대부분은 바로 keyDispatchingTimedOut 이다.
참고로으로 2번을 보자.
현재 CPU 점유율은 50%이다.
즉 50% 밖에 CPU가 동작하지 않는 상황에 ANR이 발생했다는 것은
바로 현재 Activity의 문제가 있다고 판단해야 하고 꼭 수정해야 한다.
그런데 만일 100%의 점유율에서 ANR이 발생했다면 Background로 돌아가는 다른 Process의 부하가 커서
현재 Activity를 지연시켰다고도 볼 수 있다.
그러므로 현재 CPU 점유율은 꼭 확인하고 어떤 Process가 점유율이 높은지 확인해야 한다.
과연 5초라고 어디에 정의 되어 있을까?
바로 ActivityManagerService.java라는 Frameworks 소스에 존재한다.
참고하자.
이전에 Android Application을 구성하는 4가지 Component가 존재한다고
말했던 것을 기억하는가?
현재 강의 중인 Activity와 BroadcastReceiver, Service, ContentProvider 가 있다.
그중 BroadcastReceiver, Service도 역시 ANR이 존재한다.
아래의 소스와 같이 BroadcastReceiver는 10초, Service는 20초의 응답지연 시간이 정의된다.
다음에 해당 강좌를 진행할때 상세히 알아보도록 하겠다.
위의 테스트 패키지는 아래에 제공한다. 참조하자.
2. ANR 회피 방법?
그렇다면 ANR을 회피하는 방법은 무엇일까?
아주 간단하다.
위에서 분명 ANR의 대상은 Main Thread라고 했던 것을 기억하는가?
그렇다 Main Thread에서 부하가 큰 작업을 하지 않고
다른 Thread를 생성하여 처리하는 방법이다.
간단히 이전 테스트 패키지를 수정해 보자.
위와 같이 CallB()가 부하가 큰 경우
CallB()를 별도의 Thread를 생성해서 돌린 것이다.
실행해 보면 "ANR" 버튼을 눌러도 다른 버튼이 동작하며,
ANR 다이얼로그는 볼 수 없을 것이다.
우리는 이와 같이 부하가 큰 것은 꼭 Thread로 동작 시켜야 한다.
Thread는 비 동기로 동작함으로 동작이 끝날때 까지는 진행바를 표시하기도 한다.
또한 AsyncTask를 적절히 활용하는 것도 좋은 방법이다.
기억하라. ANR은 큰 버그이다.
어떤 Application을 구현할때 ANR이 발생한다면 꼭 수정해야 하는 것이다.
3. ANR 디버깅 방법?
본 과정은 꼭 알아야 할 필요는 없다.
어렵게 여겨 진다면 생략해도 좋다.
왜냐하면 자신이 개발한 Application은 대략 어디서 ANR이 발생하는지 유추할 수 있기 때문이다.
하지만 이 과정을 안다면 좀더 강력한 개발자가 될 것이다.
또한 단말기 제조사에 근무하는 개발자라면 꼭 이 과정을 이해하자.
단말기 제조사에 근무하는 사람은 자신이 개발한 Application을 디버깅하기 보다는
다른 사람들이 개발한 Application을 디버깅하는 경우가 많기 때문이다.
ANR 디버깅을 쉽게 하는 방법은 없을까?
크게 두가지 방법이 있다.
첫번째 방법은 DDMS에서 Method Profiling 방법이다.
아래와 같이 동작 시켜 보자.
1번에서 디버깅할 Process를 선택하고
2번에서 "Start Method Profiling" 버튼을 누른다.
3번에서 바로 ANR을 유도 시키고
4번에서 "Stop Method Profiling" 버튼을 누른다.
3번 진행후 4번 버튼을 누르는 시간은 5초정도로 하자.
위와 같이 처리하면 아래와 같은 화면이 뜬다.
즉 5초 동안 호출된 함수목록이 아래와 같이 보여지게 되는 것이다.
이번 경우에는 간단히 찾을 수 있다.
1번에 호출된 수를 유심히 보자.
2번과 같이 61번씩 호출된 그룹이 있다.
3번 Call Stack를 보면 CallB()함수가 호출된 이후로 반복됨을 알 수 있다.
4번을 보면 주로 CPU를 소모한 그룹 또한 2번과 유사하다.
그 그룹을 정리하자면 sleep와 Log.i 이다.
아래의 원래 소스를 보면 1번과 같이
ANR을 유도한 것이 for 문의 Log.i 와 sleep 인 것을 확인하고 수정하면 되는 것이다.
자 다른 방법을로 찾아 보자.
ANR의 발생은 Main Thread라고 하였다.
1번을 그래프를 유심히 보자.
2번과 같이 반복되는 부분이 존재할 것이다.
바로 반복되거나 길게 이어지는 색상을 찾는 것이 중요하다.
계속이어 아래를 보자.
반복되는 부분의 일부를 1번과 같이 마우스로 드래그를 해 보자.
아래와 같이 확대가 된다.
여기서 1번과 같이 긴 막대를 선택하면
해당하는 함수가 2번과 같이 보인다.
2번을 보면 Log.i가 반복됨을 알 수 있다.
즉 대략적인 부분을 찾을 수 있는 것이다.
이렇게 Call Stack을 보는 방법을 이용해 본 경우 말고도 다양하게 활용될 수 있다.
예를 들어 특정 함수가 호출되었는지도 확인이 가능한 것이다.
두번째 방법은 dropbox를 이용하는 것이다.
이 방법은 애뮬에서 가능한 작업이다.
만일 개인적으로 구입한 안드로이드 스마트폰이 있다면
그 것으로 테스트 할 수 없다.
이유는 engineer 버젼으로 컴파일 된 디바이스이어야 하기 때문이다.
만일 eng 모드의 디바이스이면 당연히 가능하다.
(예전에 넥서스원 단말을 개발버젼으로 HTC에서 판매한 적이 있었던것 같다 그건 가능하다.)
또한 단말 제조사에서 근무중인 사람이라면 가능할 것이다.
왜냐하면 단말 제조사에서는 단말의 이미지를 eng 모드로 Full 컴파일하여
단말기에 올릴수 있기 때문이다.
또한 단말기 제조사에 종사하는 사람이라면 이 과정을 꼭 이해하길 바란다.
여기서는 애뮬로 설명토록 하겠다.
아래와 같이 DDMS에서 File Explorer을 열고
1번의 폴더의 settings.db 파일을 선택하자.
2번에서 해당 파일을 로컬 PC로 저장한다.
해당 파일을 단말기의 여러가지 셋팅 정보가 저장된 DB이다.
해당 DB의 포멧은 SQLite이다.
즉 SQLite DB 편집기가 필요하자.
개인적으로는 아래의 Program을 이용했다.
필요한 사람은 참조하길 바란다.
실행하면 아래와 같은 화면이 구동된다.
1번과 같이 파일 열기를 선택한다.
아래와 같이 저장된 DB를 선택하고 2번과 같이 열기를 누르자.
아래와 같이 화면이 보일 것이다.
이제 해당 DB의 특정 Field를 수정할 것이다.
아래와 같이 진행하자.
1번을 선택하고
2번의 Table을 선택하자.
3번의 항목을 수정할 것이다.
3번의 항목을 두번 클릭하라.
아래 1번과 같이 enabled로 변경하고
2번 버튼으로 적용하자.
자 아래의 1번과 같이 변경되었을 것이다.
나중을 위해 2번도 enabled로 변경해 두자.(안해도 된다.)
자 다시 DDMS로 돌아와서
1번을 선택하고 기존 DB를 2번을 눌러 삭제하자.
수정된 DB를 올리기 위해 DDMS에서 1번 버튼을 누르고
2번과 같이 수정된 DB를 선택하자.
3번을 누르면 애뮬에 올라갈 것이다.
아래와 같이 수정된 DB가 올라갔다.
!!! 자 애뮬을 리부팅하자.
그냥 종료하고 애뮬을 다시 시작하라는 뜻이다.
자 이전 ANR 테스트 패키지를 실행해서 ANR을 발생시켜 보라.
이전에 Settings.DB에서
dropbox:data_app_anr 항목을 enabled로 바꿨다.
이 속성이 enabled로 되어 있다면
사용자 application에서 ANR이 발생되면 특정 위치에 상세한 로그로 저장해 준다.
위에서 dropbox:data_app_crash도 enabled라고 되어 있다면
사용자 application에서 exception(crash) 발생시 상세한 로그를 저장해 준다.
이 로그는 매우 강력하다.
그렇다면 로그는 어디에 저장되는 것일까?
해당 로그는 framework에서 dropbox service가 로그를 저장해 준다.
해당 서비스는 단말 내부에 data/system/dropbox 폴더에 로그를 저장한다.
dropbox의 로그는 폰이 초기화 되기 전까지는 유지 함으로 이전 로그를 모두 볼 수 있다.
(단말사에 근무하는 사람들은 해당 정보로 많은 부분을 디버깅한다.)
어쨌든 상세한 내용은 별도의 안드로이드 디버깅에서 강의 하겠다.
로그를 확인하는 방법을 보자.
아래와 같이 명령어를 사용해 보자.
adb shell dumpsys dropbox --print > 저장할 파일명
로그를 보면 많은 내용이 존재 할 것이다.
(참고로 너무 많은 로그가 나오면
"adb shell dumpsys dropbox data_app_anr --print > 저장할 파일명"
이라고 하면 ANR만 출력된다. )
아래를 보자.
우리가 보려고 하는 것은 ANR 정보이다.
1번과 같이 data_app_anr 이라는 항목을 보아야 한다.
본 정보에는 ANR이 발생했을때 Process의 Method Call Stack을 모두 출력해 준다.
하지만 이전에 발생한 ANR도 모두 저장되어 있으므로
2번과 같이 발생한 날짜와 해당 패키지 명으로 찾는 것이 좋다.
내가 ANR이 발생된 Thread는 Main Thread라고 하였다.
그러므로 거의 대부분 3번과 같이 Main Thread의 Method Call Stack를 보면 된다.
자 Main Thread의 Call Stack을 보자.
4번과 같이 제일 마지막에 호출된 함수를 기준으로
4~5줄만 보아도 대략 왜 발생했는지 알 수 있을 것이다.
즉 4번을 보면 CallB 함수가 거의 최종 호출되었다.
우리는 그 함수를 찾아 보고 ANR을 회피 하도록 수정하면 될 것이다.
내가 단말을 개발하면서 바로 이 dropbox를 통해 많은 버그를
탐색하였고 수정할 수 있었다.
그 만큼 중요한 로그이다.
4. StrictMode란 무엇인가?
시작하기 앞서 해당 강좌는
http://android-developers.blogspot.com/2010/12/new-gingerbread-api-strictmode.html
사이트를 참조하시기 바랍니다.
한국어로 번역된 사이트도 많이 있습니다.
( 검색어 : "New Gingerbread API: StrictMode" )
사실 위에 글을 쓴 구글 개발자 피츠패트릭(Brad Fitzpatrick) 보다
잘 설명할 수 없을 것 같아서 입니다. ㅋㅋㅋ
뭐 이 API를 개발한 사람이기 때문에 당연한 것이지..... 라고 위안을... - _-;
어쨌든 StrictMode에 대해서 알아 본다.
Main Thread는 화면 처리를 담당한다.(화면에 그리거나 사용자에 대한 반응 등)
만일 사용자가 버튼을 눌렀는데 늦게 눌러진다거나 하면
답답하지 않을까? 나는 답답한데...
또한 5초이상 버튼이 눌러지는 반응을 하지 않으면
ANR이 발생된다.
StrictMode는 그것을 줄이려는 노력에서 시작되었다.
Main Thread에서는 시간이 많이 소모될 수 있는,
동작을 규정하고 막을 수 있다.
그 규정은 안드로이드에서 제공하는 범위에서 개발자가 정한다.
그 규정 위반시 안드로이드에서 제공하는 범위에서 처리를 할 수 있다.
간략히 아래의 그림으로 이해해 보자.
위와 같이 Main Thread에 StrictMode를 지정하고
규정과 위반 시 처리 내용을 정의 하면 된다.
그런데 규정 내용이 3가지가 존재한다.
파일 쓰기,읽기,네트워크 사용 이다.
왜 꼭 이 3가지 일까?
먼저 파일 I/O의 경우를 생각해 보자.
안드로이드 파일 시스템은 동시에 파일을 쓰고 읽지 못한다.
어떤 다른 Process가 열심히 파일을 쓰고 있다면
그 동안은 파일 I/O가 Lock 된다.
그러므로 파일 I/O의 큰 지연을 줄 수 있다.
네트워크 사용은
네트워크 사정과 서버의 사정에 따라
소모되는 시간은 유추하기 힘들고
시간이 많이 소모될 확율이 크다.
그러므로 크게 이 두가지 동작을
규정하고 있다. (특히 네트워크 사용은 절대 Main Thread에서 하면 안된다.)
또한 이 두가지 동작에 의해 UI반응이 느려지고 대부분 ANR이 발생시키는 원인이다.
이런 동작은 꼭 다른 Thread를 생성해서 처리하도록 하자.
자 테스트 패키지를 하나 만들어 보겠다.
해당 패키지에서는 아래와 같은 규정과 위반 시 처리 내용을 설정할 것이다.
패키지 구성은 아래와 같다.
화면 Layout은 아래와 같다.
아래의 버튼을 누르면 StrictMode 위반을 시도할 것이다.
Activity 구현 소스이다.
1번에서 StrictMode 를 설정 하였다.
StrictMode.setThreadPolicy() 함수를 호출함으로써
Main Thread는 StrictMode 정책에 따라야 하는 것이다.
2번에서 규정 내용은 파일을 읽는 동작은 막는 것이다.
3번에서 그 규정을 위반했을때는
총 4가지를 처리하게 된다.
1) penaltyLog() : Logcat에 로그를 남긴다.
2) penaltyDeath() : 해당 패키지를 강제 종료한다. (Process를 죽인다.)
3) penaltyDropBox() : DropBox에 로그를 남긴다.
4) penaltyDialog() : 화면에 위반에 대한 다이얼로그를 띄운다.
4번에서는 버튼을 누르면 SD 카드에서 "test_file.txt" 파일을 읽는 시도를 하게 된다.
즉 StrictMode를 위반하는 것이다.
일단 패키지를 설치한뒤 테스트를 위해
1번을 바로 실행하지 말고
2번과 같이 "test_file.txt"라는 파일을 만들고
3번과 같이 DDMS를 이용하여 애뮬의 SD 카드에 해당 파일을 복사해 둔다.
자 이제 1번과 같이 실행해 보자.
첫번째 위반 시 처리될 사항이 동작하였는지 보자.
penaltyLog()의 처리가 되었는지 보자.
2번과 같이 DDMS에서 Logcat 콘솔에 해당 로그가 출력되었다.
3번을 확대해 보면 위반 내요인 StrictModeDiskReadViolation 이 발생 되었다.
두번째 위반시 처리된 사항이 동작하였는지 보자.
penaltyDeath() 로 인행 해당 패키지를 강제 종료되었다.
정말 DDMS의 구동중인 Process 내역을 보면 해당 Process가 존재하지 않는 것을 알 수 있다.
세번째 위반시 처리된 사항이 동작하였는지 보자.
penaltyDropBox() 로 인해 DropBox에 로그를 남겼는지 보자.
2번의 adb shell dumpsys dropbox data_app_strictmode --print > log.txt 를 실행해 보자.
내용이 많지 않으면 꼭 파일로 저장할 필요는 없다.
그냥 adb shell dumpsys dropbox data_app_strictmode --print 이렇게 사용해도 된다.
어쨌든 생성된 3번의 파일을 열면
해당 로그가 보이게 된다.
dropbox의 장점은 해당 로그를 계속 유지 한다는 것이다.
Logcat의 경우는 리부팅후 로그가 날아갈 뿐만아니라
Logcat에 저장하는 버퍼의 한계가 있으므로 지워질 수 있다.
네번째 위반시 처리된 사항이 동작하였는지 보자.
penaltyDialog() 로 인해 화면에 다이얼로그가 떴는지 보자.
2번을 보면 확실히 다이얼로그가 구동되었다.
가장 편한 방법이 아닌가?
하지만 큰 단점은 있다.
만일 loop를 돌면서 계속 파일을 읽는다면
(사실 파일을 읽을때는 반복하면서 읽는 경우가 많다.)
다이얼로그가 계속 반복해서 보여질 것이다.
자 설정한 4가지 동작을 모두 확인해 보았다.
그렇다면 보통 어떻게 설정하는 것이 좋은까?
개인적으로는 아래와 같이 설정을 한다.
1번과 같이 규정은 파일 쓰고, 읽기, 네트워크 사용에 모두 건다.
위반시에는 logcat에 로그를 남기고 다이얼로그를 띄운다.
개발 중에는 해당 방법으로 처리하면 편리하다고 생각한다.
!!! 중요
참고로 detectDiskReads()와 detectDiskWrites()는
필수 사항이 절대 아니다.
디스크 사용량이 아주 작은 파일인 경우
꼭 detectDiskReads()와 detectDiskWrites()을 걸 필요가 없다.
이 강좌를 참조하신 분들 중 해당 셋팅을 사용하여
sharedPreferences 사용을 하다가 StrictMode에 걸려 문의하신 분이 있다.
sharedPreferences도 파일을 쓰고 읽는 작업을 함으로
해당 모드를 걸어두면 에러가 발생한다.
내가 말하는 Disk R/W에 주의해야 할 부분은
파일의 용량이 매우큰 작업을 할때이다.
오해할 소지를 드려서 죄송합니다.
2번에 setVmPolicy가 존재한다.
그것도 또다른 규정을 설정할 수 있다.
detectLeakedSqlLiteObjects() 규정이란
SQLite에서 DB를 쿼리하고 결과를 Cursor로 받는다.
그 Cursor는 사용 후 Close를 해야하는데 하지 않는 경우를 말한다.
아직 SQLite와 Content Provider에 대해서 배우지 않았으므로
그런것이 있다고만 이해하자.
어쨌든 이 경우 DropBox로 저장하도록 해서
차후 한꺼번에 확인하여 처리하는 것이 좋겠다.
( VmPolicy의 경우는 위반시 처리되는
내용 중 다이얼로그를 띄우는 것은 제공되지 않는다.
그 이유는 아주 빈번하게 발생될 수 있기 때문이다. )
위의 테스트 코드는 아래를 참조하자.
안드로이드 Application을 개발할때,
꼭 처음에 StrictMode를 설정하도록 하자.
개발이 완료되고 해당 기능을 설정하면 구조적으로
수정이 힘든 경우가 많기 때문이다.
꼭꼭꼭 기억하자.
!!! 위의 주제에 해당하는 적당한 예를 댓글로 남겨 주세요. ^^
활용 방안의 예는 다른 개발자들에게 많은 도움이 됩니다.
!!! 카페의 활성화를 위해 추천 버튼을 눌러 주세요.
|
첫댓글 아... 저는 dropbox는 처음 보았네요. 정말 유용할 것 같습니다. 감사합니다.
^^ 네 만일 플랫폼쪽으로 하시는 분이라면 플랫폼 디버깅을 위해 꼭 필요한 로그입니다.
플랫폼 디버깅 강좌도 많이 추가됐으면 좋겠습니다.
네 플랫폼 디버깅 강좌를 하기 위해서는 4가지 component 이외에도 이해해야 할 부분이
좀 많습니다.
그래서 기본 강좌가 끝난뒤 진행할 예정입니다.^^
dropbox가....이걸 말하는 거였네요..
감사합니다! 덕분에 궁금증이 풀렸어요ㅎㅎ