Tag Archives: 생강빵

[번역] 새로운 생강빵 API: StrictMode

배경

Google의 위대한 점 중 하나는 “20% 시간” (주 업무가 아닌 다른 프로젝트에 20%의 사간을 할애) 입니다.  제가 구글에 입사한 후, 다양한 프로젝트에 참여했는데 7 개의 20% 프로젝트를 가지고 있다고 농담삼아 얘기하기도 합니다.  계속해서 다시 참여하게 된 프로젝트는 안드로이드 프로젝트였습니다.  오토바이를 타고 차고문에 접근하면 자동으로 문을 열어주는 것을 포함해 제가 원하는 모든 것에 대한 접근을 허가하는 개방성에 매료되었습니다.  항상 이 프로젝트가 성공하길 원했지만 한 가지 걱정이 있었습니다: 안드로이드가 항상 자연스럽게 실행되지는 않는다는 것이었습니다.  애니메이션이 가끔 멈추기도 했고 UI 요소들이 항상 즉각적으로 반응하는 것도 아니었습니다.  분명한 것은 가끔씩 이런 일들이 전혀 엉뚱한 곳에서 발생한다는 것입니다.

SMS를 많이 사용하는 사용자로써 Cupcake (Android 1.5) 릴리즈 중 제가 맡은 20% 프로젝트 중 하나는 메시지 어플의 속도를 향상시키고 보다 자연스럽게 만드는 것이었습니다.   저는 어플을 만족스러운 상태로 만든 뒤 계속해서 다른 20% 프로젝트에 참여했습니다.  Donut (Android 1.6)이 출시됐을 때, 제가 만든 메시지 최적화 중 몇 개가 실수로 손상되었음을 알게 되었습니다.  잠시동안 우울했지만, 안드로이드가 정말로 필요한 것은 지속적이며 내장된 전반적인 성능 모니터링이란 것을 깨달았습니다.

1년 전에 안드로이드 팀에 풀타임으로 참여하게 되었고, Froyo 성능 문제를 조사하는 데 많은 시간을 보냈습니다.  특별히 ANR (애플리케이션이 메인 쓰레드의 Looper를 지연시킬 때 보게 되는 귀찮은 다이얼로그 화면) 디버깅에 많은 시간을 보냈습니다.  가지고 있는 도구를 사용해 ANR 디버깅을 하는 것은 괴롭고 지루한 작업이었습니다.  특히 다중 프로세스가 관련된 문제(다른 프로세스의 ServicesContentProvidersBinderContentResolver 작업을 하는 경우)에 있어서는 원인을 찾을 수 있는 충분한 도구가 없었습니다.  지연 문제와 ANR을 추적하기 위한 뭔가 더 나은 방법이 있어야만 했습니다.


StrictMode로 들어가기

“자네가 16 ms 영역에서 120 ms로 일하고 있는 걸 알고 있다고…”


StrictMode는 여러분이 무엇이 해당 쓰레드에서 허가되고 이것을 어길 경우 어떤 벌칙이 주어지는 지를 선언하는 정책을 쓰레드에 설정할 수 있도록 해주는 생강빵의 새로운 API 입니다.  구현하는 관점에서 이 정책은 단순히 쓰레드의 로컬 정수 비트마스크 입니다.

기본값으로 모든 것이 허가되며, 여러분이 원하지 않은 한 방해하지 않습니다.  쓰레드 정책에 활성화 시킬 수 있는 플래그는:

• detect disk writes
• detect disk reads
• detect network usage
• on a violation: log
• on a violation: crash
• on a violation: dropbox
• on a violation: show an annoying dialog

추가적으로 StrictMode는 여러분이 요청할 경우 디스크(java.io.*, android.database.sqlite.*, 등)나 네트워크(java.net.*)를 사용할 때 현재 쓰레드의 정책을 체크하는 약 12개의 훅을 가지고 있습니다.

StrictMode의 강력한 부분은 다른 서비스나 제공자에 Binder IPC 호출이 될 때마다 쓰레드 정책이 전파되고 스택 추적이 무한대의 프로세스에 연결된다는 것입니다.


