Android Programming: The Big Nerd Ranch Guide by Bill Phillips, Brian Hardy Authorized translation from the English language edition, entitled ANDROID

Save this PDF as:
 WORD  PNG  TXT  JPG

Size: px
Start display at page:

Download "Android Programming: The Big Nerd Ranch Guide by Bill Phillips, Brian Hardy Authorized translation from the English language edition, entitled ANDROID"

Transcription

1

2 Android Programming: The Big Nerd Ranch Guide by Bill Phillips, Brian Hardy Authorized translation from the English language edition, entitled ANDROID PROGRAMMING: THE BIG NERD RANCH GUIDE, 1st Edition by HARDY, BRIAN; PHILLIPS, BILL published by The Big Nerd Ranch, Inc, publishing as Big Nerd Ranch Guides, Copyright c 2013 All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. KOREAN language edition published by J-Pub Co. Copyright c 2013 이책의한국어판저작권은에이전시원을통해저작권자와의독점계약으로제이펍출판사에있습니다. 신저작권법에의해한국내에서보호를받는저작물이므로무단전재와무단복제를금합니다. 초판 1쇄발행 2013년 11월 22일지은이빌필립스, 브라이언하디옮긴이심재철펴낸이장성두펴낸곳제이펍출판신고 2009년 11월 10일제 호주소경기도파주시문발동파주출판도시 뮤즈빌딩 403호전화 / 팩스 홈페이지 / 이메일 편집부이민숙, 이슬용지신승지류유통 / 인쇄한승인쇄사 / 제본광우제책사 ISBN (93000) 값 35,000 원 이책은저작권법에따라보호를받는저작물이므로무단전재와무단복제를금지하며, 이책내용의전부또는일부를이용하려면반드시저작권자와제이펍의서면동의를받아야합니다. 잘못된책은구입하신서점에서바꾸어드립니다. 제이펍은책에관한독자여러분의아이디어와원고투고를기다리고있습니다. 책으로펴내고자하는아이디어나원고가있으신분께서는책에대한간단한개요와차례, 구성과저 ( 역 ) 자약력등을메일로보내주세요.

3 Android Programming The Big Nerd Ranch Guide 빌필립스, 브라이언하디지음 심재철옮김

4 차례 옮긴이머리말 이책에대하여 알아두자 xv xvii xxii Chapter 1 Chapter 2 처음만드는안드로이드애플리케이션 1 앱기본사항 2 안드로이드프로젝트생성하기 3 이클립스사용하기 7 사용자인터페이스의레이아웃만들기 8 뷰계층구조 13 / 위젯속성 14 / 문자열리소스생성하기 16 / 레이아웃미리보기 17 레이아웃 XML 에서뷰객체로 18 리소스와리소스 ID 19 위젯을코드와연결하기 22 import 구성하기 23 / 위젯의참조얻기 24 / 리스너설정하기 24 에뮬레이터에서실행시키기 30 안드로이드앱빌드절차 32 안드로이드빌드도구들 34 안드로이드와모델-뷰-컨트롤러 37 새로운클래스만들기 38 게터와세터메서드만들기 39 모델 - 뷰 - 컨트롤러와안드로이드 42 MVC 의장점 44 뷰계층수정하기 45 컨트롤러계층수정하기 47 장치에서실행하기 53 장치연결하기 53 / 개발용장치구성 54 아이콘추가하기 55 프로젝트에리소스추가하기 56 / XML 에서리소스참조하기 57 iv 실무에바로적용하는안드로이드프로그래밍

5 Chapter 3 Chapter 4 Chapter 5 챌린지 59 챌린지 : 리스너를 TextView 에추가한다 59 챌린지 : Previous( 이전 ) 버튼을추가한다 60 챌린지 : Button 에서 ImageButton 으로변경 61 액티비티생명주기 63 액티비티생명주기로깅하기 64 로그메시지만들기 65 / LogCat 사용하기 67 장치회전과액티비티생명주기 73 장치구성과대체리소스 73 장치회전시데이터저장하기 78 onsaveinstancestate(bundle) 오버라이드하기 79 액티비티생명주기다시알아보기 80 onsaveinstancestate(bundle) 테스트하기 82 로깅레벨과관련메서드들 83 안드로이드앱의디버깅 85 DDMS 퍼스펙티브 87 예외와스택기록 88 오작동진단하기 90 / 스택기록로깅하기 92 / 중단점설정하기 94 / 예외중단점사용하기 98 파일탐색기 100 안드로이드특유의디버깅 101 안드로이드 Lint 사용하기 101 / R 클래스관련문제들 103 두번째액티비티만들기 105 두번째액티비티준비하기 107 새로운레이아웃생성하기 107 / 새로운액티비티서브클래스생성하기 111 / 매니페스트에액티비티선언하기 113 / Cheat 버튼을 QuizActivity 에추가하기 114 액티비티시작시키기 116 인텐트로통신하기 117 액티비티간의데이터전달 119 인텐트엑스트라사용하기 120 / 자식액티비티로부터결과돌려받기 123 안드로이드가액티비티를어떻게알까? 129 챌린지 132 차례 v

6 Chapter 6 Chapter 7 Chapter 8 안드로이드 SDK 버전과호환성 133 안드로이드 SDK 버전 133 호환성과안드로이드프로그래밍 135 허니콤은큰변화였다 136 / Minimum SDK 버전 138 / Target SDK 버전 138 / 빌드 SDK 버전 138 / 상위버전의 API 코드를안전하게추가하기 140 안드로이드개발자문서 144 챌린지 : 빌드버전보여주기 148 UI 프래그먼트와프래그먼트매니저 149 UI 유연성의필요 150 프래그먼트개요 151 CriminalIntent 앱개발시작하기 153 새로운프로젝트생성하기 156 / 프래그먼트와지원라이브러리 157 / Crime 클래스생성하기 159 UI 프래그먼트의호스팅 161 프래그먼트생명주기 162 / 호스팅의두가지방법 163 / 컨테이너뷰정의하기 163 UI 프래그먼트생성하기 165 CrimeFragment 의레이아웃정의하기 165 / CrimeFragment 클래스생성하기 167 UI Fragment 를 FragmentManager 에게추가하기 171 프래그먼트트랜잭션 172 / FragmentManager 와프래그먼트생명주기 175 이책의모든액티비티가프래그먼트를사용하는이유 177 허니콤, 아이스크림샌드위치, 젤리빈과그이후버전에서의앱개발 178 레이아웃과위젯으로사용자인터페이스생성하기 179 Crime 업그레이드하기 179 레이아웃변경하기 181 위젯을코드와연결하기 183 XML 레이아웃속성을더자세히알아보기 185 스타일과테마 185 / 화면픽셀밀도, dp 와 sp 186 / 안드로이드의디자인지침 188 / 레이아웃매개변수 189 / 마진 vs. 패딩 189 그래픽레이아웃도구사용하기 191 새로운위젯추가하기 193 / 속성뷰에서속성들수정하기 194 / 아웃라인뷰에서위젯구성하기 194 / 자식위젯의레이아웃매개변수변경하기 196 / android:layout_weight 속성의동작방법 197 / 그래픽레이아웃도구요약 199 / 위젯 ID 와복수의레이아웃 199 챌린지 : 날짜의형식만들기 200 vi 실무에바로적용하는안드로이드프로그래밍

7 Chapter 9 Chapter 10 Chapter 11 Chapter 12 ListFragment 로리스트보여주기 201 CriminalIntent 의모델계층변경하기 203 싱글톤과집중데이터스토리지 203 ListFragment 생성하기 206 프래그먼트의호스팅을위한추상액티비티 208 보편적인프래그먼트호스팅레이아웃 208 / 추상액티비티클래스 210 ListFragment, ListView, ArrayAdapter 215 ArrayAdapter<T> 생성하기 219 / 리스트항목의클릭에응답하기 222 리스트항목의커스터마이징 223 리스트항목의레이아웃생성하기 224 / 어댑터서브클래스생성하기 226 프래그먼트인자사용하기 231 프래그먼트로부터액티비티시작시키기 232 엑스트라쓰기 233 / 엑스트라읽기 234 / Crime 데이터로 CrimeFragment 의뷰를변경하기 235 / 직접액세스하는방법의단점 236 프래그먼트인자 237 인자를프래그먼트에첨부하기 237 / 인자가져오기 239 리스트를다시로딩하기 240 프래그먼트로부터결과받기 242 ViewPager 사용하기 245 CrimePagerActivity 생성하기 247 코드에서뷰의레이아웃처리하기 247 / ViewPager 와 PagerAdapter 249 / CrimePagerActivity 통합하기 250 / FragmentStatePagerAdapter vs. FragmentPagerAdapter 254 ViewPager 가실제로동작하는방법 256 대화상자 259 DialogFragment 생성하기 262 DialogFragment 보여주기 263 / 대화상자의콘텐트설정하기 265 두프래그먼트간에데이터전달하기 268 DatePickerFragment 에게데이터전달하기 269 / CrimeFragment 로데이터반환하기 272 챌린지 : 더많은대화상자만들기 279 차례 vii

8 Chapter 13 Chapter 14 Chapter 15 Chapter 16 MediaPlayer 를사용한오디오재생 281 리소스추가하기 283 HelloMoonFragment 의레이아웃정의하기 285 앱테마를우리가재설정하기 286 HelloMoonFragment 생성하기 287 레이아웃프래그먼트사용하기 288 오디오재생 290 재생및정지버튼을코드와연결하기 293 챌린지 : 오디오재생을일시중지하기 294 비디오재생하기 294 챌린지 : HelloMoon 에서비디오재생하기 295 유보프래그먼트 297 프래그먼트유보시키기 298 장치회전과유보프래그먼트 299 유보프래그먼트 : 그렇게좋을까? 301 장치회전처리와 onsaveinstancestate(bundle) 302 프래그먼트가나오기전의장치회전처리 305 지역화 307 리소스를지역화하기 308 디폴트리소스 309 구성수식자 310 대체리소스들의우선순위부여 311 / 복합수식자 312 / 가장잘맞는리소스찾기 314 더많은리소스규칙과규정들 315 리소스이름 315 / 리소스디렉터리구조 316 대체리소스테스트하기 317 액션바 319 옵션메뉴 320 옵션메뉴를 XML 로정의하기 321 / 옵션메뉴생성하기 324 / 옵션메뉴선택에응답하기 328 내비게이션활성화하기 330 앱아이콘활성화하기 331 / Up 버튼에응답하기 333 viii 실무에바로적용하는안드로이드프로그래밍

9 Chapter 17 Chapter 18 Chapter 19 대체메뉴항목 337 대체메뉴파일생성하기 337 / 메뉴항목의제목토글하기 338 / 한가지만더 339 챌린지 : 텅빈리스트뷰 342 로컬파일의저장과로딩 343 CriminalIntent 의데이터를저장하고로딩하기 344 범죄 (crime) 데이터를 JSON 파일로저장하기 345 / 파일시스템으로부터범죄데이터로딩하기 351 챌린지 : 외부스토리지사용하기 354 안드로이드파일시스템과자바 I/O 354 파일과디렉터리액세스하기 355 컨텍스트메뉴와컨텍스트액션모드 357 컨텍스트메뉴리소스정의하기 359 플로팅컨텍스트메뉴구현하기 359 컨텍스트메뉴생성하기 360 / 컨텍스트메뉴등록하기 360 / 선택한액션에응답하기 362 컨텍스트액션모드구현하기 364 여러항목을선택할수있게만들기 365 / 리스트뷰의액션모드콜백메서드들 365 / 활성화된항목의배경변경하기 369 / 다른뷰에서컨텍스트액션모드구현하기 371 호환성 : 소극적하향지원인가아니면기능복제인가? 372 챌린지 : CrimeFragment 에서 Crime 데이터삭제하기 373 ActionBarSherlock 374 챌린지 : ActionBarSherlock 사용하기 378 CriminalIntent 에 ABS 통합하기 378 / 더향상된통합 379 / 훨씬더향상된통합 380 카메라 I: 뷰파인더 381 프래그먼트레이아웃생성하기 384 CrimeCameraFragment 생성하기 385 CrimeCameraActivity 생성하기 386 액티비티와카메라퍼미션을매니페스트에추가하기 387 카메라 API 사용하기 388 카메라의 open 과 release 388 / SurfaceView, SurfaceHolder, Surface 390 / 미리보기크기결정하기 395 / CrimeFragment 에서 CrimeCameraActivity 시작시키기 396 명령행에서액티비티실행시키기 402 차례 ix

10 Chapter 20 Chapter 21 Chapter 22 Chapter 23 카메라 II: 사진찍고이미지처리하기 405 사진찍기 406 카메라콜백메서드구현하기 408 / 사진크기설정하기 411 CrimeFragment 로데이터되돌려주기 413 결과데이터를받기위해 CrimeCameraActivity 시작시키기 414 / CrimeCameraFragment 에서결과데이터설정하기 414 / CrimeFragment 에서파일명읽기 415 모델계층의데이터변경하기 417 Photo 클래스추가하기 417 / Crime 에사진속성추가하기 418 / 사진속성설정하기 419 CrimeFragment 의뷰변경하기 420 ImageView 추가하기 421 / 이미지처리하기 422 더큰이미지를 DialogFragment 에보여주기 428 챌린지 : 범죄이미지의방향 431 챌린지 : 사진삭제하기 431 안드로이드의 Deprecation 431 암시적인텐트 435 버튼추가하기 436 모델계층에용의자추가하기 439 포맷문자열사용하기 440 암시적인텐트사용하기 441 암시적인텐트의구성요소 442 / 범죄보고서전송하기 443 / 안드로이드에게연락처요청하기 447 / 응답하는액티비티들확인하기 450 챌린지 : 또다른암시적인텐트 451 두패널마스터 - 디테일인터페이스 453 레이아웃의유연성추가하기 454 SingleFragmentActivity 변경하기 455 / 두개의프래그먼트컨테이너를갖는레이아웃생성하기 456 / 앨리어스리소스사용하기 458 프래그먼트의보스인액티비티 462 프래그먼트콜백인터페이스 463 장치크기결정에관해추가로알아보기 473 인텐트와태스크 475 NerdLauncher 준비하기 475 x 실무에바로적용하는안드로이드프로그래밍

11 Chapter 24 Chapter 25 Chapter 26 암시적인텐트해결하기 478 런타임시에명시적인텐트생성하기 482 태스크와 back 스택 484 NerdLauncher 를홈화면으로사용하기 488 챌린지 : 아이콘과태스크재정렬하기 488 프로세스 vs. 태스크 489 스타일과 Include 493 RemoteControl 프로젝트설정하기 494 RemoteControlActivity 설정하기 495 / RemoteControlFragment 설정하기 497 스타일을사용해서정리하기 500 레이아웃완성하기 502 include 와 merge 507 챌린지 : 스타일의상속 508 XML Drawable 과 9-Patch 509 XML drawable 510 상태리스트 Drawable 514 레이어리스트 drawable 과인셋 drawable Patch 이미지사용하기 519 HTTP & 백그라운드태스크 525 PhotoGallery 생성하기 527 네트워킹기본 530 네트워크의퍼미션요청하기 532 AsyncTask 를사용해서백그라운드스레드로실행하기 532 Main 스레드 534 백그라운드스레드 536 플리커에서 XML 가져오기 537 XmlPullParser 사용하기 541 AsyncTask 로부터 main 스레드로돌아오기 544 AsyncTask 에관해추가로알아보기 548 AsyncTask 클린업하기 549 챌린지 : 페이징 550 차례 xi