느려지는 건 싫어요

여러분은 아마 여러분의 어플에서 디스크 I/O가 생기는 모든 곳을 알고 있겠지만, 시스템 서비스와 제공자가 디스크에 쓰는 곳을 모두 알고 계시나요?  저는 모릅니다.  저도 배우고 있지만, 엄청나게 많은 코드입니다.  저희가 SDK 문서에 성능 구현에 관한 부분을 명확히 하려고 계속 작업 중이지만, 저 같은 경우는 보통 의도하지 않은 디스크 입출력 호출을 찾기 위해 StrictMode에 의존합니다.


휴대폰 디스크의 배경

잠깐만요, 디스크에 쓰는게 뭐가 문제죠?  안드로이드 디바이스는 모두 플래시 메모리에서 돌아가지 않나요?  이동장치가 없는 엄청 빠른 SSD와 같은 것 아닌가요?  주의할 필요가 없지 않나요?  불행하게도, 디스크 입출력에 대해 주의해야 합니다.

안드로이드 디바이스에 사용되는 플래시 컴포넌트나 파일시스템이 지속적으로 빠르게 움직인다고 가정할 수 없습니다.  많은 안드로이드 디바이스에 사용되는 YAFFS 파일시스템의 예를 들면, 모든 작업에 전역 잠금(global lock)을 사용합니다.  전체 디바이스에서 한번에 오직 한 개의 디스크 작업만 가능합니다.  아주 단순한 “stat” 작업도 운이 안 좋을 경우 많은 시간이 걸릴 수 있습니다.  더 전통적인 블럭 디바이스 형태의 파일시스템을 사용하는 다른 디바이스들도 블럭 로테이션 레이어가 가비지 컬렉션을 하기로 결정할 경우 가끔씩 내부 플래시 삭제 작업이 느려집니다. (관련 배경에 대한 좋은 글은 lwn.net/Articles/353411를 참조)

다행스러운 점은 모바일 디바이스의 “디스크” (혹은 파일시스템)는 일반적으로 빠르다는 것이지만, 하위 10% 정도의 지연은 가끔씩 아주 심각합니다.  또한 대부분의 파일시스템은 용량이 부족해지면 더 느려집니다.  (Google I/O Zippy Android apps talk 참고, code.google.com/p/zippy-android)


“메인” 쓰레드

안드로이드 콜백과 수명주기 이벤트는 모두 일반적으로 메인 쓰레드에서 일어납니다 (별칭은 “UI thread”).  이것은 편리하지만, 모든 애니메이션, 스크롤, 밀기 프로세스가 메인 쓰레드의 콜백으로 일어나기 때문에 주의해야 할 부분입니다.

여러분이 60 fps로 애니메이션을 실행하길 원하고 이 때 입력 이벤트가 들어오면 (또한 메인 쓰레드에서), 여러분은 그 입력 이벤트를 처리하는 코드를 실행하기 위해  16 ms가 주어집니다.  16 ms보다 길게 소요되면, 아마도 디스크에 무언가를 쓰고 있어서, 여러분의 애니메이션은 이제 멈칫하게 됩니다.  디스크 읽기는 보통 덜 하지만, 이것도 16 ms 이상 소요될 수 있습니다.  특히, YAFFS에서 입력 중에 있는 프로세스에 의해 잠금이 걸린 파일시스템을 기다리는 경우 더 그렇습니다.

네트워크는 특히 느리고 지속적이지 않기 때문에, 메인 쓰레드에서 네트워크 호출을 하면 안됩니다.  사실, Honeycomb 릴리즈에서는 Honeycomb 이전의 API 버전을 지원하지 않는 한 메인 쓰레드에서 네트워크 요청이 오면 치명적 오류가 발생하도록 만들었습니다.  따라서 Honeycomb SDK를 준비하시려면, UI 쓰레드에서 네트워크 요청을 하지 마시기 바랍니다. (아래 “자연스럽게 만들기 팁” 참조)


StrictMode 활성화

StrictMode를 사용할 때 추천하는 방식은 개발 중에는 켜고, 이것을 통해 배운다음 어플을 배포하기 전에 끄는 것입니다.

예를 들면, 여러분의 애플리케이션이나 컴포넌트의 onCreate():

 public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()
                 .penaltyLog()
                 .build());
     }
     super.onCreate();
 }

혹은, 간단히:

    public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.enableDefaults();
     }
     super.onCreate();
 }

두번 째 형태는 특별히 이전 Gingerbread API 버전을 지원하기 위해 추가되었으나, 여전히 쉽게 리플렉션이나 다른 기술들을 이용해 StrictMode를 활성화 합니다.  예를 들어, Donut (Android 1.6)을 지원하지만,  StrictMode.enableDefaults()를 호출하기 위해 충분한 리플렉션을 사용하는 한 Gingerbread 디바이스나 에뮬레이터에서 StrictMode를 사용할 수 있습니다.


StrictMode 보기

만약 기본값인 penaltyLog()를 사용 중이면, 그냥 adb logcat을 실행하고 터미널의 결과를 보시면 됩니다.  모든 정책위반 사항이 여러분에 콘솔에 나타납니다.  약간의 중복 메시지에 대한 제약은 있습니다.

더 강력한 기능을 원하실 경우, penaltyDropbox()를 실행하면 DropBoxManager에 기록이 되고 다름 명령어로 나중에 추출할 수 있습니다.
adb shell dumpsys dropbox data_app_strictmode --print


자연스럽게 만들기 팁

Threadjava.util.concurrent.* 외에 Handler, AsyncTask, AsyncQueryHandler, IntentService와 같은 안드로이드 API도 참고하시기 바랍니다.


우리의 경험

안드로이드 개발 중 저희는 매일 모든 팀이 사용하는 새로운 “dogfood” 빌드를 만들었습니다.  Gingerbread의 개발과정을 통해 StrictMode 로깅을 활성화하기 위해 매일 dogfood 빌드를 만들고 분석을 위해 발견된 모든 정책위반을 업로드 했습니다.  매 시간 MapReduce 작업이 실행되고, 프로세스/패키지에 나타나는 모든 이벤트 루프의 지연, 스택 트레이스 (크로스 프로세스를 포함), 지연 백분위수 등에 대한 쌍방향의 리포트가 생성되었습니다.

StrictMode의 데이터를 사용해 수백개의 응답 버그와 애니메이션 결함을 수정했습니다.  저희는 수천개의 특정 어플(AOSP 어플과 Google 어플 둘 다)에 대한 문제 뿐만 아니라 안드로이드 코어(예를 들어 시스템 서비스나 제공자)에 성능 최적화를 했고 이로 인해 시스템의 모든 어플들이 혜택을 보게 되었습니다.  여러분이 지금 Froyo를 사용하고 있어도, 최근 업데이트된 GMail, Google Maps, YouTube가 모두 Gingerbread 디바이스에서 수집된 StrictMode 데이터에서 혜택을 본 것입니다.

저희가 자동으로 시스템을 빠르게 할 수 없기 때문에, 쉽게 효과적으로 실행되게 하는 특정 패턴을 만드는 API를 추가했습니다.  예를 들면,  commit()의 리턴값(사실 대부분 사용하지 않는)이 필요없는 경우 commit() 대신 사용해야 할 SharedPreferences.Editor.apply() 메서드가 있습니다. 여러분은 사용자의 플랫폼 버전에 따라 조건적으로 apply()commit()를 사용할  수 있습니다.

중간의 다른 단계들을 보지 못하고 Froyo에서 Gingerbread로 넘어온 구글 직원들은 시스템 반응이 얼마나 빨라졌는지에 대해 놀랍니다.  크롬 팀의 친구들도 최근 비슷한 것을 추가했습니다.  물론, StrictMode가 모든 것을 한 것은 아닙니다.  Gingerbread의 새로운 동시 가비지 컬렉터 또한 지연 문제 해결에 큰 도움이 되었습니다.


앞으로