12 Chapter 27 Chapter 28 Chapter 29 Looper, Handler, HandlerThread 551 이미지를보여주기위해 GridView 준비하기 551 다운로드관련고려사항들 554 main 스레드와소통하기 555 백그라운드스레드만들기 557 메시지와메시지핸들러 559 메시지구조 560 / 핸들러구조 560 / 핸들러사용하기 561 / 핸들러전달하기 565 AsyncTask vs. Threads 570 챌린지 : 프리로딩과캐싱 571 검색 573 플리커검색하기 573 검색대화상자 576 검색인터페이스생성하기 576 / 검색가능액티비티 578 / 하드웨어검색버튼 581 / 검색의동작방법 582 / 론칭모드와새로운인텐트 583 / 공유프레퍼런스를사용한간단한데이터보존 586 안드로이드 3.0 이상에서 SearchView 사용하기 589 챌린지 592 백그라운드서비스 593 IntentService 생성하기 593 서비스의필요성 597 안전한백그라운드네트워킹 597 새로운결과찾기 599 AlarmManager 를사용한지연실행 601 PendingIntent 603 / PendingIntent 를사용해서알람관리하기 604 알람제어하기 605 옵션메뉴항목변경하기 606 통지 608 서비스자세히알아보기 611 서비스가하는것과못하는것 611 / 서비스의생명주기 611 / Non - sticky 서비스 612 / Sticky 서비스 613 / Bound 서비스 613 xii 실무에바로적용하는안드로이드프로그래밍

13 Chapter 30 Chapter 31 Chapter 32 Chapter 33 브로드캐스트인텐트 617 장치부팅시앱깨우기 618 매니페스트의브로드캐스트수신자 618 / 수신자를사용하는방법 621 포그라운드통지의필터링 623 브로드캐스트인텐트전송하기 623 / 동적브로드캐스트수신자 624 / private 퍼미션사용하기 627 / 순차브로드캐스트로결과받기 630 수신자와오래실행되는태스크 635 웹과웹뷰의브라우징 637 플리커데이터에서하나더알아둘사항 637 쉬운방법 : 암시적인텐트 640 더어려운방법 : WebView 641 WebChromeClient 사용하기 646 / WebView 에서의올바른방향전환 648 자바스크립트객체추가하기 649 커스텀뷰와터치이벤트 651 DragAndDraw 프로젝트설정하기 652 DragAndDrawActivity 설정하기 652 / DragAndDrawFragment 설정하기 653 커스텀뷰생성하기 655 BoxDrawingView 생성하기 655 터치이벤트처리하기 658 모션이벤트추적기록하기 660 ondraw( ) 내부에서렌더링하기 663 챌린지 : 방향회전 665 장치의위치추적하기 667 RunTracker 시작하기 667 RunActivity 설정하기 669 / RunFragment 설정하기 670 위치와 LocationManager 672 브로드캐스트되는위치갱신정보받기 675 위치데이터를사용하도록 UI 변경하기 678 더빠른해결책 : 마지막인식위치 682 실제장치와가상장치에서위치테스트하기 684 차례 xiii

14 Chapter 34 Chapter 35 Chapter 36 Chapter 37 SQLite 로컬데이터베이스 687 이동과위치를데이터베이스에저장하기 688 데이터베이스의이동데이터쿼리하기 697 CursorAdapter 를사용해서이동리스트보여주기 700 새로운이동생성하기 703 이동의상세내역처리하기 705 챌린지 : 현재 ( 위치기록중인 ) 이동식별하기 713 Loader 로비동기데이터로드하기 715 Loader 와 LoaderManager 715 RunTracker 에서로더사용하기 718 이동리스트데이터로드하기 718 하나의이동 (Run) 로드하기 723 이동 (Run) 의마지막위치 (Location) 로드하기 727 구글맵사용하기 731 맵 API 를 RunTracker 에추가하기 731 실제장치로맵테스트하기 732 / 구글플레이서비스 SDK 를설치하고사용하기 732 / 구글맵 API 키얻기 733 / RunTracker 의매니페스트변경하기 733 사용자의위치를지도에보여주기 734 이동 (Run) 의경로보여주기 739 이동 (Run) 의시작과끝에표식추가하기 744 챌린지 : 라이브위치변경 745 책을마무리하며 747 마지막챌린지 747 부담없는연락처 748 감사합니다 748 찾아보기 749 xiv 실무에바로적용하는안드로이드프로그래밍

15 옮긴이머리말 정성과최선을다했습니다. 한마디로요약해서독자여러분께드리고싶은제진심의표현입니다. 매번번역서를낼때마다항상그렇듯이, 제가저술하는마음으로이일에임했고또그렇게마무리했습니다. 용어하나하나, 내용모두에걸쳐심사숙고하고여러차례에걸친원고검토끝에완성된이책을보면서이제야마음이흐뭇해집니다. 좋은내용을독자여러분께제대로전달할수있을것이라는기대감때문일겁니다. 사회, 문화적인환경과요구의변화에따라그에대한서비스를지원하고뒷받침하는컴퓨팅환경과정보기술도같이변화하는것은당연한일이라고생각됩니다. 특히, 스마트폰이나태블릿과같은모바일기기와소프트웨어시스템의발전은소셜네트워크와같은, 사회의문화적인측면까지도변모시키고있습니다. 이책에서는모바일시스템의대명사라할수있는안드로이드애플리케이션을개발하는데필요한내용을알려줍니다. 그러나기존의여타책들과달리, 이책은실제애플리케이션에필요한지식을습득하면서만들수있게배려되어있습니다. 즉, 여러가지기능을나열하고단편적인코드를보여주는형태가아니라, 실제애플리케이션을만들어가면서핵심적인기능을점진적이고자연스럽게배우고프로그래밍하는능력을키워줍니다. 숲과나무를같이볼수있게해주는것이죠. 아마존에서 Android 로검색을해보면아시겠지만, 현재안드로이드서적중최고의베스트셀러가된것도그때문이아닌가생각됩니다. 안드로이드애플리케이션을시작하려는분들에게적극적으로권하고싶은책입니다. 아, 바로이책이야! 라고할수있을겁니다. 옮긴이머리말 xv

16 이책을번역하면서다음과같은부분에중점을두었습니다. 1. 용어를잘이해하는것이제일중요하므로용어선정에유의하였습니다. 때로는용어하나때문에많은시간을고민하기도했지요. 제가실무에서오랫동안경험했던지식을바탕으로가장적합한번역용어선정에많은노력을했습니다. 2. 여러분의이해를돕는데필요한보충설명을많이넣었습니다. 역주의형태는글을읽는데방해가될수있어서본문속에자연스럽게넣어두었습니다. 3. 본문이나소스코드에서결함이있는내용을하나하나확인해가면서수정하였습니다. 그리고최근에발표한킷캣버전으로모든코드를테스트하고관련본문을수정하였습니다. ( 원서의개정판이라고생각하셔도될듯합니다.) 참으로긴시간이었던것같습니다. 독자들에게도움이될수있는책을만들어야한다는집념이있었기에모든어려움을참아낼수있었던것같습니다. 이책을출판하는데아낌없는배려와수고를해주신제이펍출판사의장성두사장님과편집디자인에많은노력과수고를해주신이민숙과장님께진심으로감사드립니다 년 11월역자심재철드림 xvi 실무에바로적용하는안드로이드프로그래밍

17 이책에대하여 Big Nerd Ranch 에서는그동안수많은안드로이드프로그래머들을교육시켰으며지금도그렇게하고있다. 이책은그런노하우를기반으로저술되었다. 따라서안드로이드프로그래밍에필요한지식이나기법을단순히나열하고알려주는것이아니라, 여러분이직접앱을만들어가면서그런지식이나기법들을단계적으로이해하고배울수있도록이끌어준다. 안드로이드는자바언어를사용하는문화권이라고할수있다. 따라서이책을읽으면서코드를작성하고앱을개발하려면자바의핵심을어느정도는알필요가있다. 예를들어, 클래스와객체, 인터페이스, 리스너, 패키지, 내부클래스 ( 특히, 익명의내부클래스 ) 등이다. 또한, 메서드오버라이딩과같은객체지향프로그래밍기반의자바코드구현개념이나방법을알필요도있다. 그러나자바는익숙하지않더라도객체지향프로그래밍에대해잘알고있다면이책을읽는데큰무리가없을것이다. 인터페이스나익명의내부클래스와같은자바만의특성들은필요한시점에간략하게보충설명을해줄것이기때문이다. 그러나필요하다면자바책을준비해두자. 이책을읽으면서안드로이드프로그래밍을배울때다음과같이하면좋을것이다. 여러분의친구나직장동료와함께스터디그룹을만들어시작하자. 각장의내용을이해하고코드를작성할때가급적집중적으로시간을배려하자. 이책의포럼인 forums.bignerdranch.com 에참여하자. 여러분에게도움을줄수있는안드로이드프로그래머를찾아보자. 이책에대하여 xvii

18 이책의구성 이책에서는 8개의안드로이드앱을작성한다. 어떤것들은매우간단해서한장으로끝나지만, 또어떤것들은더복잡해서여러장에걸쳐만들어진다. 그중가장복잡하고긴앱은 13개장에걸쳐개발된다. 모든앱들은저마다중요한개념과기법을가르치도록설계되었으므로, 이책을읽으면서앱을작성하다보면어느새여러분이숙련된안드로이드프로그래머가된것을느끼게될것이다. 8개앱은다음과같다. GeoQuiz CriminalIntent 우리의첫번째앱이다. 안드로이드프로젝트의전체적인구성, 액티비티, 레이아웃, 명시적인텐트와같은기본적이고핵심적인내용들을배운다. 이책에서가장큰규모의앱이며, 사무실에서발생하는불미스러운일들을기록하고관리한다. 이앱을만들면서많은중요한것을배운다. 프래그먼트, 마스터-디테일사용자인터페이스, 메뉴, 카메라, 암시적인텐트등의사용법을배운다. HelloMoon 이작은앱에서는프래그먼트, 미디어 ( 오디오와비디오 ) 재생, 리소스, 지역화에대해서배운다. NerdLauncher RemoteControl PhotoGallery 안드로이드시스템에서앱을시작시키는우리의커스텀론처를만든다. 그럼으로써안드로이드의인텐트와태스크에대해자세히배운다. 이앱에서는멋진사용자인터페이스를만들기위해스타일, 상태리스트 drawable 및그외다른도구들을사용하는방법을배운다. 사진정보를갖고있는플리커 (Flickr) 사이트로부터사진들을다운로드하고보여주는플리커클라이언트앱이다. 이앱에서는안드로이드의서비스와다중스레드및웹서비스를액세스하는방법을알려준다. DragAndDraw 이간단한앱에서는터치이벤트를처리하고우리의커스텀뷰를만드는방법을배운다. RunTracker 이앱에서는 GPS 를이용해서우리가이동한경로를추적기록하고지도에보여준다. 위치 서비스, SQLite 데이터베이스, 로더, 구글맵사용법을배운다. 하나의앱이여러장에걸쳐개발되면서그상황에맞춰여러분이배워야할주제와기법들을가르쳐줄것이다. 따라서이클립스나안드로이드스튜디오에서이책의코드를작성할때앞장에서했던프로젝트를이어서사용하면된다. 물론, 다른앱이시작될때는새로운프로젝트로만들것이다. xviii 실무에바로적용하는안드로이드프로그래밍

19 각장의끝에는여러분스스로풀어볼과제가있다 ( 없는장도더러있다 ). 이책에서는그것을챌린지 (challenge) 라고하였다. 그렇다. 여러분각자가도전해서코드를작성하는것이다. 그리고그렇게함으로써각장에서배운내용을복습할수있고새로운내용도알게될것이다. 챌린지를꼭해볼것을권한다. 챌린지에따라서는안드로이드의 API 문서를찾아보면서해결해야할것도있을것이다. 따라서챌린지를해결하면더많은것을알게될것이고, 여러분의실력이더욱향상됨을느끼게될것이다. 만일풀다가막히는것이있으면 이책의포럼인 forums.bignerdranch.com 을방문하여질문을올리면도움을받을수있을것이다 ( 영어로해야하지만과감하게해보자 ). 그리고챌린지다음에는그장의주제와관련해서조금더자세히설명할필요가있는내용들이추가로있으므로참고하면도움이될것이다. 코드스타일 이책에서는안드로이드앱코드를작성할때다음과같은방식을일관되게사용한다. 리스너의코드는익명의내부클래스를사용해서구현한다. 자바에서이벤트를처리하는리스너코드는일반적으로내부클래스를사용한다. 이책에서는그중에서익명의내부클래스를일관되게사용한다. 리스너의메서드구현코드를우리가원하는위치에둘수있어서알기쉽기때문이다. 처리성능이매우중요한경우는익명의내부클래스가문제가될수있지만, 대부분의경우에는문제없이잘될것이다. 7장부터는모든사용자인터페이스구현에프래그먼트를사용한다. 많은수의안드로이드개발자들이여전히액티비티기반의코드를작성한다. 그러나프래그먼트기반으로코드를작성할것을권한다. 처음에는조금어렵게생각될수있지만, 실제로해보면그리어렵지않게익숙해질것이다. 프래그먼트를사용하면사용자인터페이스를더욱유연하게만들수있다. 자세한내용은책을읽고코드를작성하면서알게될것이다. 안드로이드하위버전과의호환성을고려한코드를작성한다. 안드로이드운영체제는그동안여러가지버전으로변경되고발전하였다 ( 자세한내용은 6 장 에서설명한다 ). 특히 4.3 젤리빈에이어 4.4 킷캣 (KitKat) 이나오면서는 512MB 의적은메모리 이책에대하여 xix