StrictMode API와 그 기능들은 계속 발전할 것입니다.  Honeycomb에서 StrictMode에 대한 좋은 기능들을 예정되어 있지만, 또 다른 어떤 기능들을 원하시는지 알려주시기 바랍니다!  “strictmode” 태그를 추가한 질문을 stackoverflow.com에 올려주시면 답변드리도록 하겠습니다.  감사합니다!


원문 링크

[번역] 안전하게 데이터 저장하기

생강빵의 출시와 함께 개발자들이 고려해야 할 안드로이드 2.3에 대한 글들을 연재할 예정입니다.  개발자들이 가장 중요하게 고려해야 할 것 중 하나는 데이터가 유실되지 않는 것입니다.  생강빵이 출시되면서 이 규칙이 살짝 변하고 있기 때문에 이 주제가 좋은 시작점이 될 것 같습니다.  이 글을 제가 쓴 것이 아니라 안드로이드 엔지니어인 Brad Fitzpatrick, Dianne Hackborn, Brian Swetland, Chris Tate의 이메일 내용에서 발췌한 것들입니다.

어떻게 당신의 데이터가 확실하게 비휘발성 저장소에 저장할 수 있을까요?  해답은 fsync()라는 로우레벨 시스템 호출과 관련되어 있습니다.  저와 같은 옛날의 C 프로그래머들은 옛날에 아주 어려운 방법으로  이것을 배웠습니다.  제가 2008년 OSCON에서 참여한 Stewart Smith의 Eat My Data: How Everybody Gets File IO Wrong 세션은 아주 즐거웠습니다.

이 부분을 안드로이드 개발자들이 고려해야 하는 이유는 2.3부터 넥서스S를 포함한 점점 증가하는 디바이스들이 YAFFS에서 버퍼링을 훨씬 많이 하는 ext4 파일시스템으로 옮겨갈 것이기 때문입니다. 따라서, 여러분이 원할 때 데이터가 영구적인 저장소로 저장되는지 더욱 적극적으로 확인해야 합니다.


여러분이 SharedPreferencesSQLite를 사용하고 있다면, 안심하셔도 됩니다.  저희가 버퍼링을 제대로 하고 있는지 검증했기 때문이죠.  하지만, 여러분이 자체적으로 디스크에 저장하는 형식을 사용하고 있다면, write()나 파일을 닫기 위해 close()를 호출할 때, 데이터가 항상 플래시 칩에 도달하는 것은 아니라는 것을 기억해야 합니다.  여러분과 하드웨어 사이에는 여러 개의 레이어가 존재합니다!  그리고, ext4 버퍼링 정책때문에 여러분이 가지고 있다고 생각했던(그러나, 없었던) POSIX 보증이 없습니다.

일부 안드로이드 디바이스는 이미 비YAFFS 파일시스템에서 실행되고 있지만, 넥서스S에서 작업하면서 프레임워크 소스에서 실제로 여러번 버퍼링 문제를 발견했습니다.  생강빵 소스가 배포되면, 파일 I/O가 어떻게 이루어 져야 하는지에 대한 예제들을 많이 찾을 수 있을 것입니다.

일단 로우 데이터에서 작업하려면 적절한 방법으로 fsync() 호출을 담당하는 java.io.RandomAccessFile의 동기화 모드 중 하나를 사용합니다.  여러분이 못하시면, 아마 아래와 같은 Java 코드를 원하실 겁니다.

     public static boolean sync(FileOutputStream stream) {
         try {
             if (stream != null) {
                 stream.getFD().sync();
             }
             return true;
         } catch (IOException e) {
         }
         return false;

일부 어플리케이션에서 여러분은 close() 호출의 리턴상태까지 체크하길 원하실 수도 있습니다.

물론 이 이야기에는 두 가지 면이 있습니다. 여러분이 fsync()를 호출하고 데이터를 강제로 저장하면, 속도가 느려질 수 있습니다; 최악의 경우 언제 끝날지 예상할 수 없을 정도로 느릴 수도 있습니다. 따라서 필요한 경우에 호출하되 불필요하게 사용하지 않도록 주의해야 합니다.


원문링크