20 를갖는장치에서도안드로이드를실행할수있게배려하고있다. 그러나아직까지도진저 브레드와같은하위버전의안드로이드를탑재한장치들이많다. 따라서이책에서는그러 한하위버전호환성을배려한추가코드작성도고려한다. 코드내역표기 이책에나오는코드들은별도의폰트를사용하여보기쉽게하였다. 그리고본문에나오는클래스, 인터페이스, 메서드들은진한글씨체로나타내어알아보기쉽게하였다. 또한, 코드리스트에나온코드들중에서새로추가할코드들은진한글씨체로표시하였으며, 삭제할코드들은글자중앙에줄을그어표시하였다. 예를들어, 다음코드에서는 maketext(...) 메서드호출을삭제하고 checkanswer(true) public void onclick(view v) Toast.makeText(QuizActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show(); checkanswer(true); 앱개발에필요한각종도구설치 xxii 쪽에서시작되는 알아두자 를참고하자. 이책에나오는모든코드는안드로이드 4.4 킷캣버전으로테스트되었으며, 이클립스프로젝트로작성되어압축파일로제공하고있다. 영문버전과한글버전두가지가있으니각자좋은대로다운로드하여참고하자. 제이펍출판사 ( 의이책의소개페이지를방문하면다운로드할수있다. 이솔루션파일들은원서와관련되어제공되는파일의일부결함을수정하고한글화및킷캣버전으로향상시킨것이다. 솔루션파일에는각장별로서브디렉터리가있으며, 그밑에프로젝트가있다. xx 실무에바로적용하는안드로이드프로그래밍

21 이책에서보여주는코드를가급적여러분이직접작성하면서배우는것이제일좋지만, 혹시라도솔루션파일에있는것을참고하고자할때는다음과같이하면된다. 이클립스메뉴의 File Import... 를선택한후첫번째대화상자에서 Android 밑의 Existing Android Code into Workspace 를선택하고 Next 버튼을누른다. 두번째대화상자에서 Browse... 버튼을누르면세번째대화상자가나오고, 거기에서솔루션파일의원하는프로젝트서브디렉터리를찾아선택후확인버튼을누른다. 그리고다시두번째대화상자에서 Copy projects into workspace 를체크하면현재작업중인이클립스워크스페이스로그프로젝트관련파일들이복사된다. Finish 버튼을누르면프로젝트 import 가종료된다. 이후에는이클립스에서그프로젝트를사용하면된다. 예를들어, 2장의 GeoQuiz 프로젝트를 import 하는두번째대화상자는다음과같다. 이처럼 import 한프로젝트는현재사용중인이클립스워크스페이스디렉터리밑에복사 되며, 필요없을때는언제든지삭제할수있다. 참고로, 워크스페이스는이클립스메뉴에 서 File Switch Workspace Other 를선택하면확인및변경이가능하다. 이책에대하여 xxi

22 알아두자 이책을보기에앞서여기서얘기하는사항들을사전에알아두고준비하자. 안드로이드 프로그래밍을배우면서앱을개발하는데필요한내용들이다. 여기서는처음안드로이드 를접하는사람들을위해안드로이드라는숲이어떻게생겼는지간단하게알아볼것이다. 안드로이드는어떻게생겼을까? 우선, 안드로이드운영체제가어떻게구성되어있는지간단하게알아보자. 쉽게생각하면 다음그림과같다. 안드로이드운영체제 (S/W) 안드로이드애플리케이션 기본앱 안드로이드프레임워크 ( 자바 ) C/C++ 라이브러리 리눅스커널 사용자앱 API 안드로이드런타임 사용자 SDK 와개발도구 안드로이드프로그래머 모바일장치 ( 폰, 태블릿등 ) xxii 실무에바로적용하는안드로이드프로그래밍

23 안드로이드는폰이나태블릿과같은모바일장치에적합하도록만들어졌다. 그리고기반이되는운영체제로리눅스커널을사용하며, 리눅스커널에서는장치드라이버를통해각장치의하드웨어를인터페이스한다. 예를들면, 디스플레이, 카메라, 오디오, Wi Fi 등이다. 또한, 안드로이드앱을리눅스프로세스로실행시킨다. C/C++ 라이브러리의코드들은안드로이드프레임워크의컴포넌트들이리눅스에서실행하는데필요한기능을제공한다. 예를들어, 그래픽처리, 웹브라우징, 네트워크인터페이스, 오디오 / 비디오처리등이다. 안드로이드런타임은안드로이드애플리케이션 ( 또는줄여서앱 ) 을리눅스프로세스로실행시켜주는달빅가상머신 (Dalvik Virtual Machine), Zygote 등을말한다. 안드로이드앱은자바코드로되어있으므로리눅스프로세스로실행될때하나의달빅가상머신인스턴스를포함한다. 그리고 Zygote 는안드로이드앱을리눅스프로세스로시작시키는역할을한다. 또한, 장치의시스템부팅시미리실행되어야할안드로이드프레임워크의핵심컴포넌트들을리눅스프로세스로실행시키는역할도한다 ( 리눅스의 init 프로세스가데몬프로세스들을실행시키듯 ). 안드로이드프레임워크는앱에서필요로하는각종서비스들을처리및관리하는역할을수행하는안드로이드컴포넌트들이며자바클래스로되어있다. 그리고안드로이드프로그래머가앱을개발하는데필요한 API를제공한다. 예를들어, 안드로이드앱의기본적인실행단위인액티비티및그것의생명주기와실행을관리하는액티비티매니저가있다. 안드로이드애플리케이션은기본앱과사용자앱으로생각할수있다. 기본앱에는구글에서제공하는전화앱 (Phone), 연락처앱 (Contacts), 웹브라우저앱등이있으며, 각장치제조사나통신사에서추가로설치한앱들이있다. 기본앱은사용자가삭제하지못하게되어있다. 반면에사용자앱은메모리가허용하는한각사용자가원하는것을얼마든지설치하고삭제할수있다. 앱개발에필요한게무엇일까? 안드로이드앱을개발하는프로그래머들은안드로이드 SDK 와개발도구들을사용해서 앱을만든다. 이때필요한것들을간단하게그림으로보면다음과같다. 알아두자 xxiii

24 통합개발환경 안드로이드프로그래머 이클립스 (Eclipse) 또는 안드로이드스튜디오 (Studio) 플러그인 (Plug-in) 안드로이드 SDK (API 라이브러리와각종도구들 ) 자바 JDK (Standard Edition) 우선, 안드로이드앱을개발하는데필요한 API 라이브러리 ( 안드로이드의각종자바클래스와인터페이스가있음 ) 가있어야한다. 또한, 코드의컴파일이나그외여러가지기능을수행하는도구들도필요하다. 이런모든것이안드로이드 SDK에포함되어있다. 그리고안드로이드앱코드는자바언어로작성되고자바 JDK(Standard Edition) API 라이브러리의클래스와인터페이스를필요로한다. 단, 사용자인터페이스를처리하는데필요한 awt나 swing 과같은자바의 GUI API는모바일장치에맞지않으므로사용하지않는다. 안드로이드앱을개발할때자바 JDK의클래스나인터페이스들은필요할때만사용하며, 대부분의경우는안드로이드 SDK의프레임워크클래스와인터페이스를사용해서코드를작성한다. 그리고프로그래머가코드를작성하고앱을개발하려면그런일을편리하게해주는개발도구나환경이필요하다. 안드로이드에서는그런통합개발도구로이클립스를사용한다. 그리고구글에서추가로언급한안드로이드스튜디오를사용할수도있다. 그러나안드로이드스튜디오는현재도개발중이라서안정적인이클립스를사용하는것이현재로는가장좋다. 그런데통합개발도구에서는 SDK의 API 라이브러리와도구들을편리하게 ( 일일이프로그래머가찾지않고 ) 연결하여사용할필요가있다. 따라서이클립스나안드로이드스튜디오에서는플러그인 (plug in) 기능을제공하여쉽게연결하게해준다. 그러므로이클립스나안드로이드스튜디오를벗어나지않고우리가필요한모든것을쉽게할수있다. 종전에는이클립스와 SDK를따로설치한후플러그인도우리가해주어야했다. 그러나이제는그럴필요가없다. 구글에서이클립스와안드로이드 SDK를같이묶어서플러그인도된상태로제공하기때문이다 ( 안드로이드스튜디오도마찬가지다 ). 따라서통합된파일을다운로드하여압축을풀면바로사용할수있다. 그것을 ADT(Android Developer Tools) 번들이라고한다 xxiv 실무에바로적용하는안드로이드프로그래밍

25 ( 안드로이드스튜디오도번들이라한다 ). 단, 자바 JDK(Standard Edition) 는별도로다운로드하여 설치해야한다. 다운로드와설치는조금뒤에서설명할것이다. 안드로이드앱을테스트하는방법은? 안드로이드앱을작성하면서실행하고테스트하려면다음그림과같이할수있다. 컴퓨터 이클립스또는안드로이드스튜디오 AVD ( 모의안드로이드실행 ) 에뮬레이터화면 실제장치 ( 안드로이드실행 ) 구글 USB 드라이버 USB 선연결 폰 태블릿 테스트하는방법은두가지가있다. 첫번째는실제장치처럼동작하는가상장치인에뮬레이터를사용하는방법이다. 이것을 AVD(Android Virtual Device) 라한다. 궁극적으로는우리가원하는실제장치로테스트해봐야겠지만, 그전에 AVD 를사용해서해볼수있다. 이책의코드를작성하면서안드로이드프로그래밍을배울때는이방법도좋을것이다. 이때우리가필요한 AVD 를생성하면된다. 그리고생성된 AVD 는실제장치와유사한시스템이미지 ( 리눅스커널과안드로이드컴포넌트 ) 를갖는파일로만들어진다. AVD 를시작시키면현재사용중인컴퓨터에서모의안드로이드시스템이실행되어실제장치와유사한화면이나타나고동작하게된다. 마치폰이나태블릿의전원을켜는것과유사하다. AVD 를설치하는방법은이책을읽으면서알게될것이다. 두번째는실제장치에서앱을실행시키는방법이다. 이때는실제장치를 USB 선으로연결해야한다. 그리고이책 54페이지의설명처럼실제장치에서필요한설정을해주면된다. 윈도우즈시스템의경우는뒤의 SDK 설치에서설명하는 SDK 매니저에서구글 USB 드라이버를설치한다 ( 디버깅도할수있게끔 ). 알아두자 xxv

26 에뮬레이터에서의한글처리 앱을개발하면서실제장치에서실행시켜테스트할때는한글문제가발생하지않는다. 그장치의언어가한글로설정되어있으면출력이잘되기때문이다. 그러면입력은어떨까? 입력역시문제없이잘된다. 한글입력을받아주는키보드앱이미리설치되어있어입력할때한글키보드가나타나서처리해주기때문이다. 그러면에뮬레이터로앱을실행할때는어떨까? 한글입력과출력모두고려할것들이있다. 예를들어, 안드로이드 4.4 킷캣버전으로에뮬레이터 (AVD) 를생성하고한글을출력하면한글은나타나지않는다. 에뮬레이터를생성할때기준이되는시스템이미지파일에포함된한글관련폰트파일에문제가있기때문이다 (4.2 버전부터이런어이없는상황이계속되고있으니구글에항의라도해야할듯하다 ). 참고로, 시스템이미지파일의이름은 system. img이며, 안드로이드 SDK를설치한디렉터리밑에있다. 예를들어, 안드로이드 4.4 킷캣 SDK의경우는 sdk\system images\android 19\armeabi v7a 밑에있다. 따라서한글출력이되게하려면문제가있는폰트파일을교체하거나또는다른한글폰트파일을 system.img 에추가한다음원래의 system.img를그것으로교체하면된다. 그런다음에 AVD 를생성하고시작시킨후실제장치에서처럼설정 언어및키보드 언어를한국어로변경한다. 그러면이후부터우리앱을실행시킬때는한글출력이잘된다 (system.img의수정및배포는안드로이드 SDK 라이선스조항에어긋나는민감한사안이라여기까지만얘기할것이다 ). 한글을입력할수있게하려면 AVD 를시작시킨후한글키보드앱을최초한번설치해주면된다. 이후로는앱에서입력을받을때실제장치처럼소프트한글키보드가화면에나타난다. 단, AVD 를생성할때하드웨어키보드항목을체크하지않도록하자. ( 인터넷검색으로구할수있는한글키보드앱은몇가지종류가있으니그중하나를설치하면된다.) 입력할때나오는한글키보드의예를보면다음과같다. 이처럼소프트한글키보드로입력할때는사용중인컴퓨터의하드웨어키보드로는입력이안되고화면에나타난키보드의자판을마우스로클릭해서입력해야한다는것을염두에두자. xxvi 실무에바로적용하는안드로이드프로그래밍

27 그리고당연한것이지만, 한글출력이가능해야만입력한한글이제대로나타날것이다. 에뮬레이터에서의한글입출력에관한사항들은라이선스에민감한사안이니유의하자. 그리고 안드로이드에뮬레이터한글 로검색을해보면여러분에게도움이되는것이틀림없이있을것이다. 앱개발도구설치하기 JDK Standard Edition Downloads 를선택하자. 그리고그다음페이 지의중간에있는 Java SE를클릭하면두개의큰버튼이보일것이다. 왼쪽에있는 Java 다운로드버튼을클릭하면다운로드페이지가나타난다. Accept License Agreement 를선택하고각자컴퓨터의플랫폼에맞는버전을선택하여원하는위치에다운로드하자. 다운로드한파일을실행하면다음과같이설치화면이나올것이다. Next 버튼을누르면설치할위치를지정할 수있는다음화면이나온다. 알아두자 xxvii

28 설치할디렉터리를변경하고싶으면 Change 버튼을눌러서변경하고, 그렇지않으면 Next 를누르자. 그러면자바런타임 (jre) 을설치한다는다음화면이나올것이다. Next 버튼을누르면설치가시작되며, 잠시후설치가끝나면잘되었다는것을알려주는다음화면이나온다. Close 버튼을눌러설치를완료하자. 자바 JDK 설치가완료되었으므로내컴퓨터의환경변수를하나추가해두자. 이변수는안드로이드스튜디오에서자바 JDK를참조할때필요하다. 각자컴퓨터의시스템등록정보를보자. 예를들어, 윈도우즈 XP의경우는내컴퓨터를열고왼쪽의시스템정보표시를선택하면된다. 그리고고급탭을선택한후환경변수를클릭하면다음과같이환경변수설정창이나올것이다. xxviii 실무에바로적용하는안드로이드프로그래밍

29 아래쪽시스템변수의새로만들기버튼을 클릭하면다음과같이새시스템변수를지 정하는대화상자가나올것이다. 그림과같이변수이름에는 JAVA_HOME 을입력하고 ( 반드시대문자이어야한다 ), 변수값에는 자바 JDK 를설치한디렉터리를지정한다. 그리고확인버튼을계속눌러서환경변수지정 을끝내자. ADT 에접속하면다음페이지가나올것이다. 이곳이안드로이드개 발자를위한모든정보를제공하는공식사이트다. 밑에있는 Get the SDK 버튼을클릭하면다음페이지가나타날것이다. 알아두자 xxix

30 오른쪽중간의 Download the SDK 버튼을클릭하면다음페이지가나타난다. I have read and agree with the above terms and conditions 를체크하고, 각자컴퓨터아키 텍처에맞게 32 bit 또는 64 bit 를선택한후 Download the SDK ADT Bundle for Windows 를 클릭하여각자원하는위치에다운로드하자. xxx 실무에바로적용하는안드로이드프로그래밍

31 설치는별로할게없다. 다운로드된압축파일을풀면된다. 압축을풀면설치된디렉터리밑에두개의서브디렉터리가생길것이다. eclipse 는이클립스 IDE 관련서브디렉터리이며, sdk는안드로이드 SDK 관련서브디렉터리다. 특히, sdk\tools 와 sdk\platform tools 에는우리앱을테스트하고디버깅해주는도구들이들어있다. 그리고 sdk\system images 에는에뮬레이터 (AVD) 를생성하여우리앱을테스트하는데필요한시스템이미지파일들이있다. 에뮬레이터의한글입출력과관련있는 system.img 파일이여기있다. 우리가안드로이드앱을개발하기위해이클립스를실행할때는 eclipse\eclipse.exe 를실행하면된다. 쉽다. 이게전부다 (eclipse.exe의바로가기를만들어두면좋을것이다 ). 그리고현재설치된안드로이드 SDK 버전의내용물들을추가하거나변경할때 ( 예를들어, sample 코드, 개발자문서, 구글 API, 구글 USB 드라이버등 ) 또는다른 SDK 버전을추가및변경할때는 SDK 매니저를사용하면된다. SDK 매니저는이클립스메뉴의 Window Android SDK Manager 를선택하면다음과같이별도창으로나타난다. 현재설치된안드로이드 SDK 버전의요소들은제일오른쪽의 Status 에 installed 로나타난 다. 안드로이드에서는여러가지버전의 SDK 를같이설치하고사용할수있다. 실제장치 알아두자 xxxi

32 의안드로이드버전에맞는앱을개발할필요가있기때문이다. 위그림에서는현재 4.4 킷캣버전 (API 19) 의 SDK가설치되어있음을알수있다. 그런데만일 4.3 젤리빈버전 (API 18) 을실행중인장치에맞는앱을개발하고자한다면 4.3 버전 SDK도필요할것이다. 물론, 이경우에 4.4 버전 SDK로앱을개발하되 4.4 버전에추가된 API를사용하지않으면된다. 그렇더라도앱을테스트할에뮬레이터 (AVD) 는 4.3 버전의것이필요하다. 그때는다음그림과같이 4.3 버전 SDK의필요한요소들을체크한후오른쪽제일아래의 Install packages 버튼을클릭하면추가로설치된다 ( 설치된것을체크하고 Delete packages 버튼을누르면삭제된다 ). 다른버전의 SDK를추가로설치할때는다음요소들을같이설치하기바란다. SDK Platform: 안드로이드프레임워크라이브러리 (API) 다. ARM EABi v7a System Image: ARM CPU 아키텍처로실행되는에뮬레이터 (AVD) 의기본시스템이미지이며, Intel x86이나 MIPS를선택할수도있다. Google APIs: 안드로이드 SDK API에구글나름의 API( 구글맵및기타 ) 가추가된것이다. xxxii 실무에바로적용하는안드로이드프로그래밍

33 그리고최신버전의안드로이드문서 (Documentation) 도설치할것을권한다. 앱을개발하면서항상참조할 API 문서가있기때문이다. 아래쪽에있는 Android Support Library( 안드로이드지원라이브러리 ) 는상위버전의새로운기능을하위버전과의호환성을유지하면서하위버전의장치에서도사용할수있게제공되는 API 라이브러리다. 앞으로이책에서자주언급할것이다. 이처럼 SDK 매니저를사용하면안드로이드 SDK의최신업데이트및설치 / 삭제를쉽게할수있다. 그리고 SDK와는별도로이클립스메뉴의 Help Check for Updates 를선택하면이클립스의새로운변경내용 ( 버전등 ) 이자동으로다운로드되어설치및적용된다. 안드로이드 ADT 번들과동일하게 에접속하면다음페이지가 나올것이다. 밑에있는 Get the SDK 버튼을클릭하면다음페이지가나타날것이다. 알아두자 xxxiii

34 그림에서마우스커서가가리키는왼쪽의 Android Studio 를클릭하면다음페이지가나타 난다. 오른쪽의 Download Android Studio 를클릭하면다운로드페이지가나타난다. xxxiv 실무에바로적용하는안드로이드프로그래밍

35 I have read and agree with the above terms and conditions 를체크하고 Download Android Studio 를클릭하여각자원하는위치에다운로드하자 ( 여기서는윈도우즈버전을다운로드한다 ). 설치는별로할게없다. 다운로드된압축파일을풀면된다. 압축을풀면설치된디렉터리밑에다섯개의서브디렉터리가생길것이다. bin, lib, license, plugins, 이렇게네개는안드로이드스튜디오와관련이있는것이고, sdk는안드로이드 SDK로서안드로이드 ADT 에설치된 SDK와동일하다. 안드로이드스튜디오도 ADT와마찬가지로실제장치와 AVD 로앱을실행할수있다. 우리가안드로이드앱을개발하기위해안드로이드스튜디오를실행할때는 bin\studio.bat 를실행하면된다 (studio.exe 를직접실행하면안된다 ). 이것도쉽다. 이게전부다 (studio.bat 의바로가기를만들어두면좋을것이다 ). 그리고기존에 ADT로개발한앱을안드로이드스튜디오에서읽어서사용할수있다. 이때는이클립스에서우리앱의프로젝트에 Gradle 빌드파일을생성한후안드로이드스튜디오에서 import 하면된다. 그리고 SDK 와는별도로안드로이드스튜디오메뉴의 Help Check for Update 를선택하면안드로이드스튜디오의새로운변경내용 ( 버전등 ) 이다운로드되어설치및적용된다. 예를들어, 현재설치된것보다새로운버전이나왔을때는다음과같이알려주며, Update and Restart 버튼을누르면자동으로새버전이다운로드되어업그레이드되고다시시작된다. 알아두자 xxxv

36 안드로이드스튜디오 버전부터는안드로이드 4.4 킷캣을지원한다. 참고로, IntelliJ 통합개발도구를기반으로현재개발진행중인안드로이드스튜디오의최신정보와버전을알아보려면다음을방문하면된다. 자, 이제부터는본격적으로안드로이드프로그래밍을배워보자! xxxvi 실무에바로적용하는안드로이드프로그래밍

37 1 처음만드는안드로이드애플리케이션 CHAPTER 이장에서는안드로이드애플리케이션 ( 줄여서 앱 ) 을만드는데필요한개념과구성요소들에관해설명한다. 이장을다읽었을때모든내용이완전히이해되지않더라도걱정하지말자. 여기에나온내용들은이책의진도를나가면서자세하게다시설명할것이니말이다. 이장에서만들앱 (app) 은 GeoQuiz 라하며, 사용자가지리에대해아는지테스트한다. 화면의질문에대한답으로사용자가 True 또는 False 버튼을터치 ( 또는클릭 ) 하면 GeoQuiz 에서즉시결과를알려준다. 그림 1.1에서는사용자가 False 버튼을눌렀을때의결과를보여준다. 1

38 그림 1. 1 터키에서가장큰도시는콘스탄티노플이아닌이스탄불이다 앱기본사항 GeoQuiz 앱은하나의액티비티 (activity) 와하나의레이아웃 (layout) 으로구성된다. 액티비티는안드로이드 SDK 클래스인 Activity의인스턴스 ( 객체 ) 이다. 액티비티는화면을통해서사용자가작업할수있게해준다. 우리는 Activity의서브클래스를만들어서앱에서필요한기능을구현한다. 간단한앱은하나의서브클래스만필요하지만, 복잡한앱에서는여러개를가질수있다. GeoQuiz 는간단한앱이므로 QuizActivity라는이름의 Activity 서브클래스하나를갖는다. QuizActivity는그림 1.1에있는사용자인터페이스를처리할것이다. 레이아웃은사용자인터페이스객체들과그것들의화면위치를정의한다. 레이아웃은 XML 로작성된정의들로구성된다. 각정의는버튼이나텍스트처럼화면에나타나는하나의객체를생성하는데사용된다. 2 CHAPTER 1 처음만드는안드로이드애플리케이션

39 GeoQuiz 앱에는 activity_quiz.xml 이라는이름의레이아웃파일이포함된다. 그리고이파일 에는그림 1.1 의사용자인터페이스가 XML 로정의되어있다. QuizActivity 와 activity_quiz.xml 간의관계를그림 1.2 에나타내었다. QuizActivity activity_quiz.xml 그림 1. 2 QuizActivity 는 activity_quiz.xml 에서정의한것들을관리한다. 자, 그러면이런내용을염두에두고 GeoQuiz 앱을만들어보자. 안드로이드프로젝트생성하기 앱작성의첫번째단계는안드로이드프로젝트 (project) 생성이다. 안드로이드프로젝트는앱을구성하는모든파일을포함한다. 새로운프로젝트를생성하려면이클립스에서 File New Android Application Project 메뉴를클릭하여새로운앱위저드를연다. 첫번째나오는대화상자 ( 그림 1.3) 에서애플리케이션이름에 GeoQuiz 를입력한다. 프로젝트이름은애플리케이션이름과동일하게자동으로바뀔것이다. 패키지이름에는 com. bignerdranch.android.geoquiz 를입력한다. 우리가입력하는패키지이름은개발자의인터넷도메인주소 (DNS) 를거꾸로하고제일뒤에추가로필요한것 ( 여기서는 geoquiz) 을붙인다. 이런식으로이름을짓는이유는패키지이름을고유하게함으로써다른장치나구글플레이등에서애플리케이션들을식별할수있게하기위함이다. 안드로이드프로젝트생성하기 3

40 그림 1. 3 새로운애플리케이션생성하기 그다음의네개필드에서는우리애플리케이션이동작하는안드로이드버전들을구성한다. GeoQuiz 앱의경우는화면에나오는디폴트설정으로하면되므로지금은변경할필요없다. 이내용에대해서는 6장에서배울것이다. 안드로이드에서는이런도구 ( 소프트웨어 ) 들이일년에한두번업그레이드되므로그림 1.3 의위저드가약간달라질수있다. 그러나그것은문제가되지않는다. 선택사항들은거의동일하기때문이다. ( 만일여러분들이보는위저드가여기서와많이다르다면도구들이많이변경된것이다. 그러나당황 하지말자. 이책의포럼 (forums.bignerdranch.com) 을방문하면가장최신버전에대해도움을받을수 있을것이다.) 그림 1.3의대화상자에서 Next 버튼을클릭한다. 그다음에나오는두번째대화상자 ( 그림 1.4) 에서는 Create custom launcher Icon( 커스텀론처아이콘생성 ) 의체크를지운다. GeoQuiz 앱에서는안드로이드의디폴트론처아이콘을사용할것이기때문이다. 그리고 Create activity( 액티비티생성 ) 는체크된상태그대로두자. 4 CHAPTER 1 처음만드는안드로이드애플리케이션

41 그림 1. 4 프로젝트구성하기 Next 버튼을클릭하자. 그다음의대화상자 ( 그림 1.5) 에서는생성할액티비티의종류를묻는다. 여기서는 Blank Activity 를선택하자. 그림 1. 5 새로운액티비티생성하기 Next 버튼을클릭하자. 안드로이드프로젝트생성하기 5

42 그림 1. 6 새로운액티비티구성하기 이위저드의마지막대화상자 ( 그림 1.6) 에서는액티비티서브클래스의이름을 QuizActivity 로지정한다. 클래스이름의끝에 Activity 를붙인것에주목하자. 반드시이렇게해야하는것은아니지만, 이런형태로이름을주면알아보기쉽기때문이다. 액티비티의새로운이름을반영하여레이아웃이름은자동으로 activity_quiz 로변경된다. 레이아웃이름은액티비티이름의순서를반대로하고, 모두소문자로하며, 단어사이에는밑줄표시를넣는다. 이런식의작명법은레이아웃은물론이고추후에배울다른리소스에도권장하는방법이다. Navigation Type( 내비게이션타입 ) 은 None으로그대로두고 Finish 버튼을클릭하자. 그러면이클립스가우리의새로운프로젝트를생성하고열어줄것이다. 6 CHAPTER 1 처음만드는안드로이드애플리케이션

43 이클립스사용하기 그림 1.7 처럼이클립스는우리프로젝트를워크벤치창에연다. ( 만일이클립스를설치하고처 음실행했다면웰컴화면이나오므로그창을닫자. 그러면워크벤치창이보일것이다.) 워크벤치창의나누어진각부분들을뷰 (view) 라고한다. 뷰를닫는다. 그림 1. 7 워크벤치창을정돈하기 탭그룹을최소화한다. 왼쪽에있는뷰는패키지탐색기 (package explorer) 다. 여기서는우리프로젝트에관련된파일들을관리할수있다. 중앙에있는뷰는에디터 (editor) 다. 우리프로젝트를시작했을때이클립스는 activity_quiz. xml을에디터창에열었을것이다. 이클립스사용하기 7

44 워크벤치창의오른쪽과아래에도뷰들이있다. 뷰이름옆에있는표시를클릭하여오른쪽에있는뷰중에아무거나닫아보자. 아래쪽에있는뷰들은탭그룹 (tab group) 으로되어있다. 이뷰들은닫지말고그그룹영역의오른쪽위에있는표시를클릭하여그룹전체를최소화해보자. 뷰를최소화하면워크벤치창의제일바깥쪽테두리에그뷰에대한툴바아이콘이나타난다. 즉, 오른쪽에열린뷰를최소화하면오른쪽테두리에나타나고, 왼쪽의뷰는왼쪽테두리에, 그리고아래쪽의탭그룹뷰들은제일아래테두리에나타난다. 그리고툴바아이콘에마우스를갖다대면관련된뷰의이름을알수있으며, 클릭하면그뷰가다시나타난다. 단, 다른뷰의영역을클릭하면나타난뷰가다시최소화된다. 따라서해당뷰를원래대로아예고정시켜서보고싶을때는툴바의제일앞에있는 restore( 복구 ) 아이콘을클릭하면된다. 사용자인터페이스의레이아웃만들기 기본적으로, 이클립스는안드로이드의그래픽레이아웃도구에 activity_quiz.xml 파일을열어서레이아웃의미리보기를보여준다. 그래픽레이아웃도구는유용한것이지만, 레이아웃이동작하는방법을잘이해하기위해서지금은일단 XML로작업할것이다. XML 소스를보기위해에디터창의밑에있는 activity_quiz.xml 탭을클릭하자. 현재, activity_quiz.xml에는디폴트액티비티레이아웃이정의되어있다. 디폴트값은안드로이드 SDK 버전에따라달라질수있지만, 여기서는리스트 1.1과같이보일것이다. 8 CHAPTER 1 처음만드는안드로이드애플리케이션

45 리스트 1. 1 디폴트액티비티레이아웃의 XML 정의하기 CODE activity_quiz.xml <RelativeLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".quizactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> 아래는 XML 버전과문자인코딩을선언하는 XML 문이다. <?xml version="1.0" encoding="utf-8"?> 그러나 activity_quiz.xml은이라인으로시작하지않는다는것에주목하자. 안드로이드 ADT 22 버전을기준으로레이아웃에서이라인은더이상필요없기때문이다. 그러나기존코드에서는여전히볼수있을것이다. 리스트 1.1의디폴트레이아웃에서는두개의위젯 (widget) 으로 RelativeLayout과 TextView 를정의하고있다. 위젯은사용자인터페이스를만드는데사용되는구성요소다. 위젯은텍스트나그래픽을보여줄수있고, 사용자와상호동작할수있으며, 화면의다른위젯을배열할수도있다. 예를들어, 버튼, 텍스트입력컨트롤, 체크상자모두위젯의타입들이다. 안드로이드 SDK에는많은위젯들이포함되어있으며, 우리가원하는사용자인터페이스 ( 화면에보여지는모습과앱과의상호동작 ) 를만들기위해그것들을구성할수있다. 안드로이드에서모든위젯은 View 클래스의인스턴스이거나, 또는 View의서브클래스 ( 예를들어, TextView나 Button) 중하나의인스턴스다. 사용자인터페이스의레이아웃만들기 9

46 그림 1.8 에서는리스트 1.1 에정의된 RelativeLayout 과 TextView 가화면에어떻게나타나는 지보여준다. RelativeLayout TextView 그림 1. 8 화면에보여진디폴트위젯들 그러나우리가만들려는 QuizActivity 의인터페이스로는다음다섯개의위젯이필요하다. 하나의 LinearLayout( 수직선형레이아웃 ) 하나의 TextView( 텍스트뷰 ) 하나의 LinearLayout( 수평선형레이아웃 ) 두개의 Button( 버튼 ) 그림 1.9 에서는이런위젯들이 QuizActivity 의인터페이스를어떻게구성하는지보여준다. 10 CHAPTER 1 처음만드는안드로이드애플리케이션

47 LinearLayout ( 수직 ) TextView LinearLayout ( 수평 ) Button 그림 1. 9 화면에보여지는 QuizActivity 의위젯들 이제는이런위젯들을 activity_quiz.xml 파일에정의할필요가있다. 리스트 1.2에있는것처럼 activity_quiz.xml 의일부내용을변경하자. 삭제할필요가있는 XML은줄이그어져있으며, 진한글씨로된부분은추가할필요가있는 XML이다. 이책에서는전체적으로이런형태를사용할것이다. 이 XML의내용이이해되지않더라도걱정말자. 어떻게동작하는지곧배울것이기때문이다. 그러나틀리지않게잘입력하자. 지금은이레이아웃 XML의이상여부가검사된것이아니므로틀리게입력한것때문에나중에문제가생길수있기때문이다. 안드로이드도구의버전에따라서는 android:text 로시작하는세개의라인에서에러가생길수도있다. 그러나곧해결할것이므로지금은그에러를무시하자. 사용자인터페이스의레이아웃만들기 11

48 리스트 1. 2 XML 로위젯정의하기 CODE activity_quiz.xml <RelativeLayout xmlns:android=" xmlns:tools=" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".quizactivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> <LinearLayout xmlns:android=" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> 이 XML 을그림 1.9 에있는사용자인터페이스와비교해보자. 모든위젯은대응되는 XML 요소 (element) 를갖는다. 요소의이름은위젯의타입을나타낸다. 12 CHAPTER 1 처음만드는안드로이드애플리케이션

49 각요소는 XLM 속성 (attribute) 들을가지며, 각속성은위젯을어떻게구성하는지를나타낸다. 요소와속성이어떻게동작하는지알려면계층적인관점으로레이아웃을살펴볼필요가 있다. 뷰계층구조 위젯들은뷰계층구조 (view hierarchy) 라고하는 View 객체들로존재한다. 그림 1.10 에서는리 스트 1.2 XML 의뷰계층구조를보여준다. 그림 위젯과속성의계층적레이아웃 이레이아웃의뷰계층구조에서근간이되는루트요소는 LinearLayout( 선형레이아웃 ) 이 다. 루트요소이므로이 LinearLayout 은 로 XML 네임스페이스를지정해야한다. 사용자인터페이스의레이아웃만들기 13

50 이 LinearLayout은 ViewGroup이라는이름의 View 서브클래스로부터상속받는다. ViewGroup은다른위젯들을포함하고배열하는위젯이다. LinearLayout 은위젯들을하나의열 ( 수직 ) 이나행 ( 수평 ) 으로배열하고싶을때사용하는레이아웃이다. 이외의다른 ViewGroup 서브클래스에는 FrameLayout( 프레임레이아웃 ), TableLayout( 테이블레이아웃 ), RelativeLayout( 상대레이아웃 ) 이있다. 위젯이 ViewGroup에포함될때그위젯은그 ViewGroup의자식 (child) 이라고한다. 그림 1.10에서루트 LinearLayout은두개의자식을갖는다. TextView와또다른 LinearLayout이다. 자식 LinearLayout은두개의 Button을자신의자식으로갖는다. 위젯속성 우리위젯의구성에사용된속성중중요한것을살펴보자. android layout_width android layout_height 이두가지속성은거의모든타입의위젯에서필요로한다. 그리고대개는다음의 match_ parent 또는 wrap_content 중하나의값으로설정된다. match_parent 자신의부모만큼크게보여질수있다. wrap_content 자신이갖는콘텐츠에필요한크기로보여질수있다. ( 때로는 fill_parent 를볼수있을것이다. 이것은 match_parent 와동일하며지금은사용하지않는값이다.) 루트 LinearLayout의경우에 height( 높이 ) 와 width( 너비 ) 속성의값은모두 match_parent 다. 왜냐하면, 루트요소이지만여전히부모를갖기때문이다. 그부모는우리앱의뷰계층구조에들어가도록안드로이드에서제공한뷰다. 우리레이아웃에있는그밖의다른위젯들은 height 와 width 속성의값으로 wrap_content 를갖는다. 그림 1.9를보면알수있듯이, 이경우자신의콘텐츠에필요한크기로보여진다. TextView는자신이갖는텍스트보다약간더크게나타난다. android:padding="24dp" 속성때문이다. 이속성은지정된양의공간을위젯크기에추가한다. 여기서는질문 14 CHAPTER 1 처음만드는안드로이드애플리케이션

51 과버튼들사이에여백을두는데사용하고있다. (dp 가뭔지궁금할것이다. 이것은 density independent pixel( 해상도에독립적인픽셀 ) 을말하며 8 장에서배울것이다.) android orientation 두개의 LinearLayout 위젯에있는이속성은자신들의자식들이수직 (vertical) 또는수평 (horizontal) 중어떤형태로나타날지결정한다. 루트 LinearLayout은수직이고, 그것의자식 LinearLayout은수평값을갖고있다. 자식들은정의된순서에따라화면에나타난다. 수직 LinearLayout의경우, 첫번째로정의된자식은제일위에나타나게된다. 수평 LinearLayout에서는첫번째로정의된자식이제일왼쪽에나타난다. ( 앱이실행되는스마트폰이나태블릿등의장치에설정된언어가아랍어나히브리어와같이오른쪽부터글씨를쓰는언어일때는첫번째자식이제일오른쪽에나타난다.) android text TextView 와 Button 위젯들은 android:text 속성을갖는데, 이속성은보여줄텍스트를나타낸다. 이속성들의값은리터럴문자열자체를나타내는것이아님에유의하자. 이값들은문자열리소스 (string resource) 에대한참조 (reference) 다. 문자열리소스는문자열파일 (string file) 이라고하는별도의 XML 파일에정의된문자열이다. 물론, android:text="true" 처럼문자열값을위젯에직접지정할수있다. 그러나이것은좋은방법이아니다. 별도의문자열파일에문자열값을두고그것을참조하게하는것이더좋다. 문자열리소스를사용하면지역화 (localization) 가얼마나쉬워지는지 15장에서알수있을것이다. 현재는 activity_quiz.xml에서참조하는문자열리소스가아직없다. 지금부터그것을추가해보자. 사용자인터페이스의레이아웃만들기 15

52 문자열리소스생성하기 모든프로젝트에는 strings.xml이라는이름의디폴트문자열파일이하나포함된다. 이클립스의패키지탐색기에서 res/values 디렉터리에있는 strings.xml을마우스로더블클릭하여연다. 에디터창에나타난그래픽인터페이스는무시하고에디터밑에있는 strings. xml 탭을선택한다. 일부문자열리소스를템플릿에서미리추가한것을볼수있을것이다. hello_world 라는이름의문자열은우리가사용하지않을것이므로삭제한다. 그리고우리레이아웃에서필요한세개의문자열을추가하자. 리스트 1. 3 문자열리소스추가하기 CODE strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">geoquiz</string> <string name="hello_world">hello, world!</string> <string name="question_text"> 콘스탄티노플은터키의가장큰도시이다.</string> <string name="true_button">true</string> <string name="false_button">false</string> <string name="menu_settings">settings</string> </resources> (menu_settings 문자열은삭제하지말자. 우리프로젝트에메뉴 (menu) 가이미준비돼있기때문이다. menu_settings 를삭제하면메뉴와연관된다른파일들에서연쇄적으로에러가날것이다.) 이처럼문자열리소스를추가한후에는 GeoQuiz 프로젝트의어떤 XML false_button 을참조하더라도앱이실행되는런타임시에 False 문자열을받게될것이다. strings.xml을저장하자. 저장하는방법은두가지다. 이클립스워크벤치의제일위메뉴바의바로밑에있는왼쪽에서두번째아이콘 (Save) 을클릭하거나, 또는메뉴바의 File Save As 를선택하면된다. 16 CHAPTER 1 처음만드는안드로이드애플리케이션

53 기본적으로, 문자열파일의이름은 strings.xml이다. 그러나우리가원하는어떤이름을주어도된다. 또한, 하나의프로젝트에여러개의문자열파일을가질수있다. 그파일들이 res/values/ 에있고, resources 라는루트요소를갖고있으며, 그것의자식문자열요소들로포함되어있다면우리문자열은언제든올바르게찾아사용될것이다. 레이아웃미리보기 이제는우리레이아웃이완성되었으므로그래픽레이아웃도구에서미리보기할수있다. 우선, 모든파일들이저장되고에러가없는지확인한다. 그다음에 activity_quiz.xml 로돌아가서에디터의밑에있는 Graphical Layout( 그래픽레이아웃 ) 탭을선택하자. 그림 그래픽레이아웃도구미리보기 (activity_quiz.xml) 사용자인터페이스의레이아웃만들기 17

54 레이아웃 XML 에서뷰객체로 activity_quiz.xml 에정의된 XML 요소들이어떻게 View 객체로되는것일까? 그답의실마리는 QuizActivity 클래스에서부터찾으면된다. GeoQuiz 프로젝트를생성했을때 Activity의서브클래스인 QuizActivity가생성되었다. QuizActivity 클래스파일은우리프로젝트의 src 디렉터리에있다. 이디렉터리는우리프로젝트의자바코드가있는곳이다. 이클립스의패키지탐색기에서 src 디렉터리의 com.bignerdranch.android.geoquiz 패키지를찾은후, 그안의 QuizActivity.java 파일을열어보면리스트 1.4와같을것이다. 이내용을잠시살펴보자. 리스트 1. 4 QuizActivity 의디폴트클래스파일 CODE QuizActivity.java package com.bignerdranch.android.geoquiz; import android.app.activity; import android.os.bundle; import android.view.menu; public class QuizActivity extends Activity public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.activity_quiz, menu); return true; ( 만일 import 문이다나타나지않는다면, 첫번째 import 문의왼쪽에있는표시를클릭하면나타 날것이다.) 18 CHAPTER 1 처음만드는안드로이드애플리케이션

55 이파일에는두개의 Activity 메서드가있다. oncreate(bundle) 과 oncreatoptionsmenu(menu) 가그것이다. 지금은 oncreatoptionsmenu(menu) 를무시하자. 메뉴에대해서는 16장에서다시자세하게설명할것이다. oncreate(bundle) 메서드는액티비티서브클래스의인스턴스가생성될때자동으로호출된다. 그리고액티비티가생성되면사용자인터페이스를처리할필요가있다. 액티비티가사용자인터페이스를처리하려면다음의메서드를호출한다. public void setcontentview(int layoutresid) 이메서드는레이아웃을뷰객체로바꾸어화면에나타낸다 ( 안드로이드에서는이것을인플레이트 (inflate) 한다고한다 ). 이때레이아웃파일에있는각위젯은자신의속성들이정의된대로인스턴스로생성된다. 이메서드를호출할때는레이아웃의리소스 ID를인자로전달하여처리할레이아웃을지정한다. 리소스와리소스 ID 레이아웃은리소스 (resource) 다. 리소스는애플리케이션의일부이며, 코드가아닌이미지파일이나오디오파일및 XML 파일과같은것들이다. 우리프로젝트의리소스들은 res 디렉터리의서브디렉터리에존재한다. 이클립스의패키지탐색기에서보면 activity_quiz.xml 이 res/layout/ 에있는것을알수있다. 문자열리소스들을포함하는문자열파일은 res/values/ 에존재한다. 코드에서리소스를액세스하려면그것의리소스 ID를사용한다. 우리레이아웃의리소스 ID는 R.layout.activity_quiz 다. GeoQuiz 앱의현재리소스 ID들을보려면이클립스의패키지탐색기에서 gen 디렉터리의내용중 R.java 를찾아서열면된다. 이파일은안드로이드빌드프로세스에서자동으로생성한것이라서우리가수정하면안된다 ( 파일의제일앞에경고주석이있다 ). 레이아웃 XML 에서뷰객체로 19

56 리스트 1. 5 현재의 GeoQuiz 리소스 ID 들 CODE R.java /* AUTO-GENERATED FILE. DO NOT MODIFY.... */ package com.bignerdranch.android.geoquiz; public final class R { public static final class attr { public static final class drawable { public static final int ic_launcher=0x7f020000; public static final class id { public static final int menu_settings=0x7f070003; public static final class layout { public static final int activity_quiz=0x7f030000; public static final class menu { public static final int activity_quiz=0x7f060000; public static final class string { public static final int app_name=0x7f040000; public static final int false_button=0x7f040003; public static final int menu_settings=0x7f040006; public static final int question_text=0x7f040001; public static final int true_button=0x7f040002;... R.layout.activity_quiz 가있는곳이바로여기다. 그것은 activity_quiz 라는이름의정수형상수이며, R 클래스의내부클래스인 layout 안에있다. 문자열들도리소스 ID를갖는다. 아직까지는우리코드에서문자열을참조하지않았다. 그러나참조를했다면다음과같은형태가되었을것이다. settitle(r.string.app_name); 리스트 1.5 를보면알수있듯이, 안드로이드는레이아웃전체에대해하나의리소스 ID 를생성했으며, 문자열의경우는각각에대해하나의리소스 ID 를생성하였다. 그러나 activity_quiz.xml 의개별적인위젯들에대한리소스 ID 는생성하지않았다. 모든위젯이 20 CHAPTER 1 처음만드는안드로이드애플리케이션

57 리소스 ID를필요로하는것은아니기때문이다. 이장에서는두개의버튼만이코드와상호동작할것이기때문에그것들만리소스 ID가필요하다. 위젯의리소스 ID를생성하려면위젯정의에 android:id 속성을포함시킨다. 자, 그러면 activity_quiz.xml 에있는각버튼에 android:id 속성을추가해보자. 리스트 1. 6 리소스 ID 를버튼에추가하기 CODE activity_quiz.xml <LinearLayout xmlns:android=" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> 각버튼의 android:id 속성값에는 + 부호가있지만, android:text 속성값에는없다는것에유의하자. ID는생성을하는것이고, 문자열은참조하는것이기때문이다. activity_quiz.xml 을저장하자. 그리고 R.java 에서 R.id 내부클래스에두개의새로운리소스 ID가생겼는지확인해보자. 레이아웃 XML 에서뷰객체로 21

58 리스트 1. 7 새로운리소스 ID 들 CODE R.java public final class R {... public static final class id { public static final int false_button=0x7f070001; public static final int menu_settings=0x7f070002; public static final int true_button=0x7f070000;... 위젯을코드와연결하기 이제는버튼들이리소스 ID를갖게되었으므로 QuizActivity 에서그것들을사용할수있다. 첫번째단계는두개의멤버변수를추가하는것이다. 다음코드를 QuizActivity.java 에입력하자. ( 이클립스의자동완성기능을사용하지말고직접입력해보자.) 파일을저장하면두개의에러가생길것이다. 리스트 1. 8 멤버변수추가하기 CODE QuizActivity.java public class QuizActivity extends Activity {... private Button mtruebutton; private Button public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_quiz); 이에러들은금방해결할수있다. 우선, 두개의멤버 ( 인스턴스 ) 변수이름앞에붙은 m에주목하자. 이것은안드로이드의작명법이며, 이책전체에서그런방법을따를것이다. 에러코드의왼쪽에있는에러플래그위에마우스를갖다대보자. 동일한에러를보여줄것이다. 즉, Button 은타입이될수없다는것이다. 22 CHAPTER 1 처음만드는안드로이드애플리케이션

59 이에러들은우리가 android.widget.button 클래스를 QuizActivity.java 에 import 할필요가 있다는것을알려준다. 따라서다음의 import 문을파일의제일앞에추가하면된다. import android.widget.button; 또는더쉬운방법으로 import 를구성할수있다. import 구성하기 우리가직접 import 문을넣지않고이클립스에게요청하면이클립스가우리코드를분석하고자바와안드로이드 SDK에서필요한것을판단해서추가해준다. 이때이클립스는우리가필요한것만을 import 하고더이상사용하지않는클래스들의 import 문은제거한다. 이렇게 import 를구성하려면다음키를누른다. Command + Shift + O(Mac 의경우 ) Ctrl + Shift + O( 윈도우즈나리눅스의경우 ) 이렇게하면에러가없어질것이다. ( 만일그래도에러가난다면코드와 XML 에잘못입력한건 없는지확인하자.) ( 이것은매우편리한기능이다. 예를들어, 바로위의 Button 클래스의경우에이클래스가어떤패키지에있는지우리가일일이기억할필요없다. 이클립스가찾아서 import 문을자동으로추가해주기때문이다. 만일같은이름의클래스가서로다른패키지에여러개있을때는우리가선택할수있게해준다.) 이제는버튼위젯들을코드와연결할수있다. 이때는다음두단계로처리한다. 생성되는 View 객체들에대한참조를얻는다. 사용자의액션에응답하기위해그객체들에대한리스너 (listener) 를설정한다. 위젯을코드와연결하기 23

60 위젯의참조얻기 액티비티에서다음의 Activity 메서드를호출하면뷰객체로인플레이트 ( 생성 ) 되는위젯의 참조를얻을수있다. public View findviewbyid(int id) 이메서드는위젯의리소스 ID를인자로받아서그위젯의 View 객체를반환한다. QuizActivity.java 에서는버튼들의리소스 ID를사용하여 View 객체참조를얻어서그것을우리의멤버변수에지정한다. 이때반환된 View 객체참조의타입을 Button으로변환 (cast) 해서변수에지정해야한다는것에유의하자. 리스트 1. 9 위젯의참조얻기 CODE QuizActivity.java public class QuizActivity extends Activity { private Button mtruebutton; private Button public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_quiz); mtruebutton = (Button)findViewById(R.id.true_button); mfalsebutton = (Button)findViewById(R.id.false_button);... 리스너설정하기 안드로이드애플리케이션은이벤트기반으로구동된다 (event-driven). 명령행에서실행되는프로그램이나스크립트와는달리, 이벤트기반으로구동되는애플리케이션들은시작된후이벤트발생을기다린다. 예를들어, 사용자가버튼을누르는경우다. ( 이벤트는운영체제나다른애플리케이션에의해발생될수도있다. 그러나사용자가발생시킨이벤트가가장확실하다.) 24 CHAPTER 1 처음만드는안드로이드애플리케이션

61 우리애플리케이션에서특정이벤트를기다리는것을가리켜그이벤트를 리스닝한다 고한다. 그리고이벤트에응답하기위해생성하는객체를리스너 (listener) 라한다. 리스너는해당이벤트의리스너인터페이스를구현하는객체다. 안드로이드 SDK에는다양한이벤트들의리스너인터페이스들이준비되어있다. 따라서우리가추가로만들필요없다. 여기서리스닝하기를원하는이벤트는버튼 클릭 (click) 이다. 그러므로우리리스너에서는 View.OnClickListener 인터페이스를구현할것이다. 자, 그러면 True 버튼부터시작해보자. QuizActivity.java 에서 oncreate( ) 의변수지정바로밑에다음코드 ( 진한글씨로된부분 ) 를추가하자. 리스트 True 버튼에리스너설정하기 CODE public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_quiz); mtruebutton = (Button)findViewById(R.id.true_button); mtruebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { // 아직은아무일도하지않지만곧코드가추가된다! ); mfalsebutton = (Button)findViewById(R.id.false_button); ( 만일뷰에서타입에러가해결되지않았다면, Command + Shift + O 또는 Ctrl + Shift + O 를사용해서 View 클래스의 import 를추가하자.) 리스트 1.10 에서는 mtruebutton 이라는 Button 이터치 ( 클릭 ) 될때우리에게알려주는리스 너를설정한다. setonclicklistener(onclicklistener) 메서드는자신의인자로리스너를받 는다. 이때그리스너는 OnClickListener 를구현하는객체다. 위젯을코드와연결하기 25

62 이리스너는익명의내부클래스 (anonymous inner class) 로구현된다. 자바의내부클래스형태가몇가지있지만그중에서제일이상하게보일것이다. 여기서 new View. OnClickListener() { 는익명의내부클래스를정의하고그것의인스턴스를생성한다는의미다. 그리고이익명의내부클래스인스턴스가 setonclicklistener(onclicklistener) 메서드의인자로전달된다. mtruebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { // Does nothing yet, but soon! ); 이책에나오는모든리스너들은익명의내부클래스로구현될것이다. 그렇게하면리스너의메서드들을우리가원하는곳에구현할수있다. 그리고익명의내부클래스는한곳에서만사용되므로이름을주는데따른부담이없기때문이다. 익명의내부클래스에서는 OnClickListener 인터페이스를구현하므로그인터페이스의유일한메서드인 onclick(view) 를반드시구현해야한다. 일단, 여기서는 onclick(view) 의실행코드를비워두었다. 그러나컴파일에러는없다. 여러분도알다시피, 자바에서는메서드내부의실행코드 ( 어떻게구현했는지를나타내는 ) 없이정의만해도구현으로간주하기때문이다. ( 만일여러분이익명의내부클래스, 리스너, 인터페이스에대해잘모르거나친숙하지않다면, 자바책을잠시살펴보고계속하는것도좋을것이다.) 이제는이것과유사한 False 버튼의리스너를설정해보자. 리스트 False 버튼에리스너설정하기 CODE QuizActivity.java... mtruebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { // 아직은아무일도하지않지만곧코드가추가된다! ); 26 CHAPTER 1 처음만드는안드로이드애플리케이션

63 mfalsebutton = (Button)findViewById(R.id.false_button); mfalsebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { // 아직은아무일도하지않지만, 곧코드가추가된다! ); 이제는버튼들이완벽하게동작하도록만들어보자. 각버튼을터치하면토스트 (toast) 라고하는팝업메시지가나타나게할것이다. 안드로이드에서토스트는사용자에게뭔가를알려주지만사용자의어떤입력이나액션도요구하지않는짤막한메시지다. 여기서는사용자가고른답의맞고틀림을알려주는토스트를만들것이다 ( 그림 1.12 참조 ). 그림 피드백을제공하는토스트 우선, strings.xml 에서우리토스트가보여줄문자열리소스들을추가한다. 위젯을코드와연결하기 27

64 리스트 토스트문자열들을추가하기 CODE strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">geoquiz</string> <string name="question_text"> 콘스탄티노플은터키의가장큰도시이다.</string> <string name="true_button">true</string> <string name="false_button">false</string> <string name="correct_toast"> 정답!</string> <string name="incorrect_toast"> 틀림!</string> <string name="menu_settings">settings</string> </resources> 토스트를생성하려면 Toast 클래스로부터다음메서드를호출한다. public static Toast maketext(context context, int resid, int duration) Context 매개변수는일반적으로 Activity의인스턴스다 (Activity는 Context의서브클래스다 ). 두번째매개변수는토스트가보여주는문자열의리소스 ID다. 문자열의리소스 ID를찾아사용하기위해서는 Toast 클래스에서 Context가필요하다. 세번째매개변수는두개의 Toast 상수중하나이며, 토스트문자열을얼마나오래보여줄지를나타낸다. 토스트를생성한후 Toast.show() 를호출하면문자열이화면에나타난다. QuizActivity 에서는각버튼의리스너에있는 maketext( ) 메서드를호출할것이다 ( 리스트 1.13 참조 ). 그리고그코드를추가할때는모든것을우리가직접입력하지말고이클립스 의자동완성기능을사용해보자. 이클립스의자동완성 (auto complete) 기능을사용하면많은시간을절약할수있으므로일찌감치알아두는것이좋다. 자, 그러면리스트 1.13에추가되는코드의입력을시작하자. Toast 클래스다음에점 (.) 을누르면자동완성팝업윈도우가나타나서 Toast 클래스로부터사용가능한메서드와상수들의내역을보여줄것이다. 28 CHAPTER 1 처음만드는안드로이드애플리케이션

65 그중에서하나를선택하려면탭키를눌러서그팝업으로포커스를이동한다. ( 만일자동 완성팝업을무시하고싶으면그냥계속타이핑하면된다. 탭키를누르지않거나또는팝업윈도우를 마우스로클릭하지않으면자동완성기능은수행되지않는다.) 팝업에나온내역중에서 maketext(context, int, int) 를선택하자. 자동완성기능에서이메서드호출코드를추가해줄것이다. 그리고첫번째인자를입력하게해준다. 이때 QuizActivity.this 를넣자. 그다음에탭키를다시누르면그다음인자로이동하여각인자값을입력할수있다. 이런식으로리스트 1.13의메서드호출코드를입력하면된다. 리스트 토스트만들기 CODE QuizActivity.java... mtruebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { Toast.makeText(QuizActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show(); ); mfalsebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { Toast.makeText(QuizActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show(); ); maketext( ) 에서는 Context 인자로 QuizActivity 의인스턴스를전달한다. 여기서 new View. OnClickListener() { 는익명의내부클래스를정의하고그것의인스턴스를생성한다는의미다. 따라서 QuizActivity.this 의 this는 View.OnClickListener 의인스턴스를참조한다. 여기서는이클립스의자동완성기능을사용했으므로 Toast 클래스를사용하기위해우리가 import 문을넣을필요없다. 자동완성팝업에서선택하면그것에필요한클래스를이클립스가자동으로 import 해주기때문이다. 위젯을코드와연결하기 29

66 에뮬레이터에서실행시키기 안드로이드애플리케이션을실행하려면스마트폰과같은하드웨어장치나가상장치 (virtual device) 중하나가필요하다. 가상장치는안드로이드에뮬레이터 (emulator) 로구현되어있으며, 안드로이드개발자도구에포함되어함께배포된다. 안드로이드가상장치 (AVD) 를생성하려면이클립스메뉴에서 Window Android Virtual Device Manager 를선택한다. 그리고 AVD Manager 대화상자가나타나면오른쪽의 New 버튼을클릭한다. 이때나타나는대화상자에서는가상장치의구성에필요한여러가지옵션을줄수있다. 그림 1.13을참조하여 AVD 이름을주자. 각자원하는이름으로지정해도되지만여기서는 Nexus_API_18 로주었다. Device( 장치 ) 는 Galaxy Nexus를선택하자 ( 화면크기가너무크지않은것이좋다 ). 그리고 Target 은 Android 4.4 API Level 19( 킷캣 ) 로선택하자. ( 단, 에뮬레이터에서한글이나오게하고싶으면책앞부분의 알아두자 참조 ). 만일윈도우즈에서실행한다면 AVD 가올바르게동작하기위해 RAM을 1024 에서 512 로변경해야한다. 그리고그래픽처리를조금빠르게하기위해제일아래오른쪽에있는 Use Host GPU를체크하자. OK 버튼을클릭한다. 그리고 AVD 생성대화상자는제일위의오른쪽를눌러그냥닫으면된다. 이처럼 AVD를생성한후에는마치지정한스마트폰에서실행하듯이이 AVD에서 GeoQuiz 앱을실행할수있다. 이클립스패키지탐색기의 GeoQuiz 프로젝트폴더에서오른쪽마우스버튼을클릭하면컨텍스트메뉴가나온다. 이메뉴의 Run As Android Application 을선택하자. 그러면이클립스는우리가생성한 AVD 를찾아서우리앱패키지를거기에설치한후실행시킬것이다. 이때 LogCat 으로자동모니터를할것인지를이클립스가물어보면 Yes 를선택하자. ( 각자컴퓨터의성능에따라다르겠지만, 에뮬레이터가처음뜰때 2~3 분정도걸릴수있으니인내심을갖고기다려야할것이다. 그런다음에우리앱을론칭할때도 30초정도의시간이소요된다. 에뮬레이터가화면에나타나는것은약 40초정도면되지만, 안드로이드의리눅스커널과컴포넌트들이부팅되므로추가시간이필요하다. 그러나안드로이드 4.4 킷캣버전부터는조금더빨라졌다.) 30 CHAPTER 1 처음만드는안드로이드애플리케이션

67 윈도우즈에서는 1024 대신 512 를입력한다. 그림 새로운 AVD 생성하기 에뮬레이터가시작되면우리가생성한 AVD 에 GeoQuiz 앱이론칭되어실행될것이다. 버튼들을눌러보면서토스트문자열이나오는것을감상해보자. ( 앱이실행되었는데응답없이그냥놔두면실제스마트폰처럼잠겨버리므로잠김을풀어야할것이다. AVD 가실제장치처럼동작하기때문이다. 이때는화면에나와있는자물쇠를마우스로클릭한후오른쪽으로끌어주면된다. 그렇지않고화면만약간어두워지면빈공간아무데를마우스로클릭하면된다.) 만일 GeoQuiz 앱이론칭될때나버튼을클릭했을때비정상적으로중단된다면, 이클립스워크벤치에 LogCat 이나타날것이다 ( 기본적으로 LogCat 뷰는화면아래쪽에있다 ). 이때는로그에수록된예외 ( 에러 ) 를살펴보자. 그것들은눈에잘띄게빨간색으로되어있다. 그리고 Text 열에서는예외의이름과문제가생긴코드의라인을알려준다. 에뮬레이터에서실행시키기 31

68 그림 번째라인에서발생한 NullPointerException 의예 각자작성한코드를이책의코드와비교하여문제발생의원인을찾아보자. 그리고다시실행해보자. 에뮬레이터는실행되는상태로두자. 매번실행할때마다에뮬레이터가부팅되는것을기다릴필요가없기때문이다. 에뮬레이터화면에서가로로된 U자화살표모양의 Back 버튼을누르면현재화면에서실행중인앱을중지시킬수있다. 그리고앱에서변경된것이있을때는이클립스에서수정후에곧바로다시실행하면된다. 그러면새로시작시킨앱이다시실행되기때문이다. 에뮬레이터가유용한것은사실이지만, 실제장치에서테스트하는것이더정확한결과를알려준다. 2장에서는 GeoQuiz 앱을실제장치에서실행할것이다. 또한, 더많은지리문제들을 GeoQuiz 앱에추가하여사용자를테스트하게할것이다. 안드로이드앱빌드절차 지금까지이장을읽는동안안드로이드앱의빌드 (build) 절차가어떻게되는지무척궁금했을것이다. 프로젝트의내용을수정하면따로명령을주지않아도이클립스가자동으로빌드해준다는것을우리는이미알고있다. 빌드를하는동안안드로이드도구들이우리의리소스와코드및 AndroidManifest.xml 파일 ( 애플리케이션에관한메타데이터를포함 ) 을가지고하나의.apk 파일로만든다. 그리고이파일은에뮬레이터에서실행될수있게디버그키가부여된다. (.apk 를대중에게배포하려면배포키를부여받아야한다. 이런절차에관한더자세한정보는안드로이드개발자문서에있으며, URL은다음과같다. tools/publishing/preparing.html) 32 CHAPTER 1 처음만드는안드로이드애플리케이션

69 자바코드컴파일 자바바이트코드 (.class) Dalvik( 달빅 ) JVM 코드로컴파일 컴파일된리소스 Dalvik 바이트코드 (.dex) 설치와실행! apk 빌드와서명 안드로이드패키지 (.apk) 그림 GeoQuiz 앱빌드절차 activity_quiz.xml 의내용을어떻게애플리케이션의 View 객체로변환하는걸까? 빌드절차의일부로 aapt(android Asset Packaging Tool) 가레이아웃파일의리소스들을컴파일한다. 그리고이렇게컴파일된리소스들은.apk 파일로통합된다. 그다음에 setcontentview( ) 가 QuizActivity 의 oncreate( ) 메서드에서호출되면, QuizActivity 는 LayoutInflater 클래스를사용해서레이아웃파일에정의된각 View 객체들의인스턴스를생성한다. 안드로이드앱빌드절차 33

70 그림 activity_quiz.xml 리소스를뷰객체로전환하기 ( 우리의뷰클래스들을 XML 로정의하는대신액티비티에서프로그램으로생성할수도있다. 그러나 좋은방법이아니다. XML 로정의하면프레젠테이션 ( 사용자인터페이스 ) 을애플리케이션로직과분리 할수있는장점이생기기때문이다. 더자세한내용은 3 장에서설명할것이다.) 서로다른 XML 속성들이어떻게동작하는지, 그리고뷰객체들이자신을어떻게화면에 보여주는지에관한더자세한내용은 8 장을참조하자. 안드로이드빌드도구들 지금까지알아보았던모든빌드작업은이클립스내에서실행되었다. 이런빌드작업은우리가사용하는 ADT 플러그인으로통합된다. 즉, aapt와같은안드로이드표준빌드도구들을 ADT 플러그인이호출하지만, 빌드절차자체는이클립스에의해관리된다. 경우에따라서는이클립스외부에서빌드작업을수행할필요가있을수있다. 이때제일쉬운방법은명령행빌드도구들을사용하는것이다. 그런도구들중가장많이알려진두개가 maven 과 ant다. ant가기능은적지만사용하기는훨씬쉽다. 어떻게사용하는지잠시알아보자. 34 CHAPTER 1 처음만드는안드로이드애플리케이션

71 우선, 다음두가지를먼저확인해야한다. ant가설치되어있고실행가능한지확인한다. 안드로이드 SDK의 tools/ 와 platform tools/ 폴더모두가실행파일검색경로 (PATH) 에포함되어있는지확인한다. 이제는우리프로젝트의디렉터리로가서다음명령을실행하자. ( 윈도우즈경우는명령프롬 프트 (cmd.exe) 를실행시킨후한다.) $ android update project p. 이클립스의프로젝트생성템플릿에는 ant가사용하는데적합한 build.xml이포함되어있지않다. 위의첫번째명령을실행하면우리가필요한 build.xml을생성한다. 한번만실행하면된다. 다음은우리프로젝트를빌드한다. debug.apk 를빌드하려면같은폴더에서다음명령을실행한다. $ ant debug 이명령은실제빌드를수행하여.apk 파일을생성한다. 그리고위치와이름은 bin/ 우리프 로젝트이름 debug.apk 다..apk 가생성되면다음명령을실행하여설치한다. $ adb install bin/ 우리프로젝트이름 -debug.apk 이렇게하면어떤장치에연결되건그장치에우리앱을설치하게될것이다. 그러나앱을 실행시키지는못할것이다. 실행을하려면우리가수동으로론처 (launcher) 를기동시킨후 에우리앱을론칭해야한다. 안드로이드앱빌드절차 35

72

73 2 안드로이드와모델-뷰-컨트롤러 CHAPTER 이장에서는 GeoQuiz 앱의기능을개선하여하나이상의질문들을처리하게할것이다. 그림 2. 1 더많은질문을할수있다! 37

74 이렇게하기위해 TrueFalse라는클래스를 GeoQuiz 프로젝트에추가할것이다. 그리고이클래스의각인스턴스는하나의 true false 문제를가질것이다. 그다음에 QuizActivity 에서그것들을관리하기위해 TrueFalse 객체들을저장하는배열을생성할것이다. 새로운클래스만들기 이클립스의패키지탐색기에서 com.bignerdranch.android.geoquiz 패키지를마우스오른쪽버튼을클릭한후메뉴의 New Class 를선택한다. 그리고그림 2.2처럼다른필드는그대로두고클래스이름에 TrueFalse를입력한후 Finish 버튼을클릭한다. 그림 2. 2 TrueFalse 클래스만들기 38 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

75 TrueFalse.java 에서두개의멤버변수와하나의생성자를추가한다. 리스트 2. 1 TrueFalse 클래스에코드추가하기 CODE TrueFalse.java public class TrueFalse { private int mquestion; private boolean mtruequestion; public TrueFalse(int question, boolean truequestion) { mquestion = question; mtruequestion = truequestion; 어째서 mquestion 의타입이 String이아닌 int일까? mquestion 변수는질문문자열리소스의리소스 ID( 항상 int 타입 ) 를갖기때문이다. 질문문자열리소스들은더뒤에서생성할것이다. mtruequestion 변수는해당질문의답이 true와 false 중어느것인지를나타낸다. 이변수들은변수값을알려주는게터 (getter) 와값을변경하는세터 (setter) 메서드가필요하다. 그리고이메서드들을만들때우리가직접입력하는대신이클립스가구현코드를생성하게할수있다. 게터와세터메서드만들기 멤버변수이름의접두사 (prefix) 를 m으로인식하고사용하도록이클립스를구성하는것이첫번째단계다. 이클립스의메뉴에서 Window Preferences 를선택하여 Preferences 대화상자를연다. 왼쪽의 Java 밑에있는 Code Style을선택하면그림 2.3과같이될것이다. 오른쪽에나오는 Conventions for variable names 테이블에서 Fields 행을선택한다. Edit 버튼을클릭한후, 필드의접두사를 m으로추가하자. 그다음에 Static Fields 의접두사를 s로추가한다. (GeoQuiz 프로젝트에서는 s 접두사를사용하지않지만, 뒤에나오는다른프로젝트에서유용하게사용될것이다.) 새로운클래스만들기 39

76 Use is prefix for getters that return boolean 이체크되었는지확인한다. ( 이것은 boolean 값 을반환하는게터메서드의접두사를 is 로해달라는의미다.) 그리고 OK 버튼을클릭한다. 그림 2. 3 자바의 code style preferences 설정하기 이처럼접두사를설정하는이유가뭘까? 이렇게설정하고이클립스에게 mquestion 의게터를생성하도록요청하면, 이클립스는 getmquestion() 이아닌 getquestion() 이라는이름으로생성하며, 또한 mtruequestion 변수의게터는 ismtruequestion() 이아닌 istruequestion() 으로생성한다. 변수이름의제일앞에있는 m을접두사로인식하고게터메서드의이름을지을때제외시키는것이다. TrueFalse.java 로돌아가서생성자정의다음라인에서오른쪽마우스버튼을클릭하자. 그리고메뉴의 Source Generate Getters And Setters 를선택하자. 그다음에각변수의게터와세터메서드를생성하기위해 Select All 버튼을클릭하자. 그리고 OK 버튼을클릭하면이클립스가리스트 2.2의네개메서드코드를자동으로생성할것이다. 40 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

77 리스트 2. 2 생성된게터와세터메서드 CODE TrueFalse.java public class TrueFalse { private int mquestion; private boolean mtruequestion; public TrueFalse(int question, boolean truequestion) { mquestion = question; mtruequestion = truequestion; public int getquestion() { return mquestion; public void setquestion(int question) { mquestion = question; public boolean istruequestion() { return mtruequestion; public void settruequestion(boolean truequestion) { mtruequestion = truequestion; TrueFalse 클래스가완성되었으므로이제는이클래스와함께동작하도록 QuizActivity 를수정할것이다. 우선, GeoQuiz 앱의각요소들이어떻게함께동작하는지살펴보자. QuizActivity 에서는 TrueFalse 객체들의배열을생성할것이다. 그다음에질문을보여주고사용자의응답에피드백을제공하기위해 TextView 및세개의 Button들과상호동작할것이다. 새로운클래스만들기 41

78 모델 컨트롤러 뷰 ( 레이아웃 ) 그림 2. 4 GeoQuiz 앱의객체다이어그램 모델 - 뷰 - 컨트롤러와안드로이드 그림 2.4의객체들은모델 (Model), 컨트롤러 (Controller), 뷰 (View) 의세부분으로분리된다는것에주목하자. 안드로이드애플리케이션은모델 뷰 컨트롤러 ( 줄여서 MVC) 라는아키텍처에맞추어설계된다. 우리애플리케이션의어떤객체든모델객체또는뷰객체또는컨트롤러객체가되어야한다는것이 MVC의주요관점이다. 모델객체들은애플리케이션의데이터와 비즈니스로직 을갖는다. 모델클래스들은우리앱과관계가있는것들을모델객체로만들기위해설계된다. 예를들어, 사용자, 가게의상품, 서버에저장된사진등이다. 또는 GeoQuiz 앱의경우처럼 true false 문제일수도있다. 모델객체들은사용자인터페이스를모른다. 데이터를보존하고관리하는것이유일한목적이다. 42 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

79 안드로이드애플리케이션에서모델클래스들은일반적으로우리가생성하는커스텀클래스들이다. 애플리케이션의모든모델객체들은모델계층 (layer) 을구성한다. GeoQuiz 의모델계층은 TrueFalse 클래스로구성된다. 뷰객체들은자신을화면에그리는방법과터치나마우스클릭과같은사용자의입력에응답하는방법을안다. 쉽게말해서, 화면에서볼수있는것이라면그것은뷰객체다. 안드로이드는구성가능한뷰클래스들을풍부하게제공한다. 또한, 우리가커스텀뷰클래스들을생성할수도있다. 애플리케이션의뷰객체들은뷰계층을구성한다. GeoQuiz 의뷰계층은 activity_quiz.xml 요소들로부터만들어지는위젯들로구성된다. 컨트롤러객체들은뷰와모델객체들을결속시키며, 애플리케이션로직 을갖는다. 컨트롤러객체들은뷰객체에의해촉발되는다양한이벤트들에응답하고, 모델객체및뷰계층과주고받는데이터의흐름을관리하기위해설계된다. 안드로이드에서컨트롤러는일반적으로 Activity, Fragment, Service의서브클래스다. (Fragment 는 7장에서, 그리고 Service는 29장에서배울것이다.) GeoQuiz 의컨트롤러계층은현재시점에서는 QuizActivity만으로구성된다. 그림 2.5에서는사용자이벤트 ( 예를들어, 버튼을누름 ) 에응답하는객체들간의제어흐름을보여준다. 이때모델과뷰객체가서로직접통신하지않는다는것에주목하자. 컨트롤러가모든것의중간에위치하여어떤객체로부터메시지를받아서다른객체에게지시를전달한다. 사용자입력 사용자는뷰객체와상호동작한다. 뷰는컨트롤러에게메시지를보낸다. 컨트롤러는모델객체들을변경한다. 뷰 컨트롤러 모델 컨트롤러는모델객체들의변경사항을뷰에반영 ( 변경 ) 한다. 그림 2. 5 사용자입력의 MVC 처리흐름 컨트롤러는뷰객체들이관심을갖는모델객체들로부터데이터를가져온다. 모델 - 뷰 - 컨트롤러와안드로이드 43

80 MVC 의장점 애플리케이션의기능이점점많아지면너무복잡해서이해하기어려울수있다. 따라서코드를클래스들로분리하면설계에도움이되고전체를이해하기쉬워진다. 개별적인변수와메서드대신클래스관점으로생각할수있기때문이다. 이와유사하게클래스들을모델과뷰및컨트롤러계층으로분리하면애플리케이션설계와이해에도움이된다. 개별적인클래스대신계층의관점으로생각할수있기때문이다. GeoQuiz 는복잡한앱이아니다. 그러나계층을분리하면여전히장점이있다는것을알수있다. 여기서는 GeoQuiz 의뷰계층에서 Next 버튼을포함하도록수정할것이다. 그리고이렇게하면바로위에서생성했던 TrueFalse 클래스의자세한것을기억할필요가없을것이다. MVC는또한클래스를더쉽게재사용할수있게해준다. 여러가지일을혼자서처리하는클래스보다는제한된책임을갖는클래스를재사용하는것이더쉽기때문이다. 예를들어, 우리의모델클래스인 TrueFalse는 true false 질문을보여주는위젯에관해아무것도모른다. 이렇게함으로써우리앱전체에서다른목적으로 TrueFalse를사용하기쉬워진다. 예로써, 모든질문의내역을한번에보여주고싶을때에도하나만보여주기위해여기서사용하는객체와동일한것을사용할수있다. 44 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

81 뷰계층수정하기 이제는 MVC에관해어느정도이해가되었을것이다. 자, 그러면 GeoQuiz 의뷰계층에서 Next 버튼을포함하도록수정해보자. 안드로이드에서뷰계층의객체들은레이아웃파일에정의된 XML로부터생성된다. GeoQuiz 앱에서는단하나의레이아웃만이 activity_quiz.xml 파일에정의되어있다. 이레이아웃은그림 2.6에있는것처럼수정이필요하다. ( 여기서는공간을절약하기위해변경되지않는속성들은나타내지않았다.) 그림 2. 6 새로운버튼! 즉, 뷰계층에변경할필요가있는것은다음과같다. TextView로부터 android:text 속성을제거한다. 이제는질문문자열을속성정의에직접넣지않을것이기때문이다. TextView에 android:id 속성을추가한다. 이위젯은리소스 ID가필요하기때문이다. 그래야만 QuizActivity의코드에서그것의텍스트문자열을설정할수있다. 루트 LinearLayout 의자식으로새로운 Button 위젯을추가한다. activity_quiz.xml 로돌아가서이내용을실제로수정해보자. 뷰계층수정하기 45

82 리스트 2. 3 새로운버튼추가와텍스트뷰수정 CODE activity_quiz.xml <LinearLayout... > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" /> <LinearLayout... >... </LinearLayout> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> activity_quiz.xml 을저장하면이제는눈에익은에러가뜰것이다. 문자열리소스가누락 되었다는경고를주기위해서다. res/values/strings.xml 로돌아가서질문문자열을삭제하고새로운버튼의문자열을추가하자. 리스트 2. 4 문자열변경하기 CODE strings.xml... <string name="app_name">geoquiz</string> <string name="question_text"> 콘스탄티노플은터키의가장큰도시이다.</string> <string name="true_button">true</string> <string name="false_button">false</string> <string name="next_button"> 다음 </string> <string name="correct_toast"> 정답!</string> CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

83 기왕에 strings.xml 파일을연김에사용자에게보여줄지리질문들의문자열을더많이추 가해보자. 리스트 2. 5 문자열변경하기 CODE strings.xml... <string name="incorrect_toast"> 틀림!</string> <string name="menu_settings">settings</string> <string name="question_oceans"> 태평양은대서양보다더크다.</string> <string name="question_mideast"> 수에즈운하는홍해와인도양을연결한다.</string> <string name="question_africa"> 나일강은이집트에서시작된다.</string> <string name="question_americas"> 아마존강은아메리카대륙에서가장긴강이다.</string> <string name="question_asia"> 바이칼호수는세계에서가장오래되고가장깊은담수호이다.</string>... 문자열안에아포스트로피 (') 를넣을필요가있을때는 \' 와같이이스케이프시퀀스 (\) 를추가해야한다. 새라인을나타내는 \n과같은모든이스케이프시퀀스를문자열리소스에사용할수있다. 수정한 strings.xml 파일을저장한후 activity_quiz.xml 로돌아가자. 그리고변경된레이아웃을그래픽레이아웃도구에서미리보기해보자. 이로써 GeoQuiz 앱의뷰계층에할일은끝났다. 이제는우리의컨트롤러클래스인 QuizActivity 에모든것을연결할때다. 컨트롤러계층수정하기 1장에서는 GeoQuiz 앱에하나만있는컨트롤러인 QuizActivity 가하는일이그리많지않았었다. activity_quiz.xml에정의된레이아웃을화면에보여주었고, 두개의버튼에리스너를설정한후토스트를만들기위해그것들을연결하였다. 그러나이제는보여줄질문들이여러개이므로 GeoQuiz 의모델과뷰계층을결합하기위해 QuizActivity 가더힘든일을해야할것이다. 컨트롤러계층수정하기 47

84 QuizActivity.java 를열자. 그리고 TextView 와새 Button 의변수들을추가하자. 또한, TrueFalse 객체들을갖는배열과그배열의인덱스를생성하자. 리스트 2. 6 변수들과 TrueFalse 배열추가하기 CODE QuizActivity.java public class QuizActivity extends Activity { private Button mtruebutton; private Button mfalsebutton; private Button mnextbutton; private TextView mquestiontextview; private TrueFalse[] mquestionbank = new TrueFalse[] { new TrueFalse(R.string.question_oceans, true), new TrueFalse(R.string.question_mideast, false), new TrueFalse(R.string.question_africa, false), new TrueFalse(R.string.question_americas, true), new TrueFalse(R.string.question_asia, true) ; private int mcurrentindex = 0;... 여기서는 TrueFalse 의생성자를여러번호출하여 TrueFalse 객체들을갖는배열을생성한다. ( 더복잡한프로젝트에서는이배열이코드의어디서든생성되고채워질것이다. 추후에만드는앱들 에서는모델데이터를저장하는더좋은방법을보게될것이다. 지금은간단하게컨트롤러에서배열 로만처리한다.) 화면에보여줄여러질문문자열들을얻기위해 mquestionbank, mcurrentindex, TrueFalse의접근자 (accessor) 메서드를사용할것이다. 우선, TextView 객체의참조를얻은후에그객체의텍스트를현재인덱스의질문문자열로설정한다. 리스트 2. 7 TextView 를연결하기 CODE QuizActivity.java public class QuizActivity extends Activity protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_quiz); mquestiontextview = (TextView)findViewById(R.id.question_text_view); 48 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

85 int question = mquestionbank[mcurrentindex].getquestion(); mquestiontextview.settext(question); mtruebutton = (Button)findViewById(R.id.true_button);... 파일을저장하면서에러가없는지확인하자. 그다음에 GeoQuiz 를실행시키자. 배열의첫번째질문이 TextView에나타나는것을볼수있을것이다. 이제는 Next( 다음 ) 버튼에대해알아보자. 제일먼저그버튼객체의참조를얻는다. 그다음에 View.OnClickListener 를그버튼에설정한다. 이리스너는배열의인덱스값을증가시키고 TextView의텍스트를변경할것이다. 리스트 2. 8 새버튼연결하기 CODE QuizActivity.java public class QuizActivity extends Activity protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_quiz); mquestiontextview = (TextView)findViewById(R.id.question_text_view); int question = mquestionbank[mcurrentindex].getquestion(); mquestiontextview.settext(question);... mfalsebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { Toast.makeText(QuizActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show(); ); mnextbutton = (Button)findViewById(R.id.next_button); mnextbutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { mcurrentindex = (mcurrentindex + 1) % mquestionbank.length; int question = mquestionbank[mcurrentindex].getquestion(); mquestiontextview.settext(question); ); 컨트롤러계층수정하기 49

86 이제는 mquestiontextview 변수를변경하는동일한코드를두곳에갖게되었다. 그코드를리스트 2.9에서처럼하나의 private 메서드에넣어보자. 그다음에 mnextbutton 의리스너와 oncreate(bundle) 의끝에서그메서드를각각호출하여이액티비티뷰에텍스트초기설정을하게한다. 리스트 2. 9 동일한코드를 updatequestion() 에모아두기 CODE QuizActivity.java public class QuizActivity extends Activity {... private void updatequestion() { int question = mquestionbank[mcurrentindex].getquestion(); protected void oncreate(bundle savedinstancestate) {... mquestiontextview = (TextView)findViewById(R.id.question_text_view); int question = mquestionbank[mcurrentindex].getquestion(); mquestiontextview.settext(question); mnextbutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { mcurrentindex = (mcurrentindex + 1) % mquestionbank.length; int question = mquestionbank[mcurrentindex].getquestion(); mquestiontextview.settext(question); updatequestion(); ); updatequestion(); GeoQuiz 앱을실행하여새로추가한 Next 버튼을테스트해보자. 이제는질문을제대로처리하게되었으므로지금부터는답을처리하는코드를생각해보자. 이때도마찬가지로동일한코드를두군데에따로작성하기보다는그코드를캡슐화하는 private 메서드를구현할것이다. QuizActivity 에추가할 private 메서드는다음과같다. 50 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

87 private void checkanswer(boolean userpressedtrue) 이메서드는사용자가 True 나 False 중어떤버튼을눌렀는지식별하는 boolean 변수를인자로받는다. 그리고사용자가선택한답이현재의 TrueFalse 객체가갖는정답과맞는지확인한다. 끝으로, 사용자의답이맞는지여부를결정한후그에적합한메시지를사용자에게보여주는 Toast를생성한다. 리스트 2.10과같이 checkanswer(boolean) 메서드의구현코드를 QuizActivity.java 에추가하자. 리스트 checkanswer(boolean) 추가하기 CODE QuizActivity.java public class QuizActivity extends Activity {... private void updatequestion() { int question = mquestionbank[mcurrentindex].getquestion(); mquestiontextview.settext(question); private void checkanswer(boolean userpressedtrue) { boolean answeristrue = mquestionbank[mcurrentindex].istruequestion(); int messageresid = 0; if (userpressedtrue == answeristrue) { messageresid = R.string.correct_toast; else { messageresid = R.string.incorrect_toast; Toast.makeText(this, messageresid, protected void oncreate(bundle savedinstancestate) {... 컨트롤러계층수정하기 51

88 그리고리스트 2.11 에있듯이, 버튼의리스너에서 checkanswer(boolean) 을호출한다. 리스트 checkanswer(boolean) 호출하기 CODE QuizActivity.java public class QuizActivity extends Activity protected void oncreate(bundle savedinstancestate) {... mtruebutton = (Button)findViewById(R.id.true_button); mtruebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { Toast.makeText(QuizActivity.this, R.string.incorrect_toast, Toast.LENGTH_SHORT).show(); checkanswer(true); ); mfalsebutton = (Button)findViewById(R.id.false_button); mfalsebutton.setonclicklistener(new View.OnClickListener() public void onclick(view v) { Toast.makeText(QuizActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show(); checkanswer(false); ); mnextbutton = (Button)findViewById(R.id.next_button);... 자, 이제우리의 GeoQuiz 앱을다시실행할준비가되었다. 지금부터는실제장치에서실 행시켜보자. 52 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

89 장치에서실행하기 GeoQuiz 앱이실제하드웨어장치에서실행될수있도록여기서는시스템과장치및애플 리케이션을설정할것이다. 장치연결하기 우선, 장치를시스템에연결한다. Mac( 맥 ) 에서개발중이라면시스템에서그장치를곧바로인식해야한다. 윈도우즈의경우는 adb(android Debug Bridge) 드라이버를설치할필요가있을것이다. 만일윈도우즈에서 adb 드라이버를찾을수없다면장치제조사의웹사이트에서다운로드하자. 이클립스의 Devices( 장치 ) 뷰를열면우리장치가인식되는것을확인할수있다. 이때이클립스워크벤치의오른쪽위에있는 DDMS 버튼을클릭하여 DDMS 퍼스펙티브를여는것이 Devices 뷰를여는가장빠른방법이다. Devices 뷰는워크벤치의왼쪽에나타난다. 이뷰에서는 AVD 와하드웨어장치들을볼수있다. 에디터나다른뷰로돌아가려면이클립스워크벤치의오른쪽위에있는 Java 버튼을클릭하면된다. 만일장치를인식하는데문제가생긴다면우선적으로 adb를리셋해보는게좋다. Devices 뷰에서오른쪽위에있는뒤집어진작은삼각형모양의화살표를클릭하여메뉴제일밑에있는 Reset adb를선택한다. 그러면잠시후에우리장치가목록에나타날것이다. adb를리셋했는데도인식이안되면안드로이드개발자사이트를방문해서도움이되는내용을찾아보자. 주소는 또는이책의포럼인 forums.bignerd.com을방문하면문제를해결하는데더많은도움을받을수있다. 장치에서실행하기 53

90 개발용장치구성 우리가테스트하려는앱은구글플레이에서받은것이아니므로우리장치에서테스트하려면실행을허용하도록다음과같이설정할필요가있다. 안드로이드 4.1 또는그이전버전으로실행중인장치에서는해당장치의 Settings 를열고 Applications 에서 Unknown sources 가체크되어있는지확인한다. 안드로이드 4.2와 4.3 및 4.4( 킷캣 ) 버전으로실행중인장치에서는 Settings Security 에서 Unknown sources 옵션을찾는다. 또한, 우리장치에서 USB 디버깅을가능하게하려면다음과같이설정할필요가있다. 안드로이드 4.0 이전버전으로실행중인장치에서는 Settings Applications Development 에서 USB debugging 을활성화 (enable) 하는옵션을찾는다. 안드로이드 4.0 또는 4.1 버전으로실행중인장치에서는 Settings Developer 에서찾는다. 안드로이드 4.2와 4.3 및 4.4( 킷캣 ) 버전에서는 Developer options 를선택한후그다음에나오는항목중에서 USB debugging 을체크하여활성화 (enable) 할수있다. 보면알수있듯이, 장치에따라옵션들이다양하다. 만일우리가사용할장치를활성화하는데문제가생길때는 앞에서처럼 GeoQuiz 앱을실행시키자. 가상장치와하드웨어장치 ( 우리시스템에연결된 ) 중어느것에서실행할것인지를선택할수있게이클립스가해줄것이다. 그리고하드웨어장치를선택하면 GeoQuiz 가그장치에론칭될것이다. ( 만일장치선택이안나오고 GeoQuiz 가에뮬레이터에서시작된다면위의설정단계를다시확인하고우리장치가제대로연결되었는지확인하자.) 54 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

91 아이콘추가하기 이제는 GeoQuiz 앱이잘실행된다. 그러나 Next 버튼이오른쪽화살표아이콘으로나타난다면사용자인터페이스가더욱보기좋을것이다. 그런화살표아이콘은이책의솔루션파일에있다 ( 이책맨앞에있는 이책에대하여 의 솔루션파일받기 참조 ). 윈도우즈시스템의경우, 내컴퓨터의탐색기에서 02_MVC/GeoQuiz/res 디렉터리를찾아보면그안에 drawable hdpi, drawable mdpi, drawable xhdpi 디렉터리들이있을것이다. 그디렉터리들의이름끝에붙은다음세가지단어는장치의화면픽셀밀도를나타낸다. mdpi hdpi xhdpi 중밀도 (medium density) 화면 (~160dpi) 고밀도 (high density) 화면 (~240dpi) 초고밀도 (extra high density) 화면 (~320dpi) ( 저밀도 (low density) 인 ldpi 도있지만, 저밀도화면의장치들은지금은한물간지오래다.) 각디렉터리안에는두개의이미지파일이있다. arrow_right.png 와 arrow_left.png 다. 이파일들은디렉터리이름에명시된화면픽셀밀도에맞춰져있다. 개발이끝나고실제사용되는앱의경우, 프로젝트에서서로다른화면픽셀밀도의이미지들을함께제공하는것이중요하다. 서로다른이미지들을같이제공하면그이미지들이화면밀도에맞게확장이나축소되는것을경감시켜주기때문이다. 우리프로젝트의모든이미지들은앱과함께설치될것이고, 운영체제 (OS) 가해당장치에가장적합한것을선택할것이다. 아이콘추가하기 55

92 프로젝트에리소스추가하기 다음단계는이미지파일들을 GeoQuiz 의리소스에추가하는것이다. 이클립스의패키지탐색기에서 res 디렉터리의내용이보이게한후, 솔루션파일의것들과이름이같은세개의서브디렉터리를찾자. 그림 2. 7 GeoQuiz 의 drawable 디렉터리에있는화살표아이콘들 그리고솔루션파일의각디렉터리에있는이미지들을패키지탐색기의대응되는디렉터리로복사하자. 만일윈도우즈의탐색기에서파일을마우스로드래그하여이클립스의패키지탐색기로드롭하면그림 2.8과같은대화상자가나타날것이다. 이때는 Link to files가아닌 Copy files 를선택하자. 56 CHAPTER 2 안드로이드와모델 - 뷰 - 컨트롤러

93 그림 2. 8 파일복사 우리앱에이미지들을포함시키는것은매우간단하다. res/drawable 폴더에.png,.jpg,.gif 파일들을추가하면자동적으로그파일들의리소스 ID 가지정된다. ( 파일이름은반드시소 문자이어야하고스페이스가있으면안된다는것에유의하자.) 파일복사가끝나면 gen/r.java를열어서 R.drawable 내부클래스에있는새로운리소스 ID들을살펴보자. 새로운리소스 ID 두개가있을것이다. R.drawable.arrow_left 와 R.drawable.arrow_right 다. 이리소스 ID들은화면밀도에제약받지않는다. 따라서앱이실행될때장치의화면밀도를우리가신경쓸필요없다. 우리코드에서는그리소스 ID들을그냥사용하면된다. 앱이실행될때해당장치에보여주는데적합한이미지를운영체제가결정하기때문이다. 안드로이드리소스시스템이어떻게동작하는지에관해서는 3장부터더자세하게배울것이다. 일단, 지금은오른쪽화살표가동작하게만들자. XML 에서리소스참조하기 리소스 ID는코드에서리소스를참조하기위해사용한다. 그러나우리는화살표를보여주기위해레이아웃정의에서 Next 버튼을구성하고자한다. 그렇다면 XML에서는리소스를어떻게참조할까? 이때는기존과약간다른구문을사용한다. 자, 그러면 activity_quiz.xml 을열고두개의속성을 Button 위젯에추가하자. 아이콘추가하기 57