├── .gitignore ├── README.md └── latexbook ├── README.md ├── activity-lifecycle.png ├── activity.tex ├── activity2-1.tex ├── activity3.tex ├── activitymanager.tex ├── android_advanced.out ├── android_advanced.tex ├── android_note.4ct ├── android_note.4tc ├── android_note.idv ├── android_note.lg ├── android_note.out ├── android_note.tex ├── android_note.tmp ├── android_note.xref ├── android_note_tech_report.out ├── android_note_tech_report.tex ├── android_pdf.out ├── android_pdf.tex ├── application.tex ├── appstore_chap1.nav ├── appstore_chap1.out ├── appstore_chap1.snm ├── appstore_chap1.tex ├── appstore_chap1.vrb ├── basic-lifecycle-paused.png ├── binder.tex ├── binderthread.png ├── binderthread.tiff ├── book.bib ├── broadcastreceiver.tex ├── chunchun.tiff ├── contentprovider.tex ├── contents.tex ├── context.png ├── context.tex ├── context_class_dgm.tiff ├── deepsleep.tex ├── device-sqlite.png ├── diagram_backstack.png ├── diagram_multiple_instances.png ├── diagram_multitasking.png ├── favorite_message.png ├── favorite_sample.png ├── header.tex ├── input.tex ├── kandroid-framework.png ├── kandroid_framework.tiff ├── lecture_201411.nav ├── lecture_201411.out ├── lecture_201411.snm ├── lecture_201411.tex ├── lecture_201411.vrb ├── myfault.tex ├── pattern.tex ├── process.tex ├── ps.png ├── ps.tiff ├── separate_process.tex ├── service-binding-tree-lifecycle.png ├── service-lifecycle.png ├── service.tex ├── service2.tex ├── sh.exe.stackdump ├── singlethread.tex ├── singletonproblem.png ├── singletonproblem.tiff ├── sourcetree.png ├── start.tex ├── system-architecture.jpg ├── system-architecture.png ├── thread.tex ├── toast.tex └── tools.tex /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | *.log 3 | *.gz 4 | *.aux 5 | *.toc 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 안드로이드 프로그래밍 Next Step : 제대로 된 앱을 만드는 컴포넌트 활용 노하우 2 | 3 | ## 예제 소스 4 | https://github.com/suribada/androidBookSample 5 | ## 기초 원고 6 | latexbook 아래 위치. 편집 검토하면서 내용이 많이 변경 7 | 8 | # 정오표 9 | - 3p. 10라인 씬 클라인언트->씬 클라이언트 10 | - 49p. 2라인 스레드가 스레드를 -> 스레드 풀에서 스레드를 11 | - 54p. 15, 16라인 DelayWorkQueue -> DelayedWorkQueue(제보해주신 loadofnightmare.9876님 감사합니다.) 12 | - 56p. 4라인 setCorePoolSiz() -> setCorePoolSize()(제보해주신 황두영님 감사합니다.) 13 | - 73p. 1라인 ContetImpl -> ContextImpl(제보해주신 김동진님 감사합니다.) 14 | - 73p. 3라인 Context Wrapper -> ContextWrapper 15 | - 86p. 코드 5-2 3라인 ActvityA -> ActivityA 16 | - 86p. 코드 5-2 7라인 제거 필요(제보해주신 loadofnightmare.9876님 감사합니다.) 17 | - 126p. SplashPage와 SplashActivity가 혼용되어 있음. SplashActivity로 맞춤 18 | - 222p. 15라인 Appplication의 데이 공유에->Application의 데이터 공유에(제보해주신 황두영님 감사합니다.) 19 | - 233p. 11라인 불필요하도록 오버라이드한 -> 불필요하도록 오버로드한 20 | - 228p 코드 9-3 (1) 비교 로직 if (each.pid == android.os.Process.myPid() && each.processName.equals(getPackageName()) { 21 | 22 | # 의미 정정 23 | 교정 및 편집으로 문장을 맞추다가 뉘앙스가 변경된 경우 24 | - 94p 아래 6라인: 떠 있는 Activity에 특별한 작업을 하기 위해서 Activity 인스턴스를 컬렉션(Colletion)에 모아둔다. 최악의 상황으로 ~ 25 | -> 떠 있는 Activity에 특별한 작업을 하기 위해서 Activity 인스턴스를 컬렉션(Colletion)에 모아두기도 한다. 이는 그다지 좋지 않은 상황이다.(제보해주신 정기용님 감사합니다.) 26 | 27 | ## 판매 위치 28 | - Yes24: http://www.yes24.com/24/goods/41085242 29 | - 알라딘: http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=110015332 30 | - 인터파크: http://book.interpark.com/product/BookDisplay.do?_method=detail&sc.shopNo=0000400000&sc.prdNo=267623437&sc.saNo=003002001&bid1=search&bid2=product&bid3=title&bid4=001 31 | - 교보문고: http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788966263073&orderClick=LET&Kc= 32 | 33 | # 기타 34 | - 문의는 Issues에 올려주세요. 35 | 36 | -------------------------------------------------------------------------------- /latexbook/README.md: -------------------------------------------------------------------------------- 1 | 2 | 원고 LaTex 파일입니다. 3 | 4 | ## 책 프로젝트의 시작 5 | 1. 기초 서적 이후에 볼만한 안드로이드 책이 많지 않습니다.. 6 | 2. 책은 아무리 신경 써서 만들어도 오류나 오타가 있기 마련인데, 종이책으로는 그 내용을 바로 잡기 어렵습니다. 7 | 3. 업데이트된 내용을 바로 전달할 수 있는 방법으로 Github을 활용하기로 합니다. 8 | 4. 문서는 LaTeX을 활용해서 Word Processor를 특별히 사용하지 않습니다. 9 | 10 | ## 사용 방법 11 | 1. http://www.ktug.org/xe/ 내용을 참고해서 LaTeX 소스를 빌드하는 환경을 만듭니다. 12 | 2. TexMaker 등 LaTeX 에디터에서 android_pdf.tex를 열어서 빌드를 실행하면 (XeTex + PDF 사용) android_pdf.pdf 문서가 생성됩니다. 13 | 14 | ## 문의 방법 15 | 1. 내용 문의는 이 페이지 상단의 Issues에 등록해 주세요. 16 | 2. 기타 문의는 suribada@gmail.com으로 연락 주세요. 17 | 18 | ## 주의사항 19 | 1. pdf 문서의 개인적인 사용은 허용됩니다. 20 | 2. %나 \begin{comment} ~ \end{comment} 사이에 있는 내용은 Comment입니다. 검증되지 않은 것 또는 맞지 않는 내용일 수 있습니다. 21 | 3. http://www.yes24.com/24/goods/41085242 종이책이 출간되었습니다. 종이책과 업데이트를 맞춰가고 싶었지만 LaTeX을 사용하는 곳이 없어 워드 파일로 다시 작업해야 해서 현재 종이책에만 최신 편집 내용이 반영되어 있습니다. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /latexbook/activity-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/activity-lifecycle.png -------------------------------------------------------------------------------- /latexbook/activity2-1.tex: -------------------------------------------------------------------------------- 1 | \begin{comment} 2 | \subsection{윈도우 옵션} 3 | // No title 4 | \begin{verbatim} 5 | requestWindowFeature(Window.FEATURE_NO_TITLE); 6 | \end{verbatim} 7 | 8 | \begin{itemize} 9 | \item \verb|FLAG_FULL_SCREEN|: 풀화면을 사용한다. 10 | \begin{verbatim} 11 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 12 | \end{verbatim} 13 | \item \verb|FLAG_SHOW_WHEN_LOCKED|: 스크린이 잠겨있을 때, 윈도우를 보이게 한다. key guard나 다른 lock screen보다 우선순위를 갖게 한다. 14 | 스크린을 켜놓기 위해 \verb|FLAG_TURN_SCREEN_ON| 과 함께 쓰일 수 있다. 15 | \item \verb|FLAG_TURN_SCREEN_ON|: 윈도우가 보여진 다음에는 스크린이 켜진 채로 있게 한다. 16 | \item \verb|FLAG_DISMISS_KEYGUARD|: key gaurd를 없앤다. \verb|| 이 퍼미션 필요하다. 17 | \item \verb|FLAG_TURN_SCREEN_ON|: 스크린을 켠다. 18 | 19 | \end{itemize} 20 | 21 | 알람 화면 같은 아래처럼 옵션을 사용할 수 있다. 22 | \begin{verbatim} 23 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 24 | | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 25 | | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); 26 | \end{verbatim} 27 | key guard가 보여지고 있는지 체크한다. 28 | \begin{verbatim} 29 | KeyguardManager keyguardManager = (KeyguardManager)getSystemService(KEYGUARD_SERVICE); 30 | boolean result = keyguardManager.inKeyguardRestrictedInputMode() 31 | \end{verbatim} 32 | 33 | 34 | % http://www.androidpub.com/1123549 검토할 것 35 | 36 | 37 | allowTaskReparenting=["true" | "false"] 디폴트는 false 38 | 39 | 40 | 41 | 42 | \end{comment} 43 | 44 | -------------------------------------------------------------------------------- /latexbook/activity3.tex: -------------------------------------------------------------------------------- 1 | \subsection{이렇게 해보자.} 2 | 3 | \subsubsection{static 메서드로 startActivity를 실행한다.} 4 | \begin{lstlisting}[frame=single] 5 | public class TodoDetailActivity extends FragmentActivity { 6 | 7 | private static final String TODO_ID = "todoId"; 8 | private static final String TODO_CONTENT = "content"; 9 | 10 | private static long todoId = 0L; 11 | 12 | public static void startActivity(Context context, long todoId, String content) { 13 | Intent intent = new Intent(context, TodoDetailActivity.class); 14 | intent.putExtra(TODO_ID, todoId); 15 | intent.putExtra(TODO_CONTENT, content); 16 | context.startActivity(intent); 17 | } 18 | \end{lstlisting} 19 | 이것은 개발자 가이드에서 Fragment에 Argument를 전달할때 쓰는 방식인데, Activity에서도 활용할만 하다.\\ 20 | Intent에 key-value를 전달할 일이 많고, key는 문자열인데, 이 문자열을 Caller와 Callee 양쪽에서 직접 문자열을 넣는 방식은 오타 가능성이 있기 때문에 잘 쓰지 않는다.\\ 21 | key를 상수로 갖고 있는 게 좋은데 Caller 쪽에 있는 게 좋을지, Callee 쪽에 있는 게 좋을 지 또 망설여진다. 자주 쓰이는 key라면 별도의 클래스에 상수로 두는 방법도 있겠지만..\\ 22 | 23 | Callee 쪽에 있다고 한다면, 아래 코드 처럼 된다. 24 | \begin{lstlisting}[frame=single] 25 | Intent intent = new Intent(this, TodoDetailActivity.class); 26 | intent.putExtra(TodoDetailActivity.TODO_ID, todoId); 27 | intent.putExtra(TodoDetailActivity.TODO_CONTENT, content); 28 | startActivity(intent); 29 | \end{lstlisting} 30 | 뭔가 깔끔한 맛이 없다. 이럴 때 Callee 쪽에 static 메서드로 startActivity를 만들어 놓는다면, Caller 쪽에서는 key 값을 신경 쓸 필요가 없고, Callee 쪽에서만 상수를 만들고 이 상수에서 꺼내서 사용하면 된다.\\ 31 | 전달하는 값이 정해져 있는 경우에 쓸 만한 방법으로 대부분이 이 경우에 해당한다. 32 | 그렇지 않을 때는 static 메서드를 너무 많이 만들 수도 없으므로 그럴 때는 Calee 쪽에 상수를 두고서 Calee 쪽에서 참조하는 방법을 그냥 쓰도록 하자.\\ 33 | 34 | \subsubsection{여러 위젯이 있을 때 동일한 위젯끼리 모아놓는다.} 35 | \begin{lstlisting}[frame=single] 36 | Button okButton, detailButton; 37 | Spinner groupSpinner, statusSpinner; 38 | \end{lstlisting} 39 | 40 | \begin{lstlisting}[frame=single] 41 | Button okButton; 42 | Spinner groupSpinner; 43 | Button detailButton; 44 | Spinner statusSpinner; 45 | \end{lstlisting} 46 | 47 | Activity에 동작이 많으면 코드가 길어지기 마련이다. 48 | 예를 들어 이벤트 처리시에 ``어떤 스피너를 바꿔야 하지?''할 때가 있는데, 아래 방식보다는 위 방식에서 찾기가 수월하다.\\ 49 | AdapterView에서 ViewHolder 패턴을 쓸때도 마찬가지로 동일한 위젯끼리 모아놓는 것이 좋다. 50 | (물론 findViewById() 해서 멤버 변수에 대입하는 것은 레이아웃 순서에 맞게 한다.)\\ 51 | 동일한 위젯이 많은 때는 Grouping을 다시 할 수도 있다. 52 | \begin{lstlisting}[frame=single] 53 | Button mondayButton, tuesdayButton, wednesdayButton, thursdayButton, fridayButton, saturdayButton, sudayButton; 54 | Button okButton, cancelButton; 55 | \end{lstlisting} 56 | 57 | \subsubsection{상수나 멤버 변수는 쓰이는 위치 근처에 선언한다} 58 | 무조건 맨 위에다가 멤버 변수를 모두 선언할 필요는 없다. 59 | 메서드 한두개에서만 쓰는 멤버 변수는 바로 그 위에다가 선언해서 변수가 어디에 있고 어디에 쓰이는지 확인하는데 시간을 들이지 않도록 하자. 60 | 61 | \subsection{이런거 하지 말자.} 62 | 63 | \subsubsection{불필요한 Casting은 하지 말자.} 64 | View나 ViewGroup에 Actvity상에서 기껏해야 Visibility만 조건에 따라 변경되는 경우가 있다. 이럴 때 굳이 findViewById()해서 LinearLayout이니 ImageView니 Casting하는 수고는 하지 말자.\\ 65 | % 샘플? 66 | 그리고 특히 Layout의 경우에는 변경될 가능성이 아주 많다. 버튼 두개면 LinearLayout 이면 충분한데, 그 버튼 한쪽에 New 마크가 붙는 사소한 이유로 RelativeLayout으로 바뀌어야 할 수가 있다. Layout param 변경이 필요한 경우에만 Casting을 한다. 67 | 68 | \subsubsection{상속 구조를 깊게 하지 말자.} 69 | 복잡한 앱에서는 액티비티의 상속구조가 4, 5단계까지 있는 것을 본 적도 있다. 이런 코드를 보면, 분석이 상당히 어렵고, 문제가 발생할 때 찾아내는 것에 어려움이 많다. 70 | 단순한 유틸리티 메서드를 위해서 상속을 하지는 말자. 로직의 흐름상 반드시 수행해야 할 코드를 중심으로 상위 클래스를 만든다.\\ 71 | 72 | 물론 쉽지는 않다. 클래스 구성을 잘 단순화 해야 하고, 여러 수단을 동원해야만 한다. 내 경우에는 AOP나 Marker Interface 도입 등을 통해서 클래스 구성을 단순화 한 적이 있다. 이 부분에 대해서는 다른 절에서 상세하게 다루기로 하자.\\ 73 | 74 | \subsubsection{Activity에서 너무 많은 Listener 인터페이스를 구현하지 말자} 75 | OnTouchListener, OnClickListener, OnItemSelectedListener 등 안드로이드에서 제공하는 Listener 인터페이스가 많고, 개발중에도 필요에 의해서 Listener 인터페이스를 만들게 된다.\\ 76 | 77 | 이런 인터페이스들을 Activity 선언에 모두 implements로 해놓고 setOnTouchListener(this), setOnClickListner(this), setOnItemSelectdLister(this) 이런 식으로 하고, 커스텀으로 만든 것까지 하면 여러 개가 될 수가 있다. 이렇게 하면 해당 이벤트의 Listener를 수정할 때, 메서드를 찾아가는 게 간단치 않다. OnClickListener 같은 경우는, onClick 메서드를 찾으면 되지만, OnItemSelectedListener 같은 건 어떨까? Listener에 메서드가 2개이다. 커스텀으로 만든 경우에는 여러 메서드를 가질 수도 있다.\\ 78 | 내가 원하는 메서드를 금방 찾을 수 있을까?\\ 79 | 80 | 이럴 때는 멤버 변수로 Listner 구현을 두는 것으로 전환하는 것도 생각해보자. 81 | 82 | \begin{lstlisting}[frame=single] 83 | private OnItemSelectedListener onItemSelectedListener = new OnItemSelectedListener() { 84 | 85 | @Override 86 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 87 | .... 88 | } 89 | 90 | @Overrides 91 | public void onNothingSelected(AdapterView parent) { 92 | ... 93 | } 94 | 95 | } 96 | \end{lstlisting} 97 | 이렇게 하면 setOnItemSelectdLister(this)가 아닌 setOnItemSelectdLister(onItemSelectedListener)를 해놓으면 찾아가는 게 그 전보다 쉬워진다. OnItemSelectedListener의 onNothingSelected()처럼 메서드가 어디에 속한 것인지 익숙하지 않은 것들은 Activity의 메서드로 나와있는 것보다는 위처럼 하는 것을 권장한다.\\ 98 | 99 | 또한 Button 같은 것이 위에서 얘기한 것처럼 Grouping이 되어 있다고 할 때 각각 OnClickListener를 만들어놓는 것이 낫다. 100 | 예를 들어 알람설정 화면에 월/화/수/목/금/토/일 같은 요일을 선택하는 Button이 있고, 알람/진동 같은 옵션도 Button으로 있다고 할 때, 같은 OnClickListener를 쓰면 코드를 깔끔하게 만들기 어렵다. 버튼 선택시 다중 선택이 되는 경우도 있고, 하나를 선택하면 다른 것의 선택이 해제되는 동작들 같은 것도 볼 수 있는데, if 문 안에서 번거롭게 처리하는 것보다 그룹별로 따로 Listener를 분리해놓고 생각하자.\\ 101 | 102 | 아래 코드는 5분, 10분, 30분 3개의 버튼 가운데서 하나만 선택되어야 하는 것인데, 다른 Listener하고 분리해서 사용한 예이다. 103 | \begin{lstlisting}[frame=single] 104 | @Override 105 | protected void onCreate(Bundle savedInstanceState) { 106 | ... 107 | mSnoozeFiveMin.setOnClickListener(snoozeListener); 108 | mSnoozeTenMin.setOnClickListener(snoozeListener); 109 | mSnoozeThirtyMin.setOnClickListener(snoozeListener); 110 | } 111 | 112 | private OnClickListener snoozeListener = new OnClickListener() { 113 | 114 | @Override 115 | public void onClick(View view) { 116 | mSnoozeFiveMin.setSelected(view == mSnoozeFiveMin); 117 | mSnoozeTenMin.setSelected(view == mSnoozeTenMin); 118 | mSnoozeThirtyMin.setSelected(view == mSnoozeThirtyMin); 119 | } 120 | 121 | }; 122 | \end{lstlisting} 123 | 124 | \begin{comment} 125 | \subsubsection{스레드에서 startActivity를 하지 말자.} 126 | 스레드에서 UI 코드가 동작 하지 않는다고 하지만, 그것은 어디까지나 ViewRootImpl의 checkThread를 거치는 경우 뿐이다.(즉 draw/redraw 하는 경우) 스레드에서 startActivity해도 동작은 한다. 127 | \end{comment} -------------------------------------------------------------------------------- /latexbook/activitymanager.tex: -------------------------------------------------------------------------------- 1 | \section{ActivityManager} 2 | ActivitiyManager를 adb shell에서 사용할 수 있다.\footnote{http://developer.android.com/tools/help/adb.html를 참고하자} 3 | Activity 뿐만 아니라 Broadcast도 보낼 수 있고, 서비스를 실행시킬 수도 있다. 4 | 아래 명령어를 쓰면 된다. 인텐트 필터가 없는 Activity의 경우에는 android:exported=''true''가 돼 있어야만 한다. 5 | 6 | 7 | adb shell am start -n com.example.android.lifecycle/.ActivityC 8 | 9 | 10 | ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents) 11 | * PendingIntentRecord{afb311e8 android startActivity} 12 | * PendingIntentRecord{aface5c0 com.android.phone broadcastIntent} 13 | * PendingIntentRecord{afd31288 android broadcastIntent} 14 | 15 | ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts) 16 | Historical broadcasts [foreground]: 17 | #0: BroadcastRecord{afc89660 android.intent.action.SCREEN_ON} 18 | #1: BroadcastRecord{afcce2b8 android.intent.action.SCREEN_OFF} 19 | 20 | Historical broadcasts [background]: 21 | #0: BroadcastRecord{afb220c8 com.android.internal.telephony.gprs-data-stall} 22 | #1: BroadcastRecord{afd39b28 android.intent.action.TIME_TICK} 23 | #2: BroadcastRecord{afda7118 com.android.internal.telephony.gprs-data-stall} 24 | #3: BroadcastRecord{afbbdbd0 android.intent.action.TIME_TICK} 25 | 26 | Sticky broadcasts: 27 | * Sticky action android.media.SCO_AUDIO_STATE_CHANGED 28 | * Sticky action android.media.ACTION_SCO_AUDIO_STATE_UPDATED 29 | * Sticky action android.net.thrott.THROTTLE_ACTION 30 | 31 | ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers) 32 | Published content providers (by class): 33 | * ContentProviderRecord{afb4b708 com.android.phone/.IccProvider} 34 | proc=ProcessRecord{afb3f580 1130:com.android.phone/1001} 35 | authority=icc 36 | * ContentProviderRecord{af8899f8 com.android.providers.settings/.SettingsProvider} 37 | proc=ProcessRecord{af88b210 1021:system/1000} 38 | authority=settings 39 | 12 connections, 0 external handles 40 | -> 1083:com.android.systemui/u0a31 s1/1 u0/0 +15h54m44s976ms 41 | -> 1099:com.example.android.softkeyboard/u0a25 s1/1 u0/0 +15h54m44s594ms 42 | -> 1130:com.android.phone/1001 s1/1 u0/0 +15h54m44s262ms 43 | -> 1175:android.process.acore/u0a3 s1/1 u0/0 +15h54m43s428ms 44 | -> 1532:com.nhn.android.ncs.calendar/u0a44 s1/1 u0/0 +15h54m34s497ms 45 | -> 1620:com.android.defcontainer/u0a4 s1/1 u0/0 +15h48m29s286ms 46 | -> 1142:com.android.launcher/u0a15 s1/1 u0/0 +15h47m58s704ms 47 | -> 3913:com.nhn.android.works.calendar/u0a45 s1/1 u0/0 +10h18m17s146ms 48 | -> 3972:com.android.deskclock/u0a19 s1/1 u0/0 +8h36m8s420ms 49 | -> 5879:com.android.settings/1000 s1/1 u0/0 +8h7m33s39ms 50 | -> 11307:com.nhn.android.calendar/u0a43 s1/1 u0/0 +11m56s939ms 51 | -> 11537:com.nhn.android.calendar:widget/u0a43 s1/1 u0/0 +11m41s359ms 52 | * ContentProviderRecord{afb4b598 com.android.providers.telephony/.SmsProvider} 53 | proc=ProcessRecord{afb3f580 1130:com.android.phone/1001} 54 | authority=sms 55 | 56 | ACTIVITY MANAGER SERVICES (dumpsys activity services) 57 | Active services: 58 | * ServiceRecord{afaf6008 com.android.phone/.TelephonyDebugService} 59 | app=ProcessRecord{afb3f580 1130:com.android.phone/1001} 60 | created=-15h54m45s289ms started=true connections=0 61 | * ServiceRecord{afb05bb0 com.android.systemui/.ImageWallpaper} 62 | app=ProcessRecord{af9a8630 1083:com.android.systemui/u0a31} 63 | created=-15h54m45s898ms started=false connections=1 64 | Connections: 65 | act=android.service.wallpaper.WallpaperService -> 1021:system/1000 66 | 67 | ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes) 68 | Process LRU list (sorted by oom_adj): 69 | PERS # 2: adj=sys /F trm= 0 1021:system/1000 (fixed) 70 | PERS #11: adj=pers /F trm= 0 1130:com.android.phone/1001 (fixed) 71 | PERS # 0: adj=pers /F trm= 0 1083:com.android.systemui/u0a31 (fixed) 72 | Proc # 3: adj=fore /FA trm= 0 11307:com.nhn.android.calendar/u0a43 (top-activity) 73 | Proc # 1: adj=prcp /F trm= 0 1099:com.example.android.softkeyboard/u0a25 (service) 74 | com.example.android.softkeyboard/.SoftKeyboard<=Proc{1021:system/1000} 75 | Proc #10: adj=home /B trm= 0 1142:com.android.launcher/u0a15 (home) \textsl{•} -------------------------------------------------------------------------------- /latexbook/android_advanced.out: -------------------------------------------------------------------------------- 1 | \BOOKMARK [1][-]{section.1}{... ...}{}% 1 2 | \BOOKMARK [2][-]{subsection.1.1}{.... ... ...}{section.1}% 2 3 | \BOOKMARK [2][-]{subsection.1.2}{.. ... ...}{section.1}% 3 4 | \BOOKMARK [2][-]{subsection.1.3}{dumpsys ...}{section.1}% 4 5 | \BOOKMARK [2][-]{subsection.1.4}{... ... ..}{section.1}% 5 6 | \BOOKMARK [3][-]{subsubsection.1.4.1}{... ... .. .. ..}{subsection.1.4}% 6 7 | \BOOKMARK [3][-]{subsubsection.1.4.2}{.. ... ..}{subsection.1.4}% 7 8 | \BOOKMARK [2][-]{subsection.1.5}{Deep Sleep}{section.1}% 8 9 | \BOOKMARK [1][-]{section.2}{Looper/Handler}{}% 9 10 | \BOOKMARK [1][-]{section.3}{.. .... Handler}{}% 10 11 | \BOOKMARK [2][-]{subsection.3.1}{.. ...}{section.3}% 11 12 | \BOOKMARK [2][-]{subsection.3.2}{Looper}{section.3}% 12 13 | \BOOKMARK [2][-]{subsection.3.3}{Message. MessageQueue}{section.3}% 13 14 | \BOOKMARK [2][-]{subsection.3.4}{Handler}{section.3}% 14 15 | \BOOKMARK [3][-]{subsubsection.3.4.1}{Handler ..}{subsection.3.4}% 15 16 | \BOOKMARK [3][-]{subsubsection.3.4.2}{Handler ..}{subsection.3.4}% 16 17 | \BOOKMARK [3][-]{subsubsection.3.4.3}{Handler .. ..}{subsection.3.4}% 17 18 | \BOOKMARK [3][-]{subsubsection.3.4.4}{... ..}{subsection.3.4}% 18 19 | \BOOKMARK [2][-]{subsection.3.5}{ANR}{section.3}% 19 20 | \BOOKMARK [1][-]{section.4}{..... ...}{}% 20 21 | \BOOKMARK [2][-]{subsection.4.1}{HandlerThread}{section.4}% 21 22 | \BOOKMARK [2][-]{subsection.4.2}{....}{section.4}% 22 23 | \BOOKMARK [3][-]{subsubsection.4.2.1}{ThreadPoolExecutor}{subsection.4.2}% 23 24 | \BOOKMARK [3][-]{subsubsection.4.2.2}{ScheduledThreadPoolExecutor}{subsection.4.2}% 24 25 | \BOOKMARK [3][-]{subsubsection.4.2.3}{Executors}{subsection.4.2}% 25 26 | \BOOKMARK [1][-]{section.5}{Activity}{}% 26 27 | \BOOKMARK [2][-]{subsection.5.1}{....}{section.5}% 27 28 | \BOOKMARK [3][-]{subsubsection.5.1.1}{startActivity. startActivityResult}{subsection.5.1}% 28 29 | \BOOKMARK [3][-]{subsubsection.5.1.2}{Activity. .... ... ....}{subsection.5.1}% 29 30 | \BOOKMARK [3][-]{subsubsection.5.1.3}{.... ... ... ...}{subsection.5.1}% 30 31 | \BOOKMARK [2][-]{subsection.5.2}{Tasks}{section.5}% 31 32 | \BOOKMARK [3][-]{subsubsection.5.2.1}{Task. ..}{subsection.5.2}% 32 33 | \BOOKMARK [3][-]{subsubsection.5.2.2}{Task ....}{subsection.5.2}% 33 34 | \BOOKMARK [3][-]{subsubsection.5.2.3}{taskAffinity}{subsection.5.2}% 34 35 | \BOOKMARK [3][-]{subsubsection.5.2.4}{Activity. Task .. ..}{subsection.5.2}% 35 36 | \BOOKMARK [2][-]{subsection.5.3}{... ....}{section.5}% 36 37 | \BOOKMARK [3][-]{subsubsection.5.3.1}{static .... startActivity. .....}{subsection.5.3}% 37 38 | \BOOKMARK [3][-]{subsubsection.5.3.2}{.. ... .. . ... .... ......}{subsection.5.3}% 38 39 | \BOOKMARK [3][-]{subsubsection.5.3.3}{... .. ... ... .. ... ....}{subsection.5.3}% 39 40 | \BOOKMARK [2][-]{subsection.5.4}{... .. ...}{section.5}% 40 41 | \BOOKMARK [3][-]{subsubsection.5.4.1}{.... Casting. .. ...}{subsection.5.4}% 41 42 | \BOOKMARK [3][-]{subsubsection.5.4.2}{.. ... .. .. ...}{subsection.5.4}% 42 43 | \BOOKMARK [3][-]{subsubsection.5.4.3}{Activity.. .. .. Listener ...... .... ..}{subsection.5.4}% 43 44 | \BOOKMARK [1][-]{section.6}{Service}{}% 44 45 | \BOOKMARK [2][-]{subsection.6.1}{Started Service}{section.6}% 45 46 | \BOOKMARK [3][-]{subsubsection.6.1.1}{Service ... ..}{subsection.6.1}% 46 47 | \BOOKMARK [3][-]{subsubsection.6.1.2}{.. ... ..}{subsection.6.1}% 47 48 | \BOOKMARK [3][-]{subsubsection.6.1.3}{... .... ... ..}{subsection.6.1}% 48 49 | \BOOKMARK [3][-]{subsubsection.6.1.4}{IntentService}{subsection.6.1}% 49 50 | \BOOKMARK [3][-]{subsubsection.6.1.5}{Service .. .. ..}{subsection.6.1}% 50 51 | \BOOKMARK [2][-]{subsection.6.2}{Bound Service}{section.6}% 51 52 | \BOOKMARK [3][-]{subsubsection.6.2.1}{Remote Binding}{subsection.6.2}% 52 53 | \BOOKMARK [3][-]{subsubsection.6.2.2}{Local Binding}{subsection.6.2}% 53 54 | \BOOKMARK [3][-]{subsubsection.6.2.3}{bindService. .. ..}{subsection.6.2}% 54 55 | \BOOKMARK [3][-]{subsubsection.6.2.4}{Binding. ..}{subsection.6.2}% 55 56 | \BOOKMARK [1][-]{section.7}{ContentProvider}{}% 56 57 | \BOOKMARK [2][-]{subsection.7.1}{SQLite}{section.7}% 57 58 | \BOOKMARK [3][-]{subsubsection.7.1.1}{DB Lock ..}{subsection.7.1}% 58 59 | \BOOKMARK [2][-]{subsection.7.2}{SQLiteOpenHelper}{section.7}% 59 60 | \BOOKMARK [2][-]{subsection.7.3}{ContentProvider}{section.7}% 60 61 | \BOOKMARK [3][-]{subsubsection.7.3.1}{ContentResolver}{subsection.7.3}% 61 62 | \BOOKMARK [3][-]{subsubsection.7.3.2}{ContentProvider ..}{subsection.7.3}% 62 63 | \BOOKMARK [3][-]{subsubsection.7.3.3}{.. execute}{subsection.7.3}% 63 64 | \BOOKMARK [2][-]{subsection.7.4}{SQLite/ContentProvider .. .}{section.7}% 64 65 | \BOOKMARK [3][-]{subsubsection.7.4.1}{.. .. ..}{subsection.7.4}% 65 66 | \BOOKMARK [3][-]{subsubsection.7.4.2}{ContentProvider .. ..}{subsection.7.4}% 66 67 | -------------------------------------------------------------------------------- /latexbook/android_advanced.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,hidelinks,10pt]{article} 2 | \usepackage{kotex} 3 | \usepackage{graphicx} 4 | \usepackage{upquote} 5 | \usepackage{listings} 6 | \usepackage{color} 7 | \usepackage{verbatim} 8 | \usepackage{hyperref} 9 | \usepackage[margin=1in]{geometry} 10 | \usepackage[T1]{fontenc} 11 | \usepackage{textcomp} 12 | \usepackage{hvfloat} 13 | 14 | %\usepackage[scale=0.75,twoside,bindingoffset=5mm,a4paper]{geometry} 15 | 16 | 17 | \definecolor{codegreen}{rgb}{0,0.6,0} 18 | \definecolor{codegray}{rgb}{0.3,0.3,0.3} 19 | \definecolor{backcolour}{rgb}{0.95,0.95,0.92} 20 | \definecolor{purple}{rgb}{0.5, 0.0, 0.5} 21 | \definecolor{burntorange}{rgb}{0.8, 0.33, 0.0} 22 | \definecolor{tearose}{rgb}{1.0, 0.92, 0.8} 23 | 24 | \renewcommand\lstlistingname{코드} 25 | 26 | \title{안드로이드 Advanced} 27 | \author{네이버 지도지역Cell, 노재춘} 28 | 29 | \begin{document} 30 | 31 | % http://latexcolor.com 32 | \maketitle 33 | 34 | \parindent=0em 35 | 36 | \lstset { 37 | language=java, 38 | backgroundcolor=\color{backcolour}, 39 | commentstyle=\color{codegreen}, 40 | keywordstyle=\color{purple}, 41 | tabsize=4, 42 | showspaces=false, 43 | showstringspaces=false, 44 | numberstyle=\color{codegray}, 45 | numbers=left, 46 | numbersep=5pt, 47 | stepnumber=5, 48 | breaklines=true 49 | } 50 | 51 | \begin{comment} 52 | \begin{abstract} 53 | 잘 만든 앱은 다 그럭저럭이지만, 그렇지 않은 앱은 제각각의 이유가 있다. 여기서는 그럭저럭한 앱을 만드는 원리와 방법을 찾아보고, 문제를 발생시키는 제각각의 이유에 대해서도 얘기해보자. 54 | \end{abstract} 55 | \end{comment} 56 | 57 | \input{binder} 58 | \input{deepsleep} 59 | 60 | \section{Looper/Handler} 61 | \input{singlethread} 62 | 63 | \section{Activity} 64 | 65 | \input{activity} 66 | \input{activity2} 67 | \input{activity3} 68 | 69 | \input{service} 70 | \input{service2} 71 | 72 | \input{contentprovider} 73 | 74 | \end{document} 75 | -------------------------------------------------------------------------------- /latexbook/android_note.4ct: -------------------------------------------------------------------------------- 1 | \expandafter\ifx\csname doTocEntry\endcsname\relax \expandafter\endinput\fi 2 | \doTocEntry\toclikechapter{}{\csname a:TocLink\endcsname{1}{x1-1000}{QQ2-1-1}{차례}}{3}\relax 3 | \doTocEntry\toclikechapter{}{\csname a:TocLink\endcsname{1}{x1-2000}{QQ2-1-2}{이 책의 구성}}{7}\relax 4 | \doTocEntry\tocchapter{}{\csname a:TocLink\endcsname{1}{Q1-1-3}{}{이 책의 구성}}{7}\relax 5 | \doTocEntry\tocchapter{1}{\csname a:TocLink\endcsname{1}{x1-30001}{QQ2-1-4}{안드로이드 프레임워크}}{8}\relax 6 | \doTocEntry\tocsection{1.1}{\csname a:TocLink\endcsname{1}{x1-40001.1}{QQ2-1-5}{프레임워크 개요}}{8}\relax 7 | \doTocEntry\tocsection{1.2}{\csname a:TocLink\endcsname{1}{x1-50001.2}{QQ2-1-6}{프레임워크 소스 구조}}{9}\relax 8 | \doTocEntry\tocsection{1.3}{\csname a:TocLink\endcsname{1}{x1-60001.3}{QQ2-1-7}{안드로이드 버전}}{11}\relax 9 | \doTocEntry\tocsubsection{1.3.1}{\csname a:TocLink\endcsname{1}{x1-70001.3.1}{QQ2-1-8}{호환성 모드}}{11}\relax 10 | \doTocEntry\tocsubsection{1.3.2}{\csname a:TocLink\endcsname{1}{x1-80001.3.2}{QQ2-1-9}{버전 체크}}{13}\relax 11 | \doTocEntry\tocchapter{2}{\csname a:TocLink\endcsname{1}{x1-90002}{QQ2-1-10}{메인 스레드와 Handler}}{15}\relax 12 | \doTocEntry\tocsection{2.1}{\csname a:TocLink\endcsname{1}{x1-100002.1}{QQ2-1-11}{메인 스레드}}{15}\relax 13 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-10011}{}{\numberline {2.1}ActivityThread.java}}{15}\relax 14 | \doTocEntry\tocsection{2.2}{\csname a:TocLink\endcsname{1}{x1-110002.2}{QQ2-1-13}{Looper}}{16}\relax 15 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-11003}{}{\numberline {2.2}Looper.java}}{16}\relax 16 | \doTocEntry\tocsection{2.3}{\csname a:TocLink\endcsname{1}{x1-120002.3}{QQ2-1-15}{Message와 MessageQueue}}{17}\relax 17 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-12002}{}{\numberline {2.3}Message.java}}{17}\relax 18 | \doTocEntry\tocsection{2.4}{\csname a:TocLink\endcsname{1}{x1-130002.4}{QQ2-1-17}{Handler}}{17}\relax 19 | \doTocEntry\tocsubsection{2.4.1}{\csname a:TocLink\endcsname{1}{x1-140002.4.1}{QQ2-1-18}{Handler 생성}}{18}\relax 20 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-14002}{}{\numberline {2.4}LooperThead}}{18}\relax 21 | \doTocEntry\tocsubsection{2.4.2}{\csname a:TocLink\endcsname{1}{x1-150002.4.2}{QQ2-1-20}{Handler 동작}}{19}\relax 22 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-15002}{}{\numberline {2.5}Handler.java}}{20}\relax 23 | \doTocEntry\tocsubsection{2.4.3}{\csname a:TocLink\endcsname{1}{x1-160002.4.3}{QQ2-1-22}{Handler 일반 용도}}{20}\relax 24 | \doTocEntry\tocsubsection{2.4.4}{\csname a:TocLink\endcsname{1}{x1-170002.4.4}{QQ2-1-23}{타이밍 이슈}}{22}\relax 25 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-17004}{}{\numberline {2.6}부정확한 지연시간}}{22}\relax 26 | \doTocEntry\tocsection{2.5}{\csname a:TocLink\endcsname{1}{x1-180002.5}{QQ2-1-25}{ANR}}{23}\relax 27 | \doTocEntry\tocchapter{3}{\csname a:TocLink\endcsname{1}{x1-190003}{QQ2-1-26}{백그라운드 스레드}}{26}\relax 28 | \doTocEntry\tocsection{3.1}{\csname a:TocLink\endcsname{1}{x1-200003.1}{QQ2-1-27}{HandlerThread}}{26}\relax 29 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-20021}{}{\numberline {3.1}Handler 스레드 사용 예제(버그 존재)}}{27}\relax 30 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-20074}{}{\numberline {3.2}Handler 스레드 사용 예제(Fixed)}}{28}\relax 31 | \doTocEntry\tocsection{3.2}{\csname a:TocLink\endcsname{1}{x1-210003.2}{QQ2-1-30}{스레드 풀}}{29}\relax 32 | \doTocEntry\tocsubsection{3.2.1}{\csname a:TocLink\endcsname{1}{x1-220003.2.1}{QQ2-1-31}{ThreadPoolExecutor}}{29}\relax 33 | \doTocEntry\tocsubsection{3.2.2}{\csname a:TocLink\endcsname{1}{x1-230003.2.2}{QQ2-1-32}{ScheduledThreadPoolExecutor}}{30}\relax 34 | \doTocEntry\tocsubsection{3.2.3}{\csname a:TocLink\endcsname{1}{x1-240003.2.3}{QQ2-1-33}{Executors}}{31}\relax 35 | \doTocEntry\tocchapter{4}{\csname a:TocLink\endcsname{1}{x1-250004}{QQ2-1-34}{Context}}{36}\relax 36 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-25004}{}{\numberline {4.1}ContextWrapper.java}}{36}\relax 37 | \doTocEntry\tocchapter{5}{\csname a:TocLink\endcsname{1}{x1-260005}{QQ2-1-36}{Activity}}{38}\relax 38 | \doTocEntry\tocsection{5.1}{\csname a:TocLink\endcsname{1}{x1-270005.1}{QQ2-1-37}{생명주기}}{38}\relax 39 | \doTocEntry\tocsubsection{5.1.1}{\csname a:TocLink\endcsname{1}{x1-280005.1.1}{QQ2-1-38}{startActivity와 startActivityForResult}}{39}\relax 40 | \doTocEntry\tocsubsection{5.1.2}{\csname a:TocLink\endcsname{1}{x1-290005.1.2}{QQ2-1-39}{Activity 전환}}{41}\relax 41 | \doTocEntry\tocsubsection{5.1.3}{\csname a:TocLink\endcsname{1}{x1-300005.1.3}{QQ2-1-40}{Configuration 변경}}{42}\relax 42 | \doTocEntry\tocsubsection{5.1.4}{\csname a:TocLink\endcsname{1}{x1-310005.1.4}{QQ2-1-41}{생명주기 메서드 사용 시 주의점}}{43}\relax 43 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-320005.1.4}{QQ2-1-42}{리소스 생성/제거}}{43}\relax 44 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-330005.1.4}{QQ2-1-43}{super.onXXX 호출 순서}}{43}\relax 45 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-340005.1.4}{QQ2-1-44}{finish 메서드 호출하고 바로 리턴}}{44}\relax 46 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-350005.1.4}{QQ2-1-45}{onXXX 메서드는 직접 호출하지 않음}}{44}\relax 47 | \doTocEntry\tocsection{5.2}{\csname a:TocLink\endcsname{1}{x1-360005.2}{QQ2-1-46}{태스크}}{45}\relax 48 | \doTocEntry\tocsubsection{5.2.1}{\csname a:TocLink\endcsname{1}{x1-370005.2.1}{QQ2-1-47}{태스크 상태}}{46}\relax 49 | \doTocEntry\tocsubsection{5.2.2}{\csname a:TocLink\endcsname{1}{x1-380005.2.2}{QQ2-1-48}{태스크 확인}}{46}\relax 50 | \doTocEntry\tocsubsection{5.2.3}{\csname a:TocLink\endcsname{1}{x1-390005.2.3}{QQ2-1-49}{taskAffinity}}{47}\relax 51 | \doTocEntry\tocsubsection{5.2.4}{\csname a:TocLink\endcsname{1}{x1-400005.2.4}{QQ2-1-50}{태스크 속성 부여}}{48}\relax 52 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-410005.2.4}{QQ2-1-51}{Callee 속성부여는 AndroidManifest.xml의 Activity 선언에 android:launchMode로 한다.}}{49}\relax 53 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-420005.2.4}{QQ2-1-52}{Caller 속성 부여는 Intent Flags에 지정한다.}}{50}\relax 54 | \doTocEntry\tocchapter{6}{\csname a:TocLink\endcsname{1}{x1-430006}{QQ2-1-53}{Service}}{52}\relax 55 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-43050}{}{\numberline {6.1}SleepService}}{53}\relax 56 | \doTocEntry\tocsection{6.1}{\csname a:TocLink\endcsname{1}{x1-440006.1}{QQ2-1-55}{Started Service}}{54}\relax 57 | \doTocEntry\tocsubsection{6.1.1}{\csname a:TocLink\endcsname{1}{x1-450006.1.1}{QQ2-1-56}{Service 재시작 방식}}{55}\relax 58 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-460006.1.1}{QQ2-1-57}{START\_NOT\_STICKY}}{56}\relax 59 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-470006.1.1}{QQ2-1-58}{START\_STICKY}}{56}\relax 60 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-480006.1.1}{QQ2-1-59}{START\_REDELIVER\_INTENT}}{56}\relax 61 | \doTocEntry\tocsubsection{6.1.2}{\csname a:TocLink\endcsname{1}{x1-490006.1.2}{QQ2-1-60}{멀티 스레드 이슈}}{56}\relax 62 | \doTocEntry\tocsubsection{6.1.3}{\csname a:TocLink\endcsname{1}{x1-500006.1.3}{QQ2-1-61}{암시적 인텐트로 서비스 실행}}{57}\relax 63 | \doTocEntry\tocsubsection{6.1.4}{\csname a:TocLink\endcsname{1}{x1-510006.1.4}{QQ2-1-62}{IntentService}}{58}\relax 64 | \doTocEntry\tocsubsection{6.1.5}{\csname a:TocLink\endcsname{1}{x1-520006.1.5}{QQ2-1-63}{Service 중복 실행 방지}}{59}\relax 65 | \doTocEntry\tocsection{6.2}{\csname a:TocLink\endcsname{1}{x1-530006.2}{QQ2-1-64}{Bound Service}}{60}\relax 66 | \doTocEntry\tocsubsection{6.2.1}{\csname a:TocLink\endcsname{1}{x1-540006.2.1}{QQ2-1-65}{리모트 바인딩}}{61}\relax 67 | \doTocEntry\tocsubsection{6.2.2}{\csname a:TocLink\endcsname{1}{x1-550006.2.2}{QQ2-1-66}{로컬 바인딩}}{62}\relax 68 | \doTocEntry\tocsubsection{6.2.3}{\csname a:TocLink\endcsname{1}{x1-560006.2.3}{QQ2-1-67}{bindService를 쓰는 이유}}{63}\relax 69 | \doTocEntry\tocsubsection{6.2.4}{\csname a:TocLink\endcsname{1}{x1-570006.2.4}{QQ2-1-68}{바인딩의 특성}}{63}\relax 70 | \doTocEntry\tocsubsection{6.2.5}{\csname a:TocLink\endcsname{1}{x1-580006.2.5}{QQ2-1-69}{Messenger}}{65}\relax 71 | \doTocEntry\tocchapter{7}{\csname a:TocLink\endcsname{1}{x1-590007}{QQ2-1-70}{ContentProvider}}{68}\relax 72 | \doTocEntry\tocsection{7.1}{\csname a:TocLink\endcsname{1}{x1-600007.1}{QQ2-1-71}{SQLite}}{68}\relax 73 | \doTocEntry\tocsubsection{7.1.1}{\csname a:TocLink\endcsname{1}{x1-610007.1.1}{QQ2-1-72}{DB Lock 문제}}{73}\relax 74 | \doTocEntry\tocsubsection{7.1.2}{\csname a:TocLink\endcsname{1}{x1-620007.1.2}{QQ2-1-73}{SQLiteOpenHelper}}{76}\relax 75 | \doTocEntry\tocsection{7.2}{\csname a:TocLink\endcsname{1}{x1-630007.2}{QQ2-1-74}{ContentProvider}}{78}\relax 76 | \doTocEntry\tocsubsection{7.2.1}{\csname a:TocLink\endcsname{1}{x1-640007.2.1}{QQ2-1-75}{ContentProvider 적용}}{79}\relax 77 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-64002}{}{\numberline {7.1}ContentProvider 예}}{80}\relax 78 | \doTocEntry\tocsubsection{7.2.2}{\csname a:TocLink\endcsname{1}{x1-650007.2.2}{QQ2-1-77}{배치 execute}}{81}\relax 79 | \doTocEntry\tocsection{7.3}{\csname a:TocLink\endcsname{1}{x1-660007.3}{QQ2-1-78}{SQLite/ContentProvider 관련 팁}}{81}\relax 80 | \doTocEntry\tocsubsection{7.3.1}{\csname a:TocLink\endcsname{1}{x1-670007.3.1}{QQ2-1-79}{쿼리 실행 확인}}{81}\relax 81 | \doTocEntry\tocsubsection{7.3.2}{\csname a:TocLink\endcsname{1}{x1-680007.3.2}{QQ2-1-80}{ContentProvider 예외 확인}}{83}\relax 82 | \doTocEntry\tocchapter{8}{\csname a:TocLink\endcsname{1}{x1-690008}{QQ2-1-81}{BroadcastReceiver}}{84}\relax 83 | \doTocEntry\tocsection{8.1}{\csname a:TocLink\endcsname{1}{x1-700008.1}{QQ2-1-82}{BroadcastReceiver 구현}}{84}\relax 84 | \doTocEntry\tocsection{8.2}{\csname a:TocLink\endcsname{1}{x1-710008.2}{QQ2-1-83}{BroadcastReceiver 등록}}{85}\relax 85 | \doTocEntry\tocsection{8.3}{\csname a:TocLink\endcsname{1}{x1-720008.3}{QQ2-1-84}{Ordered/Sticky Broadcast}}{89}\relax 86 | \doTocEntry\tocsection{8.4}{\csname a:TocLink\endcsname{1}{x1-730008.4}{QQ2-1-85}{LocalBroadcastManager}}{90}\relax 87 | \doTocEntry\tocsection{8.5}{\csname a:TocLink\endcsname{1}{x1-740008.5}{QQ2-1-86}{App Widget}}{91}\relax 88 | \doTocEntry\tocsubsection{8.5.1}{\csname a:TocLink\endcsname{1}{x1-750008.5.1}{QQ2-1-87}{App Widget 기본}}{91}\relax 89 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-75013}{}{\numberline {8.1}AppWidgetProvider.java}}{92}\relax 90 | \doTocEntry\tocsubsection{8.5.2}{\csname a:TocLink\endcsname{1}{x1-760008.5.2}{QQ2-1-89}{RemoteViews}}{92}\relax 91 | \doTocEntry\tocsubsection{8.5.3}{\csname a:TocLink\endcsname{1}{x1-770008.5.3}{QQ2-1-90}{App Widget 업데이트}}{93}\relax 92 | \doTocEntry\tocsubsection{8.5.4}{\csname a:TocLink\endcsname{1}{x1-780008.5.4}{QQ2-1-91}{유의할 점}}{94}\relax 93 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-790008.5.4}{QQ2-1-92}{메인 스레드 점유}}{94}\relax 94 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-800008.5.4}{QQ2-1-93}{부팅 중에는 initialLayout만 보임}}{94}\relax 95 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-810008.5.4}{QQ2-1-94}{ICS부터 기본 패딩}}{95}\relax 96 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-820008.5.4}{QQ2-1-95}{고해상도 폰에서 Bitmap 생성시 메모리 문제}}{95}\relax 97 | \doTocEntry\tocchapter{9}{\csname a:TocLink\endcsname{1}{x1-830009}{QQ2-1-96}{Application}}{97}\relax 98 | \doTocEntry\tocsection{9.1}{\csname a:TocLink\endcsname{1}{x1-840009.1}{QQ2-1-97}{앱 초기화}}{97}\relax 99 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-84003}{}{\numberline {9.1}Application 샘플}}{97}\relax 100 | \doTocEntry\tocsection{9.2}{\csname a:TocLink\endcsname{1}{x1-850009.2}{QQ2-1-99}{Application Callback}}{98}\relax 101 | \doTocEntry\tocsection{9.3}{\csname a:TocLink\endcsname{1}{x1-860009.3}{QQ2-1-100}{프로세스 분리}}{100}\relax 102 | \doTocEntry\tocchapter{10}{\csname a:TocLink\endcsname{1}{x1-8700010}{QQ2-1-101}{시스템 서비스}}{103}\relax 103 | \doTocEntry\tocsection{10.1}{\csname a:TocLink\endcsname{1}{x1-8800010.1}{QQ2-1-102}{네이티브 시스템 서비스}}{103}\relax 104 | \doTocEntry\tocsection{10.2}{\csname a:TocLink\endcsname{1}{x1-8900010.2}{QQ2-1-103}{자바 시스템 서비스}}{103}\relax 105 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-89003}{}{\numberline {10.1}SystemServer.java}}{106}\relax 106 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-89029}{}{\numberline {10.2}ActivityManagerService.java}}{107}\relax 107 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-89050}{}{\numberline {10.3}ContextImpl.java}}{107}\relax 108 | \doTocEntry\tocsection{10.3}{\csname a:TocLink\endcsname{1}{x1-9000010.3}{QQ2-1-107}{dumpsys 명령어}}{107}\relax 109 | \doTocEntry\tocsection{10.4}{\csname a:TocLink\endcsname{1}{x1-9100010.4}{QQ2-1-108}{시스템 서비스 이슈}}{108}\relax 110 | \doTocEntry\tocsubsection{10.4.1}{\csname a:TocLink\endcsname{1}{x1-9200010.4.1}{QQ2-1-109}{빈번한 리모트 호출}}{108}\relax 111 | \doTocEntry\tocsubsection{10.4.2}{\csname a:TocLink\endcsname{1}{x1-9300010.4.2}{QQ2-1-110}{전원 관리와 Deep Sleep}}{109}\relax 112 | \doTocEntry\tocsubsection{10.4.3}{\csname a:TocLink\endcsname{1}{x1-9400010.4.3}{QQ2-1-111}{알람 등록과 제거}}{112}\relax 113 | \doTocEntry\tocchapter{11}{\csname a:TocLink\endcsname{1}{x1-9500011}{QQ2-1-112}{구현 패턴}}{113}\relax 114 | \doTocEntry\tocsection{11.1}{\csname a:TocLink\endcsname{1}{x1-9600011.1}{QQ2-1-113}{싱글톤 패턴}}{113}\relax 115 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-96002}{}{\numberline {11.1}LocalBroadcastManager.java}}{113}\relax 116 | \doTocEntry\tocsection{11.2}{\csname a:TocLink\endcsname{1}{x1-9700011.2}{QQ2-1-115}{마커 인터페이스}}{115}\relax 117 | \par 118 | -------------------------------------------------------------------------------- /latexbook/android_note.4tc: -------------------------------------------------------------------------------- 1 | \expandafter\ifx\csname doTocEntry\endcsname\relax \expandafter\endinput\fi 2 | \doTocEntry\toclikechapter{}{\csname a:TocLink\endcsname{1}{x1-1000}{QQ2-1-1}{차례}}{3}\relax 3 | \doTocEntry\toclikechapter{}{\csname a:TocLink\endcsname{1}{x1-2000}{QQ2-1-2}{이 책의 구성}}{7}\relax 4 | \doTocEntry\tocchapter{}{\csname a:TocLink\endcsname{1}{Q1-1-3}{}{이 책의 구성}}{7}\relax 5 | \doTocEntry\tocchapter{1}{\csname a:TocLink\endcsname{1}{x1-30001}{QQ2-1-4}{안드로이드 프레임워크}}{8}\relax 6 | \doTocEntry\tocsection{1.1}{\csname a:TocLink\endcsname{1}{x1-40001.1}{QQ2-1-5}{프레임워크 개요}}{8}\relax 7 | \doTocEntry\tocsection{1.2}{\csname a:TocLink\endcsname{1}{x1-50001.2}{QQ2-1-6}{프레임워크 소스 구조}}{9}\relax 8 | \doTocEntry\tocsection{1.3}{\csname a:TocLink\endcsname{1}{x1-60001.3}{QQ2-1-7}{안드로이드 버전}}{11}\relax 9 | \doTocEntry\tocsubsection{1.3.1}{\csname a:TocLink\endcsname{1}{x1-70001.3.1}{QQ2-1-8}{호환성 모드}}{11}\relax 10 | \doTocEntry\tocsubsection{1.3.2}{\csname a:TocLink\endcsname{1}{x1-80001.3.2}{QQ2-1-9}{버전 체크}}{13}\relax 11 | \doTocEntry\tocchapter{2}{\csname a:TocLink\endcsname{1}{x1-90002}{QQ2-1-10}{메인 스레드와 Handler}}{15}\relax 12 | \doTocEntry\tocsection{2.1}{\csname a:TocLink\endcsname{1}{x1-100002.1}{QQ2-1-11}{메인 스레드}}{15}\relax 13 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-10011}{}{\numberline {2.1}ActivityThread.java}}{15}\relax 14 | \doTocEntry\tocsection{2.2}{\csname a:TocLink\endcsname{1}{x1-110002.2}{QQ2-1-13}{Looper}}{16}\relax 15 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-11003}{}{\numberline {2.2}Looper.java}}{16}\relax 16 | \doTocEntry\tocsection{2.3}{\csname a:TocLink\endcsname{1}{x1-120002.3}{QQ2-1-15}{Message와 MessageQueue}}{17}\relax 17 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-12002}{}{\numberline {2.3}Message.java}}{17}\relax 18 | \doTocEntry\tocsection{2.4}{\csname a:TocLink\endcsname{1}{x1-130002.4}{QQ2-1-17}{Handler}}{17}\relax 19 | \doTocEntry\tocsubsection{2.4.1}{\csname a:TocLink\endcsname{1}{x1-140002.4.1}{QQ2-1-18}{Handler 생성}}{18}\relax 20 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-14002}{}{\numberline {2.4}LooperThead}}{18}\relax 21 | \doTocEntry\tocsubsection{2.4.2}{\csname a:TocLink\endcsname{1}{x1-150002.4.2}{QQ2-1-20}{Handler 동작}}{19}\relax 22 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-15002}{}{\numberline {2.5}Handler.java}}{20}\relax 23 | \doTocEntry\tocsubsection{2.4.3}{\csname a:TocLink\endcsname{1}{x1-160002.4.3}{QQ2-1-22}{Handler 일반 용도}}{20}\relax 24 | \doTocEntry\tocsubsection{2.4.4}{\csname a:TocLink\endcsname{1}{x1-170002.4.4}{QQ2-1-23}{타이밍 이슈}}{22}\relax 25 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-17004}{}{\numberline {2.6}부정확한 지연시간}}{22}\relax 26 | \doTocEntry\tocsection{2.5}{\csname a:TocLink\endcsname{1}{x1-180002.5}{QQ2-1-25}{ANR}}{23}\relax 27 | \doTocEntry\tocchapter{3}{\csname a:TocLink\endcsname{1}{x1-190003}{QQ2-1-26}{백그라운드 스레드}}{26}\relax 28 | \doTocEntry\tocsection{3.1}{\csname a:TocLink\endcsname{1}{x1-200003.1}{QQ2-1-27}{HandlerThread}}{26}\relax 29 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-20021}{}{\numberline {3.1}Handler 스레드 사용 예제(버그 존재)}}{27}\relax 30 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-20074}{}{\numberline {3.2}Handler 스레드 사용 예제(Fixed)}}{28}\relax 31 | \doTocEntry\tocsection{3.2}{\csname a:TocLink\endcsname{1}{x1-210003.2}{QQ2-1-30}{스레드 풀}}{29}\relax 32 | \doTocEntry\tocsubsection{3.2.1}{\csname a:TocLink\endcsname{1}{x1-220003.2.1}{QQ2-1-31}{ThreadPoolExecutor}}{29}\relax 33 | \doTocEntry\tocsubsection{3.2.2}{\csname a:TocLink\endcsname{1}{x1-230003.2.2}{QQ2-1-32}{ScheduledThreadPoolExecutor}}{30}\relax 34 | \doTocEntry\tocsubsection{3.2.3}{\csname a:TocLink\endcsname{1}{x1-240003.2.3}{QQ2-1-33}{Executors}}{31}\relax 35 | \doTocEntry\tocchapter{4}{\csname a:TocLink\endcsname{1}{x1-250004}{QQ2-1-34}{Context}}{36}\relax 36 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-25004}{}{\numberline {4.1}ContextWrapper.java}}{36}\relax 37 | \doTocEntry\tocchapter{5}{\csname a:TocLink\endcsname{1}{x1-260005}{QQ2-1-36}{Activity}}{38}\relax 38 | \doTocEntry\tocsection{5.1}{\csname a:TocLink\endcsname{1}{x1-270005.1}{QQ2-1-37}{생명주기}}{38}\relax 39 | \doTocEntry\tocsubsection{5.1.1}{\csname a:TocLink\endcsname{1}{x1-280005.1.1}{QQ2-1-38}{startActivity와 startActivityForResult}}{39}\relax 40 | \doTocEntry\tocsubsection{5.1.2}{\csname a:TocLink\endcsname{1}{x1-290005.1.2}{QQ2-1-39}{Activity 전환}}{41}\relax 41 | \doTocEntry\tocsubsection{5.1.3}{\csname a:TocLink\endcsname{1}{x1-300005.1.3}{QQ2-1-40}{Configuration 변경}}{42}\relax 42 | \doTocEntry\tocsubsection{5.1.4}{\csname a:TocLink\endcsname{1}{x1-310005.1.4}{QQ2-1-41}{생명주기 메서드 사용 시 주의점}}{43}\relax 43 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-320005.1.4}{QQ2-1-42}{리소스 생성/제거}}{43}\relax 44 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-330005.1.4}{QQ2-1-43}{super.onXXX 호출 순서}}{43}\relax 45 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-340005.1.4}{QQ2-1-44}{finish 메서드 호출하고 바로 리턴}}{44}\relax 46 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-350005.1.4}{QQ2-1-45}{onXXX 메서드는 직접 호출하지 않음}}{44}\relax 47 | \doTocEntry\tocsection{5.2}{\csname a:TocLink\endcsname{1}{x1-360005.2}{QQ2-1-46}{태스크}}{45}\relax 48 | \doTocEntry\tocsubsection{5.2.1}{\csname a:TocLink\endcsname{1}{x1-370005.2.1}{QQ2-1-47}{태스크 상태}}{46}\relax 49 | \doTocEntry\tocsubsection{5.2.2}{\csname a:TocLink\endcsname{1}{x1-380005.2.2}{QQ2-1-48}{태스크 확인}}{46}\relax 50 | \doTocEntry\tocsubsection{5.2.3}{\csname a:TocLink\endcsname{1}{x1-390005.2.3}{QQ2-1-49}{taskAffinity}}{47}\relax 51 | \doTocEntry\tocsubsection{5.2.4}{\csname a:TocLink\endcsname{1}{x1-400005.2.4}{QQ2-1-50}{태스크 속성 부여}}{48}\relax 52 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-410005.2.4}{QQ2-1-51}{Callee 속성부여는 AndroidManifest.xml의 Activity 선언에 android:launchMode로 한다.}}{49}\relax 53 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-420005.2.4}{QQ2-1-52}{Caller 속성 부여는 Intent Flags에 지정한다.}}{50}\relax 54 | \doTocEntry\tocchapter{6}{\csname a:TocLink\endcsname{1}{x1-430006}{QQ2-1-53}{Service}}{52}\relax 55 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-43050}{}{\numberline {6.1}SleepService}}{53}\relax 56 | \doTocEntry\tocsection{6.1}{\csname a:TocLink\endcsname{1}{x1-440006.1}{QQ2-1-55}{Started Service}}{54}\relax 57 | \doTocEntry\tocsubsection{6.1.1}{\csname a:TocLink\endcsname{1}{x1-450006.1.1}{QQ2-1-56}{Service 재시작 방식}}{55}\relax 58 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-460006.1.1}{QQ2-1-57}{START\_NOT\_STICKY}}{56}\relax 59 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-470006.1.1}{QQ2-1-58}{START\_STICKY}}{56}\relax 60 | \doTocEntry\tocparagraph{}{\csname a:TocLink\endcsname{1}{x1-480006.1.1}{QQ2-1-59}{START\_REDELIVER\_INTENT}}{56}\relax 61 | \doTocEntry\tocsubsection{6.1.2}{\csname a:TocLink\endcsname{1}{x1-490006.1.2}{QQ2-1-60}{멀티 스레드 이슈}}{56}\relax 62 | \doTocEntry\tocsubsection{6.1.3}{\csname a:TocLink\endcsname{1}{x1-500006.1.3}{QQ2-1-61}{암시적 인텐트로 서비스 실행}}{57}\relax 63 | \doTocEntry\tocsubsection{6.1.4}{\csname a:TocLink\endcsname{1}{x1-510006.1.4}{QQ2-1-62}{IntentService}}{58}\relax 64 | \doTocEntry\tocsubsection{6.1.5}{\csname a:TocLink\endcsname{1}{x1-520006.1.5}{QQ2-1-63}{Service 중복 실행 방지}}{59}\relax 65 | \doTocEntry\tocsection{6.2}{\csname a:TocLink\endcsname{1}{x1-530006.2}{QQ2-1-64}{Bound Service}}{60}\relax 66 | \doTocEntry\tocsubsection{6.2.1}{\csname a:TocLink\endcsname{1}{x1-540006.2.1}{QQ2-1-65}{리모트 바인딩}}{61}\relax 67 | \doTocEntry\tocsubsection{6.2.2}{\csname a:TocLink\endcsname{1}{x1-550006.2.2}{QQ2-1-66}{로컬 바인딩}}{62}\relax 68 | \doTocEntry\tocsubsection{6.2.3}{\csname a:TocLink\endcsname{1}{x1-560006.2.3}{QQ2-1-67}{bindService를 쓰는 이유}}{63}\relax 69 | \doTocEntry\tocsubsection{6.2.4}{\csname a:TocLink\endcsname{1}{x1-570006.2.4}{QQ2-1-68}{바인딩의 특성}}{63}\relax 70 | \doTocEntry\tocsubsection{6.2.5}{\csname a:TocLink\endcsname{1}{x1-580006.2.5}{QQ2-1-69}{Messenger}}{65}\relax 71 | \doTocEntry\tocchapter{7}{\csname a:TocLink\endcsname{1}{x1-590007}{QQ2-1-70}{ContentProvider}}{68}\relax 72 | \doTocEntry\tocsection{7.1}{\csname a:TocLink\endcsname{1}{x1-600007.1}{QQ2-1-71}{SQLite}}{68}\relax 73 | \doTocEntry\tocsubsection{7.1.1}{\csname a:TocLink\endcsname{1}{x1-610007.1.1}{QQ2-1-72}{DB Lock 문제}}{73}\relax 74 | \doTocEntry\tocsubsection{7.1.2}{\csname a:TocLink\endcsname{1}{x1-620007.1.2}{QQ2-1-73}{SQLiteOpenHelper}}{76}\relax 75 | \doTocEntry\tocsection{7.2}{\csname a:TocLink\endcsname{1}{x1-630007.2}{QQ2-1-74}{ContentProvider}}{78}\relax 76 | \doTocEntry\tocsubsection{7.2.1}{\csname a:TocLink\endcsname{1}{x1-640007.2.1}{QQ2-1-75}{ContentProvider 적용}}{79}\relax 77 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-64002}{}{\numberline {7.1}ContentProvider 예}}{80}\relax 78 | \doTocEntry\tocsubsection{7.2.2}{\csname a:TocLink\endcsname{1}{x1-650007.2.2}{QQ2-1-77}{배치 execute}}{81}\relax 79 | \doTocEntry\tocsection{7.3}{\csname a:TocLink\endcsname{1}{x1-660007.3}{QQ2-1-78}{SQLite/ContentProvider 관련 팁}}{81}\relax 80 | \doTocEntry\tocsubsection{7.3.1}{\csname a:TocLink\endcsname{1}{x1-670007.3.1}{QQ2-1-79}{쿼리 실행 확인}}{81}\relax 81 | \doTocEntry\tocsubsection{7.3.2}{\csname a:TocLink\endcsname{1}{x1-680007.3.2}{QQ2-1-80}{ContentProvider 예외 확인}}{83}\relax 82 | \doTocEntry\tocchapter{8}{\csname a:TocLink\endcsname{1}{x1-690008}{QQ2-1-81}{BroadcastReceiver}}{84}\relax 83 | \doTocEntry\tocsection{8.1}{\csname a:TocLink\endcsname{1}{x1-700008.1}{QQ2-1-82}{BroadcastReceiver 구현}}{84}\relax 84 | \doTocEntry\tocsection{8.2}{\csname a:TocLink\endcsname{1}{x1-710008.2}{QQ2-1-83}{BroadcastReceiver 등록}}{85}\relax 85 | \doTocEntry\tocsection{8.3}{\csname a:TocLink\endcsname{1}{x1-720008.3}{QQ2-1-84}{Ordered/Sticky Broadcast}}{89}\relax 86 | \doTocEntry\tocsection{8.4}{\csname a:TocLink\endcsname{1}{x1-730008.4}{QQ2-1-85}{LocalBroadcastManager}}{90}\relax 87 | \doTocEntry\tocsection{8.5}{\csname a:TocLink\endcsname{1}{x1-740008.5}{QQ2-1-86}{App Widget}}{91}\relax 88 | \doTocEntry\tocsubsection{8.5.1}{\csname a:TocLink\endcsname{1}{x1-750008.5.1}{QQ2-1-87}{App Widget 기본}}{91}\relax 89 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-75013}{}{\numberline {8.1}AppWidgetProvider.java}}{92}\relax 90 | \doTocEntry\tocsubsection{8.5.2}{\csname a:TocLink\endcsname{1}{x1-760008.5.2}{QQ2-1-89}{RemoteViews}}{92}\relax 91 | \doTocEntry\tocsubsection{8.5.3}{\csname a:TocLink\endcsname{1}{x1-770008.5.3}{QQ2-1-90}{App Widget 업데이트}}{93}\relax 92 | \doTocEntry\tocsubsection{8.5.4}{\csname a:TocLink\endcsname{1}{x1-780008.5.4}{QQ2-1-91}{유의할 점}}{94}\relax 93 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-790008.5.4}{QQ2-1-92}{메인 스레드 점유}}{94}\relax 94 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-800008.5.4}{QQ2-1-93}{부팅 중에는 initialLayout만 보임}}{94}\relax 95 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-810008.5.4}{QQ2-1-94}{ICS부터 기본 패딩}}{95}\relax 96 | \doTocEntry\tocsubsubsection{}{\csname a:TocLink\endcsname{1}{x1-820008.5.4}{QQ2-1-95}{고해상도 폰에서 Bitmap 생성시 메모리 문제}}{95}\relax 97 | \doTocEntry\tocchapter{9}{\csname a:TocLink\endcsname{1}{x1-830009}{QQ2-1-96}{Application}}{97}\relax 98 | \doTocEntry\tocsection{9.1}{\csname a:TocLink\endcsname{1}{x1-840009.1}{QQ2-1-97}{앱 초기화}}{97}\relax 99 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-84003}{}{\numberline {9.1}Application 샘플}}{97}\relax 100 | \doTocEntry\tocsection{9.2}{\csname a:TocLink\endcsname{1}{x1-850009.2}{QQ2-1-99}{Application Callback}}{98}\relax 101 | \doTocEntry\tocsection{9.3}{\csname a:TocLink\endcsname{1}{x1-860009.3}{QQ2-1-100}{프로세스 분리}}{100}\relax 102 | \doTocEntry\tocchapter{10}{\csname a:TocLink\endcsname{1}{x1-8700010}{QQ2-1-101}{시스템 서비스}}{103}\relax 103 | \doTocEntry\tocsection{10.1}{\csname a:TocLink\endcsname{1}{x1-8800010.1}{QQ2-1-102}{네이티브 시스템 서비스}}{103}\relax 104 | \doTocEntry\tocsection{10.2}{\csname a:TocLink\endcsname{1}{x1-8900010.2}{QQ2-1-103}{자바 시스템 서비스}}{103}\relax 105 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-89003}{}{\numberline {10.1}SystemServer.java}}{106}\relax 106 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-89029}{}{\numberline {10.2}ActivityManagerService.java}}{107}\relax 107 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-89050}{}{\numberline {10.3}ContextImpl.java}}{107}\relax 108 | \doTocEntry\tocsection{10.3}{\csname a:TocLink\endcsname{1}{x1-9000010.3}{QQ2-1-107}{dumpsys 명령어}}{107}\relax 109 | \doTocEntry\tocsection{10.4}{\csname a:TocLink\endcsname{1}{x1-9100010.4}{QQ2-1-108}{시스템 서비스 이슈}}{108}\relax 110 | \doTocEntry\tocsubsection{10.4.1}{\csname a:TocLink\endcsname{1}{x1-9200010.4.1}{QQ2-1-109}{빈번한 리모트 호출}}{108}\relax 111 | \doTocEntry\tocsubsection{10.4.2}{\csname a:TocLink\endcsname{1}{x1-9300010.4.2}{QQ2-1-110}{전원 관리와 Deep Sleep}}{109}\relax 112 | \doTocEntry\tocsubsection{10.4.3}{\csname a:TocLink\endcsname{1}{x1-9400010.4.3}{QQ2-1-111}{알람 등록과 제거}}{112}\relax 113 | \doTocEntry\tocchapter{11}{\csname a:TocLink\endcsname{1}{x1-9500011}{QQ2-1-112}{구현 패턴}}{113}\relax 114 | \doTocEntry\tocsection{11.1}{\csname a:TocLink\endcsname{1}{x1-9600011.1}{QQ2-1-113}{싱글톤 패턴}}{113}\relax 115 | \doTocEntry\toclol{}{\csname a:TocLink\endcsname{1}{x1-96002}{}{\numberline {11.1}LocalBroadcastManager.java}}{113}\relax 116 | \doTocEntry\tocsection{11.2}{\csname a:TocLink\endcsname{1}{x1-9700011.2}{QQ2-1-115}{마커 인터페이스}}{115}\relax 117 | -------------------------------------------------------------------------------- /latexbook/android_note.idv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/android_note.idv -------------------------------------------------------------------------------- /latexbook/android_note.lg: -------------------------------------------------------------------------------- 1 | htfcss: ecbx font-weight: bold; 2 | htfcss: ecsx font-weight: bold; 3 | htfcss: ecbi font-weight: bold; font-style: italic; 4 | htfcss: ecit font-style: italic; font-family: monospace; 5 | htfcss: ecss font-family: sans-serif; 6 | htfcss: ecff font-family: fantasy; 7 | htfcss: ecti font-style: italic; 8 | htfcss: ectt font-family: monospace; 9 | htfcss: ecbx font-weight: bold; 10 | htfcss: ecsx font-weight: bold; 11 | htfcss: ecbi font-weight: bold; font-style: italic; 12 | htfcss: ecit font-style: italic; font-family: monospace; 13 | htfcss: ecss font-family: sans-serif; 14 | htfcss: ecff font-family: fantasy; 15 | htfcss: ecti font-style: italic; 16 | htfcss: ectt font-family: monospace; 17 | htfcss: ecbx font-weight: bold; 18 | htfcss: ecsx font-weight: bold; 19 | htfcss: ecbi font-weight: bold; font-style: italic; 20 | htfcss: ecit font-style: italic; font-family: monospace; 21 | htfcss: ecss font-family: sans-serif; 22 | htfcss: ecff font-family: fantasy; 23 | htfcss: ecti font-style: italic; 24 | htfcss: ectt font-family: monospace; 25 | htfcss: ecbx font-weight: bold; 26 | htfcss: ecsx font-weight: bold; 27 | htfcss: ecbi font-weight: bold; font-style: italic; 28 | htfcss: ecit font-style: italic; font-family: monospace; 29 | htfcss: ecss font-family: sans-serif; 30 | htfcss: ecff font-family: fantasy; 31 | htfcss: ecti font-style: italic; 32 | htfcss: ectt font-family: monospace; 33 | htfcss: ecbx font-weight: bold; 34 | htfcss: ecsx font-weight: bold; 35 | htfcss: ecbi font-weight: bold; font-style: italic; 36 | htfcss: ecit font-style: italic; font-family: monospace; 37 | htfcss: ecss font-family: sans-serif; 38 | htfcss: ecff font-family: fantasy; 39 | htfcss: ecti font-style: italic; 40 | htfcss: ectt font-family: monospace; 41 | htfcss: ecbx font-weight: bold; 42 | htfcss: ecsx font-weight: bold; 43 | htfcss: ecbi font-weight: bold; font-style: italic; 44 | htfcss: ecit font-style: italic; font-family: monospace; 45 | htfcss: ecss font-family: sans-serif; 46 | htfcss: ecff font-family: fantasy; 47 | htfcss: ecti font-style: italic; 48 | htfcss: ectt font-family: monospace; 49 | htfcss: ecbx font-weight: bold; 50 | htfcss: ecsx font-weight: bold; 51 | htfcss: ecbi font-weight: bold; font-style: italic; 52 | htfcss: ecit font-style: italic; font-family: monospace; 53 | htfcss: ecss font-family: sans-serif; 54 | htfcss: ecff font-family: fantasy; 55 | htfcss: ecti font-style: italic; 56 | htfcss: ectt font-family: monospace; 57 | htfcss: ecbx font-weight: bold; 58 | htfcss: ecsx font-weight: bold; 59 | htfcss: ecbi font-weight: bold; font-style: italic; 60 | htfcss: ecit font-style: italic; font-family: monospace; 61 | htfcss: ecss font-family: sans-serif; 62 | htfcss: ecff font-family: fantasy; 63 | htfcss: ecti font-style: italic; 64 | htfcss: ectt font-family: monospace; 65 | htfcss: cmmi font-style: italic; 66 | htfcss: cmmib font-style: italic; font-weight: bold; 67 | File: android_note.html 68 | File: android_note.css 69 | File: android_note.tmp 70 | File: android_note2.html 71 | File: android_note3.html 72 | File: android_note4.html 73 | File: android_note5.html 74 | File: android_note6.html 75 | File: android_note7.html 76 | File: android_note8.html 77 | File: android_note9.html 78 | File: android_note10.html 79 | File: android_note11.html 80 | File: android_note12.html 81 | File: android_note13.html 82 | File: android_note14.html 83 | File: android_note15.html 84 | File: android_note16.html 85 | File: android_note17.html 86 | File: android_note18.html 87 | File: android_note19.html 88 | File: android_note20.html 89 | File: android_note21.html 90 | File: android_note22.html 91 | File: android_note23.html 92 | File: android_note24.html 93 | File: android_note25.html 94 | File: android_note26.html 95 | File: android_note27.html 96 | File: android_note28.html 97 | File: android_note29.html 98 | File: android_note30.html 99 | File: android_note31.html 100 | File: android_note32.html 101 | File: android_note33.html 102 | File: android_note34.html 103 | File: android_note35.html 104 | File: android_note36.html 105 | File: android_note37.html 106 | File: android_note38.html 107 | File: android_note39.html 108 | File: android_note40.html 109 | File: android_note41.html 110 | File: android_note42.html 111 | File: android_note43.html 112 | File: android_note44.html 113 | File: android_note45.html 114 | File: android_note46.html 115 | File: android_note47.html 116 | File: android_note48.html 117 | File: android_note49.html 118 | File: android_note50.html 119 | File: android_note51.html 120 | File: android_note52.html 121 | Css: p.noindent { text-indent: 0em } 122 | Css: td p.noindent { text-indent: 0em; margin-top:0em; } 123 | Css: p.nopar { text-indent: 0em; } 124 | Css: p.indent{ text-indent: 1.5em } 125 | Css: @media print {div.crosslinks {visibility:hidden;}} 126 | Css: a img { border-top: 0; border-left: 0; border-right: 0; } 127 | Font_Css("4"): .small-caps{font-variant: small-caps; } 128 | Font_Css("10"): .htf-cmbx {font-weight: bold; font-style:normal;} 129 | Font_Css("12"): .htf-calligraphy {font-family:cursive} 130 | Font_Css("14"): .htf-italic {font-style: italic;} 131 | Font_Css("16"): .htf-bold {font-weight: bold;} 132 | Font_Css("12"): .htf-calligraphy-bold {font-family:cursive ; font-weight: bold; } 133 | Css: center { margin-top:1em; margin-bottom:1em; } 134 | Css: td center { margin-top:0em; margin-bottom:0em; } 135 | Css: .Canvas { position:relative; } 136 | Css: img.math{vertical-align:middle;} 137 | Css: li p.indent { text-indent: 0em } 138 | Css: li p:first-child{ margin-top:0em; } 139 | Css: li p:last-child, li div:last-child { margin-bottom:0.5em; } 140 | Css: li p~ul:last-child, li p~ol:last-child{ margin-bottom:0.5em; } 141 | Css: .enumerate1 {list-style-type:decimal;} 142 | Css: .enumerate2 {list-style-type:lower-alpha;} 143 | Css: .enumerate3 {list-style-type:lower-roman;} 144 | Css: .enumerate4 {list-style-type:upper-alpha;} 145 | Css: div.newtheorem { margin-bottom: 2em; margin-top: 2em;} 146 | Css: .obeylines-h,.obeylines-v {white-space: nowrap; } 147 | Css: div.obeylines-v p { margin-top:0; margin-bottom:0; } 148 | Css: .overline{ text-decoration:overline; } 149 | Css: .overline img{ border-top: 1px solid black; } 150 | Css: td.displaylines {text-align:center; white-space:nowrap;} 151 | Css: .centerline {text-align:center;} 152 | Css: .rightline {text-align:right;} 153 | Css: div.verbatim {font-family: monospace; white-space: nowrap; text-align:left; clear:both; } 154 | Css: .fbox {padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } 155 | Css: div.fbox {display:table} 156 | Css: div.center div.fbox {text-align:center; clear:both; padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } 157 | Css: div.minipage{width:100%;} 158 | Css: div.center, div.center div.center {text-align: center; margin-left:1em; margin-right:1em;} 159 | Css: div.center div {text-align: left;} 160 | Css: div.flushright, div.flushright div.flushright {text-align: right;} 161 | Css: div.flushright div {text-align: left;} 162 | Css: div.flushleft {text-align: left;} 163 | Css: .underline{ text-decoration:underline; } 164 | Css: .underline img{ border-bottom: 1px solid black; margin-bottom:1pt; } 165 | Css: .framebox-c, .framebox-l, .framebox-r { padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } 166 | Css: .framebox-c {text-align:center;} 167 | Css: .framebox-l {text-align:left;} 168 | Css: .framebox-r {text-align:right;} 169 | Css: span.thank-mark{ vertical-align: super } 170 | Css: span.footnote-mark sup.textsuperscript, span.footnote-mark a sup.textsuperscript{ font-size:80%; } 171 | Css: div.tabular, div.center div.tabular {text-align: center; margin-top:0.5em; margin-bottom:0.5em; } 172 | Css: table.tabular td p{margin-top:0em;} 173 | Css: table.tabular {margin-left: auto; margin-right: auto;} 174 | Css: td p:first-child{ margin-top:0em; } 175 | Css: td p:last-child{ margin-bottom:0em; } 176 | Css: div.td00{ margin-left:0pt; margin-right:0pt; } 177 | Css: div.td01{ margin-left:0pt; margin-right:5pt; } 178 | Css: div.td10{ margin-left:5pt; margin-right:0pt; } 179 | Css: div.td11{ margin-left:5pt; margin-right:5pt; } 180 | Css: table[rules] {border-left:solid black 0.4pt; border-right:solid black 0.4pt; } 181 | Css: td.td00{ padding-left:0pt; padding-right:0pt; } 182 | Css: td.td01{ padding-left:0pt; padding-right:5pt; } 183 | Css: td.td10{ padding-left:5pt; padding-right:0pt; } 184 | Css: td.td11{ padding-left:5pt; padding-right:5pt; } 185 | Css: table[rules] {border-left:solid black 0.4pt; border-right:solid black 0.4pt; } 186 | Css: .hline hr, .cline hr{ height : 1px; margin:0px; } 187 | Css: .tabbing-right {text-align:right;} 188 | Css: span.TEX {letter-spacing: -0.125em; } 189 | Css: span.TEX span.E{ position:relative;top:0.5ex;left:-0.0417em;} 190 | Css: a span.TEX span.E {text-decoration: none; } 191 | Css: span.LATEX span.A{ position:relative; top:-0.5ex; left:-0.4em; font-size:85%;} 192 | Css: span.LATEX span.TEX{ position:relative; left: -0.4em; } 193 | Css: div.float, div.figure {margin-left: auto; margin-right: auto;} 194 | Css: div.float img {text-align:center;} 195 | Css: div.figure img {text-align:center;} 196 | Css: .marginpar {width:20%; float:right; text-align:left; margin-left:auto; margin-top:0.5em; font-size:85%; text-decoration:underline;} 197 | Css: .marginpar p{margin-top:0.4em; margin-bottom:0.4em;} 198 | Css: table.equation {width:100%;} 199 | Css: .equation td{text-align:center; } 200 | Css: td.equation { margin-top:1em; margin-bottom:1em; } 201 | Css: td.equation-label { width:5%; text-align:center; } 202 | Css: td.eqnarray4 { width:5%; white-space: normal; } 203 | Css: td.eqnarray2 { width:5%; } 204 | Css: table.eqnarray-star, table.eqnarray {width:100%;} 205 | Css: div.eqnarray{text-align:center;} 206 | Css: div.array {text-align:center;} 207 | Css: div.pmatrix {text-align:center;} 208 | Css: table.pmatrix {width:100%;} 209 | Css: span.pmatrix img{vertical-align:middle;} 210 | Css: div.pmatrix {text-align:center;} 211 | Css: table.pmatrix {width:100%;} 212 | Css: span.bar-css {text-decoration:overline;} 213 | Css: img.cdots{vertical-align:middle;} 214 | Css: .partToc a, .partToc, .likepartToc a, .likepartToc {line-height: 200%; font-weight:bold; font-size:110%;} 215 | Css: .chapterToc a, .chapterToc, .likechapterToc a, .likechapterToc, .appendixToc a, .appendixToc {line-height: 200%; font-weight:bold;} 216 | Css: .index-item, .index-subitem, .index-subsubitem {display:block} 217 | Css: div.caption {text-indent:-2em; margin-left:3em; margin-right:1em; text-align:left;} 218 | Css: div.caption span.id{font-weight: bold; white-space: nowrap; } 219 | Css: h1.partHead{text-align: center} 220 | Css: p.bibitem { text-indent: -2em; margin-left: 2em; margin-top:0.6em; margin-bottom:0.6em; } 221 | Css: p.bibitem-p { text-indent: 0em; margin-left: 2em; margin-top:0.6em; margin-bottom:0.6em; } 222 | Css: .paragraphHead, .likeparagraphHead { margin-top:2em; font-weight: bold;} 223 | Css: .subparagraphHead, .likesubparagraphHead { font-weight: bold;} 224 | Css: .quote {margin-bottom:0.25em; margin-top:0.25em; margin-left:1em; margin-right:1em; text-align:justify;} 225 | Css: .verse{white-space:nowrap; margin-left:2em} 226 | Css: div.maketitle {text-align:center;} 227 | Css: h2.titleHead{text-align:center;} 228 | Css: div.maketitle{ margin-bottom: 2em; } 229 | Css: div.author, div.date {text-align:center;} 230 | Css: div.thanks{text-align:left; margin-left:10%; font-size:85%; font-style:italic; } 231 | Css: div.author{white-space: nowrap;} 232 | Css: .quotation {margin-bottom:0.25em; margin-top:0.25em; margin-left:1em; } 233 | Css: h1.partHead{text-align: center} 234 | Css: .chapterToc, .likechapterToc {margin-left:0em;} 235 | Css: .chapterToc ~ .likesectionToc, .chapterToc ~ .sectionToc, .likechapterToc ~ .likesectionToc, .likechapterToc ~ .sectionToc {margin-left:2em;} 236 | Css: .chapterToc ~ .likesectionToc ~ .likesubsectionToc, .chapterToc ~ .likesectionToc ~ .subsectionToc, .chapterToc ~ .sectionToc ~ .likesubsectionToc, .chapterToc ~ .sectionToc ~ .subsectionToc, .likechapterToc ~ .likesectionToc ~ .likesubsectionToc, .likechapterToc ~ .likesectionToc ~ .subsectionToc, .likechapterToc ~ .sectionToc ~ .likesubsectionToc, .likechapterToc ~ .sectionToc ~ .subsectionToc {margin-left:4em;} 237 | Css: .chapterToc ~ .likesectionToc ~ .likesubsectionToc ~ .likesubsubsectionToc, .chapterToc ~ .likesectionToc ~ .likesubsectionToc ~ .subsubsectionToc, .chapterToc ~ .likesectionToc ~ .subsectionToc ~ .likesubsubsectionToc, .chapterToc ~ .likesectionToc ~ .subsectionToc ~ .subsubsectionToc, .chapterToc ~ .sectionToc ~ .likesubsectionToc ~ .likesubsubsectionToc, .chapterToc ~ .sectionToc ~ .likesubsectionToc ~ .subsubsectionToc, .chapterToc ~ .sectionToc ~ .subsectionToc ~ .likesubsubsectionToc, .chapterToc ~ .sectionToc ~ .subsectionToc ~ .subsubsectionToc, .likechapterToc ~ .likesectionToc ~ .likesubsectionToc ~ .likesubsubsectionToc, .likechapterToc ~ .likesectionToc ~ .likesubsectionToc ~ .subsubsectionToc, .likechapterToc ~ .likesectionToc ~ .subsectionToc ~ .likesubsubsectionToc, .likechapterToc ~ .likesectionToc ~ .subsectionToc ~ .subsubsectionToc, .likechapterToc ~ .sectionToc ~ .likesubsectionToc ~ .likesubsubsectionToc, .likechapterToc ~ .sectionToc ~ .likesubsectionToc ~ .subsubsectionToc, .likechapterToc ~ .sectionToc ~ .subsectionToc ~ .likesubsubsectionToc .likechapterToc ~ .sectionToc ~ .subsectionToc ~ .subsubsectionToc {margin-left:6em;} 238 | Css: .likesectionToc , .sectionToc {margin-left:0em;} 239 | Css: .likesectionToc ~ .likesubsectionToc, .likesectionToc ~ .subsectionToc, .sectionToc ~ .likesubsectionToc, .sectionToc ~ .subsectionToc {margin-left:2em;} 240 | Css: .likesectionToc ~ .likesubsectionToc ~ .likesubsubsectionToc, .likesectionToc ~ .likesubsectionToc ~ .subsubsectionToc, .likesectionToc ~ .subsectionToc ~ .likesubsubsectionToc, .likesectionToc ~ .subsectionToc ~ .subsubsectionToc, .sectionToc ~ .likesubsectionToc ~ .likesubsubsectionToc, .sectionToc ~ .likesubsectionToc ~ .subsubsectionToc, .sectionToc ~ .subsectionToc ~ .likesubsubsectionToc, .sectionToc ~ .subsectionToc ~ .subsubsectionToc {margin-left:4em;} 241 | Css: .likesubsectionToc, .subsectionToc {margin-left:0em;} 242 | Css: .likesubsectionToc ~ .subsubsectionToc, .subsectionToc ~ .subsubsectionToc {margin-left:2em;} 243 | Css: .figure img.graphics {margin-left:10%;} 244 | Css: .lstlisting .label{margin-right:0.5em; } 245 | Css: div.lstlisting{font-family: monospace; white-space: nowrap; margin-top:0.5em; margin-bottom:0.5em; } 246 | Css: div.lstinputlisting{ font-family: monospace; white-space: nowrap; } 247 | Css: .lstinputlisting .label{margin-right:0.5em;} 248 | Css: div.verbatiminput {font-family: monospace; white-space: nowrap; } 249 | Css: div#colorbox1{background-color:rgb(255,234,204);} 250 | Css: div#colorbox2{background-color:rgb(255,234,204);} 251 | Css: div#colorbox3{background-color:rgb(255,234,204);} 252 | tex4ht.c error: 21 253 | --- characters --- 254 | Font("cmmi","10","10","100") 255 | Font("cmsy","7","7","100") 256 | Font("cmsy","10","10","100") 257 | Font("ecrm","1000","10","100") 258 | Font("nanummjmc","5","10","172") 259 | Font("nanummjmb","4","10","172") 260 | Font("nanummjmb","8","10","172") 261 | Font("nanummjmc","7","10","172") 262 | Font("nanummjmae","","10","172") 263 | Font("nanummjmbc","","10","172") 264 | Font("nanummjmc","8","10","172") 265 | Font("nanummjmc","1","10","172") 266 | Font("ecrm","1200","12","100") 267 | Font("nanummjmb","1","10","120") 268 | Font("nanummjmc","7","10","120") 269 | Font("nanummjmcd","","10","120") 270 | Font("ectt","1000","10","100") 271 | Font("nanummjmcc","","10","100") 272 | Font("nanummjmb","8","10","100") 273 | Font("nanummjmc","7","10","100") 274 | Font("nanummjmad","","10","100") 275 | Font("nanummjmc","1","10","100") 276 | Font("nanummjmc","5","10","100") 277 | Font("nanummjmb","4","10","100") 278 | Font("nanummjmd","5","10","100") 279 | Font("nanummjmc","6","10","100") 280 | Font("nanummjmd","0","10","100") 281 | Font("nanummjmac","","10","100") 282 | Font("nanummjmc","2","10","100") 283 | Font("nanummjmc","8","10","100") 284 | Font("nanummjmbc","","10","100") 285 | Font("nanummjmd","6","10","100") 286 | Font("nanummjmba","","10","100") 287 | Font("nanummjmc","0","10","100") 288 | Font("nanummjmb","3","10","100") 289 | Font("nanummjmb","7","10","100") 290 | Font("nanummjmd","4","10","100") 291 | Font("nanummjmae","","10","100") 292 | Font("nanummjmbd","","10","100") 293 | Font("nanummjmd","2","10","100") 294 | Font("nanummjmd","1","10","100") 295 | Font("nanummjmbe","","10","100") 296 | Font("nanummjmc","9","10","100") 297 | Font("nanummjmb","9","10","100") 298 | Font("nanummjmb","5","10","100") 299 | Font("nanummjmce","","10","100") 300 | Font("nanummjmc","4","10","100") 301 | Font("nanummjmb","2","10","100") 302 | Font("nanummjmbb","","10","100") 303 | Font("nanummjmd","3","10","100") 304 | Font("nanummjmcf","","10","100") 305 | Font("nanummjmcd","","10","100") 306 | Font("nanummjmb","1","10","100") 307 | Font("nanummjmb","0","10","100") 308 | Font("nanummjmaf","","10","100") 309 | Font("nanummjmd","7","10","100") 310 | Font("tcrm","1000","10","100") 311 | Font("ecrm","0900","9","100") 312 | Font("ecrm","0800","8","100") 313 | Font("ectt","0800","8","100") 314 | Font("nanummjmb","9","10","80") 315 | Font("nanummjmcc","","10","80") 316 | Font("nanummjmac","","10","80") 317 | Font("nanummjmd","5","10","80") 318 | Font("nanummjmc","7","10","80") 319 | Font("nanummjmb","2","10","80") 320 | Font("nanummjmbc","","10","80") 321 | Font("nanummjmb","3","10","80") 322 | Font("nanummjmb","8","10","80") 323 | Font("nanummjmb","0","10","80") 324 | Font("nanummjmc","6","10","80") 325 | Font("nanummjmae","","10","80") 326 | Font("nanummjmc","5","10","80") 327 | Font("nanummjmd","3","10","80") 328 | Font("ecbx","1000","10","100") 329 | Font("nanummjmca","","10","100") 330 | Font("nanummjmb","6","10","100") 331 | Font("nanummjmc","1","10","80") 332 | Font("nanummjmc","8","10","80") 333 | Font("nanummjmd","0","10","80") 334 | Font("nanummjmc","4","10","80") 335 | Font("nanummjmc","0","10","80") 336 | Font("nanummjmba","","10","80") 337 | Font("nanummjmd","6","10","80") 338 | Font("nanummjmc","2","10","80") 339 | Font("nanummjmcf","","10","80") 340 | Font("nanummjmb","4","10","80") 341 | Font("nanummjmb","5","10","80") 342 | Font("nanummjmb","7","10","80") 343 | Font("nanummjmc","9","10","80") 344 | Font("nanummjmcd","","10","80") 345 | Font("nanummjmbd","","10","80") 346 | Font("nanummjmd","1","10","80") 347 | Font("nanummjmc","3","10","100") 348 | Font("nanummjmbb","","10","80") 349 | Font("nanummjmbf","","10","100") 350 | Font("nanummjmcb","","10","100") 351 | Font("nanummjmd","2","10","80") 352 | Font("nanummjmbe","","10","80") 353 | Font("nanummjmad","","10","80") 354 | Font("nanummjbc","7","10","100") 355 | Font("nanummjbc","4","10","100") 356 | Font("nanummjbb","2","10","100") 357 | Font("nanummjbac","","10","100") 358 | Font("nanummjbc","8","10","100") 359 | Font("nanummjbd","5","10","100") 360 | Font("nanummjbc","5","10","100") 361 | Font("nanummjbb","5","10","100") 362 | Font("nanummjbc","2","10","100") 363 | Font("nanummjbb","3","10","100") 364 | Font("nanummjbcc","","10","100") 365 | Font("nanummjbd","0","10","100") 366 | Font("nanummjbd","6","10","100") 367 | Font("nanummjbb","8","10","100") 368 | Font("nanummjbb","4","10","100") 369 | Font("nanummjbba","","10","100") 370 | Font("nanummjmaf","","10","80") 371 | Font("nanummjmd","7","10","80") 372 | Font("nanummjmd","4","10","80") 373 | Font("nanummjbbc","","10","100") 374 | Font("nanummjbb","0","10","100") 375 | Font("nanummjbb","9","10","100") 376 | Font("nanummjbb","7","10","100") 377 | Font("nanummjbae","","10","100") 378 | Font("nanummjbc","6","10","100") 379 | Font("nanummjbd","2","10","100") 380 | Font("nanummjbad","","10","100") 381 | Font("nanummjbd","1","10","100") 382 | Font("nanummjbc","1","10","100") 383 | Font("nanummjbd","3","10","100") 384 | Font("nanummjbd","7","10","100") 385 | Font("ecrm","0700","7","100") 386 | Font("tcrm","0700","7","100") 387 | Font("nanummjmb","6","10","80") 388 | Font("nanummjbbd","","10","100") 389 | Font("nanummjbc","9","10","100") 390 | Font("nanummjmb","1","10","80") 391 | Font("nanummjmbf","","10","80") 392 | Font("nanummjmce","","10","80") 393 | -------------------------------------------------------------------------------- /latexbook/android_note.out: -------------------------------------------------------------------------------- 1 | \BOOKMARK [0][-]{chapter*.6}{이 책의 구성}{}% 1 2 | \BOOKMARK [0][-]{chapter.1}{안드로이드 프레임워크}{}% 2 3 | \BOOKMARK [1][-]{section.1.1}{프레임워크 개요}{chapter.1}% 3 4 | \BOOKMARK [1][-]{section.1.2}{프레임워크 소스}{chapter.1}% 4 5 | \BOOKMARK [1][-]{section.1.3}{안드로이드 버전}{chapter.1}% 5 6 | \BOOKMARK [2][-]{subsection.1.3.1}{호환성 모드}{section.1.3}% 6 7 | \BOOKMARK [2][-]{subsection.1.3.2}{버전 체크}{section.1.3}% 7 8 | \BOOKMARK [0][-]{chapter.2}{메인 스레드와 Handler}{}% 8 9 | \BOOKMARK [1][-]{section.2.1}{메인 스레드}{chapter.2}% 9 10 | \BOOKMARK [1][-]{section.2.2}{Looper}{chapter.2}% 10 11 | \BOOKMARK [1][-]{section.2.3}{Message와 MessageQueue}{chapter.2}% 11 12 | \BOOKMARK [1][-]{section.2.4}{Handler}{chapter.2}% 12 13 | \BOOKMARK [2][-]{subsection.2.4.1}{Handler 생성}{section.2.4}% 13 14 | \BOOKMARK [2][-]{subsection.2.4.2}{Handler 동작}{section.2.4}% 14 15 | \BOOKMARK [2][-]{subsection.2.4.3}{Handler 용도}{section.2.4}% 15 16 | \BOOKMARK [2][-]{subsection.2.4.4}{타이밍 이슈}{section.2.4}% 16 17 | \BOOKMARK [1][-]{section.2.5}{UI 업데이트}{chapter.2}% 17 18 | \BOOKMARK [1][-]{section.2.6}{ANR}{chapter.2}% 18 19 | \BOOKMARK [0][-]{chapter.3}{백그라운드 스레드}{}% 19 20 | \BOOKMARK [1][-]{section.3.1}{HandlerThread}{chapter.3}% 20 21 | \BOOKMARK [1][-]{section.3.2}{스레드 풀}{chapter.3}% 21 22 | \BOOKMARK [2][-]{subsection.3.2.1}{ThreadPoolExecutor}{section.3.2}% 22 23 | \BOOKMARK [2][-]{subsection.3.2.2}{ScheduledThreadPoolExecutor}{section.3.2}% 23 24 | \BOOKMARK [2][-]{subsection.3.2.3}{Executors}{section.3.2}% 24 25 | \BOOKMARK [1][-]{section.3.3}{AsyncTask}{chapter.3}% 25 26 | \BOOKMARK [2][-]{subsection.3.3.1}{병렬 실행 시 실행 순서}{section.3.3}% 26 27 | \BOOKMARK [2][-]{subsection.3.3.2}{Activity 종료 시점과 불일치}{section.3.3}% 27 28 | \BOOKMARK [2][-]{subsection.3.3.3}{AsyncTask 취소}{section.3.3}% 28 29 | \BOOKMARK [2][-]{subsection.3.3.4}{예외 처리 메서드 부재}{section.3.3}% 29 30 | \BOOKMARK [0][-]{chapter.4}{Context}{}% 30 31 | \BOOKMARK [0][-]{chapter.5}{Activity}{}% 31 32 | \BOOKMARK [1][-]{section.5.1}{생명주기}{chapter.5}% 32 33 | \BOOKMARK [2][-]{subsection.5.1.1}{startActivity\(\)와 startActivityForResult\(\)}{section.5.1}% 33 34 | \BOOKMARK [2][-]{subsection.5.1.2}{Activity 전환}{section.5.1}% 34 35 | \BOOKMARK [2][-]{subsection.5.1.3}{생명주기 메서드 사용 시 주의점}{section.5.1}% 35 36 | \BOOKMARK [1][-]{section.5.2}{Configuration 변경}{chapter.5}% 36 37 | \BOOKMARK [2][-]{subsection.5.2.1}{리소스 반영}{section.5.2}% 37 38 | \BOOKMARK [2][-]{subsection.5.2.2}{Configuration\040qualifier}{section.5.2}% 38 39 | \BOOKMARK [2][-]{subsection.5.2.3}{데이터 복구}{section.5.2}% 39 40 | \BOOKMARK [2][-]{subsection.5.2.4}{android:configChanges 속성}{section.5.2}% 40 41 | \BOOKMARK [2][-]{subsection.5.2.5}{Configuration 확인}{section.5.2}% 41 42 | \BOOKMARK [1][-]{section.5.3}{태스크}{chapter.5}% 42 43 | \BOOKMARK [2][-]{subsection.5.3.1}{태스크 상태}{section.5.3}% 43 44 | \BOOKMARK [2][-]{subsection.5.3.2}{태스크 확인}{section.5.3}% 44 45 | \BOOKMARK [2][-]{subsection.5.3.3}{taskAffinity}{section.5.3}% 45 46 | \BOOKMARK [2][-]{subsection.5.3.4}{태스크 속성 부여}{section.5.3}% 46 47 | \BOOKMARK [1][-]{section.5.4}{}{chapter.5}% 47 48 | \BOOKMARK [0][-]{chapter.6}{Service}{}% 48 49 | \BOOKMARK [1][-]{section.6.1}{Started\040Service}{chapter.6}% 49 50 | \BOOKMARK [2][-]{subsection.6.1.1}{Service 재시작 방식}{section.6.1}% 50 51 | \BOOKMARK [2][-]{subsection.6.1.2}{멀티 스레드 이슈}{section.6.1}% 51 52 | \BOOKMARK [2][-]{subsection.6.1.3}{암시적 인텐트로 서비스 실행}{section.6.1}% 52 53 | \BOOKMARK [2][-]{subsection.6.1.4}{IntentService}{section.6.1}% 53 54 | \BOOKMARK [2][-]{subsection.6.1.5}{Service 중복 실행 방지}{section.6.1}% 54 55 | \BOOKMARK [1][-]{section.6.2}{Bound\040Service}{chapter.6}% 55 56 | \BOOKMARK [2][-]{subsection.6.2.1}{리모트 바인딩}{section.6.2}% 56 57 | \BOOKMARK [2][-]{subsection.6.2.2}{로컬 바인딩}{section.6.2}% 57 58 | \BOOKMARK [2][-]{subsection.6.2.3}{바인딩의 특성}{section.6.2}% 58 59 | \BOOKMARK [2][-]{subsection.6.2.4}{Messenger}{section.6.2}% 59 60 | \BOOKMARK [0][-]{chapter.7}{ContentProvider}{}% 60 61 | \BOOKMARK [1][-]{section.7.1}{SQLite}{chapter.7}% 61 62 | \BOOKMARK [2][-]{subsection.7.1.1}{DB Lock 문제}{section.7.1}% 62 63 | \BOOKMARK [2][-]{subsection.7.1.2}{SQLiteOpenHelper}{section.7.1}% 63 64 | \BOOKMARK [1][-]{section.7.2}{ContentProvider}{chapter.7}% 64 65 | \BOOKMARK [2][-]{subsection.7.2.1}{ContentProvider 적용}{section.7.2}% 65 66 | \BOOKMARK [2][-]{subsection.7.2.2}{배치 실행}{section.7.2}% 66 67 | \BOOKMARK [1][-]{section.7.3}{SQLite/ContentProvider 관련 팁}{chapter.7}% 67 68 | \BOOKMARK [2][-]{subsection.7.3.1}{쿼리 실행 확인}{section.7.3}% 68 69 | \BOOKMARK [2][-]{subsection.7.3.2}{ContentProvider 예외 확인}{section.7.3}% 69 70 | \BOOKMARK [0][-]{chapter.8}{BroadcastReceiver}{}% 70 71 | \BOOKMARK [1][-]{section.8.1}{BroadcastReceiver 구현}{chapter.8}% 71 72 | \BOOKMARK [1][-]{section.8.2}{BroadcastReceiver 등록}{chapter.8}% 72 73 | \BOOKMARK [1][-]{section.8.3}{Ordered\040Broadcast}{chapter.8}% 73 74 | \BOOKMARK [1][-]{section.8.4}{Sticky\040Broadcast}{chapter.8}% 74 75 | \BOOKMARK [1][-]{section.8.5}{LocalBroadcastManager}{chapter.8}% 75 76 | \BOOKMARK [1][-]{section.8.6}{App\040Widget}{chapter.8}% 76 77 | \BOOKMARK [2][-]{subsection.8.6.1}{App Widget 기본}{section.8.6}% 77 78 | \BOOKMARK [2][-]{subsection.8.6.2}{RemoteViews}{section.8.6}% 78 79 | \BOOKMARK [2][-]{subsection.8.6.3}{App Widget 업데이트}{section.8.6}% 79 80 | \BOOKMARK [2][-]{subsection.8.6.4}{유의할 점}{section.8.6}% 80 81 | \BOOKMARK [0][-]{chapter.9}{Application}{}% 81 82 | \BOOKMARK [1][-]{section.9.1}{앱 초기화}{chapter.9}% 82 83 | \BOOKMARK [1][-]{section.9.2}{Application\040Callback}{chapter.9}% 83 84 | \BOOKMARK [1][-]{section.9.3}{프로세스 분리}{chapter.9}% 84 85 | \BOOKMARK [0][-]{chapter.10}{시스템 서비스}{}% 85 86 | \BOOKMARK [1][-]{section.10.1}{시스템 서비스 기본}{chapter.10}% 86 87 | \BOOKMARK [1][-]{section.10.2}{dumpsys 명령어}{chapter.10}% 87 88 | \BOOKMARK [1][-]{section.10.3}{시스템 서비스 이슈}{chapter.10}% 88 89 | \BOOKMARK [2][-]{subsection.10.3.1}{빈번한 리모트 호출}{section.10.3}% 89 90 | \BOOKMARK [2][-]{subsection.10.3.2}{전원 관리와 Deep Sleep}{section.10.3}% 90 91 | \BOOKMARK [2][-]{subsection.10.3.3}{알람 등록과 제거}{section.10.3}% 91 92 | \BOOKMARK [0][-]{chapter.11}{구현 패턴}{}% 92 93 | \BOOKMARK [1][-]{section.11.1}{싱글톤 패턴}{chapter.11}% 93 94 | \BOOKMARK [1][-]{section.11.2}{마커 인터페이스}{chapter.11}% 94 95 | \BOOKMARK [1][-]{section.11.3}{Fragment 정적 생성}{chapter.11}% 95 96 | -------------------------------------------------------------------------------- /latexbook/android_note.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,hidelinks,11pt]{book} 2 | \usepackage[11pt]{moresize} % 11pt로 키우면서 fontsize 조정 필요 3 | %\documentclass[a4paper,hidelinks,11pt,openany]{book} % pdf 전용 4 | \usepackage{kotex} %[hangul]을 추가하면 제 1장, 제 1절 식으로 들어감 5 | \usepackage{graphicx} 6 | \usepackage{upquote} 7 | \usepackage{listings} 8 | \usepackage{color} 9 | \usepackage{verbatim} 10 | \usepackage{hyperref} 11 | % \usepackage[top=1in, bottom=1in, outer=2.5cm, inner=2.5cm, headsep=14pt]{geometry} %pdf 전용 12 | \usepackage[top=1in, bottom=1in, outer=2cm, inner=3cm, headsep=14pt]{geometry} 13 | \usepackage[T1]{fontenc} 14 | \usepackage{textcomp} 15 | \usepackage{fancyhdr} 16 | \usepackage{dhucs-cmap} %pdf에서 한글 복사가 되는 옵션 17 | \usepackage{makecell} %표에서 한 셀에서 linebreak 18 | \usepackage{longtable} %여러 페이지에 걸쳐있는 테이블 19 | \usepackage{fontspec} 20 | \usepackage{amssymb} % 책 인용 위해서 21 | %\setmainfont{나눔고딕} % 이걸 쓰면 섹션 타이틀이 bold와 아닌 것이 섞인다. 22 | %\setmainfont{Times New Roman} % Roman (rm) 23 | %\setsansfont{Arial} % Sans-serif (sf) 24 | \setmonofont{Courier New} % Monospace (tt) 25 | 26 | \pagestyle{fancy} %상단에 페이지와 현재 절 표현하기 27 | 28 | \makeatletter 29 | \DeclareRobustCommand{\format@sec@number}[2]{{\normalfont\upshape#1}#2} 30 | \renewcommand{\chaptermark}[1]{% 31 | \markboth{\format@sec@number{\ifnum\c@secnumdepth>\m@ne\@chapapp\ \thechapter. \fi}{#1}}{}} 32 | \renewcommand{\sectionmark}[1]{% 33 | \markright{\format@sec@number{\ifnum\c@secnumdepth>\z@\thesection \fi}{ #1}}} 34 | \makeatother 35 | 36 | \fancyhf{} 37 | \fancyhead[RE]{\nouppercase{\leftmark}} 38 | \fancyhead[LO]{\nouppercase{\rightmark}} 39 | \fancyhead[LE,RO]{\thepage} 40 | 41 | %\usepackage[scale=0.75,twoside,bindingoffset=5mm,a4paper]{geometry} 42 | 43 | \renewcommand{\baselinestretch}{1.3} 44 | \renewcommand\theadalign{cb} 45 | \renewcommand\theadfont{\bfseries} 46 | \renewcommand\theadgape{\Gape[4pt]} 47 | \renewcommand\cellgape{\Gape[4pt]} 48 | 49 | \def\contentsname{차례} 50 | \definecolor{codegreen}{rgb}{0,0.6,0} 51 | \definecolor{codegray}{rgb}{0.3,0.3,0.3} 52 | \definecolor{backcolour}{rgb}{0.95,0.95,0.92} 53 | \definecolor{purple}{rgb}{0.5, 0.0, 0.5} 54 | \definecolor{burntorange}{rgb}{0.8, 0.33, 0.0} 55 | \definecolor{tearose}{rgb}{1.0, 0.92, 0.8} 56 | 57 | \renewcommand\lstlistingname{코드} 58 | 59 | \title{안드로이드의 정석} 60 | \author{노재춘} 61 | 62 | \begin{document} 63 | % 한글 맞춤법 https://search.naver.com/search.naver?where=nexearch&query=%ED%95%9C%EA%B8%80+%EB%A7%9E%EC%B6%A4%EB%B2%95+%EA%B2%80%EC%82%AC%EA%B8%B0&sm=top_sug.pre&fbm=0&acr=1&acq=%ED%95%9C%EA%B8%80+%EB%A7%9E%EC%B6%A4%EB%B2%95&qdt=0&ie=utf8 64 | 65 | %https://en.wikibooks.org/wiki/LaTeX/Labels_and_Cross-referencing 66 | % http://latexcolor.com 67 | \maketitle 68 | \parindent=0em 69 | 70 | \input{header} 71 | 72 | \tableofcontents 73 | 74 | %\newfontfamily\listingsfont[Scale=0.7]{Courier} 75 | %\newfontfamily\listingsfontinline[Scale=0.8]{Courier New} 76 | 77 | \lstset { 78 | language=java, 79 | basicstyle=\ttfamily\footnotesize, 80 | %basicstyle=\fontfamily{mono}\selectfont\small, 81 | backgroundcolor=\color{backcolour}, 82 | commentstyle=\color{codegreen}, 83 | keywordstyle=\color{purple}, 84 | tabsize=4, 85 | showspaces=false, 86 | showstringspaces=false, 87 | numberstyle=\color{codegray}, 88 | numbers=left, 89 | numbersep=5pt, 90 | stepnumber=5, 91 | firstnumber=1, 92 | breaklines=tr} 93 | \newpage 94 | 95 | \input{contents} 96 | 97 | \input{start} 98 | 99 | \input{singlethread} 100 | 101 | \input{context} 102 | 103 | \input{activity} 104 | %\input{activity2-1}\right 105 | %\input{activity3} 106 | %\section{안드로이드 SDK 도구} 107 | 108 | %%\input{tools} 109 | %%\input{intent} 110 | 111 | \input{service} 112 | \input{contentprovider} 113 | \input{broadcastreceiver} 114 | \input{application} 115 | \input{binder} 116 | \input{pattern} 117 | 118 | %\input{deepsleep} 119 | 120 | %\input{activitymanager} 121 | %\input{toast} 122 | 123 | % https://www.bookmake.co.kr/print/product.html?goods_cd=10000153# 124 | 125 | \bibliography{book} 126 | \bibliographystyle{plain} 127 | 128 | 129 | \end{document} 130 | -------------------------------------------------------------------------------- /latexbook/android_note.tmp: -------------------------------------------------------------------------------- 1 | 2 | /* css.sty */ -------------------------------------------------------------------------------- /latexbook/android_note_tech_report.out: -------------------------------------------------------------------------------- 1 | \BOOKMARK [1][-]{section.1}{..... .....}{}% 1 2 | \BOOKMARK [2][-]{subsection.1.1}{..... ..}{section.1}% 2 3 | \BOOKMARK [2][-]{subsection.1.2}{..... .. ..}{section.1}% 3 4 | \BOOKMARK [1][-]{section.2}{.. .... Handler}{}% 4 5 | \BOOKMARK [2][-]{subsection.2.1}{.. ...}{section.2}% 5 6 | \BOOKMARK [2][-]{subsection.2.2}{Looper}{section.2}% 6 7 | \BOOKMARK [2][-]{subsection.2.3}{Message. MessageQueue}{section.2}% 7 8 | \BOOKMARK [2][-]{subsection.2.4}{Handler}{section.2}% 8 9 | \BOOKMARK [3][-]{subsubsection.2.4.1}{Handler ..}{subsection.2.4}% 9 10 | \BOOKMARK [3][-]{subsubsection.2.4.2}{Handler ..}{subsection.2.4}% 10 11 | \BOOKMARK [3][-]{subsubsection.2.4.3}{Handler. .. ..}{subsection.2.4}% 11 12 | \BOOKMARK [3][-]{subsubsection.2.4.4}{... ..}{subsection.2.4}% 12 13 | \BOOKMARK [2][-]{subsection.2.5}{ANR}{section.2}% 13 14 | \BOOKMARK [1][-]{section.3}{..... ...}{}% 14 15 | \BOOKMARK [2][-]{subsection.3.1}{HandlerThread}{section.3}% 15 16 | \BOOKMARK [2][-]{subsection.3.2}{....}{section.3}% 16 17 | \BOOKMARK [3][-]{subsubsection.3.2.1}{ThreadPoolExecutor}{subsection.3.2}% 17 18 | \BOOKMARK [3][-]{subsubsection.3.2.2}{ScheduledThreadPoolExecutor}{subsection.3.2}% 18 19 | \BOOKMARK [3][-]{subsubsection.3.2.3}{Executors}{subsection.3.2}% 19 20 | -------------------------------------------------------------------------------- /latexbook/android_pdf.out: -------------------------------------------------------------------------------- 1 | \BOOKMARK [0][-]{chapter*.6}{이 책의 구성}{}% 1 2 | \BOOKMARK [0][-]{chapter.1}{안드로이드 프레임워크}{}% 2 3 | \BOOKMARK [1][-]{section.1.1}{프레임워크 개요}{chapter.1}% 3 4 | \BOOKMARK [1][-]{section.1.2}{프레임워크 소스}{chapter.1}% 4 5 | \BOOKMARK [1][-]{section.1.3}{안드로이드 버전}{chapter.1}% 5 6 | \BOOKMARK [2][-]{subsection.1.3.1}{호환성 모드}{section.1.3}% 6 7 | \BOOKMARK [2][-]{subsection.1.3.2}{버전 체크}{section.1.3}% 7 8 | \BOOKMARK [0][-]{chapter.2}{메인 스레드와 Handler}{}% 8 9 | \BOOKMARK [1][-]{section.2.1}{메인 스레드}{chapter.2}% 9 10 | \BOOKMARK [1][-]{section.2.2}{Looper}{chapter.2}% 10 11 | \BOOKMARK [1][-]{section.2.3}{Message와 MessageQueue}{chapter.2}% 11 12 | \BOOKMARK [1][-]{section.2.4}{Handler}{chapter.2}% 12 13 | \BOOKMARK [2][-]{subsection.2.4.1}{Handler 생성}{section.2.4}% 13 14 | \BOOKMARK [2][-]{subsection.2.4.2}{Handler 동작}{section.2.4}% 14 15 | \BOOKMARK [2][-]{subsection.2.4.3}{Handler 용도}{section.2.4}% 15 16 | \BOOKMARK [2][-]{subsection.2.4.4}{Handler의 타이밍 이슈}{section.2.4}% 16 17 | \BOOKMARK [1][-]{section.2.5}{UI 변경 메커니즘}{chapter.2}% 17 18 | \BOOKMARK [1][-]{section.2.6}{ANR}{chapter.2}% 18 19 | \BOOKMARK [0][-]{chapter.3}{백그라운드 스레드}{}% 19 20 | \BOOKMARK [1][-]{section.3.1}{HandlerThread}{chapter.3}% 20 21 | \BOOKMARK [1][-]{section.3.2}{스레드 풀}{chapter.3}% 21 22 | \BOOKMARK [2][-]{subsection.3.2.1}{ThreadPoolExecutor}{section.3.2}% 22 23 | \BOOKMARK [2][-]{subsection.3.2.2}{ScheduledThreadPoolExecutor}{section.3.2}% 23 24 | \BOOKMARK [2][-]{subsection.3.2.3}{Executors}{section.3.2}% 24 25 | \BOOKMARK [1][-]{section.3.3}{AsyncTask}{chapter.3}% 25 26 | \BOOKMARK [2][-]{subsection.3.3.1}{Activity 종료 시점과 불일치}{section.3.3}% 26 27 | \BOOKMARK [2][-]{subsection.3.3.2}{AsyncTask 취소}{section.3.3}% 27 28 | \BOOKMARK [2][-]{subsection.3.3.3}{예외 처리 메서드 없음}{section.3.3}% 28 29 | \BOOKMARK [2][-]{subsection.3.3.4}{병렬 실행 시 실행 순서가 보장되지 않음}{section.3.3}% 29 30 | \BOOKMARK [0][-]{chapter.4}{Context}{}% 30 31 | \BOOKMARK [0][-]{chapter.5}{Activity}{}% 31 32 | \BOOKMARK [1][-]{section.5.1}{생명주기}{chapter.5}% 32 33 | \BOOKMARK [2][-]{subsection.5.1.1}{startActivity\(\)와 startActivityForResult\(\)}{section.5.1}% 33 34 | \BOOKMARK [2][-]{subsection.5.1.2}{Activity 전환 시 생명주기}{section.5.1}% 34 35 | \BOOKMARK [2][-]{subsection.5.1.3}{생명주기 메서드 사용 시 주의사항}{section.5.1}% 35 36 | \BOOKMARK [1][-]{section.5.2}{Configuration 변경}{chapter.5}% 36 37 | \BOOKMARK [2][-]{subsection.5.2.1}{리소스 반영}{section.5.2}% 37 38 | \BOOKMARK [2][-]{subsection.5.2.2}{Configuration\040qualifier}{section.5.2}% 38 39 | \BOOKMARK [2][-]{subsection.5.2.3}{데이터 복구}{section.5.2}% 39 40 | \BOOKMARK [2][-]{subsection.5.2.4}{android:configChanges 속성}{section.5.2}% 40 41 | \BOOKMARK [2][-]{subsection.5.2.5}{Configuration 확인}{section.5.2}% 41 42 | \BOOKMARK [1][-]{section.5.3}{태스크}{chapter.5}% 42 43 | \BOOKMARK [2][-]{subsection.5.3.1}{태스크 상태}{section.5.3}% 43 44 | \BOOKMARK [2][-]{subsection.5.3.2}{태스크 확인}{section.5.3}% 44 45 | \BOOKMARK [2][-]{subsection.5.3.3}{taskAffinity}{section.5.3}% 45 46 | \BOOKMARK [2][-]{subsection.5.3.4}{태스크 속성 부여}{section.5.3}% 46 47 | \BOOKMARK [1][-]{section.5.4}{}{chapter.5}% 47 48 | \BOOKMARK [0][-]{chapter.6}{Service}{}% 48 49 | \BOOKMARK [1][-]{section.6.1}{Started\040Service}{chapter.6}% 49 50 | \BOOKMARK [2][-]{subsection.6.1.1}{Service 재시작 방식}{section.6.1}% 50 51 | \BOOKMARK [2][-]{subsection.6.1.2}{멀티 스레드 이슈}{section.6.1}% 51 52 | \BOOKMARK [2][-]{subsection.6.1.3}{외부 프로세스에서 암시적 인텐트로 Service 실행}{section.6.1}% 52 53 | \BOOKMARK [2][-]{subsection.6.1.4}{IntentService}{section.6.1}% 53 54 | \BOOKMARK [2][-]{subsection.6.1.5}{Service 중복 실행 방지}{section.6.1}% 54 55 | \BOOKMARK [1][-]{section.6.2}{Bound\040Service}{chapter.6}% 55 56 | \BOOKMARK [2][-]{subsection.6.2.1}{리모트 바인딩}{section.6.2}% 56 57 | \BOOKMARK [2][-]{subsection.6.2.2}{로컬 바인딩}{section.6.2}% 57 58 | \BOOKMARK [2][-]{subsection.6.2.3}{바인딩의 특성}{section.6.2}% 58 59 | \BOOKMARK [2][-]{subsection.6.2.4}{Messenger}{section.6.2}% 59 60 | \BOOKMARK [0][-]{chapter.7}{ContentProvider}{}% 60 61 | \BOOKMARK [1][-]{section.7.1}{SQLite}{chapter.7}% 61 62 | \BOOKMARK [2][-]{subsection.7.1.1}{DB Lock 문제}{section.7.1}% 62 63 | \BOOKMARK [2][-]{subsection.7.1.2}{SQLiteOpenHelper}{section.7.1}% 63 64 | \BOOKMARK [1][-]{section.7.2}{ContentProvider}{chapter.7}% 64 65 | \BOOKMARK [2][-]{subsection.7.2.1}{ContentProvider 적용}{section.7.2}% 65 66 | \BOOKMARK [2][-]{subsection.7.2.2}{배치 실행}{section.7.2}% 66 67 | \BOOKMARK [1][-]{section.7.3}{SQLite/ContentProvider 관련 팁}{chapter.7}% 67 68 | \BOOKMARK [2][-]{subsection.7.3.1}{쿼리 실행 확인}{section.7.3}% 68 69 | \BOOKMARK [2][-]{subsection.7.3.2}{ContentProvider 예외 확인}{section.7.3}% 69 70 | \BOOKMARK [0][-]{chapter.8}{BroadcastReceiver}{}% 70 71 | \BOOKMARK [1][-]{section.8.1}{BroadcastReceiver 구현}{chapter.8}% 71 72 | \BOOKMARK [1][-]{section.8.2}{BroadcastReceiver 등록}{chapter.8}% 72 73 | \BOOKMARK [1][-]{section.8.3}{Ordered\040Broadcast}{chapter.8}% 73 74 | \BOOKMARK [1][-]{section.8.4}{Sticky\040Broadcast}{chapter.8}% 74 75 | \BOOKMARK [1][-]{section.8.5}{LocalBroadcastManager}{chapter.8}% 75 76 | \BOOKMARK [1][-]{section.8.6}{App\040Widget}{chapter.8}% 76 77 | \BOOKMARK [2][-]{subsection.8.6.1}{App Widget 기본}{section.8.6}% 77 78 | \BOOKMARK [2][-]{subsection.8.6.2}{RemoteViews}{section.8.6}% 78 79 | \BOOKMARK [2][-]{subsection.8.6.3}{App Widget 업데이트}{section.8.6}% 79 80 | \BOOKMARK [2][-]{subsection.8.6.4}{유의할 점}{section.8.6}% 80 81 | \BOOKMARK [0][-]{chapter.9}{Application}{}% 81 82 | \BOOKMARK [1][-]{section.9.1}{앱 초기화}{chapter.9}% 82 83 | \BOOKMARK [1][-]{section.9.2}{Application\040Callback}{chapter.9}% 83 84 | \BOOKMARK [1][-]{section.9.3}{프로세스 분리}{chapter.9}% 84 85 | \BOOKMARK [0][-]{chapter.10}{시스템 서비스}{}% 85 86 | \BOOKMARK [1][-]{section.10.1}{시스템 서비스 기본}{chapter.10}% 86 87 | \BOOKMARK [1][-]{section.10.2}{dumpsys 명령어}{chapter.10}% 87 88 | \BOOKMARK [1][-]{section.10.3}{시스템 서비스 이슈}{chapter.10}% 88 89 | \BOOKMARK [2][-]{subsection.10.3.1}{빈번한 리모트 호출을 줄여야 함}{section.10.3}% 89 90 | \BOOKMARK [2][-]{subsection.10.3.2}{전원 관리와 Deep Sleep}{section.10.3}% 90 91 | \BOOKMARK [2][-]{subsection.10.3.3}{알람 등록과 제거}{section.10.3}% 91 92 | \BOOKMARK [0][-]{chapter.11}{구현 패턴}{}% 92 93 | \BOOKMARK [1][-]{section.11.1}{싱글톤 패턴}{chapter.11}% 93 94 | \BOOKMARK [1][-]{section.11.2}{마커 인터페이스}{chapter.11}% 94 95 | \BOOKMARK [1][-]{section.11.3}{Fragment 정적 생성}{chapter.11}% 95 96 | \BOOKMARK [1][-]{section.11.4}{AOP}{chapter.11}% 96 97 | -------------------------------------------------------------------------------- /latexbook/android_pdf.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,hidelinks,11pt, openany]{book} 2 | \usepackage[11pt]{moresize} % 11pt로 키우면서 fontsize 조정 필요 3 | \usepackage{kotex} %[hangul]을 추가하면 제 1장, 제 1절 식으로 들어감 4 | \usepackage{graphicx} 5 | \usepackage{upquote} 6 | \usepackage{listings} 7 | \usepackage{color} 8 | \usepackage{verbatim} 9 | \usepackage{hyperref} 10 | \usepackage[top=1in, bottom=1in, outer=2.5cm, inner=2.5cm, headsep=14pt]{geometry} %pdf 전용 11 | \usepackage[T1]{fontenc} 12 | \usepackage{textcomp} 13 | \usepackage{fancyhdr} 14 | \usepackage{dhucs-cmap} %pdf에서 한글 복사가 되는 옵션 15 | \usepackage{makecell} %표에서 한 셀에서 linebreak 16 | \usepackage{longtable} %여러 페이지에 걸쳐있는 테이블 17 | \usepackage{fontspec} 18 | \usepackage{amssymb} % 책 인용 위해서 19 | %\setmainfont{나눔고딕} % 이걸 쓰면 섹션 타이틀이 bold와 아닌 것이 섞인다. 20 | %\setmainfont{Times New Roman} % Roman (rm) 21 | %\setsansfont{Arial} % Sans-serif (sf) 22 | \setmonofont{Courier New} % Monospace (tt) 23 | 24 | \pagestyle{fancy} %상단에 페이지와 현재 절 표현하기 25 | 26 | \makeatletter 27 | \DeclareRobustCommand{\format@sec@number}[2]{{\normalfont\upshape#1}#2} 28 | \renewcommand{\chaptermark}[1]{% 29 | \markboth{\format@sec@number{\ifnum\c@secnumdepth>\m@ne\@chapapp\ \thechapter. \fi}{#1}}{}} 30 | \renewcommand{\sectionmark}[1]{% 31 | \markright{\format@sec@number{\ifnum\c@secnumdepth>\z@\thesection \fi}{ #1}}} 32 | \makeatother 33 | 34 | \fancyhf{} 35 | \fancyhead[RE]{\nouppercase{\leftmark}} 36 | \fancyhead[LO]{\nouppercase{\rightmark}} 37 | \fancyhead[LE,RO]{\thepage} 38 | 39 | %\usepackage[scale=0.75,twoside,bindingoffset=5mm,a4paper]{geometry} 40 | 41 | \renewcommand{\baselinestretch}{1.3} 42 | \renewcommand\theadalign{cb} 43 | \renewcommand\theadfont{\bfseries} 44 | \renewcommand\theadgape{\Gape[4pt]} 45 | \renewcommand\cellgape{\Gape[4pt]} 46 | 47 | \def\contentsname{차례} 48 | \definecolor{codegreen}{rgb}{0,0.6,0} 49 | \definecolor{codegray}{rgb}{0.3,0.3,0.3} 50 | \definecolor{backcolour}{rgb}{0.95,0.95,0.92} 51 | \definecolor{purple}{rgb}{0.5, 0.0, 0.5} 52 | \definecolor{burntorange}{rgb}{0.8, 0.33, 0.0} 53 | \definecolor{tearose}{rgb}{1.0, 0.92, 0.8} 54 | 55 | \renewcommand\lstlistingname{코드} 56 | 57 | \title{안드로이드의 정석} 58 | \author{노재춘} 59 | 60 | \begin{document} 61 | % 한글 맞춤법 https://search.naver.com/search.naver?where=nexearch&query=%ED%95%9C%EA%B8%80+%EB%A7%9E%EC%B6%A4%EB%B2%95+%EA%B2%80%EC%82%AC%EA%B8%B0&sm=top_sug.pre&fbm=0&acr=1&acq=%ED%95%9C%EA%B8%80+%EB%A7%9E%EC%B6%A4%EB%B2%95&qdt=0&ie=utf8 62 | 63 | %https://en.wikibooks.org/wiki/LaTeX/Labels_and_Cross-referencing 64 | % http://latexcolor.com 65 | \maketitle 66 | \parindent=0em 67 | 68 | \input{header} 69 | 70 | \tableofcontents 71 | 72 | %\newfontfamily\listingsfont[Scale=0.7]{Courier} 73 | %\newfontfamily\listingsfontinline[Scale=0.8]{Courier New} 74 | 75 | \lstset { 76 | language=java, 77 | basicstyle=\ttfamily\footnotesize, 78 | %basicstyle=\fontfamily{mono}\selectfont\small, 79 | backgroundcolor=\color{backcolour}, 80 | commentstyle=\color{codegreen}, 81 | keywordstyle=\color{purple}, 82 | tabsize=4, 83 | showspaces=false, 84 | showstringspaces=false, 85 | numberstyle=\color{codegray}, 86 | numbers=left, 87 | numbersep=5pt, 88 | stepnumber=5, 89 | firstnumber=1, 90 | breaklines=tr} 91 | \newpage 92 | 93 | \input{contents} 94 | 95 | \input{start} 96 | 97 | \input{singlethread} 98 | 99 | \input{context} 100 | 101 | \input{activity} 102 | %\input{activity2-1}\right 103 | %\input{activity3} 104 | %\section{안드로이드 SDK 도구} 105 | 106 | %%\input{tools} 107 | %%\input{intent} 108 | 109 | \input{service} 110 | \input{contentprovider} 111 | \input{broadcastreceiver} 112 | \input{application} 113 | \input{binder} 114 | \input{pattern} 115 | 116 | %\input{deepsleep} 117 | 118 | %\input{activitymanager} 119 | %\input{toast} 120 | 121 | % https://www.bookmake.co.kr/print/product.html?goods_cd=10000153# 122 | 123 | \bibliography{book} 124 | \bibliographystyle{plain} 125 | 126 | 127 | \end{document} 128 | -------------------------------------------------------------------------------- /latexbook/application.tex: -------------------------------------------------------------------------------- 1 | \chapter{Application} 2 | Application도 Activity나 Service와 마찬가지로 ContextWrapper를 상속한다. 3 | % Application은 다른 컴포넌트들과 상호 작용하는 것이 없으므로, 컴포넌트로 보기는 어렵다고 생각했는데, ComponentCallbacks 인터페이스 API에서는 Application도 Component라고 말하고 있다. 4 | Application은 단독으로 만들어져서 실행하지는 않고, 다른 컴포넌트가 실행 요청을 받으면 앱 프로세스가 생성되면서 Application이 먼저 생성되고 해당 컴포넌트가 그 다음에 실행된다. 5 | 즉 Activity, Service, BroadcastReceiver, ContentProvider 가운데서 어느 것이든지 외부에서(앱 아이콘, Notification, Alarm, 다른 앱) 실행하려고 할 때, Application이 이미 생성되어 있다면 생성하지 않고 아니면 Application을 먼저 생성한다. 그리고서 해당 컴포넌트를 시작한다. 6 | 앞에서도 설명했지만 Application의 onCreate()보다 ContentProvider의 onCreate()가 먼저 실행되는 것은 주의하도록 하자.\\ 7 | 8 | 프레임워크 소스에서는 ActivityThread의 handleBindApplication() 메서드에서 Application의 onCreate()를 실행한다. 9 | Context만 전달된다면 Context의 getApplicationContext() 메서드로 언제든지 Application 인스턴스를 구할 수 있다. 10 | Activity에서는 특히 getApplication() 메서드를 사용해서 가져오기도 한다. 11 | 12 | \section{앱 초기화} 13 | Application은 다른 컴포넌트보다 먼저 실행되기 때문에 앱을 위한 초기화 작업을 Application의 onCreate()에서 주로 실행한다. 14 | 그리고 onCreate() 메서드는 가능한 한 빨리 끝나야만 한다. 15 | 앱 아이콘을 클릭해서 Activity를 새로 시작할 때, Application의 onCreate()에서 시간이 오래 걸린다면 검은 화면이 오래 보이거나 화면이 늦게 뜨는 문제가 생긴다.\\ 16 | 17 | % SplashPage에서 초기화 하는 것과 비교 18 | Application은 앱 프로세스의 메인 클래스인 ActivityThread에 mInitialApplication이라는 인스턴스 변수로 있기 때문에, 앱 프로세스가 살아있는 동안 계속 남아 있는 인스턴스이다.\footnote{Back 키를 통해서 화면을 다 벗어났어도 프로세스가 바로 종료되지는 않는다. 이 상태에서는 다시 앱 아이콘을 클릭해도 Application의 onCreate()는 실행되지 않는다.} 19 | 따라서 global application state를 저장하기 좋은 조건을 가지고 있다.\\ 20 | 21 | Application의 일반적인 사용 패턴을 보도록 하자. 22 | \newpage 23 | \begin{lstlisting}[frame=single, caption=Application 샘플, label=application] 24 | public class ShoppingApplication extends Application { 25 | 26 | private static ShoppingApplication application; 27 | 28 | private ArrayList cart = new ArrayList(); // (1) 29 | 30 | private ProductRepository productRepository; // (2) 31 | 32 | @Override 33 | public void onCreate() { 34 | super.onCreate(); 35 | application = this; // (3) 36 | /* load application properties */ 37 | AppConfig.initialize(this); // (4) 38 | productRepository = new ProductRepository(this); 39 | startService(new Intent(this, CategoryUpdaterService.class)); // (5) 40 | } 41 | 42 | public ArrayList getCart() { 43 | return cart; 44 | } 45 | 46 | public void addCartProduct(CartProduct product) { 47 | if (!cart.contains(product)) { 48 | cart.add(product); 49 | } 50 | } 51 | 52 | public void clearCart() { 53 | cart.clear(); 54 | } 55 | 56 | public ProductRepository getProductRepository() { 57 | return productRepository; 58 | } 59 | 60 | public static ShoppingApplication getApplication() { // (6) 61 | return application; 62 | } 63 | 64 | } 65 | \end{lstlisting} 66 | \begin{itemize} 67 | \item 샘플을 위해서 5라인(1)에서 쇼핑 앱에서 쇼핑카드를 DB가 아닌 Application의 인스턴스 변수로 놓고, 이를 여러 곳에서 데이터 공유하도록 했다. 68 | Context의 getApplicationContext() 메서드나 Activity의 getApplication() 메서드로 가져와서 ShoppingApplication으로 캐스팅한다면, getCart(), addCartProduct(), clearCart() 같은 메서드를 호출할 수 있다. 69 | 70 | \item 7라인(2)에서 productRepository는 앱에서 단일 인스턴스로 사용하기로 한다. Application을 통해서만 인스턴스를 가져온다는 규칙을 정한다면, 싱글톤 패턴 대신 사용 가능한 방식이다. 71 | 72 | \item 14라인(4)에서는 앱을 위한 설정 초기화 작업을 하고 있다. Context가 전달되어야 하는 이런 작업들이 있다. 73 | 외부 라이브러리를 사용하는 경우에는 Application의 onCreate()에서 라이브러리에 필요한 초기화를 하는 경우가 많다. 74 | 다른 컴포넌트에 앞서서 Application의 onCreate()가 실행되기 때문에 `이른 초기화'가 필요한지 `늦은 초기화'로 해도 되는지 구분이 필요하다. 75 | 예를 들어, 프로세스가 뜨면서 App Widget 하나만 실행하는 데도 Activity에서만 필요한 초기화 작업도 하고 있다면 불필요하게 메모리와 시간을 허비하게 된다. 76 | 77 | \item 16라인(5)에서 startService()를 통해서 역시 앱을 위한 초기화 작업을 진행한다. 일반적으로 백그라운드 스레드로 처리하는 작업을 안정적으로 처리하기 위해서 Service를 이용한다. 78 | 79 | \item 12라인(3), 37라인(6)은 좋은 방법은 아닌 듯 하지만 많이 쓰는 방법이다. 80 | 메서드 호출 단계가 깊어지면서, 여러 클래스를 거칠 때 계속해서 생성자나 메서드에 Context를 파라미터에 전달하는 것은 번거롭다. 81 | 이때 ShoppingApplication.getApplication() 메서드를 통해서 Application 또는 Context에 접근할 수 있다. 82 | 예를 들어, InputValidator 클래스에서 부적절한 입력을 받으면 특정 문자열을 리턴하는 메서드가 있다면, ShoppingApplication.getApplication().getText(R.string.invalid\_input) 식으로 사용할 수 있다. 83 | \end{itemize} 84 | 85 | 많은 문서에서 쇼핑카트의 예처럼 데이터 공유에 Application을 사용하면 된다고 하지만, 이 방식도 문제가 있다. 86 | 메모리가 적거나 다른 프로세스가 사용자에게 즉각적으로 반응해야 할 때, 프로세스는 종료됐다가 재시작되기도 한다. 87 | 이때 Application의 onCreate()는 다시 호출되지만, addCartProduct() 메서드로 추가해놓은 쇼핑카트 목록은 사라져 버린다. 88 | 이런 케이스도 있다는 것을 알고 데이터 공유에 신중해야 한다. 89 | 결론적으로 사라져버려도 문제가 없고 남아있으면 유용한 캐시같은 것이 Appplication의 데이터 공유에 적용되는 것이 맞다. 90 | 91 | \section{Application Callback} 92 | Application에서는 onCreate() 메서드만 오버라이드하는 경우가 많지만, 93 | 다른 메서드도 유용한 경우가 있으니 어떤 것이 있는지 살펴보자. 94 | 참고로 onTerminated() 메서드는 emulated process 용도에서나 동작하고 실제 단말에서는 동작하지 않는다. 이 메서드는 무시하는 게 좋다. 95 | % 얘기 하는 이유는 여러 책에서 언급이 돼 있어서 96 | 메서드 가운데서 register/unregister 메서드를 제외하고는 ComponentCallbacks2 인터페이스의 3개의 메서드가 있다. 97 | Application과 Activity뿐만 아니라 Service와 ContentProvider, Fragment도 ComponentCallbacks2 인터페이스를 구현하고 있다.\\ 98 | 99 | Application에서 3개의 메서드는 ICS 이전에는 비어있는 메서드였고, ICS부터는 기본 동작이 registerComponentCallbacks() 메서드를 통해 동록된 콜백의 메서드를 다시 호출하고 있다. 100 | 따라서 오버라이드할 때는 super.onXxx() 메서드도 호출해야 한다. 101 | 이제 ComponentCallbacks2 인터페이스의 메서드를 각각 살펴보자. 102 | \begin{itemize} 103 | \item onConfigurationChanged(Configuration newConfig): Activity 외에 다른 컴포넌트는 Configuration이 변경되어도 재시작하지 않고 onConfigurationChanged() 메서드가 불린다. 104 | 그 가운데서 Application의 onConfigurationChanged()가 먼저 실행된다. 105 | 필요한 경우를 생각해보면, 캘린더 앱 실행 중에 환경설정에서 언어가 변경되었을 때 휴일 정보를 새로 업데이트해야 한다면, 여기에서 startService()를 실행해서 휴일 정보를 Service에서 가져오면 된다. 106 | 107 | \item onLowMemory(): 전체 시스템에 메모리가 부족해서 백그라운드 프로세드들이 모두 kill될 가능성이 있을 때 호출된다. 잡고 있는 캐시나 불필요한 리소스를 해제(release)하는 작업을 하면 된다. 108 | 리턴 이후에 GC가 실행된다. ICS 이상에서는 onTrimMemory()에서 관련 로직을 처리하는 것을 권장한다. 109 | % 재현이 쉽지 않다. 110 | 111 | \item onTrimMemory(int level): ICS부터 사용 가능하다. 파라미터에 전달되는 level에 따라 onLowMemory() 메서드보다 세분화해서 처리할 수 있다. level에는 ComponentCallbacks2의 상수값이 전달된다. 112 | \end{itemize} 113 | 114 | Application Callback은 3가지가 있다. 모두 register/unregister 메서드가 있고, Callback은 여러 개를 등록할 수 있다.\\ 115 | 116 | Application.ActivityLifecycleCallbacks(ICS), \\ 117 | ComponentCallbacks(ICS), \\ 118 | Application.OnProviderAssistDataListener(젤리빈 API 레벨 18)\\ 119 | 120 | ComponentCallbacks는 ICS 이전에서 Application에 onConfigurationChanged(), onLowMemory() 메서드로 있던 것을 ICS 이상에서는 onTrimMemory() 메서드가 추가되고 Callback을 여러 개 등록할 수 있게 하였다.\\ 121 | 122 | onTrimMemory()는 ComponentCallbacks가 아닌 이를 상속한 ComponentCallbacks2 인터페이스에 있으므로 onTrimMemory()를 쓸 때는 registerComponentCallbacks()에 ComponentCallbacks2 구현체를 넘기면 된다.\\ 123 | 124 | \begin{comment} 125 | ComponentCallbacks가 필요한 경우를 생각해보자. 126 | 예를 들어, ListView에서 스크롤하면서 현재 스크롤된 화면에 맞게 네트워크나 DB를 통해서 데이터를 불러오는 기능이 있다. 127 | 이때 이전에 스크롤했던 위치로 돌아갈 때 다시 데이터를 불러오면 속도가 그리 좋지 않으니, 한번 가져온 것은 캐시에 저장하는 경우가 있다. 128 | 만일 메모리 상태가 좋지 않다면 onTrimMemory()에서 level에 따라 캐시에 저장된 데이터량을 조금씩 줄이는 방법으로 ComponentCallbacks/ComponentCallbacks2를 사용할 수 있다 129 | Activity와 Service도 ComponentCallbacks2 인터페이스를 구현하고 있으므로 동일한 방식을 사용할 수 있다. 130 | \end{comment} 131 | 132 | Application.ActivityLifecycleCallbacks은 Activity의 생명주기마다 대응하는 메서드가 있고, 생명주기가 끝날 때마다 호출한다. 133 | 앱에서 메모리 문제가 있을 때, 어떤 Activity의 어느 생명주기에서 메모리가 크게 증가하는지 확인하기 위해 ActivityLifecycleCallbacks에 로그를 남기는 방법을 생각할 수 있다. 134 | \begin{lstlisting}[frame=single] 135 | @Override 136 | public void onCreate() { 137 | ... 138 | registerActivityLifecycleCallbacks(activityLifecycleCallbacks); // (1) 139 | } 140 | 141 | private ActivityLifecycleCallbacks activityLifecycleCallbacks 142 | = new ActivityLifecycleCallbacks() { // (2) 143 | 144 | @Override 145 | public void onActivityCreated(Activity activity, 146 | Bundle savedInstanceState) { 147 | logMemInfo(activity, "create"); 148 | } 149 | 150 | @Override 151 | public void onActivityStarted(Activity activity) { 152 | logMemInfo(activity, "start"); 153 | } 154 | 155 | @Override 156 | public void onActivityResumed(Activity activity) { 157 | logMemInfo(activity, "resume"); 158 | } 159 | 160 | @Override 161 | public void onActivityPaused(Activity activity) { 162 | logMemInfo(activity, "pause"); 163 | } 164 | 165 | 166 | @Override 167 | public void onActivityStopped(Activity activity) { 168 | logMemInfo(activity, "stop"); 169 | } 170 | 171 | @Override 172 | public void onActivitySaveInstanceState(Activity activity, 173 | Bundle outState) { 174 | logMemInfo(activity, "saveInstanceState"); 175 | } 176 | 177 | @Override 178 | public void onActivityDestroyed(Activity activity) { 179 | logMemInfo(activity, "destroy"); 180 | } 181 | 182 | private void logMemInfo(Activity activity, String method) { // (3) 183 | MemoryInfo mi = new MemoryInfo(); 184 | Debug.getMemoryInfo(mi); 185 | 186 | Log.d(TAG, activity.getClass().getSimpleName() + " " + method 187 | + " phase MemoryInfo(total) pss=" + mi.getTotalPss() 188 | + ", sharedDirty=" + mi.getTotalSharedDirty() 189 | + ", privateDirty=" + mi.getTotalPrivateDirty() + "."); 190 | } 191 | 192 | }; 193 | \end{lstlisting} 194 | \begin{itemize} 195 | \item 4라인(1)에서 ActivityLifecycleCallbacks를 등록한다. 196 | \item 8라인(2)부터 ActivityLifecycleCallbacks 인터페이스를 구현한 익명 클래스를 만든다. Activity의 각 생명주기마다 대응하는 메소드가 있다. 197 | \item 48라인(3)에서 logMemInfo() 메서드는 메모리 정보를 로그로 남긴다. 각 생명주기마다 logMemInfo()를 호출하고 있으므로 생명주기 메서드 실행 후의 메모리 정보를 알 수 있다. 198 | \end{itemize} 199 | 200 | \begin{comment} 201 | startService 하는 경우가 있는데, 어차피 시작 Activity의 onResume 이후에 실행된다. 따라서 Application에 꼭 넣을 것인지, 시작 Activity에 할 것인지 따져봐야 한다. 202 | 왜냐하면 Application.onCreate는 앱이 죽어 있을 때도 BroadcastReceiver, Service, ContentProvider를 다른 앱에서 사용하고자 할 때도 실행하기 때문이다. 203 | 204 | restore/full backup일때는 우리가 만든 Application.onCreate를 타지 않는다. 205 | 206 | ActivityThread.main 207 | ActivityThread.attach 208 | ActivityManagerService.attachApplication 209 | ActivityManagerService.attacheApplicationLocked 210 | IApplicationThread.bindApplication 211 | 212 | \end{comment} 213 | 214 | \section{프로세스 분리} 215 | 각 프로세스별로 사용 가능한 메모리는 제한되어 있다. 안드로이드는 버전별/단말별로 메모리 제한에 차이가 있다. 216 | 허니콤부터는 anroid:largeHeap 옵션도 쓸 수 있지만 단말에 따라서 이 옵션이 소용없는 경우도 있고(제조사별로 차이가 있음), 이 옵션으로 인해 GC 시간이 오래 걸리거나 다른 앱의 실행에 악영향을 줄 수도 있다.\\ 217 | 218 | 주로 메모리 이슈 때문에 프로세스를 분리하는데, Activity/Service/ContentProvider/BroacastReceiver는 모두 프로세스 분리가 가능하다. 219 | 동일 패키지에서 생성된 별도 프로세스는 pid(process id)는 다르지만, 동일한 uid(user id)를 가지고 동일하게 파일과 리소스에 접근할 수 있다.\\ 220 | 221 | 이제 프로세스 분리가 필요한 경우를 생각해보자. 222 | \begin{itemize} 223 | \item Activity 자체가 무거운 경우이다. 사진공유 앱에서 기본 카메라 앱을 실행시키지 않고 자신의 카메라 Activity를 가지고 있는 경우가 있는데, 이런 카메라 Activity는 별도 프로세스로 분리하는 것이 낫다. 224 | 225 | \item Activity가 무겁지는 않지만 동시에 Service에서 백그라운드 스레드로 작업을 진행한다면 OutOfMemoryError가 발생할 수 있다. 226 | 테스트 시에 에러가 잘 나지 않다가도 사용자 단말에서 가령 10\%라도 발생한다면, 이때는 Service나 Activity의 프로세스를 분리할 것인지 고민해야 한다. 227 | Service가 독립적인 부분이 많다면 Service를 분리하는 게 낫다. 228 | 만일 Service에 DB 작업이 많다면 DB Lock 문제 때문에 ContentProvider를 도입해야 하므로, 작업이 복잡해질 경우 Activity를 분리하는 게 좋겠다. 정답은 없고 케이스별로 다르다. 229 | % 분리함으로써 변경이 많이 필요한 쪽보다는 간단하게 분리 가능하는 게 나을 것이다. 230 | 231 | \item App Widget의 사이즈가 클 때는(4x4, 5x5 등), App Widget도 프로세스 분리를 고려해보자. 232 | \end{itemize} 233 | 234 | Application도 AndroidManifest.xml에 android:process 값을 넣을 수는 있는데, 이 값은 앱의 컴포넌트가 실행되는 기본 프로세스를 얘기하는 것으로 프로세스 분리와는 의미가 다르다. 쓸 일이 거의 없다.\\ 235 | 236 | 컴포넌트의 프로세스를 분리하면 Application은 각 프로세스마다 새로 뜬다. 프로세스가 달라지면 Zygote에 의해서 fork된 이후에 새로 ActivityThread를 띄우고 Application을 새로 생성하는 것이다. 237 | 설명을 위해서 \pageref{application} 페이지의 코드 \ref{application}을 다시 한번 보자. 238 | 앱에는 ShoppingApplication과 CategoryUpdaterService(디폴트 프로세스)가 있고, ActivityA(메인 Activity, 디폴트 프로세스)와 Activity\-B(and\-roid:process=``:ca\-mera'')가 있다고 하자. 239 | ActivityA에서 버튼을 클릭하면 ActivityB가 뜬다고 할 때, 각각 어떤 프로세스에서 실행되는지 확인해보자. 240 | 241 | \begin{enumerate} 242 | \item ShoppingApplication - 디폴트 프로세스 243 | \item ActivityA - 디폴트 프로세스 244 | \item CategoryUpdaterService - 디폴트 프로세스(이제 ActivityA에서 ActivityB를 띄운다.) 245 | \item ShoppingApplication - :camera 프로세스 246 | \item ActivityB - :camera 프로세스 247 | \item CategoryUpdaterService - 디폴트 프로세스 248 | \end{enumerate} 249 | 250 | 컴포넌트가 프로세스 분리되어 있어도 Application만은 어느 프로세스에서도 한번은 실행되어야 한다(4). 251 | 그리고 또 하나 주목할 부분은 분리된 프로세스에서 Application에서 띄우는 CategoryUpdaterService는 다시 디폴트 프로세스에서 실행된다는 점이다(6).\\ 252 | 253 | 프로세스 분리된 컴포넌트가 뜰 때마다 Application의 onCreate() 메서드는 매번 호출될 가능성이 있고, 가령 Application의 onCreate() 메서드에서 Service를 시작한다면 이 Service가 자주 실행될 가능성이 있다. 254 | 기본 프로세스에서만 Service가 동작하게 하려면 아래처럼 작성할 수 있다. 255 | 256 | \begin{lstlisting}[frame=single] 257 | @Override 258 | public void onCreate() { 259 | ... 260 | if (isDefaultProcess()) { 261 | startService(new Intent(this, CategoryUpdaterService.class)); 262 | } 263 | } 264 | 265 | private boolean isDefaultProcess() { 266 | int pid = android.os.Process.myPid(); 267 | ActivityManager activityManager = (ActivityManager) getSystemService( 268 | Context.ACTIVITY_SERVICE); 269 | List processInfos 270 | = activityManager.getRunningAppProcesses(); 271 | 272 | for (RunningAppProcessInfo each : processInfos) { 273 | if (each.pid == pid && each.processName.equals(getPackageName())) { 274 | return true; 275 | } 276 | } 277 | return false; 278 | } 279 | \end{lstlisting} 280 | 281 | 프로세스 분리시 주의할 점을 얘기해보자. 282 | 당연한 얘기지만 간과해서는 안되는 게 메모리가 공유되지 않는 것이다. 283 | 그래서 프로세스에서 캐시 용도로 만든 것이든 싱글톤 인스턴스에서 공유하는 값이든 분리된 별도 프로세스에서는 소용이 없다. 284 | 앱을 만들다가 프로세스 분리한 것을 잊고서 왜 값이 안 나오는지 쓸데없이 고민한 경험을 가진 개발자도 있을 것이다. 285 | 이때는 값을 따로 다시 로딩하는 방법을 사용하거나, 값을 공유하기 위해서 SharedPreferences나 DB를 사용해야 한다.\\ 286 | 287 | 한편, 분리된 프로세스에서 각각 SharedPreferences를 쓸 때 288 | SharedPreferences는 어차피 xml 파일에서 읽어오는 것이기 때문에 한번 읽어오는 것은 문제가 되지 않는다. 289 | 그런데 다른 프로세스에서 값을 변경한 것을 다시 가져오는 것은 문제가 단순하지 않다. 290 | 허니콤 이후에는 Context의 getSharedPreferences(String name, int mode) 메서드를 사용할 때, mode에 291 | Context.MODE\_PRIVATE을 전달하면 값을 제대로 가져올 수 없다. 292 | 진저브레드까지는 multi process 용도로 사용이 가능했지만, 그 이후에는 Context.MODE\_MULTI\_PROCESS를 사용하지 않으면 다른 프로세스에서 변경된 값을 읽어올 수 없다.\\ 293 | 294 | 이해를 돕기 위해서 샘플로 확인해보자.\\ 295 | \underline{\bfseries 프로세스 A: 쓰기} 296 | \begin{lstlisting}[frame=single] 297 | SharedPreferences sharedPreferences = getSharedPreferences("MyPrefernce", 298 | Context.MODE_PRIVATE); 299 | SharedPreferences.Editor editor = sharedPreferences.edit(); 300 | int value = new Random().nextInt(1000); 301 | editor.putInt("randomValue", value); 302 | editor.apply(); 303 | Toast.makeText(this, "write value=" + value, Toast.LENGTH_LONG).show(); 304 | \end{lstlisting} 305 | 306 | \underline{\bfseries 프로세스 B: 읽기} 307 | \begin{lstlisting}[frame=single] 308 | SharedPreferences sharedPreferences = getSharedPreferences("MyPrefernce", 309 | Context.MODE_PRIVATE); 310 | int value = sharedPreferences.getInt("randomValue", 0); 311 | Toast.makeText(this, "read value=" + value, Toast.LENGTH_LONG).show(); 312 | \end{lstlisting} 313 | 314 | 프로세스 A에서 쓰기를 하고 프로세스 B에서 읽기를 하면 한번은 제대로 동작한다. 315 | 그런데 또 반복해서 쓰기와 읽기를 하면 어떤 일이 벌어질까? 316 | 프로세스 B에서는 첫 번째에 읽었던 값을 여전히 출력한다. 317 | 이때 읽는 쪽에서 Context.MODE\_PRIVATE을 Context.MODE\_MULTI\_PROCESS로 바꾸기만 하면, 매번 reload해서 값을 읽어오게 된다. 318 | 프레임워크 소스에서 Context.MODE\_MULTI\_\-PROCESS 용도는 SharedPreferences reload 외에는 없다. 319 | 즉 쓰기를 하는 쪽에서는 이 옵션을 쓸 필요가 없다.\\ 320 | 321 | 그런데 마시멜로에서는 다시 Context.MODE\_MULTI\_\-PROCESS를 deprecation시켰다. 322 | 결론적으로 권장되는 것은 SharedPreferences를 다시 ContentProvider로 감싸서 다른 프로세스에서 접근하는 것이고, 323 | ContentObserver를 등록해서 데이터가 변경되면 requery하는 방식이다. 324 | %SharedPreferences에 관련한 자세한 이야기는 뒤에 다시 다루도록 하자. 325 | 326 | % android:multiprocess 옵션은 Activity와 ContentProvider만 있음. 327 | 328 | -------------------------------------------------------------------------------- /latexbook/appstore_chap1.nav: -------------------------------------------------------------------------------- 1 | \beamer@endinputifotherversion {3.26pt} 2 | \headcommand {\slideentry {0}{0}{1}{1/1}{}{0}} 3 | \headcommand {\beamer@framepages {1}{1}} 4 | \headcommand {\slideentry {0}{0}{2}{2/2}{}{0}} 5 | \headcommand {\beamer@framepages {2}{2}} 6 | \headcommand {\slideentry {0}{0}{3}{3/3}{}{0}} 7 | \headcommand {\beamer@framepages {3}{3}} 8 | \headcommand {\slideentry {0}{0}{4}{4/4}{}{0}} 9 | \headcommand {\beamer@framepages {4}{4}} 10 | \headcommand {\slideentry {0}{0}{5}{5/5}{}{0}} 11 | \headcommand {\beamer@framepages {5}{5}} 12 | \headcommand {\slideentry {0}{0}{6}{6/6}{}{0}} 13 | \headcommand {\beamer@framepages {6}{6}} 14 | \headcommand {\slideentry {0}{0}{7}{7/7}{}{0}} 15 | \headcommand {\beamer@framepages {7}{7}} 16 | \headcommand {\slideentry {0}{0}{8}{8/8}{}{0}} 17 | \headcommand {\beamer@framepages {8}{8}} 18 | \headcommand {\slideentry {0}{0}{9}{9/9}{}{0}} 19 | \headcommand {\beamer@framepages {9}{9}} 20 | \headcommand {\slideentry {0}{0}{10}{10/10}{}{0}} 21 | \headcommand {\beamer@framepages {10}{10}} 22 | \headcommand {\slideentry {0}{0}{11}{11/11}{}{0}} 23 | \headcommand {\beamer@framepages {11}{11}} 24 | \headcommand {\slideentry {0}{0}{12}{12/12}{}{0}} 25 | \headcommand {\beamer@framepages {12}{12}} 26 | \headcommand {\slideentry {0}{0}{13}{13/13}{}{0}} 27 | \headcommand {\beamer@framepages {13}{13}} 28 | \headcommand {\slideentry {0}{0}{14}{14/14}{}{0}} 29 | \headcommand {\beamer@framepages {14}{14}} 30 | \headcommand {\slideentry {0}{0}{15}{15/15}{}{0}} 31 | \headcommand {\beamer@framepages {15}{15}} 32 | \headcommand {\slideentry {0}{0}{16}{16/16}{}{0}} 33 | \headcommand {\beamer@framepages {16}{16}} 34 | \headcommand {\slideentry {0}{0}{17}{17/17}{}{0}} 35 | \headcommand {\beamer@framepages {17}{17}} 36 | \headcommand {\slideentry {0}{0}{18}{18/18}{}{0}} 37 | \headcommand {\beamer@framepages {18}{18}} 38 | \headcommand {\slideentry {0}{0}{19}{19/19}{}{0}} 39 | \headcommand {\beamer@framepages {19}{19}} 40 | \headcommand {\slideentry {0}{0}{20}{20/20}{}{0}} 41 | \headcommand {\beamer@framepages {20}{20}} 42 | \headcommand {\slideentry {0}{0}{21}{21/21}{}{0}} 43 | \headcommand {\beamer@framepages {21}{21}} 44 | \headcommand {\slideentry {0}{0}{22}{22/22}{}{0}} 45 | \headcommand {\beamer@framepages {22}{22}} 46 | \headcommand {\slideentry {0}{0}{23}{23/23}{}{0}} 47 | \headcommand {\beamer@framepages {23}{23}} 48 | \headcommand {\slideentry {0}{0}{24}{24/24}{}{0}} 49 | \headcommand {\beamer@framepages {24}{24}} 50 | \headcommand {\slideentry {0}{0}{25}{25/25}{}{0}} 51 | \headcommand {\beamer@framepages {25}{25}} 52 | \headcommand {\slideentry {0}{0}{26}{26/26}{}{0}} 53 | \headcommand {\beamer@framepages {26}{26}} 54 | \headcommand {\slideentry {0}{0}{27}{27/27}{}{0}} 55 | \headcommand {\beamer@framepages {27}{27}} 56 | \headcommand {\slideentry {0}{0}{28}{28/28}{}{0}} 57 | \headcommand {\beamer@framepages {28}{28}} 58 | \headcommand {\slideentry {0}{0}{29}{29/29}{}{0}} 59 | \headcommand {\beamer@framepages {29}{29}} 60 | \headcommand {\slideentry {0}{0}{30}{30/30}{}{0}} 61 | \headcommand {\beamer@framepages {30}{30}} 62 | \headcommand {\slideentry {0}{0}{31}{31/31}{}{0}} 63 | \headcommand {\beamer@framepages {31}{31}} 64 | \headcommand {\slideentry {0}{0}{32}{32/32}{}{0}} 65 | \headcommand {\beamer@framepages {32}{32}} 66 | \headcommand {\slideentry {0}{0}{33}{33/33}{}{0}} 67 | \headcommand {\beamer@framepages {33}{33}} 68 | \headcommand {\slideentry {0}{0}{34}{34/34}{}{0}} 69 | \headcommand {\beamer@framepages {34}{34}} 70 | \headcommand {\slideentry {0}{0}{35}{35/35}{}{0}} 71 | \headcommand {\beamer@framepages {35}{35}} 72 | \headcommand {\slideentry {0}{0}{36}{36/36}{}{0}} 73 | \headcommand {\beamer@framepages {36}{36}} 74 | \headcommand {\slideentry {0}{0}{37}{37/37}{}{0}} 75 | \headcommand {\beamer@framepages {37}{37}} 76 | \headcommand {\slideentry {0}{0}{38}{38/38}{}{0}} 77 | \headcommand {\beamer@framepages {38}{38}} 78 | \headcommand {\beamer@partpages {1}{38}} 79 | \headcommand {\beamer@subsectionpages {1}{38}} 80 | \headcommand {\beamer@sectionpages {1}{38}} 81 | \headcommand {\beamer@documentpages {38}} 82 | \headcommand {\def \inserttotalframenumber {38}} 83 | -------------------------------------------------------------------------------- /latexbook/appstore_chap1.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/appstore_chap1.out -------------------------------------------------------------------------------- /latexbook/appstore_chap1.snm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/appstore_chap1.snm -------------------------------------------------------------------------------- /latexbook/appstore_chap1.tex: -------------------------------------------------------------------------------- 1 | \documentclass{beamer} 2 | 3 | \usetheme{CambridgeUS} 4 | \usefonttheme[onlylarge]{structurebold} 5 | \setbeamerfont*{frametitle}{size=\normalsize,series=\bfseries} 6 | \setbeamertemplate{navigation symbols}{} 7 | 8 | \usepackage{kotex} 9 | \usepackage{listings} 10 | 11 | \definecolor{codegreen}{rgb}{0,0.6,0} 12 | \definecolor{codegray}{rgb}{0.3,0.3,0.3} 13 | \definecolor{backcolour}{rgb}{0.95,0.95,0.92} 14 | \definecolor{purple}{rgb}{0.5, 0.0, 0.5} 15 | 16 | \newcommand\Fontvi{\fontsize{8}{9.6}\selectfont} 17 | 18 | \begin{document} 19 | 20 | \lstset { 21 | language=java, 22 | backgroundcolor=\color{backcolour}, 23 | commentstyle=\color{codegreen}, 24 | keywordstyle=\color{purple}, 25 | tabsize=4, 26 | showspaces=false, 27 | showstringspaces=false, 28 | breaklines=true 29 | } 30 | 31 | \title{안드로이드 컴포넌트의 이해} 32 | \author[노재춘]{\texttt{suribada@gmail.com}} 33 | \date[\today]{네이버 지도지역서비스개발랩} 34 | 35 | \begin{frame} 36 | \titlepage 37 | \end{frame} 38 | 39 | \begin{frame} 40 | SQLite DB Lock 문제는 왜 발생하고, 해결 방법은 무엇인가? 41 | \end{frame} 42 | 43 | \setbeamercovered{transparent=20} 44 | \begin{frame} 45 | \frametitle{SQLite 기본} 46 | \begin{itemize} 47 | \item 시퀄라이트인가? 에스큐엘라이트냐? 48 | \item Thread에서 쿼리를 실행하는 것이 좋은가? 49 | \end{itemize} 50 | \end{frame} 51 | 52 | \begin{frame} 53 | \frametitle{군더더기 코드들} 54 | \begin{itemize} 55 | \item 쿼리를 실행하는 메소드마다 synchronized를 해놓는다. 56 | \item 단순 쿼리조차도 DB Transaction을 해놓는다. 57 | \end{itemize} 58 | \end{frame} 59 | 60 | \begin{frame} 61 | \frametitle{문제들} 62 | \begin{itemize} 63 | \item android.database.sqlite.SQLiteDatabaseLockedException이 발생하지만 원인을 알 수 없고, 재현이 잘 안 된다. 64 | \item 왜 사용자들은 crash가 많을까? 65 | \item 타이밍 이슈는 언제나 힘들다. 66 | \end{itemize} 67 | \end{frame} 68 | 69 | \begin{frame} 70 | \frametitle{Lock 기본 내용} 71 | \begin{itemize} 72 | \item 읽기 할 때는 Shared Lock을 잡는다. 다른 Shared Lock과 공존할 수 있다. 73 | \item 쓰기 할 때는 Exclusive Lock을 잡는다. 다른 Lock을 허용하지 않는다. 74 | \end{itemize} 75 | \end{frame} 76 | 77 | \begin{frame} 78 | \frametitle{Lock의 상태 5가지(1)} 79 | \begin{itemize} 80 | \item UNLOCKED: 기본상태로 읽기나 쓰기가 안 된다. 81 | \item SHARED: 읽기만 되고 쓰기는 안 된다. 동시에 여러 개의 프로세스가 Shared Lock을 가질 수 있다. 하나 이상의 Shared Lock이 활성화 되어 있다면, 다른 프로세스에서 쓰기를 할 수 없다. 쓰기를 위해서는 Shared Lock이 모두 해제될 때까지 대기한다. 82 | \item RESERVED: 프로세스가 미래 어느 시점에 쓰기를 하려고 한다는 일종의 Flag Lock이다. Reserved Lock은 한번에 하나의 Reserved Lock만 있을 수 있으며, 여러 개의 Shared Lock과 공존할 수 있다. Reserved Lock 상태에서는 새로운 Shared Lock을 더 잡을 수도 있다. 83 | \end{itemize} 84 | \end{frame} 85 | 86 | \begin{frame} 87 | \frametitle{Lock의 상태 5가지(2)} 88 | \begin{itemize} 89 | \item PENDING: 가능한한 빨리 Lock을 잡고 있는 프로세스가 쓰기를 하려고 한다. 즉, 현재의 모든 Shared Lock이 clear되면서 Exclusive Lock을 가지려 한다. Pending Lock 상태에서는 이미 존재하는 Shared Lock은 허용되지만, 새로운 Shared Lock을 잡을 수는 없다. 90 | \item EXCLUSIVE: 파일에 쓰기 위해서 필요하다. 오직 하나의 Exclusive Lock만 허용되고, 다른 Lock은 공존할 수 없다. SQLite에서는 동시성을 높이기 위해서 Exclusive Lock을 잡는 시간을 최소화하려 하는데, 우리가 만드는 코드 내에서도 Exclusive Lock 구간을 줄이도록 노력해야 한다. 91 | \end{itemize} 92 | \end{frame} 93 | 94 | \begin{frame} 95 | \frametitle{Lock 문제 재현} 96 | \begin{itemize} 97 | \item https://github.com/touchlab/Android-Database-Locking-Collisions-Example 98 | \item 에러가 발생해야 하는데 안 날 수도 있다. 이 때는 스레드 갯수를 늘려보면 반드시 발생시킬 수 있다. 99 | \end{itemize} 100 | \end{frame} 101 | 102 | \begin{frame} 103 | \frametitle{Lock 문제 재현을 통한 결론} 104 | \begin{itemize} 105 | \item 여러 스레드에서 별도의 SQLiteDatabase 인스턴스를 가지고 있으면 읽기와 쓰기가 함께 있는 경우 Lock 에러를 발생시킨다. 106 | \item 반대로, 여러 스레드에서도 오직 하나의 SQLiteDatabase 인스턴스 만을 가지고 명령을 실행한다면, Lock 에러는 발생하지 않는다. 107 | \end{itemize} 108 | \end{frame} 109 | 110 | \begin{frame} 111 | \frametitle{왜 그럴까?} 112 | \begin{itemize} 113 | \item 안드로이드에서 SQLite의 default threading mode가 serialized이기 때문이다. 즉 명령어들은 순차적으로 실행된다. 114 | \item threading mode 관련한 내용은 SQLite 사이트를 참고하자. http://www.sqlite.org/threadsafe.html 115 | \end{itemize} 116 | \end{frame} 117 | 118 | \begin{frame} 119 | \frametitle{하나의 인스턴스만?} 120 | \begin{itemize} 121 | \item SQLiteDatabase 인스턴스를 하나만 가지고서 serialized mode로 동작한다면 속도가 느려지진 않을까? 결론적으로 그렇다.\\ 122 | 쓰기와 읽기가 함께 있는 것이라면 serialized mode가 되어야만 Lock 문제가 없기 때문에 하나의 인스턴스를 사용해야 하지만, 읽기만 있다면 굳이 하나의 인스턴스를 고집할 필요가 없다. 123 | \end{itemize} 124 | \end{frame} 125 | 126 | \begin{frame} 127 | DB Lock과 Transaction은 왜 한 묶음으로 얘기하는 것일까? 128 | \end{frame} 129 | 130 | \begin{frame} 131 | \frametitle{DB Lock과 Transaction} 132 | \begin{itemize} 133 | \item 일반적인 CUD에서는 Lock을 잡아봐야 아주 짧은 시간이기 때문에 큰 영향을 주지는 않는다. 가장 Lock을 오래 잡을 수 있는 케이스로는 쓰기를 한꺼번에 해야 하는 Transaction이다. 134 | \end{itemize} 135 | \end{frame} 136 | 137 | \begin{frame} 138 | \frametitle{Transaction 사용하기} 139 | \begin{itemize} 140 | \item 필요한 경우에는 가능하면 쓰는 것이 좋다. \\ 141 | ex) 여러 데이터를 한꺼번에 입력하기 142 | \item 케이스마다 다르지만, 실행 속도가 10분의 1 이하로 줄어들기도 한다. 143 | \end{itemize} 144 | \end{frame} 145 | 146 | \begin{frame}[fragile] 147 | \frametitle{Transaction 기본 패턴} 148 | \begin{verbatim} 149 | db.beginTransaction(); 150 | try { 151 | ... 152 | db.setTransactionSuccessful(); 153 | } catch (Exception e) { 154 | ... 155 | } finally { 156 | db.endTransaction(); 157 | } 158 | \end{verbatim} 159 | \end{frame} 160 | 161 | \begin{frame} 162 | \frametitle{SQLite Transaction mode(1)} 163 | \begin{itemize} 164 | \item deferred, immediate, exclusive의 세 가지 모드를 사용하고, 디폴트는 deferred이다.\\(http://sqlite.org/lang\_transaction.html) 165 | \end{itemize} 166 | \end{frame} 167 | 168 | \begin{frame} 169 | \frametitle{SQLite Transaction mode(2)} 170 | \begin{itemize} 171 | \item defered: 말 그대로 Lock을 가능한한 뒤로 미룬다. Transaction을 시작할 때는 Lock을 잡지 않고, 첫 read operation이 있을 때 Shared Lock을 잡고, 첫 write operation이 있을때 Reserved Lock을 잡는다. 최대한 Lock이 뒤로 미뤄지기 때문에 다른 프로세스나 쓰레드에서 DB 작업을 더 할 수가 있다. 172 | \item immediate: Transaction을 시작할 때 Reserved Lock이 잡힌다. Reserved Lock은 2개 이상 잡힐 수 없으므로, 다른 immidate Transaction을 시작할 수는 없다. 그래도 다른 프로세스나 쓰레드에서 읽기를 할 수는 있다. 173 | \item exclusive: Transaction을 시작할 때 Exclusive Lock이 잡히므로, 다른 DB 작업을 도저히 할 수가 없다. 174 | \end{itemize} 175 | \end{frame} 176 | 177 | \begin{frame} 178 | \frametitle{SQLite Transaction mode(3)} 179 | \begin{itemize} 180 | \item 앞의 설명대로라면 가능한한 exclusive 보다는 immediate, immediate보다는 defered를 쓰고 싶지만, 안드로이드에서 지원하는 것은 exclusive와 immediate 두 가지뿐이다. 게다가 immediate 모드는 Level 11 Honeycomb부터 지원하기 시작했다.\\ 181 | \item SQLite 사이트에서 보면 defered가 디폴트라서 이 기준으로 쓰여 있는 문서들이 있어서 혼동되는 경우가 있다. 주의해서 보도록 하자. 182 | \end{itemize} 183 | \end{frame} 184 | 185 | \begin{frame}[fragile] 186 | \frametitle{Transaction 기본 패턴 수정} 187 | \begin{verbatim} 188 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 189 | db.beginTransactionNonExclusive(); 190 | } else { 191 | db.beginTransaction(); 192 | } 193 | try { 194 | ... 195 | db.setTransactionSuccessful(); 196 | } catch (Exception e) { 197 | ... 198 | } finally { 199 | db.endTransaction(); 200 | } 201 | \end{verbatim} 202 | \end{frame} 203 | 204 | 205 | \begin{frame} 206 | \frametitle{SQLiteOpenHelper(1)} 207 | \begin{itemize} 208 | \item SQLiteDatabase는 SQLite에 접근하는 클래스로, SQL 명령어를 실행하고 데이터베이스 관리를 하는 메소드들을 가지고 있다. SQLite를 사용하기 위해서는 꼭 거쳐야 하는 클래스이지만, 실제 프로젝트에서는 SQLiteDatabase를 직접 생성해서 사용하는 경우는 드물다.\\ 209 | 바로 Helper 클래스인 SQLiteOpenHelper를 상속해서 사용하는데, 여기서 Database 생성이나 Database 버전 관리를 알아서 해준다. 210 | \end{itemize} 211 | \end{frame} 212 | 213 | \begin{frame} 214 | \frametitle{SQLiteOpenHelper(2)} 215 | \begin{itemize} 216 | \item 일반적으로 앱은 업데이트 됨에 따라 DB 테이블이 추가되거나, 칼럼이 변경되거나, 앱에 필요한 기본 데이터가 필요한 경우가 생겨난다. 그래서 버전 관리는 필수적인데, SQLiteDatabase를 가지고 직접 하지 말고, 반드시 SQLiteOpenHelper를 이용한다. 217 | \item SQLiteOpenHelper는 추상 클래스이면서 일종의 Template Method 패턴을 만들어놓은 것으로, 이 클래스를 상속해서 onCreate와 onUpgrade 메소드를 구현한 Database Helper를 작성하면 된다. 218 | \end{itemize} 219 | \end{frame} 220 | 221 | \begin{frame}[fragile] 222 | \frametitle{Database Helper 예제} 223 | \Fontvi 224 | \begin{verbatim} 225 | public class DatabaseHelper extends SQLiteOpenHelper { 226 | private static final String DATABASE_NAME = "loader_throttle.db"; 227 | private static final int DATABASE_VERSION = 2; 228 | 229 | DatabaseHelper(Context context) { 230 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 231 | } 232 | 233 | @Override 234 | public void onCreate(SQLiteDatabase db) { 235 | db.execSQL("CREATE TABLE " + MainTable.TABLE_NAME + " (" 236 | .... 237 | } 238 | 239 | @Override 240 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 241 | for (int i = oldVersion + 1; i <= newVersion; i++) { 242 | processUpgrade(i); 243 | } 244 | } 245 | } 246 | \end{verbatim} 247 | \end{frame} 248 | 249 | \begin{frame} 250 | \frametitle{SQLiteOpenHelper 살펴보기(1)} 251 | \begin{itemize} 252 | \item 생성자에 DATABASE\_NAME이 들어가는 것이 바로 DB 파일명이다.\\ 253 | 그럼 DB를 여러 개 쓴다면 Database Helper가 여러 개 필요하다는 말일까? 바로 그렇다. 하나의 앱에서 경우에 따라서 여러개의 DB를 사용할 수도 있고, 이것들은 각각 기본적으로 Database Helper가 필요하게 된다.\\ 254 | 가능하면 DB를 하나로 사용하는 것이 좋지만, 상황상 어쩔 수 없는 케이스도 있고 DB Lock 문제를 효과적으로 대응하기 위해서 DB를 분리해놓을 수도 있다.(읽기 전용 데이터를 위한 DB, 읽기+쓰기 용도 DB) 255 | \end{itemize} 256 | \end{frame} 257 | 258 | \begin{frame} 259 | \frametitle{SQLiteOpenHelper 살펴보기(2)} 260 | \begin{itemize} 261 | \item Database 생성은 어느 시점에 될까? 생성자에서 해주는 것으로 생각할 수 있지만 그렇지 않다.\\ 262 | 실제로 Database 열기/생성(openOrCreate)은 SQLiteOpenHelper의 getReadableDatabase/getWritableDatabase 메소드를 호출할 때이다. 더 정확하게 얘기하면 SQLiteOpenHelper에는 SQLiteDatabase 인스턴스를 하나 가지고 있는데, 이 인스턴스가 이미 앞에 이미 생성되었으면 그것을 사용하고, 그렇지 않은 경우에 열기/생성을 하고 (getWritableDatabase에서만) onCreate와 onUpgrade 메소드가 실행된다. 263 | \end{itemize} 264 | \end{frame} 265 | 266 | \begin{frame} 267 | \frametitle{SQLiteOpenHelper 살펴보기(3)} 268 | \begin{itemize} 269 | \item onCreate, onUpgrade에는 테이블 생성/수정 뿐 아니라, 많은 기본 데이터 INSERT 등도 필요하다.\\ 270 | 이럴 때 Transaction을 쓰고 싶은데, SQLiteOpenHelper에는 이미 onCreate와 onUpgrade에 Transaction 처리가 되어 있어, 별도로 Transaction 처리를 할 필요가 없다. 271 | \end{itemize} 272 | \end{frame} 273 | 274 | \begin{frame} 275 | \frametitle{SQLiteOpenHelper 살펴보기(4)} 276 | \Fontvi 277 | \begin{itemize} 278 | \item DATABASE\_NAME 위치에 null을 넣으면 파일 DB가 아닌 메모리 DB가 된다. 파일 DB라도 속도가 느리진 않지만 메모리 DB는 그보다 훨씬 속도가 낫다. 하지만 DB가 close되면 함께 사라져버리는 휘발성 DB이므로, 일종의 Cache 용도로 사용하는 것이 좋다.\\ 279 | DB를 안 쓰고 Cache 자료구조를 만들수도 있는데, 굳이 메모리 DB를 쓰면 좋은 경우는 무엇일까? 바로 그 안에서 쿼리를 마음껏 실행할 수 있다는 것이다.\\ 280 | 이를테면 여러 칼럼이 있고 각 칼럼별로 정렬을 바꾸어주는 기능이 있다고 하면, 이런때 정렬할 때마다 File IO를 하는 것보다는 Memory DB에 담아놓고서 정렬을 하면 결과가 훨씬 빨라질 것이다. 파일 DB에서 raw 데이터를 가져와서 조합 생성해낸 결과 데이터 목록을 메모리 DB로 만들어서 사용하는 것도 고려해볼 수 있다.\\ 281 | 282 | 당연한 얘기지만, 메모리 DB에서는 DB 버전 업그레이드가 의미가 없으므로 version은 신경 쓸 필요 없다. 1로 해놓고 다시는 변경하지 말자. 283 | \end{itemize} 284 | \end{frame} 285 | 286 | \begin{frame}[fragile] 287 | \frametitle{SQLiteOpenHelper 살펴보기(5)} 288 | \begin{itemize} 289 | \item Database Helper는 앱 전체에 걸쳐 하나의 인스턴스를 가지고 있어야만, DB Lock 문제에서 자유롭다. 290 | 그래서 일반적으로 Singleton 패턴을 만들어서 사용한다. 291 | 292 | \Fontvi 293 | \begin{verbatim} 294 | public static DatabaseHelper extends SQLiteOpenHelper { 295 | private static DatabaseHelper instance; 296 | 297 | public static synchronized DatabaseHelper getInstance(Context context) { 298 | if (instance == null) { 299 | instance = new DatabaseHelper(context.getApplicationContext()); 300 | } 301 | return instance; 302 | } 303 | 304 | private DatabaseHelper(Context context) { 305 | .... 306 | } 307 | } 308 | \end{verbatim} 309 | \end{itemize} 310 | \end{frame} 311 | 312 | \begin{frame} 313 | \frametitle{SQLiteOpenHelper 살펴보기(6)} 314 | \begin{itemize} 315 | \item close 메소드는 실제 거의 호출할 일이 없다. close는 SQLiteDatabase 인스턴스의 close를 호출하고, SQLiteDatabase 인스턴스를 null로 만든다. 매번 close 하지 않고, SQLiteDatabase 인스턴스를 계속 재사용해도 문제가 없고, 또 다른 이유는 close 시점 때문에 문제가 발생할 수 있다는 데 있다.\\ 316 | 하나의 쓰레드에서 getWritableDatabase를 한 이후에 query를 한다고 하자. 다른 쓰레드에서는 뭔가 작업을 하고 close를 실행한다. 시점에 따라서 getWritableDatabase 이후에 close가 되고, 이것을 가지고 query를 하게 되면, 에러가 발생하게 된다. 317 | \end{itemize} 318 | \end{frame} 319 | 320 | \begin{frame} 321 | \frametitle{ContentProvider를 적용할 것인가?} 322 | 외부 앱에 데이터를 제공하기 위해서는 ContentProvider를 만들어야 한다.\\ 323 | 그런데 하나의 앱에서만 사용한다면, ContentProvider를 굳이 쓰지 말라는 가이드와 가능하면 ContentProvider를 쓰라는 가이드가 혼재해 있다. 324 | \end{frame} 325 | 326 | \begin{frame} 327 | \frametitle{로컬에서 ContentProvider를 쓰면 좋은 점} 328 | \begin{itemize} 329 | \item method signature를 따라야 하므로, API의 일관성을 유지할 수 있다. 330 | \item CursorLoader, AsyncQueryHandler 같은 유용한 클래스들에서 ContentProvider의 Uri가 전달되어야만 동작한다. 331 | \item 하나의 앱에서도 프로세스가 분리될 수 있다. 이를테면 Service가 메모리나 CPU 점유가 커서 프로세스를 분리했다면, 각각 Database Helper를 사용한다면 Lock문제가 언제든 발생할 수 있다. 이때 앱 프로세스에 ContentProvider를 두고, 이 ContentProvider를 Service 프로세스에서 ContentResolver를 통해서 접근하면 유일한 Database Helper를 유지할 수 있다. 332 | \end {itemize} 333 | \end{frame} 334 | 335 | \begin{frame} 336 | \frametitle{로컬에서 ContentProvider를 쓰면 좋지 않은 점} 337 | \begin{itemize} 338 | \item 속도가 조금 느리다. 339 | \item groupBy, having, limit 같은 파라미터를 ContentResolver에서 전달할 수 없다. 필요한 경우 Uri나 다른 파라미터에 억지로 끼워넣어서 전달해야만 한다. 340 | \item ContentResolver를 통하므로, 별도의 public 메소드를 만들어봤자 접근할 수가 없다. 341 | \end {itemize} 342 | \end{frame} 343 | 344 | \begin{frame}[fragile] 345 | \frametitle{ContentProvicer는 DB Lock 문제에서 자유로운가?(1)} 346 | \begin{itemize} 347 | \item ContentProvider를 사용하면 멀티 스레드 환경에서 Lock 문제가 없이 잘 동작하기 때문에 ContentProvider를 쓰면 thread safe 하다고 잘못 아는 경우가 있는데, thread safe는 ContentProvider를 써서 그런 것이 아니라 ContentProvider를 만드는 일반적인 패턴으로 인한 것이다.\\ 348 | onCreate에서 Database Helper를 하나 생성해놓고 이것을 사용하는 패턴이 주로 사용되고 있다. 349 | 350 | \Fontvi 351 | \begin{verbatim} 352 | @Override 353 | public boolean onCreate() { 354 | mOpenHelper = new DatabaseHelper(getContext()); 355 | return true; 356 | } 357 | \end{verbatim} 358 | \end {itemize} 359 | \end{frame} 360 | 361 | \begin{frame} 362 | \frametitle{ContentProvicer는 DB Lock 문제에서 자유로운가?(2)} 363 | \begin{itemize} 364 | \item onCreate는 처음 사용할 때 단 한번만 실행되고, 하나의 ContentProvider는 디바이스에서 오직 하나만 존재하기 때문에 Database Helper도 오직 하나뿐이다. 따라서 내부적으로 명령어가 serialize되면서 thread safe가 되는 것이다. 365 | \end {itemize} 366 | \end{frame} 367 | 368 | \begin{frame} 369 | \frametitle{ContentProvider 만들때 주의할 점} 370 | \begin{itemize} 371 | \item ContentProvider.onCreate 메소드는 Application.onCreate 이전에 실행된다. 따라서 Application.onCreate 메소드가 먼저 실행되었다고 가정하고 만들면 안 된다. 372 | \item onCreate는 메인 쓰레드에서 실행하고 다른 메소드들은 일반적으로 별도의 쓰레드에서 실행하므로, Content Provider의 메소드들간에는 thread safe에 주의하도록 하자.(멤버 변수를 함부로 쓰면 안 된다!) 373 | \end {itemize} 374 | \end{frame} 375 | 376 | \begin{frame}[fragile] 377 | \frametitle{ContentProvider batch execute(1)} 378 | \begin{itemize} 379 | \item ContentProvider에서는 여러 개의 명령어를 한꺼번에 실행할 수 있는 방법도 제공한다. 속도 향상을 위해서 Transaction을 쓰는 것으로 혼동할 수도 있지만, 실제로 하는 일은 ContentProviderOperation 목록을 한꺼번에 전송해서 하나씩 순차적으로 수행하는 것에 지나지 않는다. 380 | \Fontvi 381 | \begin{verbatim} 382 | ArrayList operations = new ArrayList(); 383 | operations.add(ContentProviderOperation.newInsert(NotePad.CONTENT_URI) 384 | .withValue(NotePad.Notes.COLUMN_NAME_TITLE, "Lunch") 385 | .withValue(NotePad.Notes.COLUMN_NAME_NOTE, "Kimchi") 386 | .withValue(NotePad.COLUMN_NAME_CREATE_DATE, Long.valueOf(System.currentTimeMillis())) 387 | .build()); 388 | operations.add(ContentProviderOperation.newUpdate(NotePad.CONTENT_URI) 389 | .withSelection(NotePad.Notes._ID + "=?", new String[] {3}) 390 | .withValue(NotePad.Notes.COLUMN_NAME_TITLE, "Lunch2") 391 | .withValue(NotePad.Notes.COLUMN_NAME_NOTE, "Kimchi2") 392 | .withValue(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, Long.valueOf(System.currentTimeMillis())) 393 | .build()); 394 | .... 395 | mContext.getContentResolver().applyBatch(NotePad.AUTHORITY, operations); 396 | \end{verbatim} 397 | \end {itemize} 398 | \end{frame} 399 | 400 | 401 | \begin{frame}[fragile] 402 | \frametitle{ContentProvider batch execute(2)} 403 | 다른 프로세스에서 실행한다면, 하나씩 Binder를 거쳐서 명령어를 주고 받는 것보다 한꺼번에 보내는 것이므로 속도 향상은 있을 수 있지만, Transaction을 쓰는 것처럼 비약적인 속도 향상까지는 아니다.\\ 404 | ContentProvider 쪽에서는 만일 Tranaction을 써서 속도 향상을 시키고 싶다면, 405 | applyBatch(ArrayList$<$ContentProviderOperation$>$ operations) 메소드를 오버라이드 하는 방법이 있다. 406 | \Fontvi 407 | \begin{verbatim} 408 | @Override 409 | public ContentProviderResult[] applyBatch(ArrayList operations) { 410 | SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 411 | db.beginTransaction(); 412 | try { 413 | ContentProviderResult[] result = super.applyBatch(operations); 414 | db.setTransactionSuccessful(); 415 | return result; 416 | } finally { 417 | db.endTransaction(); 418 | } 419 | } 420 | \end{verbatim} 421 | \end{frame} 422 | 423 | 424 | \begin{frame}[fragile] 425 | \frametitle{쿼리 실행 속도 체크} 426 | 일반적으로 속도 체크를 위해서 코드 상에 쿼리 실행 전후 시간차이를 로그로 남겨서 확인한다. 이 방식도 지속적으로 확인이 필요할 때는 유용하지만, 시기가 지나면 불필요한 로깅 코드가 남게 된다.\\ 427 | adb shell에서 dumpsys dbinfo를 하면 db별로 최근 쿼리 실행 속도와 바인드된 파라미터들을 확인할 수 있다. 428 | 가장 최근 것부터 나온다.(쿼리를 prepare하고 execute하는 것을 볼 수 있다.) 429 | 430 | \Fontvi 431 | \begin{verbatim} 432 | Most recently executed operations: 433 | 0: [2014-09-30 10:40:04.548] executeForLastInsertedRowId took 5ms - succeeded, 434 | sql="INSERT INTO system(value,name) VALUES (?,?)", 435 | bindArgs=["1", "volume_ring_last_audible_speaker"] 436 | 1: [2014-09-30 10:40:04.548] prepare took 0ms - succeeded, 437 | sql="INSERT INTO system(value,name) VALUES (?,?)" 438 | \end{verbatim} 439 | 440 | \end{frame} 441 | 442 | \end{document} -------------------------------------------------------------------------------- /latexbook/appstore_chap1.vrb: -------------------------------------------------------------------------------- 1 | \frametitle{쿼리 실행 속도 체크} 2 | 일반적으로 속도 체크를 위해서 코드 상에 쿼리 실행 전후 시간차이를 로그로 남겨서 확인한다. 이 방식도 지속적으로 확인이 필요할 때는 유용하지만, 시기가 지나면 불필요한 로깅 코드가 남게 된다.\\ 3 | adb shell에서 dumpsys dbinfo를 하면 db별로 최근 쿼리 실행 속도와 바인드된 파라미터들을 확인할 수 있다. 4 | 가장 최근 것부터 나온다.(쿼리를 prepare하고 execute하는 것을 볼 수 있다.) 5 | 6 | \Fontvi 7 | \begin{verbatim} 8 | Most recently executed operations: 9 | 0: [2014-09-30 10:40:04.548] executeForLastInsertedRowId took 5ms - succeeded, 10 | sql="INSERT INTO system(value,name) VALUES (?,?)", 11 | bindArgs=["1", "volume_ring_last_audible_speaker"] 12 | 1: [2014-09-30 10:40:04.548] prepare took 0ms - succeeded, 13 | sql="INSERT INTO system(value,name) VALUES (?,?)" 14 | \end{verbatim} 15 | 16 | -------------------------------------------------------------------------------- /latexbook/basic-lifecycle-paused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/basic-lifecycle-paused.png -------------------------------------------------------------------------------- /latexbook/binderthread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/binderthread.png -------------------------------------------------------------------------------- /latexbook/binderthread.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/binderthread.tiff -------------------------------------------------------------------------------- /latexbook/book.bib: -------------------------------------------------------------------------------- 1 | @article{thisisandroidbook, 2 | title={이것이 안드로이드다} 3 | author={박성근} 4 | year={2014} 5 | publisher{한빛미디어} 6 | } 7 | 8 | @article{javaparallelbook, 9 | title={멀티 코어를 100% 활용하는 자바 병렬 프로그래밍(역서)} 10 | author={브라이언 게츠} 11 | year={2008} 12 | publisher{에이콘} 13 | } 14 | 15 | 16 | @article{effectivejava, 17 | title={Effective Jave 2nd Edition} 18 | author={조슈아 블로쉬} 19 | year={2008} 20 | publisher{에이콘} 21 | } 22 | 23 | @article{androidthreading, 24 | title={안드로이드 멀티스레딩(역서)} 25 | author={안데르스 예란손} 26 | year={2015} 27 | publisher{한빛미디어} 28 | } -------------------------------------------------------------------------------- /latexbook/chunchun.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/chunchun.tiff -------------------------------------------------------------------------------- /latexbook/contents.tex: -------------------------------------------------------------------------------- 1 | \begin{comment} 2 | \begin{abstract} 3 | 잘 만든 앱은 다 그럭저럭이지만, 그렇지 않은 앱은 제각각의 이유가 있다. 여기서는 그럭저럭한 앱을 만드는 원리와 방법을 찾아보고, 문제를 발생시키는 제각각의 이유에 대해서도 얘기해보자. 4 | \end{abstract} 5 | \end{comment} 6 | 7 | \chapter*{이 책의 구성} 8 | 안드로이드 컴포넌트에 대한 기본 내용을 중심으로 서술하였다. 각 내용은 근거를 제시하기 위해서 프레임워크 소스나 샘플 소스를 가지고 설명한다. 9 | 여러 장에서 반복되는 내용도 있기 때문에 당장 이해되지 않는 부분이 있더라도 끝까지 읽도록 하자.\\ 10 | 11 | 1장에서는 프레임워크 스택의 기본 내용과, 프레임워크 소스를 참고하고 활용하는 방법에 대해 얘기한다.\\ 12 | 13 | 2장에서는 안드로이드 컴포넌트가 실행되는 메인 스레드의 동작 방식을 설명한다. 14 | Handler, Looper, Message, MessageQueue의 관계를 이해하고 나면, 안드로이드 컴포넌트의 여러 실행 문제를 해결할 수 있다. 15 | 개발하면서 골칫거리 가운데 하나인 ANR에 대해서도 원인과 결과, 그리고 해결 방식에 대해서 다룬다.\\ 16 | 17 | 3장에서는 2장의 Handler와 내용이 연결되는 HandlerThread와 스레드 풀, AsyncTask에 대한 내용을 다룬다.\\ 18 | 19 | 4장에서는 Activity, Service, Application의 상위 클래스이면서, 안드로이드 컴포넌트를 실행하거나 리소스를 참조할 때 필요한 Context 클래스에 대해서 살펴본다.\\ 20 | 21 | 5장부터 9장까지는 Activity, Service, ContentProvider, BroadcastReceiver, Application까지 이슈 중심으로 안드로이드 컴포넌트를 설명한다.\\ 22 | 23 | 10장에서는 시스템 서비스 목록을 정리하고, 시스템 서비스와 Service 컴포넌트와 차이점을 얘기한다. 시스템의 상태를 알기 위해 dumpsys 명령어를 활용하는 방법과 시스템 서비스의 여러 이슈에 대해서도 살펴본다.\\ 24 | 25 | 11장에서는 앱 개발에서 사용하는 구현 패턴을 얘기한다. 싱글톤과 마커 인터페이스, Fragment 정적 생성 항목을 언급한다.\\ 26 | 27 | 프레임워크 소스와 샘플은 안드로이드 버전에 관계 없이 공통되는 내용으로 주로 설명하였는데, 버전에 따라 동작이 바뀌는 게 있다면 가급적 언급하였다. 28 | 언급한 내용 외에도 버전에 따른 내용이 달라서 혼동이 되는 부분이 있다면 이메일(suribada@gmail.com)로 알려주기 바란다. 29 | 30 | \addcontentsline{toc}{chapter}{이 책의 구성} -------------------------------------------------------------------------------- /latexbook/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/context.png -------------------------------------------------------------------------------- /latexbook/context.tex: -------------------------------------------------------------------------------- 1 | \chapter{Context} 2 | 앱을 개발하다보면 항상 Context 클래스를 만나게 된다. 3 | Context가 없으면 Activity를 시작할 수도, Broadcast를 발생시킬 수도, Service를 실행할 수도 없다. 4 | 리소스에 접근할 때도 Context를 통해야만 가능하다. 5 | Context는 여러 컴포넌트의 상위 클래스면서 Context를 통해서 여러 컴포넌트가 연결돼 있으므로 Context 자체에 대해서 살펴보는 것은 컴포넌트를 이해하는 데도 도움이 된다.\\ 6 | 7 | Context는 추상 클래스인데 메서드 구현이 거의 없고 상수 정의와 추상 메서드로 이루어진다. 8 | 그럼 Context의 하위 클래스는 어떤 게 있는가? 주요한 것을 얘기하면, 직접 상속한 것은 ContextWrapper이고 ContextWrapper를 상속한 Activity, Service, Application이 있다(BroadcastReceiver, ContentProvider는 Context를 상속한 것이 아니다).\\ 9 | 10 | % http://blog.naver.com/PostView.nhn?blogId=huewu&logNo=110085457720&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView 11 | Context와 컴포넌트 중간에 있는 ContextWrapper를 먼저 살펴보자. 12 | ContextWrapper 클래스는 그 이름처럼 Context를 래핑한 ContextWrapper(Context base) 생성자를 갖고 있다. 13 | \begin{lstlisting}[frame=single, caption=ContextWrapper.java] 14 | Context mBase; 15 | 16 | public ContextWrapper(Context base) { // (1) 17 | mBase = base; 18 | } 19 | 20 | protected void attachBaseContext(Context base) { // (2) 21 | if (mBase != null) { 22 | throw new IllegalStateException("Base context already set"); 23 | } 24 | mBase = base; 25 | } 26 | 27 | public Context getBaseContext() { 28 | return mBase; 29 | } 30 | 31 | ... 32 | 33 | @Override 34 | public Context getApplicationContext() { 35 | return mBase.getApplicationContext(); // (3) 36 | } 37 | 38 | ... 39 | 40 | @Override 41 | public void startActivity(Intent intent, Bundle options) { 42 | mBase.startActivity(intent, options); // (4) 43 | } 44 | 45 | ... 46 | 47 | @Override 48 | public void sendBroadcast(Intent intent) { 49 | mBase.sendBroadcast(intent); // (5) 50 | } 51 | 52 | ... 53 | 54 | @Override 55 | public Resources getResources() { 56 | return mBase.getResources(); // (6) 57 | } 58 | \end{lstlisting} 59 | \begin{itemize} 60 | \item 3라인(1), 7라인(2)에서 base 파라미터에 전달되는 것은 Context의 여러 메서드를 직접 구현한 ContextImpl 인스턴스이다. 61 | ContextImpl은 앱에서 생성해서 사용할 수 있는 public 클래스는 아니지만, 소스는 공개되어 있으니 한번씩은 살펴보도록 하자. 62 | 63 | \item ContextWrapper의 여러 메서드는 base의 메서드를 그대로 다시 호출하고 있다(3, 4, 5, 6). 64 | 65 | \item Activity, Service, Application은 3라인(1)의 생성자를 사용하지 않고, 실제로는 7라인(2)의 attachBaseContext()를 사용한다. Activity, Service, Application 모두 내부적으로 ActivityThread에서 컴포넌트를 시작하면서 내부적으로 각 컴포넌트의 attach() 메서드를 실행하고 attach() 메서드에서 또다시 attachBaseContext()를 실행하는 것을 볼 수 있다. 66 | \end{itemize} 67 | 68 | 이제 또 다른 질문을 해보자. 그럼 ContextImpl은 앱에서 싱글톤으로 단 1개의 인스턴스를 가지고서 ContextWrapper에 전달하는가? 69 | 이것은 ContextWrapper에 getBaseContext()와 getApplicationContext()라는 2개의 메서드가 별도인 것을 보면 싱글톤이 아닌 것을 유추할 수 있다.\footnote{Context에는 getBaseContext() 메서드는 없고 getApplicationContext() 메서드만 있다.}\\ 70 | 71 | Activity, Service, Application 컴포넌트는 각각 생성한 ContextImpl을 하나씩 Wrapping하고 있고 getBaseContext()는 각각 ContextImpl 인스턴스를 리턴한다. getApplicationContext()는 Application 인스턴스를 리턴하는 것으로 Application은 앱에서 1개 밖에 없고 어디서나 동일한 인스턴스를 반환한다.\\ 72 | 73 | ContextImpl을 보면 메서드를 기능별로 나누면 3개의 그룹이 있다. 74 | \begin{itemize} 75 | \item 앱 패키지 정보, 내/외부 파일, SharedPreferences, 데이터베이스 등과 관련한 Helper 메서드가 있다. 76 | 77 | \item Activity, BroadcastReceiver, Service와 같은 컴포넌트 관련 메서드와 퍼미션 체크 메서드가 있고, 이들 메서드는 system\_server 프로세스의 ActivityManagerService의 메서드를 다시 호출한다. 78 | 79 | \item ActivityManagerService 를 포함한 시스템 서비스에 접근하기 위해서 getSystemService() 메서드가 있다. 80 | ContextImpl의 정적 초기화 블록(static initializer block)에서 클래스가 최초 로딩될 때 시스템 서비스를 매핑하고, 이후에는 getSystemService() 메서드에서 매핑을 바로 사용하고 있다. 81 | % http://warmz.tistory.com/50에서 초기화 블록 내용을 알아보자. 82 | 시스템 서비스는 Context 클래스에서 XXX\_SERVICE와 같이 상수명으로 모두 매핑되어 있고, Context가 전달되는 어디서든 getSystemService(Context.ALARM\_SERVICE)와 같이 시스템 서비스를 가져다 쓸 수 있다. 83 | \end{itemize} 84 | 85 | \includegraphics[scale=0.5]{context}\\ 86 | 객체지향의 원칙에서 상속보다는 구성을 사용하라고 하는데, 이 클래스 다이어그램을 보면 원칙에 들어맞는다. 87 | 바로 Activity, Service, Application에서 ContextImpl을 직접 상속하지 않고, ContextImpl의 메서드를 호출하는 형태이다. 88 | 이렇게 하면 ContextImpl의 변수가 노출되지 않고, ContextWrapper에서는 ContextImpl의 public 메서드만 호출하게 된다. 또한 각 컴포넌트별로 사용하는 기능에 대한 제어도 단순해진다.\\ 89 | %ContextWrapper 소스에 있는 내용 90 | 91 | 코드에서 Context를 쓰는 방법을 생각해보자. 92 | Activity를 예로 들어보면 여기서 쓸 수 있는 Context 인스턴스는 3개가 있다. 93 | \begin{enumerate} 94 | \item Activity 인스턴스 자신(this) 95 | \item getBaseContext()를 통해 가져오는 ContexImpl 인스턴스 96 | \item getApplicationContext()를 통해 가져오는 Application 인스턴스: Activity의 getApplication() 메서드로 가져오는 것과 같은 인스턴스이다. 97 | \end{enumerate} 98 | 99 | 각각의 인스턴스가 다르므로 캐스팅을 함부로 하면 안된다. 이를테면 getBaseContext()로 가져온 것을 Activity로 캐스팅하면 ClassCastException이 발생한다.\\ 100 | 101 | View 클래스를 보면 생성자에 Context가 들어간다. 이 Context가 어디서 온 것인지 아래 코드를 통해 확인해보자. 102 | \begin{lstlisting}[frame=single] 103 | @Override 104 | public void onCreate(Bundle savedInstanceState) { 105 | super.onCreate(savedInstanceState); 106 | setContentView(R.layout.main); 107 | statusView = (TextView) findViewById(R.id.status_view); 108 | Log.d(TAG, (statusView.getContext() == this)); 109 | Log.d(TAG, (statusView.getContext() == getBaseContext())); 110 | Log.d(TAG, (statusView.getContext() == getApplicationContext())); 111 | } 112 | \end{lstlisting} 113 | 첫 번째 로그에서만 true가 나오는 것을 볼 수 있다. View 클래스는 생성자에 Context가 전달되어야 한다. 114 | 테스트해보면 Activity에서 쓸 수 있는 3가지 Context 모두 다 전달 가능하다. 115 | 그러나 View와 연관이 깊은 것은 Activity이므로 Activity를 전달하는 것을 이해할 수 있을 것이다. 116 | 내부적으로 setContentView() 메서드에서 사용하는 LayoutInflator에 Activity 인스턴스가 전달되고, View 생성자의 Context 파라미터에 Activity 인스턴스가 전달된다. 117 | -------------------------------------------------------------------------------- /latexbook/context_class_dgm.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/context_class_dgm.tiff -------------------------------------------------------------------------------- /latexbook/deepsleep.tex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/deepsleep.tex -------------------------------------------------------------------------------- /latexbook/device-sqlite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/device-sqlite.png -------------------------------------------------------------------------------- /latexbook/diagram_backstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/diagram_backstack.png -------------------------------------------------------------------------------- /latexbook/diagram_multiple_instances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/diagram_multiple_instances.png -------------------------------------------------------------------------------- /latexbook/diagram_multitasking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/diagram_multitasking.png -------------------------------------------------------------------------------- /latexbook/favorite_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/favorite_message.png -------------------------------------------------------------------------------- /latexbook/favorite_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/favorite_sample.png -------------------------------------------------------------------------------- /latexbook/header.tex: -------------------------------------------------------------------------------- 1 | \chapter*{머리말} 2 | \section*{대상 독자} 3 | 이 책은 안드로이드 앱을 만든 경험을 가진 개발자를 대상으로 한 것이다. 4 | 앱을 개발하면서 자신이 정말 제대로 만들고 있는 지, 문제를 올바르게 해결하고 있는 지 의문을 가지고 있었다면 이 책이 도움이 될 것이다.\\ 5 | 6 | 이 책에서는 기본 원리를 이해하고 이를 올바르게 적용하는 것에 중점을 두었다. 7 | 다른 책에서는 작게 다뤄지는 내용들이라도 실무에서 중요하다고 생각하는 것에 많은 내용을 할애하였다. 8 | 기초 서적을 보고 나서는 그 다음에 볼만한 게 많지 않은데 이것도 `두 번째 책' 가운데 하나가 되었으면 하는 바램이다.\\ 9 | 10 | 경험자를 대상으로 하므로 기초적인 내용은 안다는 전제에서 시작하는 내용이 많다. 11 | 전혀 생소한 내용이라면 관련해서 검색을 통해 찾아보고, 분량이 많지도 않으므로 어쨌든 쭉 읽어보고 반복해서 읽는 것을 추천한다. 12 | 내용 가운데서 독자가 정말 궁금하고 알고 싶던 것이 있다면 그건 필자의 기쁨이 될 것이다. 13 | 14 | \section*{에피소드} 15 | 필자는 5년이 넘는 기간 동안 다양한 앱을 겪어왔다. 물론 그 가운데서 잘 만든 것도 있지만 그렇지 않은 것도 많았다. 16 | 안드로이드의 기본 원리를 알고 있다면, 해서는 안되는 수많은 코드들이 있었다. 17 | 남들이 만든 코드도 그렇지만 필자가 만든 것도 나중에 생각해보면 왜 그랬을까 하는 부끄러운 것도 없지 않다.\\ 18 | 19 | 안드로이드는 문서화가 잘 되어 있는 편이 아니고 개발자 사이트에도 기본 원리를 알려주는 내용이 많지 않다. 20 | 기본 원리를 모르는 상태에서 문제가 없어 보이는 정도의 수준에서만 개발하다 보면, 군더더기 코드가 많아지고 요구 사항이나 안드로이드 버전, 다양한 단말 같은 환경 변화에 취약해지는 문제가 생긴다. 21 | 문제가 발생하면 어떻게든 꼼수로 해결하려는 많은 시도를 봐왔는데, 결국 문제 해결은 먼 곳이 아니라 기본 원리에서부터 시작해야만 22 | 단순하고 확실하게 해결할 수 있다는 것을 경험으로 알게 되었다.\\ 23 | 24 | 기본 원리를 알려면 결국 안드로이드 내부 구조에 대한 이해가 필요하다. 25 | 내부 구조에 대해서 설명하는 어려운 책이나 강의도 있지만, 26 | 그 내용이 어려운 수준에서 끝나고 실무에 어떻게 적용하면 되는지는 또 각자의 몫이 되었다. 27 | 이론과 실무의 차이를 좁히려는 시도로서 스터디를 꾸리고 강의를 하면서 28 | 남에게 전달하기 위해서 더 깊이 있게 탐구하게 되었다. 29 | 그리고 결과물로서 책을 내는 시도를 하게 되었는데, 단 한 줄의 내용을 쓰기 위해 며칠을 테스트하고 고민하기도 하면서 분량에 비해 많은 시간이 소요되었다. 30 | 공부하면 할수록 공부할 게 많아지는 건 한동안 나의 일상이 될 것 같다. 31 | 32 | \section*{감사의 글} 33 | 내용 검토를 해준 이효근 님, 윤신주 님, 김태중 님, 김성수 님, 원형식 님, 송지철 님, 이정민 님, 임원석 님, 이종권 님, 임은령 님, 김성호 님, 김재희 님에게 감사드린다. 34 | 이 분들 덕분에 내용에서 많은 문제가 수정될 수 있었다. 이 책은 혼자서 한 게 아니라 함께 만들어낸 것이다.\\ 35 | 36 | 내가 IT 업계에서 밥을 먹고 살 수 있도록 많은 도움과 격려를 해준 이창신 군, 최희탁 군, 강용석 군에게도 고마움을 전해야겠다. 37 | 오래 전 잠시 IT 업계를 떠나있을 때 동고동락한 이정훈 형도 내게 고마운 사람이다.\\ 38 | 39 | 항상 내 편이 되어준 부모님과 형제들, 40 | 오랜 시간을 함께하고 기다려준 아내에게도 감사한다. 삶의 이유가 되어주는 현종과 서윤에게도 사랑과 고마움을 전한다. 41 | 42 | \begin{comment} 43 | 필자는 회사에서 능력을 인정받거나 화려한 기술을 가진 것도 아닌 평범한 개발자이다. 44 | 다만 문제를 겪고 해결할 때마다 메모하고 시간을 내서 내용을 정리한 것 뿐이다. 45 | 정리한 내용은 지력이 갈수록 떨어지는 필자를 위한 것이기도 하다.\\ 46 | 47 | 나도 책과 인터넷에서 많은 도움을 받았다. 48 | 오래 전 팀 동료가 그런 얘기를 했다. ``국내 IT서적은 다 쓰레기다'' 그 동료는 책 꽂이에 원서만 나열해놓고 있었다. 49 | 어쩌면 쓰레기 하나를 더 만들었는지도 모르겠다. 50 | 51 | 좌충우돌한 이야기 52 | 53 | 예민한 내용이 많아서 오류가 있을 수 있다. 혹시 오류라고 생각한다면 제보 바란다. 54 | 55 | 죄와벌 이나 태백산맥 같은 역작을 내는 것도 아닌데, 무슨 시간이 이리 많이 걸리는지.. 56 | \end{comment} -------------------------------------------------------------------------------- /latexbook/input.tex: -------------------------------------------------------------------------------- 1 | adb shell getevent 해보자. 2 | 3 | InputDispatcher.cpp (/frameworks/services/input/InputDispatcher.cpp) 에서 4 | findFocusedWindowTargetsLocked로 타겟 정함 5 | dispatchEventLocked로 전달함 6 | 7 | /frameworks/native/include/utils/Thread.h 에서 threadLoop 메소드 확인 8 | true를 리턴하면 계속되고, 아니면 끝난다. 9 | /frameworks/native/libs/utils/Threads.cpp 10 | 11 | /frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java 12 | interceptKeyBeforeQueueing에서 VOLUME, POWER 처리(Reader에서 notifyKey 할때) 13 | interceptKeyBeforeDispatching에서 HOME, MENU 버튼 처리(Reader에서 dispach하 할때) 14 | 15 | SystemServer.init2 16 | ServerThread.run 17 | InputManagerService.start 18 | NativeInputManager.nativeStart 19 | InputManager.start 20 | InputDispatcherThread.run 21 | InputReaderThread.run 22 | 23 | 24 | InputReaderThread.run 25 | InputReaderThread.threadLoop 26 | InputReader.loopOnce 27 | EventHub.getEvents 28 | InputReader.processEventsLocked 29 | InputReader.processEventsForDeviceLocked 30 | InputDevice.process 31 | InputMapper.process 32 | 33 | InputMapper들은 InputReader.cpp에 있다. 34 | 35 | KeyboardInputMapper.process 36 | KeyboardInputMapper.processKey 37 | InputDispatcher.notifyKey 38 | InputDispatcherPolicyInterface.interceptKeyBeforeQueueing 39 | InputDispatcher.enqueueInboundEventLocked 40 | 이렇게 Queue에 넣는다. 41 | 42 | 43 | TouchInputMapper.process 44 | TouchInputMappper.sync 45 | TouchInputMapper.consumeRawEvents 46 | TouchInputMapper.dispatchVirtualKey 47 | InputDispatcher.notifyKey 48 | 49 | 50 | InputDispatcherThread.run 51 | InputDispatcherThread.threadLoop 52 | InputDispatcher.dispatchOnce 53 | InputDispatcher.dispatchOnceInnerLocked 54 | Queue.dequeAtHead 55 | InputDispatcher.dispatchMotionLocked 56 | InputDispatcher.findFocusedWindowTargetsLocked 57 | InputDispatcher.dispatchEventLocked 58 | InputDispatcher.prepareDispatchCycleLocked 59 | InputDispatcher.enqueueDispatchEntriesLocked 60 | InputDispatcher.startDispatchCycleLocked 61 | InputPublisher.publishMotionEvent 62 | InputChannel.sendMessage 63 | 64 | 소켓 통신 65 | 66 | ViewRootImpl.setView 67 | Session.addToDisplay 68 | WindowManagerService.addWindow 69 | WindowManagerService.registerInputChannel 70 | InputManagerService.registerInputChannel 71 | NativeInputManager.registerInputChannel 72 | InputDispatcher.registerInputChannel 73 | Looper.addFd 74 | Session.addTodisplay 사용하는 곳은 75 | ViewRootImpl, WallPaperService, SurfaceView 세 곳이다. 76 | 77 | InputDispatcher.handleReceiveCallback 78 | InputPublisher.receiveFinishedSignal 79 | InputPublisher.receiveMessage 80 | 81 | 82 | http://blog.csdn.net/powq2009/article/details/8426271 83 | 84 | http://berebereport.tistory.com/65 85 | 86 | getevent, setevent 87 | http://dry-kiss.blogspot.kr/2012/03/android-adb.html 88 | 89 | http://blog.lciel.jp/blog/2013/12/12/android-touch-event-2/ 90 | 91 | getevent 하면 하나의 이벤트에 대해서 여러 라인에 걸쳐서 결과가 나온다. ABS_X, ABS_Y(X, Y 좌표) -------------------------------------------------------------------------------- /latexbook/kandroid-framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/kandroid-framework.png -------------------------------------------------------------------------------- /latexbook/kandroid_framework.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/kandroid_framework.tiff -------------------------------------------------------------------------------- /latexbook/lecture_201411.nav: -------------------------------------------------------------------------------- 1 | \beamer@endinputifotherversion {3.26pt} 2 | \headcommand {\slideentry {0}{0}{1}{1/1}{}{0}} 3 | \headcommand {\beamer@framepages {1}{1}} 4 | \headcommand {\slideentry {0}{0}{2}{2/2}{}{0}} 5 | \headcommand {\beamer@framepages {2}{2}} 6 | \headcommand {\slideentry {0}{0}{3}{3/3}{}{0}} 7 | \headcommand {\beamer@framepages {3}{3}} 8 | \headcommand {\slideentry {0}{0}{4}{4/4}{}{0}} 9 | \headcommand {\beamer@framepages {4}{4}} 10 | \headcommand {\slideentry {0}{0}{5}{5/5}{}{0}} 11 | \headcommand {\beamer@framepages {5}{5}} 12 | \headcommand {\slideentry {0}{0}{6}{6/6}{}{0}} 13 | \headcommand {\beamer@framepages {6}{6}} 14 | \headcommand {\slideentry {0}{0}{7}{7/7}{}{0}} 15 | \headcommand {\beamer@framepages {7}{7}} 16 | \headcommand {\slideentry {0}{0}{8}{8/8}{}{0}} 17 | \headcommand {\beamer@framepages {8}{8}} 18 | \headcommand {\slideentry {0}{0}{9}{9/9}{}{0}} 19 | \headcommand {\beamer@framepages {9}{9}} 20 | \headcommand {\slideentry {0}{0}{10}{10/10}{}{0}} 21 | \headcommand {\beamer@framepages {10}{10}} 22 | \headcommand {\slideentry {0}{0}{11}{11/11}{}{0}} 23 | \headcommand {\beamer@framepages {11}{11}} 24 | \headcommand {\slideentry {0}{0}{12}{12/12}{}{0}} 25 | \headcommand {\beamer@framepages {12}{12}} 26 | \headcommand {\slideentry {0}{0}{13}{13/13}{}{0}} 27 | \headcommand {\beamer@framepages {13}{13}} 28 | \headcommand {\slideentry {0}{0}{14}{14/14}{}{0}} 29 | \headcommand {\beamer@framepages {14}{14}} 30 | \headcommand {\slideentry {0}{0}{15}{15/15}{}{0}} 31 | \headcommand {\beamer@framepages {15}{15}} 32 | \headcommand {\slideentry {0}{0}{16}{16/16}{}{0}} 33 | \headcommand {\beamer@framepages {16}{16}} 34 | \headcommand {\slideentry {0}{0}{17}{17/17}{}{0}} 35 | \headcommand {\beamer@framepages {17}{17}} 36 | \headcommand {\slideentry {0}{0}{18}{18/18}{}{0}} 37 | \headcommand {\beamer@framepages {18}{18}} 38 | \headcommand {\slideentry {0}{0}{19}{19/19}{}{0}} 39 | \headcommand {\beamer@framepages {19}{19}} 40 | \headcommand {\slideentry {0}{0}{20}{20/20}{}{0}} 41 | \headcommand {\beamer@framepages {20}{20}} 42 | \headcommand {\slideentry {0}{0}{21}{21/21}{}{0}} 43 | \headcommand {\beamer@framepages {21}{21}} 44 | \headcommand {\slideentry {0}{0}{22}{22/22}{}{0}} 45 | \headcommand {\beamer@framepages {22}{22}} 46 | \headcommand {\slideentry {0}{0}{23}{23/23}{}{0}} 47 | \headcommand {\beamer@framepages {23}{23}} 48 | \headcommand {\slideentry {0}{0}{24}{24/24}{}{0}} 49 | \headcommand {\beamer@framepages {24}{24}} 50 | \headcommand {\slideentry {0}{0}{25}{25/25}{}{0}} 51 | \headcommand {\beamer@framepages {25}{25}} 52 | \headcommand {\slideentry {0}{0}{26}{26/26}{}{0}} 53 | \headcommand {\beamer@framepages {26}{26}} 54 | \headcommand {\slideentry {0}{0}{27}{27/27}{}{0}} 55 | \headcommand {\beamer@framepages {27}{27}} 56 | \headcommand {\slideentry {0}{0}{28}{28/28}{}{0}} 57 | \headcommand {\beamer@framepages {28}{28}} 58 | \headcommand {\slideentry {0}{0}{29}{29/29}{}{0}} 59 | \headcommand {\beamer@framepages {29}{29}} 60 | \headcommand {\slideentry {0}{0}{30}{30/30}{}{0}} 61 | \headcommand {\beamer@framepages {30}{30}} 62 | \headcommand {\slideentry {0}{0}{31}{31/31}{}{0}} 63 | \headcommand {\beamer@framepages {31}{31}} 64 | \headcommand {\slideentry {0}{0}{32}{32/32}{}{0}} 65 | \headcommand {\beamer@framepages {32}{32}} 66 | \headcommand {\slideentry {0}{0}{33}{33/33}{}{0}} 67 | \headcommand {\beamer@framepages {33}{33}} 68 | \headcommand {\slideentry {0}{0}{34}{34/34}{}{0}} 69 | \headcommand {\beamer@framepages {34}{34}} 70 | \headcommand {\slideentry {0}{0}{35}{35/35}{}{0}} 71 | \headcommand {\beamer@framepages {35}{35}} 72 | \headcommand {\slideentry {0}{0}{36}{36/36}{}{0}} 73 | \headcommand {\beamer@framepages {36}{36}} 74 | \headcommand {\slideentry {0}{0}{37}{37/37}{}{0}} 75 | \headcommand {\beamer@framepages {37}{37}} 76 | \headcommand {\slideentry {0}{0}{38}{38/38}{}{0}} 77 | \headcommand {\beamer@framepages {38}{38}} 78 | \headcommand {\slideentry {0}{0}{39}{39/39}{}{0}} 79 | \headcommand {\beamer@framepages {39}{39}} 80 | \headcommand {\slideentry {0}{0}{40}{40/40}{}{0}} 81 | \headcommand {\beamer@framepages {40}{40}} 82 | \headcommand {\slideentry {0}{0}{41}{41/41}{}{0}} 83 | \headcommand {\beamer@framepages {41}{41}} 84 | \headcommand {\slideentry {0}{0}{42}{42/42}{}{0}} 85 | \headcommand {\beamer@framepages {42}{42}} 86 | \headcommand {\slideentry {0}{0}{43}{43/43}{}{0}} 87 | \headcommand {\beamer@framepages {43}{43}} 88 | \headcommand {\slideentry {0}{0}{44}{44/44}{}{0}} 89 | \headcommand {\beamer@framepages {44}{44}} 90 | \headcommand {\slideentry {0}{0}{45}{45/45}{}{0}} 91 | \headcommand {\beamer@framepages {45}{45}} 92 | \headcommand {\slideentry {0}{0}{46}{46/46}{}{0}} 93 | \headcommand {\beamer@framepages {46}{46}} 94 | \headcommand {\slideentry {0}{0}{47}{47/47}{}{0}} 95 | \headcommand {\beamer@framepages {47}{47}} 96 | \headcommand {\slideentry {0}{0}{48}{48/48}{}{0}} 97 | \headcommand {\beamer@framepages {48}{48}} 98 | \headcommand {\slideentry {0}{0}{49}{49/49}{}{0}} 99 | \headcommand {\beamer@framepages {49}{49}} 100 | \headcommand {\slideentry {0}{0}{50}{50/50}{}{0}} 101 | \headcommand {\beamer@framepages {50}{50}} 102 | \headcommand {\slideentry {0}{0}{51}{51/51}{}{0}} 103 | \headcommand {\beamer@framepages {51}{51}} 104 | \headcommand {\slideentry {0}{0}{52}{52/52}{}{0}} 105 | \headcommand {\beamer@framepages {52}{52}} 106 | \headcommand {\slideentry {0}{0}{53}{53/53}{}{0}} 107 | \headcommand {\beamer@framepages {53}{53}} 108 | \headcommand {\slideentry {0}{0}{54}{54/54}{}{0}} 109 | \headcommand {\beamer@framepages {54}{54}} 110 | \headcommand {\slideentry {0}{0}{55}{55/55}{}{0}} 111 | \headcommand {\beamer@framepages {55}{55}} 112 | \headcommand {\slideentry {0}{0}{56}{56/56}{}{0}} 113 | \headcommand {\beamer@framepages {56}{56}} 114 | \headcommand {\slideentry {0}{0}{57}{57/57}{}{0}} 115 | \headcommand {\beamer@framepages {57}{57}} 116 | \headcommand {\slideentry {0}{0}{58}{58/58}{}{0}} 117 | \headcommand {\beamer@framepages {58}{58}} 118 | \headcommand {\slideentry {0}{0}{59}{59/59}{}{0}} 119 | \headcommand {\beamer@framepages {59}{59}} 120 | \headcommand {\slideentry {0}{0}{60}{60/60}{}{0}} 121 | \headcommand {\beamer@framepages {60}{60}} 122 | \headcommand {\slideentry {0}{0}{61}{61/61}{}{0}} 123 | \headcommand {\beamer@framepages {61}{61}} 124 | \headcommand {\slideentry {0}{0}{62}{62/62}{}{0}} 125 | \headcommand {\beamer@framepages {62}{62}} 126 | \headcommand {\slideentry {0}{0}{63}{63/63}{}{0}} 127 | \headcommand {\beamer@framepages {63}{63}} 128 | \headcommand {\slideentry {0}{0}{64}{64/64}{}{0}} 129 | \headcommand {\beamer@framepages {64}{64}} 130 | \headcommand {\slideentry {0}{0}{65}{65/65}{}{0}} 131 | \headcommand {\beamer@framepages {65}{65}} 132 | \headcommand {\slideentry {0}{0}{66}{66/66}{}{0}} 133 | \headcommand {\beamer@framepages {66}{66}} 134 | \headcommand {\slideentry {0}{0}{67}{67/67}{}{0}} 135 | \headcommand {\beamer@framepages {67}{67}} 136 | \headcommand {\slideentry {0}{0}{68}{68/68}{}{0}} 137 | \headcommand {\beamer@framepages {68}{68}} 138 | \headcommand {\slideentry {0}{0}{69}{69/69}{}{0}} 139 | \headcommand {\beamer@framepages {69}{69}} 140 | \headcommand {\slideentry {0}{0}{70}{70/70}{}{0}} 141 | \headcommand {\beamer@framepages {70}{70}} 142 | \headcommand {\slideentry {0}{0}{71}{71/71}{}{0}} 143 | \headcommand {\beamer@framepages {71}{71}} 144 | \headcommand {\slideentry {0}{0}{72}{72/72}{}{0}} 145 | \headcommand {\beamer@framepages {72}{72}} 146 | \headcommand {\slideentry {0}{0}{73}{73/73}{}{0}} 147 | \headcommand {\beamer@framepages {73}{73}} 148 | \headcommand {\slideentry {0}{0}{74}{74/74}{}{0}} 149 | \headcommand {\beamer@framepages {74}{74}} 150 | \headcommand {\slideentry {0}{0}{75}{75/75}{}{0}} 151 | \headcommand {\beamer@framepages {75}{75}} 152 | \headcommand {\slideentry {0}{0}{76}{76/76}{}{0}} 153 | \headcommand {\beamer@framepages {76}{76}} 154 | \headcommand {\slideentry {0}{0}{77}{77/77}{}{0}} 155 | \headcommand {\beamer@framepages {77}{77}} 156 | \headcommand {\slideentry {0}{0}{78}{78/78}{}{0}} 157 | \headcommand {\beamer@framepages {78}{78}} 158 | \headcommand {\slideentry {0}{0}{79}{79/79}{}{0}} 159 | \headcommand {\beamer@framepages {79}{79}} 160 | \headcommand {\slideentry {0}{0}{80}{80/80}{}{0}} 161 | \headcommand {\beamer@framepages {80}{80}} 162 | \headcommand {\slideentry {0}{0}{81}{81/81}{}{0}} 163 | \headcommand {\beamer@framepages {81}{81}} 164 | \headcommand {\slideentry {0}{0}{82}{82/82}{}{0}} 165 | \headcommand {\beamer@framepages {82}{82}} 166 | \headcommand {\slideentry {0}{0}{83}{83/83}{}{0}} 167 | \headcommand {\beamer@framepages {83}{83}} 168 | \headcommand {\slideentry {0}{0}{84}{84/84}{}{0}} 169 | \headcommand {\beamer@framepages {84}{84}} 170 | \headcommand {\slideentry {0}{0}{85}{85/85}{}{0}} 171 | \headcommand {\beamer@framepages {85}{85}} 172 | \headcommand {\slideentry {0}{0}{86}{86/86}{}{0}} 173 | \headcommand {\beamer@framepages {86}{86}} 174 | \headcommand {\slideentry {0}{0}{87}{87/87}{}{0}} 175 | \headcommand {\beamer@framepages {87}{87}} 176 | \headcommand {\slideentry {0}{0}{88}{88/88}{}{0}} 177 | \headcommand {\beamer@framepages {88}{88}} 178 | \headcommand {\slideentry {0}{0}{89}{89/89}{}{0}} 179 | \headcommand {\beamer@framepages {89}{89}} 180 | \headcommand {\slideentry {0}{0}{90}{90/90}{}{0}} 181 | \headcommand {\beamer@framepages {90}{90}} 182 | \headcommand {\slideentry {0}{0}{91}{91/91}{}{0}} 183 | \headcommand {\beamer@framepages {91}{91}} 184 | \headcommand {\slideentry {0}{0}{92}{92/92}{}{0}} 185 | \headcommand {\beamer@framepages {92}{92}} 186 | \headcommand {\slideentry {0}{0}{93}{93/93}{}{0}} 187 | \headcommand {\beamer@framepages {93}{93}} 188 | \headcommand {\slideentry {0}{0}{94}{94/94}{}{0}} 189 | \headcommand {\beamer@framepages {94}{94}} 190 | \headcommand {\slideentry {0}{0}{95}{95/95}{}{0}} 191 | \headcommand {\beamer@framepages {95}{95}} 192 | \headcommand {\slideentry {0}{0}{96}{96/96}{}{0}} 193 | \headcommand {\beamer@framepages {96}{96}} 194 | \headcommand {\slideentry {0}{0}{97}{97/97}{}{0}} 195 | \headcommand {\beamer@framepages {97}{97}} 196 | \headcommand {\slideentry {0}{0}{98}{98/98}{}{0}} 197 | \headcommand {\beamer@framepages {98}{98}} 198 | \headcommand {\slideentry {0}{0}{99}{99/99}{}{0}} 199 | \headcommand {\beamer@framepages {99}{99}} 200 | \headcommand {\slideentry {0}{0}{100}{100/100}{}{0}} 201 | \headcommand {\beamer@framepages {100}{100}} 202 | \headcommand {\slideentry {0}{0}{101}{101/101}{}{0}} 203 | \headcommand {\beamer@framepages {101}{101}} 204 | \headcommand {\slideentry {0}{0}{102}{102/102}{}{0}} 205 | \headcommand {\beamer@framepages {102}{102}} 206 | \headcommand {\slideentry {0}{0}{103}{103/103}{}{0}} 207 | \headcommand {\beamer@framepages {103}{103}} 208 | \headcommand {\slideentry {0}{0}{104}{104/104}{}{0}} 209 | \headcommand {\beamer@framepages {104}{104}} 210 | \headcommand {\beamer@partpages {1}{104}} 211 | \headcommand {\beamer@subsectionpages {1}{104}} 212 | \headcommand {\beamer@sectionpages {1}{104}} 213 | \headcommand {\beamer@documentpages {104}} 214 | \headcommand {\def \inserttotalframenumber {104}} 215 | -------------------------------------------------------------------------------- /latexbook/lecture_201411.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/lecture_201411.out -------------------------------------------------------------------------------- /latexbook/lecture_201411.snm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/lecture_201411.snm -------------------------------------------------------------------------------- /latexbook/lecture_201411.vrb: -------------------------------------------------------------------------------- 1 | \frametitle{Service 중복 실행 방지} 2 | 여러 곳에서 startService를 하는 경우, 모두 실행하지 않고 이미 시작되었으면 나머지는 skip 하고자 할 때가 있다.\\ 3 | 소스 코드는 교재 참고 4 | -------------------------------------------------------------------------------- /latexbook/myfault.tex: -------------------------------------------------------------------------------- 1 | \begin{comment} 2 | 내가 잘못 알던 내용이다. ㅎㅎ 문제 없다. 3 | 아래의 코드를 한번 보자. 4 | \begin{lstlisting}[frame=single] 5 | private Context context; 6 | 7 | public AccountPreference(Context context) { 8 | this.context = context; 9 | } 10 | public boolean isLogin() { 11 | SharedPreferences pref = context.getSharedPreferences(PREF_CONFIG_KEY, Context.MODE_PRIVATE); 12 | return pref.getBoolean(PREF_IS_LOGIN, false); 13 | } 14 | 15 | public void setLogin(boolean isLogin) { 16 | SharedPreferences pref = context.getSharedPreferences(PREF_CONFIG_KEY, Context.MODE_PRIVATE); 17 | SharedPreferences.Editor editor = pref.edit(); 18 | editor.putBoolean(PREF_IS_LOGIN, isLogin); 19 | editor.apply(); 20 | } 21 | \end{lstlisting} 22 | 23 | SharedPreferences에는 여러 개의 key가 있다면, 이 클래스는 어느 정도 커질 가능성이 있다. 24 | 그런데 context.getSharedPreferences가 계속 반복되기 때문에 멤버 변수로 빼고 싶은 마음이 든다.\\ 25 | 그래서 아래처럼 변경해보았다. 과연 문제가 없을까? 26 | 27 | \begin{lstlisting}[frame=single] 28 | private SharedPreferences pref; 29 | 30 | public AccountPreference(Context context) { 31 | this.pref = context.getSharedPreferences(PREF_CONFIG_KEY, Context.MODE_PRIVATE); 32 | } 33 | 34 | public boolean isLogin() { 35 | return pref.getBoolean(PREF_IS_LOGIN, false); 36 | } 37 | 38 | public void setLogin(boolean isLogin) { 39 | SharedPreferences.Editor editor = pref.edit(); 40 | editor.putBoolean(PREF_IS_LOGIN, isLogin); 41 | editor.apply(); 42 | } 43 | \end{lstlisting} 44 | 45 | 이제 아래 코드를 한번 보자. 첫번째 라인은 preference xml 파일에서 가져온 값이 false였다고 가정한다. 46 | \begin{lstlisting}[frame=single] 47 | accountPreference.isLogin(); // false 48 | accountPreference.setLogin(true); 49 | accountPreference.isLogin(); // false or true? 50 | \end{lstlisting} 51 | 여기서 3번째 라인의 결과는 true가 나오길 기대할 테지만, 전혀 그렇지 않다. 52 | 53 | \begin{lstlisting}[frame=single] 54 | private static final HashMap sSharedPrefs = 55 | new HashMap(); 56 | 57 | public SharedPreferences getSharedPreferences(String name, int mode) { 58 | SharedPreferencesImpl sp; 59 | synchronized (sSharedPrefs) { 60 | sp = sSharedPrefs.get(name); 61 | if (sp == null) { 62 | File prefsFile = getSharedPrefsFile(name); 63 | sp = new SharedPreferencesImpl(prefsFile, mode); 64 | sSharedPrefs.put(name, sp); 65 | return sp; 66 | } 67 | } 68 | if ((mode & Context.MODE_MULTI_PROCESS) != 0 || 69 | getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { 70 | // If somebody else (some other process) changed the prefs 71 | // file behind our back, we reload it. This has been the 72 | // historical (if undocumented) behavior. 73 | sp.startReloadIfChangedUnexpectedly(); 74 | } 75 | return sp; 76 | } 77 | \end{lstlisting} 78 | 79 | \end{comment} -------------------------------------------------------------------------------- /latexbook/pattern.tex: -------------------------------------------------------------------------------- 1 | \chapter{구현 패턴} 2 | 디자인 패턴을 공부하고 나면 어디에든 다 쓰고 싶은데, 꼭 필요한 때에만 쓰는 것이 좋다. 3 | 이 장에서는 디자인 패턴 외에도 복잡한 앱을 개발하는 데 도움이 될 만한 패턴을 이야기해보자. 4 | %항목에 따라서 동의하기 어려운 게 있다면, 이런 생각이 있다는 정도로 넘어가도 좋겠다. 5 | 6 | \section{싱글톤 패턴} 7 | \label{sec:singleton} 8 | % http://www.doubleencore.com/2013/06/context/ 9 | 앱에서 싱글톤을 잘못 사용하면 메모리 누수 가능성이 많다. 10 | 구조가 복잡한 앱을 보면 속도 이슈 때문에 싱글톤을 많이 사용하는데, 어디선가 메모리 누수가 발생하면 찾을 때 어려움이 많다. 11 | 가급적이면 싱글톤은 꼭 필요한 곳에만 사용하자.\\ 12 | 13 | 싱글톤이라도 Context는 전달해야 유용하게 쓸 수 있는 경우가 많은데, 그럼 Context를 그냥 전달하면 되는가 하면 그렇지 않다. 14 | Context를 그대로 전달하는 경우, 만일 그게 Activity라면 그 인스턴스는 싱글톤에 참조로 남아서 메모리에 계속 남는 문제가 생긴다. 15 | 싱글톤을 만들 때 소스 패턴은 아래와 같다. 이것은 support-v4에 포함된 LocalBroadcastManager의 소스이다. 16 | \begin{lstlisting}[frame=single, caption=LocalBroadcastManager.java] 17 | private final Context mAppContext; 18 | 19 | private static final Object mLock = new Object(); 20 | private static LocalBroadcastManager mInstance; 21 | 22 | public static LocalBroadcastManager getInstance(Context context) { 23 | synchronized (mLock) { 24 | if (mInstance == null) { 25 | mInstance = new LocalBroadcastManager( 26 | context.getApplicationContext()); // (1) 27 | } 28 | return mInstance; 29 | } 30 | } 31 | 32 | private LocalBroadcastManager(Context context) { 33 | mAppContext = context; 34 | ... 35 | } 36 | \end{lstlisting} 37 | 10라인(1)에서 context.getApplicationContext()를 사용함으로써 계속 떠있고 하나뿐인 Application 인스턴스를 Context로 쓰겠다는 의미이다. 38 | 많은 문서에서도 싱글톤을 만들 때 신경 쓰지 않고서 Context를 그대로 전달한 것을 볼 수 있는데 따라하면 안된다. 39 | 특히 SQLiteOpenHelper를 상속한 DB Helper를 싱글톤으로 사용하면서 Context를 그대로 전달한 경우를 보기도 했다.\\ 40 | 41 | % http://stackoverflow.com/questions/3346080/android-references-to-a-context-and-memory-leaks 에도 나옴 42 | 43 | 이제 조금 더 들어가서 이 내용을 검증하는 방법도 알아보자. 44 | 이론적으로 맞는데 정말 그런지 확인하는 방법이 있을까? 45 | 방법은 2가지가 있는데 여기서 검증할 내용은 다음과 같다. 46 | \begin{itemize} 47 | \item 싱글톤에 그대로 넘긴 Activity가 GC가 되지 않는가? 48 | \item getApplicationContext()로 싱글톤에 넘기면 Activity는 적절한 시기에 GC가 이뤄지는가? 49 | \end{itemize} 50 | 51 | 검증을 위해서 이제 3개의 클래스가 등장한다.\\ 52 | 53 | 첫 번째는 싱글톤 클래스이다. 54 | \begin{lstlisting}[frame=single] 55 | public class CalendarManager { 56 | 57 | private static final Object lock = new Object(); 58 | private static CalendarManager instance; 59 | 60 | public static CalendarManager getInstance(Context context) { 61 | synchronized (mLock) { 62 | if (instance == null) { 63 | instance = new CalendarManager(context); // (1) 64 | //instance = new CalendarManager(context.getApplicationContext()); 65 | } 66 | return instance; 67 | } 68 | } 69 | 70 | private Context context; 71 | 72 | private CalendarManager(Context context) { 73 | this.context = context; 74 | } 75 | 76 | public String getText() { 77 | return context.getString(R.string.hello_world); 78 | } 79 | 80 | } 81 | \end{lstlisting} 82 | 반례로서 9라인(1)에서 Context를 직접 전달해서 인스턴스를 생성하였다.\\ 83 | 84 | 두 번째는 싱글톤을 사용하는 Activity이다. AndroidManifest.xml의 Activity 선언에 android:config\-Changes 속성을 따로 넣지 말자. 85 | \begin{lstlisting}[frame=single] 86 | public class CalendarRelatedActivity extends Activity { 87 | 88 | @Override 89 | protected void onCreate(Bundle savedInstanceState) { 90 | super.onCreate(savedInstanceState); 91 | final TextView textView = new TextView(this); 92 | textView.setText("first run"); 93 | setContentView(textView); 94 | CalendarManager manager = CalendarManager.getInstance(this); // (1) 95 | } 96 | 97 | @Override 98 | protected void onDestroy() { 99 | Log.d(TAG, "CalenderRelatedActivity onDestroy"); 100 | super.onDestroy(); 101 | } 102 | 103 | @Override 104 | protected void finalize() throws Throwable { // (2) 105 | Log.d(TAG, "CalenderRelatedActivity finalize"); 106 | super.finalize(); 107 | } 108 | 109 | } 110 | \end{lstlisting} 111 | \begin{itemize} 112 | \item 9라인(1)에서 싱글톤 인스턴스를 가져오는데 Activity 자신인 this를 전달한다. 113 | \item 19라인(2)에서 GC가 될때 불리는 finalize() 메서드를 오버라이드하고 로그를 남긴다. 114 | \end{itemize} 115 | 116 | 세 번째는 CalendarRelatedActivity의 Caller이면서, gc()를 호출하거나 메모리 사용량을 계속 키워서 GC를 유도하는 Activity이다. 117 | \begin{lstlisting}[frame=single] 118 | public class SingletonTestActivity extends Activity { 119 | 120 | @Override 121 | protected void onCreate(Bundle savedInstanceState) { 122 | super.onCreate(savedInstanceState); 123 | setContentView(R.layout.two_buttons); 124 | } 125 | 126 | public void onClickAnotherActivity(View view) { 127 | startActivity(new Intent(this, CalendarRelatedActivity.class)); // (1) 128 | } 129 | 130 | public void onClickGc(View view) { 131 | Runtime.getRuntime().gc(); // (2) 132 | } 133 | 134 | private Bitmap bitmap; 135 | 136 | private int width = 480, height = 720; 137 | 138 | public void onClickMoreMemory(View view) { 139 | width *= 2; 140 | bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // (3) 141 | } 142 | 143 | } 144 | \end{lstlisting} 145 | \begin{itemize} 146 | \item 10라인(1)에서 CalendarRelatedActivity를 시작한다. 147 | 148 | \item 14라인(2)에서 직접 gc()를 호출한다. 퍼포먼스를 떨어뜨릴 수도 있기 때문에 앱에서 gc()를 직접 호출하는 것은 권장되지 않는다. 149 | 여기서는 단지 테스트를 위한 것이다. Activity가 onDestroy()까지 불리고서 바로 메모리에서 제거되는 것은 아니기 때문에 gc()를 실행해서 메모리에서 제거를 유도한다. 150 | 151 | \item 23라인(3)에서 클릭할 때마다 createBitmap()에서 width를 2배씩 늘려가면서 메모리 사용량을 키워가면서 GC를 유도한다. 14라인과 역할은 동일하다. 152 | \end{itemize} 153 | 154 | 첫 번째 검증 방법은 다음과 같다. 155 | \begin{enumerate} 156 | \item SingletonTestActivity에서 onClickAnotherActivity()을 통해 CalendarRelatedActivity를 실행시킨다. 157 | 158 | \item CalendarRelatedActivity에서는 바로 Back 키를 통해서 Activity를 종료한다. 이때 onDestory() 메서드가 불린다. 159 | 160 | \item SingletonTestActivity에서 onClickGc()를 실행하고서 로그를 확인하면, CalendarRelatedActivity의 finalize() 메서드가 불리지 않는다. Heap Dump 결과로 확인해보면 CalendarManager에서 참조가 남아있는 것을 볼 수 있다. 161 | 162 | \item 다른 방법으로도 확인해보자. 최근 앱에서 제거하거나 환경설정에서 앱을 강제 종료하고서 다시 CalendarRelatedActivity를 실행시킨다. 163 | 164 | \item CalendarRelatedActivity에서 화면 방향을 가로/세로 여러 번 회전한다. 이때 Heap Dump 결과를 보면 회전할 때마다 인스턴스 갯수가 늘어난 것을 볼 수 있다. 165 | 인스턴스 개수를 확인하고 Back 키를 통해서 Activity를 종료한다. 166 | 167 | \item SingletonTestActivity에서 onClickButtonGc()를 실행하고서 로그를 확인한다. CalendarRelatedActivity의 finalize() 메서드가 여러 번 불리는데, 앞에서 생성된 인스턴스 개수에서 1회 적게 불린다. 168 | 즉 1개의 인스턴스가 남아있는데 Heap Dump 결과로 확인하면 역시 CalendarManager에서 참조가 남아있다. 169 | \end{enumerate} 170 | 171 | 두 번째 검증 방법은 다음과 같다. 172 | \begin{enumerate} 173 | \item CalendarRelatedActivity에서 바로 Back 키를 통해서 Activity를 종료시킨다. 이때 onDestory() 메서드가 불린다. 174 | \item SingletonTestActivity에서 onClickMoreMemory()를 여러 번 실행하면서 로그를 확인한다. 175 | \end{enumerate} 176 | 177 | 로그 내용은 아래와 같다. 178 | \begin{lstlisting}[frame=single] 179 | 09-02 20:46:40.909 20337-20337/com.naver.android.sample D/suribada: CalenderRelatedActivity onDestroy 180 | 09-02 20:46:44.079 20337-20337/com.naver.android.sample D/dalvikvm: GC_FOR_ALLOC freed 201K, 31% free 8533K/12332K, paused 8ms, total 12ms 181 | 09-02 20:46:44.079 20337-20337/com.naver.android.sample I/dalvikvm-heap: Grow heap (frag case) to 16.449MB for 5529616-byte allocation 182 | 09-02 20:46:48.119 20337-20337/com.naver.android.sample D/dalvikvm: GC_FOR_ALLOC freed 2719K, 37% free 11216K/17736K, paused 8ms, total 11ms 183 | 09-02 20:46:48.129 20337-20337/com.naver.android.sample I/dalvikvm-heap: Grow heap (frag case) to 24.342MB for 11059216-byte allocation 184 | ... 185 | 09-02 20:46:59.589 20337-20337/com.naver.android.sample D/dalvikvm: GC_FOR_ALLOC freed 43201K, 49% free 92216K/179752K, paused 13ms, total 14ms 186 | 09-02 20:46:59.589 20337-20337/com.naver.android.sample I/dalvikvm-heap: Forcing collection of SoftReferences for 176947216-byte allocation 187 | 09-02 20:46:59.609 20337-20337/com.naver.android.sample D/dalvikvm: GC_BEFORE_OOM freed 9K, 49% free 92206K/179752K, paused 15ms, total 15ms 188 | 09-02 20:46:59.609 20337-20337/com.naver.android.sample E/dalvikvm-heap: Out of memory on a 176947216-byte allocation. 189 | \end{lstlisting} 190 | 191 | 계속 메모리 사용량이 늘어나면서 마지막 라인에서 OOM이 발생했는데, 여기서 포인트는 OOM이 발생했다는 것이 아니다. 192 | OOM이 발생할 때까지도 CalenderRelatedActivity의 finalize()가 불리지 않았다. 193 | 즉 CalenderRelatedActivity는 끝까지 GC 대상이 아니었다는 것이다.\\ 194 | 195 | 이제 CalendarManager에서 주석으로 있던 부분으로 대체해서 테스트해보자. 196 | 즉 context를 그대로 전달하지 않고 context.getApplicationContext()를 전달한 것이다. 197 | 첫 번째 검증 방법을 수행하면 finalize()가 정상적으로 불리는 것을 알 수 있다. 198 | 그리고 두 번째 검증 방법을 수행하면 아래와 같은 로그를 보게 된다. 199 | \begin{lstlisting}[frame=single] 200 | 09-02 20:51:27.099 25785-25785/com.naver.android.sample D/suribada: CalenderRelatedActivity onDestroy 201 | 09-02 20:51:29.739 25785-25785/com.naver.android.sample D/dalvikvm: GC_FOR_ALLOC freed 199K, 31% free 8532K/12332K, paused 15ms, total 19ms 202 | 09-02 20:51:29.739 25785-25785/com.naver.android.sample I/dalvikvm-heap: Grow heap (frag case) to 16.448MB for 5529616-byte allocation 203 | 09-02 20:51:29.749 25785-25794/com.naver.android.sample D/suribada: CalenderRelatedActivity finalize // (1) 204 | 09-02 20:51:30.399 25785-25785/com.naver.android.sample D/dalvikvm: GC_FOR_ALLOC freed 2719K, 37% free 11214K/17736K, paused 12ms, total 12ms 205 | ... 206 | 09-02 20:51:36.829 25785-25785/com.naver.android.sample E/dalvikvm-heap: Out of memory on a 176947216-byte allocation. 207 | \end{lstlisting} 208 | 4라인(1)에서 CalenderRelatedActivity의 finalize()가 불리었으니, 정상적으로 GC된 것을 알 수 있다. 209 | 210 | \section{마커 인터페이스} 211 | 마커 인터페이스\footnote{$\ulcorner$이펙티브 자바$\lrcorner$, 조슈아 블로크 저, 이병준 역, 인사이트, 2014, 규칙 37을 참고하자.} 212 | 는 메서드가 선언이 없는 인터페이스로, 표식(marking) 용도로 인터페이스를 사용하는 것이다. 213 | Serializable도 마커 인터페이스의 예이다. 이 절에서는 마커 인터페이스를 활용하는 방법을 생각해보자.\\ 214 | % http://blog.doortts.com/153 215 | 216 | 복잡한 부분은 클래스 구성을 잘 하면 쉽게 해결되는 부분이 많다. 217 | 쇼핑 앱을 예로 들어보자. 상품에는 여러 카테고리가 있어서, 구매가 이뤄지면 카테고리별로 진행하는 일이 다르다. 218 | \begin{itemize} 219 | \item A/B 카테고리는 구매패턴 분석을 위해 통계 DB에 데이터를 넣는다(someOperationW). 220 | \item A/C/E 카테고리는 문자로 상품제공자에게 알리고(someOperationX), C/D/E 카테고리는 메일로 알린다(SomeOperationY). 221 | \item B/D/E 카테고리는 사용자에게 다시 알림을 보낸다(someOperationZ). 222 | \end{itemize} 223 | 224 | 이 로직을 구현하는 기본적인 방법은 아래와 같다. 225 | \begin{lstlisting}[frame=single] 226 | if (A || B) { 227 | someOperationW(); 228 | } 229 | ... 230 | if (A || C || E) { 231 | someOperationX(); 232 | } 233 | ... 234 | if (C || D || E) { 235 | someOperationY(); 236 | } 237 | ... 238 | if (B || D || E) { 239 | someOperationZ(); 240 | } 241 | ... 242 | \end{lstlisting} 243 | 처음에는 이 방법도 쓸만한 것 같다. 244 | 그런데 어느 날 카테고리가 추가되고 Operation도 여러 개 더해진다면 주의를 기울여야 한다. 245 | 만일 평소에 작업하던 개발자가 자리라도 비운다면 긴장도가 높아질 것이다. 246 | 이 복잡도를 해결하는 시도는 아래처럼 할 수 있다. 247 | \begin{itemize} 248 | \item 부모 클래스에는 someOperationXxx() 메서드를 모두 구현한다. 249 | \item 각 카테고리를 나타내는 자식 클래스를 만들어서 someOperationXxx()를 순차적으로 호출하거나, 필요한 경우 오버라이드한다(오버라이드 필요성은 있다. 메일을 보낼 때 특정 문구가 필요한 경우가 그런 예일 것이다). 250 | \end{itemize} 251 | 252 | 이렇게 하면 해당 카테고리의 정책에 집중할 수 있어서 상황은 나아지지만 남은 문제점도 있다. 253 | \begin{itemize} 254 | \item 작업은 순서가 중요한 경우가 많다. 해당 카테고리의 특정 API를 네트워크 호출하고, 그 결과를 DB에 저장하는 경우를 예로 들 수 있다. 255 | 자식 클래스에서 부모 클래스의 메서드를 여러 개 호출하는 식이면, 순서를 잘못 호출할 가능성이 생긴다. 256 | 257 | \item 어떤 카테고리에서 someOperationW(), someOperationY(), someOperationZ()를 순차적으로 실행하고 있었는데, 정책이 바뀌면서 someOperationY()는 하지 않기로 했다. 258 | 이때 개발자는 어떤 행동을 할까? 259 | 언제 또 바뀔지 모른다며 주석으로 처리하려는 유혹에 빠지기 쉽다. 여기저기 주석 처리가 되면서 코드가 누더기가 되는 것은 물론이고, 주석을 해제해야 할 때가 되면 실수할 가능성이 많아진다. 260 | 261 | \item 카테고리가 추가될 때 기존 카테고리와 유사한 부분이 많다면, 이를 또 상속하고 싶은 유혹이 생긴다. 262 | 카테고리끼리의 상속은 정책이 바뀐다면 뜯어고칠 부분이 많아진다. 263 | 264 | \end{itemize} 265 | 266 | 3가지 중에서 첫 번째 내용이 제일 중요하다. 267 | 클래스 상속을 피할 수는 없지만, 부모 클래스에서는 작업 순서까지 지정하고 상속 단계를 최소한으로 할 수 있는 방법을 찾는 게 좋다.\\ 268 | 269 | 여기에서 필자가 선택한 방법이 마커 인터페이스이다. 마커 인터페이스를 통해 두 번째와 세 번째도 문제가 해결되는 것을 확인해보자. 270 | 지금 상황에서 마커 인터페이스를 적용하는 방법을 보자. 271 | 먼저 각 작업을 마커 인터페이스로 매핑한다.\\ 272 | 273 | \begin{lstlisting}[frame=single] 274 | public interface Markable1 { } 275 | 276 | public interface Markable2 { } 277 | 278 | public interface Markable3 { } 279 | 280 | public interface Markable4 { } 281 | \end{lstlisting} 282 | 283 | 284 | 이제 각 카테고리별로 자식 클래스를 만든다. 285 | 286 | \begin{lstlisting}[frame=single] 287 | Aclass extends Category implemtens Markable1, Markable2 { } 288 | 289 | Bclass extends Category implements Markable1, Markable4 { } 290 | 291 | Cclass extends Category implements Markable2, Markable3 { } 292 | 293 | Dclass extends Category implements Markable3, Markable4 { } 294 | 295 | Eclass extends Category implements Markable2, Markable3, Markable4 { } 296 | \end{lstlisting} 297 | 298 | 299 | 부모 클래스에서는 인스턴스를 체크해서 해당 작업을 진행한다. 300 | \begin{lstlisting}[frame=single] 301 | if (this instanceof Markable1) { 302 | someOperationW(); 303 | } 304 | ... 305 | if (this instanceof Markable2) { 306 | someOperationX(); 307 | } 308 | ... 309 | if (this instanceof Markable3) { 310 | someOperationY(); 311 | } 312 | ... 313 | if (this instanceof Markable4) { 314 | someOperationZ(); 315 | } 316 | ... 317 | \end{lstlisting} 318 | 이렇게 하면 부모 클래스에서 각 Operation 순서가 정해지고, 319 | 각 카테고리의 정책이 바뀌면 카테고리 클래스에서 implements하는 것만 조정하면 된다. 320 | 작업이 추가되면 MarkableN 인터페이스를 1개 더 만들고서 규칙에 맞추기만 하면 된다.\\ 321 | 322 | 마커 인터페이스는 파라미터가 많은 메서드를 호출할 때도 도움이 된다. 323 | 아래와 같은 메서드 시그너처가 있다고 하자. 324 | \begin{lstlisting}[frame=single] 325 | public static void showPhoneDialog(Context context, String phoneNumber, 326 | String keyword, boolean isNaverUser, boolean isKorean) { 327 | ... 328 | } 329 | \end{lstlisting} 330 | 331 | 안드로이드 프레임워크 소스에도, 전달되는 파라미터가 많은 메서드를 흔하게 볼 수 있다. ActivityThread의 bindApplication() 메서드를 보면 파라미터가 18개나 된다. 332 | 파라미터가 많다면 메서드 내에서 파라미터에 따른 조건문도 복잡해진다. 333 | 이렇게 파라미터가 많은 메서드에서, 동일한 타입이 연달아 나오는 경우 실수할 가능성이 많다. 334 | showPhoneDialog() 메서드에서 phoneNumber와 keyword, 그리고 isNaverUser와 isKorean은 타입이 동일하기 때문에 호출하는 쪽에서 데이터를 반대로 잘못 넣을 수 있다. 335 | 타입은 맞기 때문에 컴파일러에서는 검증할 게 없고, 결국 사람이 제대로 값을 써주어야 한다. 336 | Builder 패턴을 통해 파라미터 개수를 줄이려는 노력을 할 수도 있다. 그래도 아직은 파라미터가 아주 많지는 않으니까(5개 이내) 파라미터를 더 늘리지 않는 쪽에 주의해보자.\\ 337 | 338 | 앱에 기능을 추가하면서 여기서도 showPhoneDialog() 메서드를 호출하기로 했다. 339 | 그런데 이 기능에서는 showPhoneDialog()에서 하는 여러 작업 가운데 한 가지는 필요 없다는 얘기가 들려왔다. 340 | 내부에서 savePhoneState() 메서드를 호출하는데, 이 기능에서만은 savePhoneState() 메서드 호출이 불필요하다는 것이다. 341 | 이때 고민이 된다. boolean 파라미터를 하나 추가하고서 이것으로 체크할까? 342 | 그러면 boolean 파라미터가 3개 연속이 되어서 호출하는 쪽에서 더 혼동이 생길 것이다.\\ 343 | 344 | 가장 먼저 떠오르는 해결방법은 아래와 같다. 345 | \begin{lstlisting}[frame=single] 346 | public static void showPhoneDialog(Context context, String phoneNumber, 347 | String keyword, boolean isNaverUser, boolean isKorean) { 348 | showPhoneDialog(context, phoneNumber, keyword, isNaverUser, isKorean, 349 | false); 350 | } 351 | 352 | public static void showPhoneDialog(Context context, String phoneNumber, 353 | String keyword, boolean isNaverUser, boolean isKorean, 354 | boolean ignorePhoneState) { 355 | ... 356 | if (!ignorePhoneState) { 357 | savePhoneState(); 358 | } 359 | ... 360 | } 361 | \end{lstlisting} 362 | 다른 쪽 호출 로직에 덜 영향을 주기 위해 두 번째 메서드로 오버로드했지만, 두 번째 메서드도 결국 public 메서드이기 때문에 역시 파라미터가 많은 문제점을 안고 있다. 363 | 이런 때에 쓸 수 있는 방법도 바로 마커 인터페이스다. Context 파라미터에 컴포넌트 this가 주로 전달되는데, 컴포넌트에 마커 인터페이스를 하나 연결해주면 된다. 364 | 365 | \begin{lstlisting}[frame=single] 366 | public TaxiActivity extends Activity implements IgnorePhoneState { 367 | ... 368 | } 369 | \end{lstlisting} 370 | 371 | \begin{lstlisting}[frame=single] 372 | public static void showPhoneDialog(Context context, String phoneNumber, 373 | String keyword, boolean isNaverUser, boolean isKorean) { 374 | ... 375 | if (!context instanceof IgnorePhoneState) { 376 | savePhoneState(); 377 | } 378 | ... 379 | } 380 | \end{lstlisting} 381 | 382 | 마커 인터페이스는 마커 애노테이션(Marker Annotation)으로 대체 가능한 것을 알 수 있다. 383 | 384 | \begin{lstlisting}[frame=single] 385 | @Retention(RetentionPolicy.RUNTIME) 386 | @interface Markable1 { 387 | } 388 | 389 | @Retention(RetentionPolicy.RUNTIME) 390 | @interface Markable2 { 391 | } 392 | 393 | @Retention(RetentionPolicy.RUNTIME) 394 | @interface Markable3 { 395 | } 396 | 397 | @Retention(RetentionPolicy.RUNTIME) 398 | @interface Markable4 { 399 | } 400 | \end{lstlisting} 401 | 402 | \begin{lstlisting}[frame=single] 403 | Class clazz = this.getClass(); 404 | if (clazz.isAnnotationPresent(Markable1.class)) { 405 | someOperationW(); 406 | } 407 | ... 408 | if (clazz.isAnnotationPresent(Markable2.class)) { 409 | someOperationX(); 410 | } 411 | ... 412 | if (clazz.isAnnotationPresent(Markable3.class)) { 413 | someOperationY(); 414 | } 415 | ... 416 | if (clazz.isAnnotationPresent(Markable4.class)) { 417 | someOperationZ(); 418 | } 419 | ... 420 | \end{lstlisting} 421 | 422 | 이런 경우에는 인터페이스나 애노테이션을 쓰는 게 별 차이가 있어 보이지 않는다. 다만 인터페이스를 구현하면 자식 클래스에도 영향이 있지만, 애노테이션을 쓰는 경우 상속과 관련없이 각 클래스마다 개별적으로 애노테이션을 지정해야 한다. 어느 것이 맞는 게 아니라 방식을 이해하고 선택하도록 하자. 423 | 424 | \begin{comment} 425 | \subsection{EnumSet 또는 bit flag 사용} 426 | 오버라이딩할 필요가 있는데 속성의 수도 많아 메서드를 수십 개 만들어야 하는 상황이라면 EnumSet을 활용할 수도 있다. 427 | 428 | \begin{lstlisting}[frame=single] 429 | @Override 430 | protected EnumSet getAttributes() { 431 | return EnumSet.of(Attr.A, Attr.B, Attr.D); 432 | } 433 | 434 | \end{lstlisting} 435 | 436 | 상위 클래스에서 다음과 같이 쓴다. 437 | \begin{lstlisting}[frame=single] 438 | EnumSet enumSet = getAttributes(); 439 | if (enumSet.contains(Attr.A)) { 440 | someOperationW(); 441 | } 442 | \end{lstlisting} 443 | 444 | EnumSet과 유사한 방식으로 bit field를 사용할 수도 있다. Effective Java Item 32에는 bit field 보다는 EnumSet을 쓰라고 나오지만, bit field도 안드로이드에서 많이 쓰이는 방식이다.\footnote{안드로이드에서는 퍼포먼스 이슈 때문에 Enum보다는 int 상수를 쓰라는 가이드가 아직 남아있다. Effective Java와 상충되는 여러 가지 가이드가 있지만, 갈수록 고려할 내용에서 줄어들고 있다.} 445 | 446 | \subsubsection{하위 클래스에서 boolean 리턴 메서드 오버라이드} 447 | boolean을 리턴하는 메서드를 쓰는 것도 비슷하긴 하다. 448 | 449 | 부모 클래스에서 아래와 같이 사용한다. 450 | 451 | \begin{lstlisting}[frame=single] 452 | public void execute() { 453 | if (isAttribute1()) { 454 | someOperationW(); 455 | } 456 | ... 457 | } 458 | 459 | protected boolean isAttribute1() { 460 | return false; 461 | } 462 | \end{lstlisting} 463 | 464 | 하위 클래스에서 isAttributeA() 메서드를 필요할 때 오버라이드 하는 방식이다. 465 | 나도 이 방법을 쓰는 경우가 있긴 한데, marker interface는 좀 더 복잡한 케이스에 더 맞는다.\\ 466 | 467 | 경우를 좀 생각해보자. 468 | 해야 할 if 문이 10개라고 하면 10개의 boolean을 리턴하는 메서드가 필요해진다. 469 | 하위 클래스에서는 어느 것에 true이고, 어느 것에 false인지를 정해주면 되는데, 갯수가 많아지면, 나중에 정책이 바뀔 때 작업에서 혼돈이 있지 않을까.\\ 470 | 471 | 한 가지 비유를 생각해보았다. 472 | 10권의 지정된 책이 있다. 내가 속한 조직의 구성원들한테, 각 책을 갖고 있는지 여부에 따라서 뭔가를 해주고 싶다. 책을 사준다든가, 교육을 보내준다든가 하는 게 있을 것이다.\\ 473 | 474 | 이때 각각의 boolean 리턴 메서드를 만들 수 있다. hasEffectiveJava(), hasAndroidHacks(), ... 475 | 각 구성원 클래스는 이것을 오버라이드 해서 갖고 있는 책에다 true를 리턴하게 하면 된다.\\ 476 | 477 | 그런데 어느날 책 장터가 열려서 각자 갖고 있는 책들이 다 바뀌어 버렸다. 478 | 이때 또 각각 찾아서 true/false를 바꿔주면 된다.\\ 479 | 480 | 그런데 이런 경우에 이게 더 쉽지 않을까? 책1 가지고 있는지, 책2 가지고 있는지 계속 묻는 거 보다, 자기가 갖고 있는 책만 나열하라고 하는 것이다. 481 | boolean 메서드를 쓰는 방식이 책 각각에 대해서 묻는 것이고, marker interface를 쓰는 건 갖고 있는 것만 나열하는 방식이다.\\ 482 | 483 | 한두개라면 boolean 메서드 방식이 확실히 더 낫겠지만, 갯수가 막 늘어나면 한계가 보인다는 것이다. 484 | 결국 100\% 정답은 없고, 저울질을 해봐야 한다.\\ 485 | 486 | marker interface의 장점이 또 하나 있다. 487 | 10권에서 한권이 늘어나도, 수정이 간단하다. 인터페이스 하나 만들고, 각 하위 클래스에다 implements 에 인터페이스 추가만 하면 된다. 488 | 아무리 IDE가 편리해 졌다지만, 메서드 생성하고 true/false 리턴하는 번거로움 보다 낫다.\\ 489 | \end{comment} 490 | 491 | \section{Fragment 정적 생성} 492 | 아래 2가지 패턴 가운데 첫 번째 패턴이 개발자 사이트나 많은 샘플에서 주로 쓰고 있다. 그런데 왜인지는 잘 나와있지 않다. 493 | \begin{lstlisting}[frame=single] 494 | public static ContentFragment newInstance(int left, int right) { 495 | ContentFragment fragment = new ContentFragment(); 496 | 497 | Bundle args = new Bundle(); 498 | args.putInt(LEFT, left); 499 | args.putInt(RIGHT, right); 500 | fragment.setArguments(args); 501 | 502 | return fragment; 503 | } 504 | 505 | @Override 506 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 507 | Bundle savedInstanceState) { 508 | View view = inflater.inflate(R.layout.content_result, container, false); 509 | TextView result = (TextView) view.findViewById(R.id.result); 510 | int sum = getArguments().getInt(LEFT) + getArguments().getInt(RIGHT); 511 | result.setText("결과=" + sum); 512 | return view; 513 | } 514 | \end{lstlisting} 515 | 516 | \begin{lstlisting}[frame=single] 517 | private int left; 518 | private int right; 519 | 520 | public void set(int left, int right) { 521 | this.left = left; 522 | this.right = right; 523 | } 524 | 525 | @Override 526 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 527 | Bundle savedInstanceState) { 528 | View view = inflater.inflate(R.layout.content_result, container, false); 529 | TextView result = (TextView) view.findViewById(R.id.result); 530 | int sum = left + right; 531 | result.setText("결과=" + sum); 532 | return view; 533 | } 534 | \end{lstlisting} 535 | 536 | 첫 번째 방식은 사용하려는 변수를 어렵게 넣고 빼는 거 같아서, 사용하기에는 두 번째가 편한 것 같다. 537 | 그래서 두 번째 방식을 선택하려는 유혹이 있는데, 첫 번째 방식을 권장하는 이유는 바로 Configuration 변경과 Activity의 예기치 않은 종료에 대응할 수 있기 때문이다. 538 | 첫 번째 방식은 값을 유지하고 두 번째는 값을 유지하지 못한다.\\ 539 | 540 | 프레임워크 소스를 통해서 원인을 파악해보자. 541 | \begin{itemize} 542 | \item Fragment에는 FragmentState라는 Parcelable을 구현한 내부 클래스가 있고 여기에 Fragment의 setArguments() 메서드에 전달한 Arguments Bundle을 저장한다. 543 | \item Activity의 onSaveInstanceState()에서 FragmentManagerImpl의 saveAllState()를 호출해서 FragmentState 값들을 저장한다. 544 | \item FragmentState에 속한 것이 아닌, 두 번째 방식에서 우리가 만든 Fragment의 변수들은 당연히 저장이 안된다. 545 | \end{itemize} 546 | 547 | 두 번째 방법에서도 값을 유지할 수는 있는데, 가장 단순한 방법은 첫 번째 방법과 유사하게 set() 메서드에서 단순 대입이 아닌 setArguments()에 Bundle로 담아서 전달하는 것이다. 548 | 값을 유지하는 좀 더 복잡한 방법으로는 Fragment에서 onSaveInstanceState()를 오버라이드하는 것이다. 549 | \begin{lstlisting}[frame=single] 550 | @Override 551 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 552 | Bundle savedInstanceState) { 553 | View view = inflater.inflate(R.layout.content_result, container, false); 554 | TextView result = (TextView) view.findViewById(R.id.result); 555 | if (savedInstanceState != null) { 556 | left = savedInstanceState.getInt(LEFT); 557 | right = savedInstanceState.getInt(RIGHT); 558 | } 559 | int sum = left + right; 560 | result.setText("결과=" + sum); 561 | return view; 562 | } 563 | 564 | @Override 565 | public void onSaveInstanceState(Bundle outState) { 566 | outState.putInt(LEFT, left); 567 | outState.putInt(RIGHT, right); 568 | super.onSaveInstanceState(outState); 569 | } 570 | \end{lstlisting} 571 | onSaveInstanceState()를 꼭 써야하는 상황은 있겠지만, 전달된 값을 유지하기 위해서 이런 식으로 하진 말자. 프레임워크에서 이미 제공하는 기능을 피할 이유가 없다.\\ 572 | 573 | 첫 번째 방식이 선호되는 또 다른 이유는 LEFT나 RIGHT 같은 변수명이 외부에 노출되지 않는 장점 때문이다. 574 | Activity간 전환에서도 Intent의 Extra Bundle 키 값을 서로 알 필요가 없게 하기 위해서 정적인 생성 방식을 사용하기도 한다. 575 | 576 | \section{AOP} 577 | 578 | AOP를 굳이 설명은 하지 않고 아래 링크로 대체하겠습니다. 579 | http://isstory83.tistory.com/90 580 | 581 | 582 | 583 | 안드로이드 캘린더앱에서는 적용 여부 검토 예정으로, 기본 테스트만 해본 상태입니다. 584 | 585 | 586 | 패스코드 적용하던 중에 고민을 해보았는데요. 587 | 상위에서 패스코드 관련 처리를 하는 상속구조로 하거나, 588 | 액티비티를 파라미터로 전달해서 Helper 클래스를 필요한 곳에서 매번 호출하거나 해야 합니다. 589 | 590 | 상속 구조는 상당히 문제가 많죠.. 본래 상속해야 하는 게 BaseOneActivity, BaseAnotherActivity가 각각 있었다면, 591 | 각각에다가 동일한 코드를 붙여줘야 하는 문제가 있어요. 592 | 상속 구조가 복잡한 프로젝트에서는 What the hell 이 될 수 있어요. 593 | (제가 거쳐온 프로젝트에서는 네이버앱이 가장 상속 구조가 복잡했어요.) 594 | 595 | Helper를 쓰면 그나마 나은 부분이 있지만, 열심히 코드마다 카피를 해줘야 하니까요. 596 | 이보다 나은 방법이 있을까 하다가 AOP 적용을 떠올려봤습니다. 597 | 598 | 웹개발 해보신 분들은 spring aop 써보신 분들 계실텐요. 이제는 기억도 가물하지만 쓸모가 많았어요. 599 | 로깅이나 속도 체크, 트랜잭션 등 반복되는 동일 로직을 매번 코드에 심지않고 Advice에 넣어서 실행 범위를 정해서 실행하도록 했죠. 600 | 601 | 602 | spring aop 쓸때는 aspect compiler가 필요하진 않았는데(내부적으로 프락시를 만드는 형태인 경우. 이게 간편하니까요.), 603 | 604 | 안드로이드에서 할 때는 aspect 컴파일러가 필요해집니다. 605 | 606 | 관련한 프로그램 설치 관련해서는 아래 페이지 참고하면 되고요. 607 | http://ecogeo.tistory.com/category/Android?page=3 608 | 609 | eclipse나 ant, maven 모두 지원 가능하므로 로컬 빌드 뿐만 아니라 CI 빌드도 문제는 없습니다.(서버에서 aspectj를 깔아야겠죠.) 610 | 611 | 처음 적용하려면 번거로움도 있고 이해하는 데 어려움은 있겠지만, 해놓고 나면 편리한 부분이 많을 듯 합니다. 612 | 613 | 스프링에서도 aspectj 와 함께 사용하는 것이 더 기능이 많다고 나오네요. 614 | http://javajigi.net/pages/viewpage.action?pageId=1081 615 | 616 | 아래 링크들도 시간되면 함 보세요. 617 | 618 | http://www.eclipse.org/aspectj/doc/next/adk15notebook/ataspectj.html 619 | http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte:fdl:aop:aspectj 620 | 621 | \begin{comment} 622 | \section{Model View Presenter 패턴} 623 | 전체적으로 쓰기에는 무리가 있다. 624 | 뷰 네비게이션 정도로만 사용하는 것이 좋지 않을까. 625 | Presenter를 쓰면 뷰를 다른 뷰로 교체하는 게 수월한 부분은 큰 이익입니다. 626 | 여러 개의 뷰를 하나의 Presenter로 커버하는 건 간단치는 않은 듯 하고요. 627 | "리스트뷰가 그리드뷰로 바뀌었다" 이 정도의 요구사항이면 그럴 수 있지만, 실제 요구사항은 정말 복잡다단하잖아요. 628 | 629 | Presenter에서 네트웍 상태를 체크해서 뷰에 반영하기 위해서는 어찌해야 될까 생각해봤습니다. 630 | Context 가 Presenter에 전달되어 있어야 겠죠. Context가 전달되어야만 할 수 있는 게 무지 많습니다. 631 | 632 | Context만 전달되면 모든게 다 될까요? 경우에 따라서는 액티비티가 전달이 되어야 할 수도 있습니다. 633 | 그렇다면 액티비티에서는 바로 XXX 메서드호출해서 체크하는 것을 Presenter에서는 ((Activity) context).XXX 해야하는 경우까지 생길 수 있겠죠. 634 | 635 | 요지는 뭔가 하면 디펜던시를 어떻게 완전히 끊느냐입니다. 636 | 637 | 웹에서 유명한 프레임웍이 스트러츠와 스프링이 있습니다. 638 | 초기에 스트러츠에서는 액션을 만들때 HttpServletRequest라는 걸 계속 전달을 해왔습니다. 근데 이 Servlet API 종속때문에 테스트도 어렵고, 그 자체로 완결성을 갖기가 어려웠어요. 639 | 그런데 스프링에서는 프레임웍 내부에서 알아서 다 하고, 개발자가 POJO 만 만들면 되었어요. 640 | POJO만 만들었으니, 이건 꼭 웹에서만 사용하는 것도 아니고, 일반 애플리케이션에서도 사용하는 데 아무 문제가 없었죠. 641 | 642 | 안드로이드 MVP 패턴을 전적으로 사용함에도 코드 복잡성을 줄이기 위해서는 일종의 프레임웍까지 함께 있어야 한다는 게 제 생각이고요. 643 | 모바일에서 속도를 희생시키지 않으면서도 잘만든 프레임웍을 만드는 건 꽤 어려운 일인 듯 합니다만.. 644 | 645 | \end{comment} -------------------------------------------------------------------------------- /latexbook/ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/ps.png -------------------------------------------------------------------------------- /latexbook/ps.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/ps.tiff -------------------------------------------------------------------------------- /latexbook/separate_process.tex: -------------------------------------------------------------------------------- 1 | \section{Looper/Handler} 2 | -------------------------------------------------------------------------------- /latexbook/service-binding-tree-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/service-binding-tree-lifecycle.png -------------------------------------------------------------------------------- /latexbook/service-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/service-lifecycle.png -------------------------------------------------------------------------------- /latexbook/sh.exe.stackdump: -------------------------------------------------------------------------------- 1 | Stack trace: 2 | Frame Function Args 3 | 0028B9A8 610823D1 (00000000, 00000000, 00000000, 00000000) 4 | End of stack trace 5 | -------------------------------------------------------------------------------- /latexbook/singletonproblem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/singletonproblem.png -------------------------------------------------------------------------------- /latexbook/singletonproblem.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/singletonproblem.tiff -------------------------------------------------------------------------------- /latexbook/sourcetree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/sourcetree.png -------------------------------------------------------------------------------- /latexbook/start.tex: -------------------------------------------------------------------------------- 1 | \chapter{안드로이드 프레임워크} 2 | 먼저 안드로이드 프레임워크의 소프트웨어 스택에 대한 기본적인 내용과 프레임워크 소스 활용 방안, 그리고 안드로이드 버전에 따른 이슈를 살펴보자. 3 | 4 | \section{프레임워크 개요} 5 | 아래는 여러 문서에도 많이 나오는 그림인데 각 스택의 내용을 간단히 살펴보자.\\ 6 | 7 | \includegraphics[scale=0.65]{system-architecture} 8 | \begin{itemize} 9 | \item 안드로이드에서 제공하는 기본 앱(Home, Camera, Dialer, Browser 등)과 일반 앱은 Applications 스택에 있고, Applications는 Application Framework 스택 위에서 동작한다. 10 | 기본 앱은 소스가 공개돼 있으므로 참고 자료로 활용할 수 있다. 단말 제조사들은 일반적으로 기본 앱을 커스터마이징하고, 제조사 단말에 특화된 앱도 추가로 제공한다. 11 | 요점은 단말에 깔린 기본 앱과 우리가 만드는 앱은 동일한 레벨이라는 것이다. 다만 기본 앱은 시스템 권한을 사용할 수 있는 게 다르다. 12 | 13 | \item Application Framework는 자바 기반의 여러 Manager가 있다. 내부적으로는 JNI를 연결해서 네이티브 C/C++로 작성된 것이 많다(Telephony Manager, Location Manager 등 주로 하드웨어 관련한 것이 그렇다). 14 | 각종 Manager는 system\_server라는 별도 프로세스에서 실행되고, 각각 시스템 서비스 형태로 있어서 앱에서는 Context의 getSystemService(String name) 메서드로 접근해서 사용한다. 15 | 별도의 프로세스에서 실행되므로 시스템 서비스 접근은 Binder IPC를 이용한 프로세스 간 통신이 필요하다. 16 | 17 | \item Android Runtime의 Core Libraries에는 android.jar가 있다. 여기에는 android.* 패키지, com.android.* 패키지, 자바 기본 패키지(java.*, javax.*), Apache HttpClient(마시멜로에서 제거됨), DOM/SAX/XM\-LPullParser 등 자바 클래스와 안드로이드 기본 리소스(android.R)가 포함되어 있다. 18 | 19 | \item Dalvik Virtual Machine은 자바/C/C++로 작성되어 있다(롤리팝에서는 Dalvik을 대체한 ART가 적용).\footnote{\url{https://source.android.com/devices/tech/dalvik/art.html}을 참고하자.} Register 기반의 가상 머신(Virtual Machine)으로 자바 가상 머신보다 명령이 단순하고 속도가 빠르다. 20 | % Dalvik Virtual Machine은 한 번에 여러 가상 머신 인스턴스를 띄울 수 있다. 21 | % J2SE/J2EE 자바 애플리케이션을 만들다보면 여러 가상 머신 인스턴스는 당연한 얘기지만, J2ME에서는 여러 Midlet이 하나의 가상 머신에서 돌아가는 방식이었고 안드로이드에서는 이 방식을 변경하였다. (SVN, MVM이 있어서 하나의 가상 머신이 맞는 얘기가 아님) 22 | 23 | \item Libraries에는 3가지 범주의 라이브러리가 있다.\footnote{HAL(Hardware Abstraction Layer)는 별도로 나오기도 하고 Libraries에 포함하기도 한다.} 24 | \begin{itemize} 25 | \item Bionic이라는 커스텀 C 라이브러리(libc)\footnote{\url{http://surai.tistory.com/28}을 참고하자.} 26 | \item WebKit/SQLite/OpenGL 같은 기능 라이브러리 27 | \item 네이티브 시스템 서비스인 Surface Manager, Media Framework\\ 28 | (/system/bin/surfaceflinger와 /system/bin/mediaserver 프로세스로 각각 실행) 29 | \end{itemize} 30 | 31 | \item 안드로이드의 커널은 리눅스 커널을 기반으로 불필요한 것은 제거하고(X-Window, glibc, 표준 리눅스 유틸리티 일부 등) 확장 패치한 것이다(Binder, Ashmem, Low Memory Killer 등).\\ 32 | 33 | 확장 패치한 내용 가운데 Binder IPC는 프로세스 간 통신에 사용하는 방식이다. 34 | Binder에서 많이 혼동되는 게 Binder IPC(Inter Process Communication)와 Binder RPC(Remote Procedure Call)라는 두 용어인데, IPC는 하부 메커니즘이고 RPC는 IPC의 용도(리모트 콜)이다. 35 | 안드로이드 컴포넌트 가운데 Service와 ContentProvider는 Binder를 통해서 다른 프로세스에서 접근할 수 있다. 36 | 앱 프로세스에는 Binder Thread라는 네이티브 스레드 풀이 있고(16개까지 생성), 다른 프로세스에서는 이 스레드 풀을 통해서 접근한다. 37 | DDMS에서 보면 Binder\_1, Binder\_2와 같은 이름의 스레드가 바로 Binder Thread에 속한 것이다. 38 | 39 | \end{itemize} 40 | % 안드로이드 SDK는 Java 프로그래밍 언어를 사용하여 안드로이드 플랫폼 상의 애플리케이션 개발에 필요한 API들과 도구들을 제공한다.(kandroid) 41 | \section{프레임워크 소스} 42 | 4.X 이상 버전의 프레임워크 소스는 Android SDK Manager를 이용해서 Sources for Android SDK를 선택해서 다운로드할 수 있고, IDE에서 소스를 연결해서 보는 경우에 유용하다. 43 | 2.X 버전의 소스를 확인하거나 네이티브까지 소스를 확인하려면, \url{https://android.googlesource.com}에서 관련 소스를 다운로드하면 된다.\footnote{\url{http://source.android.com/source/downloading.html}을 참고하자.} 참고로 3.X 허니콤 소스는 공개되어 있지 않다. 44 | 3.X는 태블릿 전용 버전이고, 만일 소스가 공개되어 이 버전이 폰에 이식된다면 파편화 문제가 심각해질 우려가 있어서 비공개로 결정되었다고 한다. 45 | 커널 소스는 \url{https://source.android.com/source/building-kernels.html}을 참고해서 별도로 다운로드한다.\\ 46 | 47 | 프레임워크 소스를 다운로드하는 곳으로 \url{https://github.com/android}도 있는데, 여기는 읽기 전용 미러링 GitHub 계정이다. 48 | 항목별 소스 다운로드나 웹브라우저로 소스 보기에는 더 편리하다는 장점이 있다.\\ 49 | 50 | https://android.googlesource.com에서 소스를 다운로드한 결과는 아래와 같다. 51 | 52 | \includegraphics[scale=0.7]{sourcetree} 53 | 54 | 여기서 주요 디렉터리만 살펴보도록 하자. 55 | \begin{itemize} 56 | \item frameworks : 안드로이드 프레임워크. android로 시작하는 자바 패키지 포함 57 | \item libcore: 자바 core 패키지 포함 58 | \item system : 안드로이드 init 프로세스 59 | \item packages : 안드로이드 기본 애플리케이션 60 | \item bionic: 안드로이드 표준 C 라이브러리 61 | \item dalvik: 달빅 가상 머신 62 | \item cts: 안드로이드 호환성 테스트 관련 63 | \item build: 빌드 시 사용 64 | \end{itemize} 65 | 66 | 개발 시에 소스를 버전별로 모두 다운로드하지는 않고, 보통 프레임워크 소스는 67 | \url{http://grepcode.com}에서 확인하기도 한다. 링크가 잘 되어 있고 버전별로 변경 내용을 확인해 볼 수도 있다.\\ 68 | 69 | 개발하다가 궁금한 게 있을 때, 프레임워크 소스에서 확인하지 않고 \href{http://stackoverlow.com}{StackOverflow}에 의존하는 경향을 가진 이들도 있다. % 면접에서 이런 분을 보기도 했다. 70 | StackOverflow에는 다양한 수준과 경험을 가진 사람들이 있어서 답변들이 어느 것이 맞는지 모호하기도 하고, 정작 내가 원하는 답은 없을 때도 있다. 71 | StackOverflow에서 답변을 찾았다고 해도 그게 맞는 것인지 검증하려면 테스트를 수행하고 프레임워크 소스로 재차 검증하는 것이 좋다.\\ 72 | 73 | 예를 들어보자. 74 | ListView의 아이템 레이아웃에 CheckBox가 있으면 ItemClick이 정상적으로 동작하지 않는다. 75 | 이런 내용은 책이나 강의에는 잘 안 나오기 때문에 처음 겪을 때 당황하게 된다. 76 | StackOverflow에서 검색해보면 아이템 레이아웃의 CheckBox 속성에서 android:focusable=``false''로 하라고 나온다. 이 상태에서 어쨌든 문제가 해결된다. 77 | 그런데 다른 ListView의 아이템 레이아웃에 ImageButton이 추가되었는데 또 ItemClick이 동작하지 않는다. 다시 검색을 해본다. 78 | 이런저런 얘기가 많은데 명확한 답변이 잘 안 보인다(물론 그 가운데 답이 있다). 역시 android:focusable=``false''를 써보지만 해결되지 않는다. 79 | 이때 2가지를 찾아봐야 한다. 80 | \begin{enumerate} 81 | \item focusable 속성이 ListView의 OnItemClickListener에 주는 영향이 있는가? focusable 속성으로 문제가 해결되지 않으니 다른 조건이 더 있을가? 82 | \item ImageButton의 문제가 있을까? 83 | \end{enumerate} 84 | 85 | 먼저 ListView의 OnItemClickListener에 View의 focusable 속성이 주는 영향을 찾아보자. 86 | setOnItemClickListener() 메서드는 ListView의 상위 클래스인 AdapterView에 있다. 87 | 여기에서 OnItemClickListener를 사용하는 위치는 performItemClick() 메서드이고, 또 다시 호출 위치를 따라가보면 AbsListView의 onTouchUp() 메서드에서 Child View의 hasFocusable() == true일 때 클릭이 동작하지 않는 것을 볼 수 있다. Child View의 다른 조건은 없다.\\ 88 | 89 | 이제 CheckBox의 focusable 기본값은 어디에 있을까? 이 값은 style에 지정되어 있다. 90 | CheckBox의 style은 /frameworks/base/core/res/res/values/styles.xml\footnote{/platforms/android-XX/data/res 아래에서도 볼 수 있다.}에서 ``Widget.CompoundButton.Chec\-kBox''를 보면 된다.\footnote{CheckBox 소스를 보면 defStyleAttr 파라미터 자리에 com.android.internal.R.attr.checkboxStyle이 있다. 91 | attrs.xml을 보면 checkboxStyle에 reference로 지정되어 있는데 다른 곳에서 정의한다는 의미이다. 92 | 결국 themes.xml에서 보면 ``@style/Widget.CompoundButton.CheckBox''로 다시 정의된 것을 볼 수 있다.} 93 | parent를 쓰지 않고서도 속성을 상속하는 암묵적 상속이 있는데, 바로 CheckBox의 style은 ``Widget.CompoundButton'' style을 상속한다. 94 | 여기서 android:focusable 속성이 true로 되어 있다. 95 | 따라서 레이아웃에서 CheckBox 속성을 android:focusable=``false''로 하면 style이 다시 오버라이드되어 ListView에서 정상적으로 ItemClick이 동작한다.\\ 96 | 97 | ImageButton의 경우는 android:focusable=``false''로 해도 반영되지 않는데, 그 이유는 ImageButton 생성자에서 setFocusable(true)을 실행해서 레이아웃의 속성을 다시 오버라이드하기 때문이다. 98 | \begin{lstlisting}[frame=single] 99 | public ImageButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 100 | super(context, attrs, defStyleAttr, defStyleRes); 101 | setFocusable(true); 102 | } 103 | \end{lstlisting} 104 | 따라서 ListAdapter의 getView() 메서드에서 ImageButton에 setFocusable(false)를 실행해서 또다시 오버라이드하면 ItemClick 이 문제없이 동작한다.\\ 105 | 106 | 프레임워크 리소스에서 styles.xml을 보면 CheckBox, RadioButton, ToggleButton, Switch, SeekBar, EditText, ImageButton 등 위젯의 android:focusable 속성이 true이다. 107 | 이런 위젯이 ListView에 포함된다면 주의해야 한다. 108 | ListView의 각 아이템 레이아웃은 일반적으로 ViewGroup이고 그 안에 focusable 위젯을 가지고 있다면 다른 방법도 가능하다. 109 | 바로 ViewGroup 레이아웃 속성에 android:descendantFocusability=``blocksDesc\-endants''를 넣으면 된다. 이 속성은 ViewGroup의 hasFocusable() 메서드에서 체크하고 있다.\\ 110 | 111 | 요점은 검색만으로 문제를 해결하고 왜 그런지를 모른다면 ListView에서 CheckBox나 ImageButton은 각각의 Tip으로만 남고 까먹기도 쉽다. 112 | 프레임워크 소스 레벨에서 검증해보면, 이후에 비슷한 문제를 맞닥뜨려도 어디서부터 문제를 찾으면 되는지 알 수 있다.\\ 113 | 114 | 프레임워크 소스는 각 단말 제조사에서 커스터마이징하는 경우가 많아서 예외 스택의 라인 번호가 차이가 있어 스택을 가지고 문제 위치를 쉽게 찾지 못한다. 115 | 이 때문에 프레임워크 소스를 그대로 사용하는 레퍼런스 폰인 Nexus 시리즈를 개발에 활용하는 것은 많은 도움이 된다. 116 | 앱 프로세스 내부에서 실행되는 클래스에 대해서는 디버깅 모드에서 Breakpoint를 잡아서 값을 확인할 수도 있다.\\ 117 | 118 | 안드로이드 내부 구조를 다 파헤쳐볼 것도 아닌데, 전체 프레임워크 소스를 다운로드할 필요가 있을까 싶다. 119 | 필자의 경우에도 다운로드하는 걸 망설였지만, 다운로드하고서 많은 도움이 되었다. C/C++ 소스까지 깊이 이해하지는 않아도 대략이라도 내부 구조를 알 수 있고, 특히 앱의 크래시 문제를 해결하는 데 쓸모가 있다. 120 | 안드로이드 동작 방식에 대한 이해와 경험으로 문제를 크래시를 해결하는 경우가 많지만, 실제 개발에서 크래시가 코어 자바 쪽에서 생기는 경우도 있다. 121 | 아래와 같은 에러를 보게 된다면 원인을 어떻게 찾을 것인가. 두 번째 에러는 현장에서 수많은 크래시를 발생시킨 것이다. 122 | \begin{lstlisting}[frame=single] 123 | Caused by: java.lang.NullPointerException 124 | at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1004) 125 | at java.text.DateFormat.parse(DateFormat.java:624) 126 | .... 127 | \end{lstlisting} 128 | 129 | \begin{lstlisting}[frame=single] 130 | Caused by: java.lang.ArrayIndexOutOfBoundsException: length=2; index=2 131 | at java.util.regex.Matcher.group(Matcher.java:358) 132 | at java.util.regex.Matcher.appendEvaluated(Matcher.java:138) 133 | at java.util.regex.Matcher.appendReplacement(Matcher.java:111) 134 | at java.util.regex.Matcher.replaceFirst(Matcher.java:304) 135 | at java.lang.String.replaceFirst(String.java:1793) 136 | .... 137 | \end{lstlisting} 138 | 139 | 안드로이드에서 사용하는 코어 자바 라이브러리는 JDK에서 자바 소스를 찾으면 안된다. 140 | 코어 자바 라이브러리는 Apache Harmony를 기반으로 만든 것이다.\footnote{안드로이드 누가에서는 Open JDK 기반으로 변경된다고 한다.} 141 | 2011년도에 Apache Harmony는 종료되었지만 안드로이드에서는 이후에도 조금씩 업데이트하고 있다. 142 | 코어 자바 소스는 /libcore/luni/src/main/java 디렉터리 아래에서 찾고 해당 라인 위치에서 원인을 찾아서 해결해야 한다. 143 | libcore는 기반이 되는 자바 버전이 바뀌기 전에는 거의 변경되지 않으므로, 하나의 버전을 다운로드해서 여러 버전에서 참고해볼 수 있다. 144 | 프로요까지 자바5, 젤리빈까지 자바6, 마시멜로까지 자바7, 현재 최신 버전은 자바8(일부만 구현) 기반이다. 145 | 146 | \section{안드로이드 버전} 147 | 안드로이드는 버전에 따라 차이가 많기 때문에, 내용 설명에서 안드로이드 버전을 가지고 얘기하는 경우가 많다. 148 | 표로 먼저 정리해놓고 내용에서 참고하도록 하자. 굳이 본문에서 언급되지 않는 하위 버전은 표에서 제외하였다.\\ 149 | 150 | \begin{tabular}{|c|c|c|}\hline 151 | 코드네임 & API 레벨 & 안드로이드 버전 \\ \hline 152 | 프로요 & 8 & 2.2 \\ \hline 153 | 진저브레드 & 9 & 2.2\\ \cline{2-3} 154 | & 10 & 2.3\\ \hline 155 | 허니콤 & 11 & 3.0\\ \cline{2-3} 156 | & 12 & 3.1\\ \cline{2-3} 157 | & 13 & 3.2\\ \hline 158 | ICS(아이스크림 샌드위치) & 14 & 4.0-4.0.2\\ \cline{2-3} 159 | & 15 & 4.0.3-4.0.4\\ \hline 160 | 젤리빈 & 16 & 4.1\\ \cline{2-3} 161 | & 17 & 4.2\\ \cline{2-3} 162 | & 18 & 4.3\\ \hline 163 | 킷캣 & 19 & 4.4-4.4.2\\ \cline{2-3} 164 | & 20 & 4.4.3-4.4.4\\ \hline 165 | 롤리팝 & 21 & 5.0\\ \cline{2-3} 166 | & 22 & 5.1\\ \hline 167 | 마시멜로 & 23 & 6.0\\ \hline 168 | 누가 & 24 & 7.0\\ \hline 169 | \end{tabular}\\ 170 | 171 | 코드네임과 API 레벨이 일대일 매핑되지 않고, API 레벨과 안드로이드 버전도 마찬가지로 매핑되지 않아서 혼동되는 경우도 많다. 172 | 안드로이드 버전의 앞자리 숫자가 바뀌는 11(허니콤), 14(ICS), 21(롤리팝), 23(마시멜로), 24(누가)는 기억하도록 하자. 173 | 이 책에서는 설명에서 주로 코드네임으로 언급하고, 한 코드네임 안에서 여러 API 레벨이 있는 경우 구분이 필요할 때 API 레벨까지 언급하기로 한다. 174 | 여러 API 레벨이 있는데 코드네임만 언급하는 경우는 가장 낮은 API 레벨부터 해당하는 경우이다. 이를테면 허니콤이라고 하면 API 레벨 11 안드로이드 3.0을 얘기한다.\\ 175 | 176 | 각 버전의 히스토리를 알기 위해서는 아래 링크를 보자. 177 | \begin{itemize} 178 | \item \url{https://en.wikipedia.org/wiki/Android_version_history} 179 | \item \url{http://socialcompare.com/en/comparison/android-versions-comparison} 180 | \end{itemize} 181 | 아래와 같이 공식적인 링크가 있지만 최신 버전 위주로 정리되어 있고 한 번에 보기에 편하지 않다. 182 | \begin{itemize} 183 | \item \url{https://www.android.com/history/} 184 | \item \url{http://developer.android.com/intl/ko/about/dashboards/index.html} 185 | \end{itemize} 186 | 187 | \subsection{호환성 모드} 188 | 앱이 동작하는 안드로이드 버전을 지정하기 위해서는 AndroidManifest.xml에서 uses-sdk 항목에 android:min\-SdkVersion과 android:targetSdkVersion에 버전을 기재한다. 189 | 만든 지 오래된 앱에서 minSdkVersion만 지정하고 targetSdkVersion을 지정하지 않은 것을 본 적이 있는데, targetSdkVersion을 명시하지 않으면 minSdkVersion과 동일한 값으로 지정된다. 190 | targetSdkVersion은 반드시 지정하도록 하자. 해당 버전까지는 테스트해봤는데 앱 실행에 문제가 없다는 의미이고, 그 버전까지는 호환성 모드를 쓰지 않겠다는 뜻이다.\\ 191 | 192 | 호환성 모드는 안드로이드 버전이 올라가더라도 앱의 기존 동작이 바뀌는 것을 방지하기 위한 것이다. 193 | 프레임워크 소스를 보면 targetSdkVersion을 가지고 체크하는 부분이 많다. 194 | \url{http://androidxref.com}에서 버전을 선택하고 Symbol에서 targetSdkVersion을 쓰고 검색하면 확인할 수 있다.\\ 195 | 196 | 버전에 따라서 기존 동작이 변경되고 호환성 모드로 동작하는 건 어떤 게 있을까? 몇 가지 예를 들어보자. 197 | \begin{itemize} 198 | \item AsyncTask에서 태스크를 실행하면 허니콤부터 병렬 실행이 아닌 순차 실행으로 변경되었다. 199 | 이것도 targetSdkVersion이 10이하이면 안드로이드 버전이 4.X 대라고 해도 기존과 동일하게 병렬 실행으로 동작한다. 200 | ScrollView, ListView나 ViewPager에서 포커스된 화면의 데이터를 가져올 때 AsyncTask를 통해서 가져온다면, 순차 실행으로는 적합하지 않다. 201 | 이때는 AsyncTask를 사용하지 않고 관련한 용도의 다른 라이브러리(네트워크 용도라면 Volley 등)를 사용하거나, AsyncTask를 버전에 따라 다르게 동작하도록 코드를 작성해야 한다.\\ 202 | 203 | 아래는 support-v4에 포함된 AsyncTaskCompat을 간략히 한 것이다. 204 | \begin{lstlisting}[frame=single] 205 | public static AsyncTask executeParallel( 207 | AsyncTask task, Params... params) { 208 | if (task == null) { 209 | throw new IllegalArgumentException("task can not be null"); 210 | } 211 | if (Build.VERSION.SDK_INT >= 11) { 212 | task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); 213 | } else { 214 | task.execute(params); 215 | } 216 | return task; 217 | } 218 | \end{lstlisting} 219 | 모든 버전에서 병렬 실행을 위해 위 메서드와 내용상 동일하게 별도로 만든 적이 있는데, AsyncTaskCompat을 바로 사용하도록 하자. 220 | 221 | \item 메인 스레드 상에서 네트워크 통신이 진저브레드 API 레벨 9까지는 허용되었으나, 그 이후에는 에러를 발생시킨다. 222 | targetSdkVersion을 높여서 NetworkOnMainThreadException이 발생한다면, 백그라운드 스레드에서 네트워크 통신을 하도록 변경해야 한다. 223 | 224 | \item 하드웨어 가속(Hardware Acceleration)은 GPU를 가지고 View에서 Canvas에 그리는 작업을 하는 것이다. 225 | 하드웨어 가속은 허니콤에서 처음 시작되었고 targetSdkVersion 14 이상에서는 디폴트 옵션이다. 226 | 하드웨어 가속을 사용할 때 눈에 띄는 속도 향상은 SlidingPaneLayout이나 여러 Sliding Menu 라이브러리에서 애니메이션 시 끊김 현상이 없다는 것이다. 227 | 필자의 경우도 애니메이션 끊김 현상이 단지 하드웨어 가속 옵션만으로 해결되는 경우를 여러 번 겪었다. 228 | 그래서 가능하면 하드웨어 가속을 쓰는 게 좋지만 항상 속도가 향상되는 것은 아니다. 229 | \url{http://developer.android.com/intl/ko/guide/topics/graphics/hardware-accel.html}을 참고해서 테스트하고 수준별(Application, Activity, Window, View)로 오버라이드하는 방법을 사용하자. 230 | 231 | \item targetSdkVersion 14 이상에서는 App Widget에서 기본 패딩이 존재한다. 232 | 기존에는 셀의 사이즈를 꽉 채웠지만 targetSdkVersion이 올라가면 기본 패딩 때문에 App Widget의 실제 사이즈는 기존보다 작아진다. 233 | 관련해서 \ref{subsubsec:icspadding}절에 상세하게 설명하였다. 234 | 235 | \item targetSdkVersion 21 이상에서는 startService()나 bindService()를 실행할 때 명시적 인텐트를 사용해야 한다. 236 | \end{itemize} 237 | 238 | 결론적으로 단말 버전에 따라 최신 기능을 쓸 수 있기 때문에 targetSdkVersion은 높여서 쓰는 것이 권장된다. 다만 targetSdkVersion을 높일 때는 테스트할 내용이 많다. 239 | 240 | \begin{comment} 241 | 시스템의 DPI가 낮게 변경되는 건, 안드로이드가 대화면 장치(xlargeScreens)에서 242 | minSdkVersion이 낮은 앱을 호환성 모드로 실행하기 때문이라고 합니다. 243 | 244 | 이해하려고 읽는 김에 Best Practice - Screen Compatibility Mode를 정리하다가, 후반엔 듬성듬성 번역해 봤습니다. 245 | http://developer.android.com/guide/practices/screen-compat-mode.html 246 | 247 | 248 | Screen Compatibility Mode 249 | 250 | 251 | * 안드로이드 3.0 이전에 개발된 앱이 고dpi, 고dp 화면에서 너무 작게 보이는 현상을 방지하기 위한 모드. 252 | 253 | 호환모드 상태일 때: 254 | 255 | 호환모드를 사용하지 않을 때: 256 | 257 | 258 | 버전 1.6 부터 안드로이드는 다양한 화면 사이즈를 지원했다. 259 | 하지만, 앱이 Supporting Multiple Screens의 가이드를 잘 따르지 않았다면, 대형 화면에서 문제가 발생할 수 있다. 260 | 문제를 겪는 앱을 위해, 화면 호환성 모드가 개발되었다. 261 | 262 | 호환성 모드에는 2가지 버전이 존재한다. 263 | 264 | Version 1 (Android 1.6 - 3.1) 265 | (설명 있는데 번역하기 귀찮. 되게 옛날 얘기인듯.) 266 | 이 버전의 호환성 모드를 끄기 위해서는, android:minSdkVersion 혹은 android:targetSdkVersion이 4 이상으로 되어 있거나, android:resizable이 true로 되어 있어야 한다. 267 | 268 | Version 2 (Android 3.2 and greater) 269 | 시스템은 어플리케이션의 레이아웃을 normal size handset (약 320dp x 480dp screen) 으로 그리고 이미지를 뻥튀기해 (scale up) 화면을 채운다. 270 | 기본적으로 레이아웃에 줌인 해서 크게 보는 것과 같다. 계단 현상이 발생한다. 271 | 272 | 이 기능은 안드로이드 3.2에서 최신 태블릿 디바이스에서 Supporting Multiple Screens의 기법들을 아직 도입하지 않은 어플리케이션들을 더 잘 지원하기 위해 도입되었다. 273 | 274 | 일반적으로, 안드로이드 3.2 이상 탑재한 대화면 디바이스에서는 어플리케이션 manifest에 명시적으로 대화면 지원이 선언되어 있지 않아도사용자가 화면 호환성 모드를 활성화 할 수 있다. 275 | (중략) 276 | 277 | 278 | 279 | 개발자는 자신의 어플리케이션이 화면 호환성 모드를 사용할지를 제어할 수 있다. 280 | 이하의 방법은 안드로이드 3.2 이상에서 사용 화면 호환성 모드를 활성화/비활성화 하는 방법이다. 281 | 282 | 283 | 화면 호환성 모드 끄기 284 | 안드로이드 3.0 이하를 버전을 대상으로 어플리케이션을 개발했지만, 어플리케이션이 태블릿과 같은 대화면 디바이스에서도 적절하게 리사이즈 되도록 만들었을 경우, 사용자 경험을 최상으로 유지하기 위해서는 화면 호환성 모드를 꺼야 한다. 285 | 286 | 기본적으로, 다음 중 하나의 조건이 참이면 화면 호환 모드를 사용자가 선택할 수 있게 된다. 287 | 288 | * android:minSdkVersion, android:targetSdkVersion가 모두 10 이하이면서 엘리먼트로 대화면 지원을 명시적으로 선언하지 않은 경우. 289 | * android:minSdkVersion, android:targetSdkVersion가 모두 11 이상이면서 엘리먼트로 대화면이 지원되지 않음을 명시적으로 선언한 경우. 290 | 291 | 292 | 사용자가 화면 호환성 모드를 선택할 수 없도록 하려면 다음과 같이 해야 한다. 293 | * 가장 쉬운 방법: 294 | 엘리먼트에 android:xlargeScreens 속성을 true로 준다. 295 | 이게 전부다. 이 선언은 어플리케이션이 모든 대화면 사이즈를 지원하기 때문에, 시스템이 레이아웃을 화면에 맞춰 리사이즈한다. 이 방법은 속성에 어떤 값이 들어가 있던지 상관 없다. 296 | 297 | * 쉽지만 다른 영향이 있는 방법: 298 | 엘리먼트에 android:targetSdkVersion 속성을 11 이상으로 준다. 299 | 이 선언은 어플리케이션이 안드로이드 3.0을 지원하므로, 태블릿과 같은 대화면에서도 동작하도록 작성되었다는 선언이다. 300 | 301 | 주의 : 안드로이드 3.0 이상을 지정할 경우, UI에 Holographic 테마의 이펙트가 활성화되고, 액티비티에 ActionBar가 추가되고, 시스템 바의 OptionsMenu 버튼이 사라진다. 302 | 303 | 304 | 만약 위 변경을 적용한 뒤에도 화면 호환성 모드가 나타나면, manifest의 에 false로 선언된 속성이 없는지 확인해 봐야 한다. 가장 좋은 실천법은 지원하는 화면 사이즈를 엘리먼트를 이용해 항상 명시적으로 선언하는 것이므로, 여하간이 엘리먼트를 사용하는 게 좋다. 305 | \end{comment} 306 | 307 | \subsection{버전 체크} 308 | 앱에 들어가는 기능을 정할 때는 버전별 히스토리가 의미가 있지만, 앱 개발 시에 버전 관련한 주요 관심은 `특정 클래스와 메서드가 이 버전에서 쓸 수 있는가'이다. 309 | 런타임에 해당 단말 버전에 존재하지 않는 클래스와 메서드가 사용된다면 크래시가 발생하므로, 앞의 AsyncTaskCompat과 같이 if 문을 사용해서 버전을 체크하는 코드를 사용한다.\\ 310 | 311 | 별게 아닌 것 같지만 앱을 배포하고서 많은 크래시를 유발하는 게 이 부분이다.\footnote{필자도 버전 문제로 다량의 크래시를 유발한 적이 있다.} 312 | 모든 안드로이드 버전의 단말을 가지고 앱의 모든 기능을 테스트하는 게 아니기 때문에, if 문으로 분기하지 않거나 비교 레벨을 잘못 기재한 경우에 실제 사용자 단말에서 많은 크래시가 발생한다. 313 | 이때 즉시 문제를 수정해서 앱을 패치해보지만 이미 발생한 수많은 크래시는 어쩔 수 없다.\\ 314 | 315 | 예를 들어보자. SharedPreferences.Editor에는 데이터를 반영하는 용도의 apply() 메서드와 commit() 메서드가 있다. 316 | 그런데 commit() 메서드는 XML 파일에 동기 반영이고 apply() 메서드는 비동기 반영이라서 apply() 메서드를 쓰는 것이 권장된다. 317 | 그런데 apply() 메서드는 API 레벨 9부터 사용 가능하다. 속도 향상을 위해서 개선된 버전의 메서드를 사용하는 것은 당연하므로 버전에 따른 분기가 필요하다. 318 | \begin{lstlisting}[frame=single] 319 | public static apply(SharedPreferences.Editor editor) { 320 | if (Build.VERSION.SDK_INT >= 9) { 321 | editor.apply(); 322 | } else { 323 | editor.commit(); 324 | } 325 | } 326 | \end{lstlisting} 327 | 328 | 그런데 이런 버전 관련 분기 코드가 여기저기 다른 패키지에 산재해있다면 그것도 문제다. 329 | 코드 리뷰 시에 문제점을 금방 발견하기 어렵기도 해서 compat 패키지 같은 것을 만들고 이 안에서 기능 단위로 클래스를 만드는 것을 권장한다.\\ 330 | 331 | 한편 support-v4에는 이미 많은 -Compat 클래스가 있다. ViewCompat, ActivityCompat, WindowCompat, NotificationCompat, AsyncTaskCompat 등이 있고, 앞에서 작성했던 코드를 불필요하게 만드는 SharedPreferencesCompat.EditorCompat까지도 있다.\\ 332 | %SharedPreferencesCompat.EditorCompat.apply(editor)와 같이 사용하면 된다. 333 | 334 | ScrollView에서 스크롤하다가 멈추면 스크롤 위치를 보정하는 기능을 만든 적이 있다. 335 | 그런데 이 기능이 진저브레드 이상에 있는 OverScroll 모드와 충돌하는 문제가 생겼다. 336 | 마지막까지 스크롤해서 OverScroll이 된다면 계속 떨면서 위아래로 왔다갔다 하는 현상이 생기는데 이 문제를 해결하기 위해서 처음에는 아래와 같이 코드를 작성하였다. 337 | \begin{lstlisting}[frame=single] 338 | if (Build.VERSION.SDK_INT >= 9) { 339 | listview.setOverScrollMode(View.OVER_SCROLL_NEVER); 340 | } 341 | \end{lstlisting} 342 | 이것도 ViewCompat을 쓰면 간단하다. 343 | \begin{lstlisting}[frame=single] 344 | ViewCompat.setOverScrollMode(listView, ViewCompat.OVER_SCROLL_NEVER); 345 | \end{lstlisting} 346 | ViewCompat.OVER\_SCROLL\_NEVER 상수처럼 -Compat에는 상위 버전에 있는 여러 상수값이 동일하게 선언되어 있는 경우가 많다.\\ 347 | 348 | support-v4에 호환 메서드가 이미 있다면 이것을 먼저 사용하고 없을 때에만 별도로 작성하자. 349 | 버전마다 동작이 다르게 코드를 작성할 때는 ViewCompat 구조를 활용하는 것도 좋다. ViewCompat의 일부 소스를 보자. 350 | \begin{lstlisting}[frame=single] 351 | public class ViewCompat { 352 | 353 | public static final int OVER_SCROLL_ALWAYS = 0; 354 | 355 | public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; 356 | 357 | public static final int OVER_SCROLL_NEVER = 2; 358 | 359 | ... 360 | 361 | interface ViewCompatImpl { //(1) 362 | public boolean canScrollHorizontally(View v, int direction); 363 | public boolean canScrollVertically(View v, int direction); 364 | public int getOverScrollMode(View v); 365 | public void setOverScrollMode(View v, int mode); 366 | ... 367 | } 368 | 369 | static class BaseViewCompatImpl implements ViewCompatImpl { // (2) 370 | public boolean canScrollHorizontally(View v, int direction) { 371 | return false; 372 | } 373 | public boolean canScrollVertically(View v, int direction) { 374 | return false; 375 | } 376 | public int getOverScrollMode(View v) { 377 | return OVER_SCROLL_NEVER; 378 | } 379 | public void setOverScrollMode(View v, int mode) { 380 | // Do nothing; API doesn't exist 381 | } 382 | ... 383 | } 384 | 385 | static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl { // (3) 386 | ... 387 | } 388 | 389 | static class GBViewCompatImpl extends EclairMr1ViewCompatImpl { // (4) 390 | @Override 391 | public int getOverScrollMode(View v) { 392 | return ViewCompatGingerbread.getOverScrollMode(v); // (5) 393 | } 394 | @Override 395 | public void setOverScrollMode(View v, int mode) { 396 | ViewCompatGingerbread.setOverScrollMode(v, mode); // (6) 397 | } 398 | } 399 | 400 | static class HCViewCompatImpl extends GBViewCompatImpl { 401 | ... 402 | } 403 | 404 | static class ICSViewCompatImpl extends HCViewCompatImpl { 405 | @Override 406 | public boolean canScrollHorizontally(View v, int direction) { 407 | return ViewCompatICS.canScrollHorizontally(v, direction); 408 | } 409 | @Override 410 | public boolean canScrollVertically(View v, int direction) { 411 | return ViewCompatICS.canScrollVertically(v, direction); 412 | } 413 | ... 414 | } 415 | 416 | static class JBViewCompatImpl extends ICSViewCompatImpl { 417 | ... 418 | } 419 | 420 | static class JbMr1ViewCompatImpl extends JBViewCompatImpl { 421 | ... 422 | } 423 | 424 | static class KitKatViewCompatImpl extends JbMr1ViewCompatImpl { 425 | ... 426 | } 427 | 428 | static final ViewCompatImpl IMPL; 429 | static { // (7) 430 | final int version = android.os.Build.VERSION.SDK_INT; 431 | if (version >= 19) { 432 | IMPL = new KitKatViewCompatImpl(); 433 | } else if (version >= 17) { 434 | IMPL = new JbMr1ViewCompatImpl(); 435 | } else if (version >= 16) { 436 | IMPL = new JBViewCompatImpl(); 437 | } else if (version >= 14) { 438 | IMPL = new ICSViewCompatImpl(); 439 | } else if (version >= 11) { 440 | IMPL = new HCViewCompatImpl(); 441 | } else if (version >= 9) { 442 | IMPL = new GBViewCompatImpl(); 443 | } else { 444 | IMPL = new BaseViewCompatImpl(); 445 | } 446 | } 447 | 448 | public static boolean canScrollHorizontally(View v, int direction) { 449 | return IMPL.canScrollHorizontally(v, direction); 450 | } 451 | 452 | public static boolean canScrollVertically(View v, int direction) { // (8) 453 | return IMPL.canScrollVertically(v, direction); 454 | } 455 | 456 | public static int getOverScrollMode(View v) { 457 | return IMPL.getOverScrollMode(v); 458 | } 459 | 460 | public static void setOverScrollMode(View v, int overScrollMode) { 461 | IMPL.setOverScrollMode(v, overScrollMode); 462 | } 463 | 464 | ... 465 | } 466 | \end{lstlisting} 467 | \begin{itemize} 468 | \item 11라인(1)의 ViewCompatImpl 인터페이스에는 Compat에서 사용하는 메서드 목록이 들어간다. 469 | 470 | \item 79라인(7)의 정적 초기화 블록에서 버전에 따라 인터페이스 구현체가 IMPL 변수에 들어간다. 471 | 472 | \item 19라인(2)에 기본 구현체가 있고 35라인(3) 이후 각 버전의 구현체는 낮은 버전의 구현체를 상속하고 변경 내용은 오버라이드한다. 473 | 474 | \item OverScrollMode 관련한 메서드는 진저브레드부터 있으므로 39라인(4)의 GBViewCompatImpl에서 getOverScrollMode()와 setOverScrollMode() 메서드를 오버라이드하는 것을 볼 수 있다. 475 | 476 | \item 102라인(8)부터 있는 정적 메서드는 결과적으로 버전에 매핑돼 있는 구현체의 메서드를 호출한다. 477 | \end{itemize} 478 | 479 | ViewCompat의 42라인(5), 46라인(6)에서 사용하는 ViewCompatGingerbread은 package private 클래스이다. 480 | \begin{lstlisting}[frame=single] 481 | class ViewCompatGingerbread { 482 | public static int getOverScrollMode(View v) { 483 | return v.getOverScrollMode(); 484 | } 485 | 486 | public static void setOverScrollMode(View v, int mode) { 487 | v.setOverScrollMode(mode); 488 | } 489 | } 490 | \end{lstlisting} 491 | 492 | \begin{comment} 493 | 이를테면 ArrayAdapter의 addAll 메서드 같은 게 API 레벨 11부터 있다. 494 | 495 | ClipBoardManager는 2개가 있다. 496 | 497 | http://developer.android.com/intl/ko/reference/android/os/Build.VERSION\_CODES.html 498 | \end{comment} -------------------------------------------------------------------------------- /latexbook/system-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/system-architecture.jpg -------------------------------------------------------------------------------- /latexbook/system-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suribada/android_book/c976d6c0845c7476290d6b105e48d97ae62d7fd1/latexbook/system-architecture.png -------------------------------------------------------------------------------- /latexbook/thread.tex: -------------------------------------------------------------------------------- 1 | \section{스레드} 2 | \subsection{AsyncTask} 3 | 4 | \subsection{이런거 하지 말자.} 5 | 스레드 실행 시간을 미리 예측하고서 하지 말자. 어떤 작업이 2초면 반드시 끝나겠거니 하고서 하는 것은 동작을 운에 따르는 것이다. 6 | -------------------------------------------------------------------------------- /latexbook/toast.tex: -------------------------------------------------------------------------------- 1 | 줄까여러 곳에서 사용하는 클래스라면, 메인 스레드에서도 일반 스레드에서도 사용할 가능성이 있다. 내 경우에는 특정 예외 케이스에 Toast 보여주기를 시도하였다. 2 | 3 | \colorbox{backcolour}{\parbox[t]{15cm}{ 4 | Toast는 앱에서 발생시키지만, Toast를 발생시킨 화면과 무관하게 동작하는 것을 볼 수 있다. 화면이 바뀌거나 앱이 종료되었는데도 Toast는 남아서 뜨는 것도 흔히 볼 수 있다. 5 | Toast도 생각해보면 UI 화면이라고 할 수 있으므로, 메인 스레드에서만 show를 할 수 있을 것 같은데 그렇진 않다.\\ 6 | 디바이스에서 ps 를 해보면 /system/bin/servicemanager가 떠있는데, 이것이 Context의 getSystemService로 가져오는 시스템 서비스들의 프로세스이다. 그 가운데 Toast는 내부적으로 Context.NOTIFICATIN\_SERVICE(NotificationManager)를 사용하고, aidl 파일은 /frameworks/base/core/java/android/app/INotificationManager.aidl이고, Stub 구현은 /frameworks/base/services/java/com/android/server/NotificationManagerService.java이다.\\ 7 | Toast의 show 메소드는 INofificationManager의 enqueToast를 호출하는 것으로 여기서도 Queue에 넣는 역할을 하게 되고, 결국 띄우는 것은 servicemanager 프로세스에서 Binder Call하는 것이다.(스레드풀 이용) 8 | }}\newline \newline 9 | 10 | % 스레드에서 Window 생성이 문제가 없는지 확인해보자. 11 | 12 | 맨 먼저 직접 Toast를 사용해보자. 13 | \begin{lstlisting}[frame=single] 14 | public void process(final Context context, String input) { 15 | if (TextUtils.isEmpty(input)) { 16 | Toast.makeText(context, "Error occurred", Toast.LENGTH_LONG).show(); 17 | return; 18 | } 19 | ... 20 | } 21 | \end{lstlisting} 22 | 일반 스레드에서는 문제가 발생한다. 바로 Toast에서 내부적으로 Handler를 사용하기 때문이다. 23 | \begin{lstlisting}[frame=single] 24 | 09-12 15:49:19.058: E/AndroidRuntime(30478): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 25 | 09-12 15:49:19.058: E/AndroidRuntime(30478): at android.os.Handler.(Handler.java:121) 26 | 09-12 15:49:19.058: E/AndroidRuntime(30478): at android.widget.Toast$TN.(Toast.java:322) 27 | 09-12 15:49:19.058: E/AndroidRuntime(30478): at android.widget.Toast.(Toast.java:91) 28 | 09-12 15:49:19.058: E/AndroidRuntime(30478): at android.widget.Toast.makeText(Toast.java:238) 29 | \end{lstlisting} 30 | 31 | 그래서 일반 스레드에서 아래처럼 사용하는 것도 가능은 하다. 32 | \begin{lstlisting}[frame=single] 33 | public void process(final Context context, String input) { 34 | if (TextUtils.isEmpty(input)) { 35 | Looper.prepare(); 36 | Toast.makeText(context, "Error occurred", Toast.LENGTH_LONG).show(); 37 | Looper.loop(); 38 | return; 39 | } 40 | ... 41 | } 42 | \end{lstlisting} 43 | 하지만 Looper.loop가 무한반복문이기 때문에 계속 리턴되지 않는 문제가 생긴다. Looper.myLooper().quit() 메소드를 실행할 수 있는 위치도 loop 다음 라인에는 할 수 없어서 메인 스레드나 다른 스레드에서 quit 해야만 한다. 44 | 45 | 그렇다면 계속 떠있는 게 아무 문제없는 MainLooper를 사용하는 방법은 무엇일까? 바로 MainLooper와 연결된 Handler를 사용하는 것으로 Handler의 네 생성자 가운데 세 번째 생성자를 사용하는 것이다. 46 | \begin{lstlisting}[frame=single] 47 | private Handler handler = new Handler(Looper.getMainLooper()); 48 | 49 | public void process(final Context context, String input) { 50 | if (TextUtils.isEmpty(input)) { 51 | handler.post(new Runnable() { 52 | 53 | public void run() { 54 | Toast.makeText(context, "Error occurred", Toast.LENGTH_LONG).show(); 55 | } 56 | }); 57 | return; 58 | } 59 | ... 60 | } 61 | \end{lstlisting} 62 | 63 | IntentService의 onHandleIntent에서 Toast띄우는 건 주의하자. 이미 Looper가 생성되어 있기 때문에 그 Looper를 기준으로 쓰이는데, onDestroy에서 Looper가 끝나고 나면, Toast “sending message to a Handler on a dead thread” 이게 뜬다. 64 | 65 | 스레드 안에서도 Looper만 있다면 Toast를 띄울 수 있다. 66 | 67 | 스레드에서 띄우는 경우 Toast가 계속 남을 수도 있다. hide도 결국 callback을 호출하는 것뿐 68 | 69 | % 스레드에서 Window 만드는 게 가능한가? 70 | -------------------------------------------------------------------------------- /latexbook/tools.tex: -------------------------------------------------------------------------------- 1 | android-sdk 아래 platform-tools와 tools 디렉토리가 있다. 2 | 3 | \subsection{Hierachy Viewer} 4 | Hierarchy는 타켓 폰에서 기본적으로는 볼 수가 없으므로, emulator를 띄우고, Hierarchy View를 활용한다. 5 | adt가 버전업되면서 문제가 사라졌다.(확인 필요..) 6 | 7 | \subsection{adb} 8 | adb 에서 많이 사용하는 명령어가 adb uninstall 패키지명이다. 9 | 폰에서 프로그램을 찾아서 uninstall 하는 것보다 빠르다. 10 | 안드로이드의 특성상 여러 폰을 가지고 테스트를 하는데, 개발자 여럿이서 동일한 폰을 가지고 테스트하게 된다. 11 | 이때 다른 pc에서 올린 것을 가지고 와서 사용할 때 바로 올리진 못하고, uninstall 해야 한다. 12 | 13 | adb uninstall com.suribada.notepad 14 | 15 | adb install bin/*.apk 명령어는 기존에 앱이 깔려있는 경우에는 쓸 수 없다. 16 | adb install -r [] bin/*.apk 업어쓴다. 17 | 18 | emulator를 포함한 여러개의 device를 연결했을 때는, 19 | adb devices를 통해 기기를 확인할 수 있다. 20 | 21 | adb 연결에 문제가 생겼을때 리스타트하는 방법은 다음과 같다. 22 | 23 | \begin{verbatim} 24 | adb -s emulator-5554 kill-server 25 | adb -s emulator-5554 start-server 26 | \end{verbatim} 27 | 28 | 이클립스에서 logcat 결과가 잘 안 보이는 단말기도 있다. 29 | adb devices 30 | adb -s 디바이스명 shell 31 | logcat 해서 볼 수 있다. 32 | 33 | adb -e shell(에뮬레이터) 34 | adb -d shell(디바이스) 35 | 36 | 37 | adb push 디렉토리 /mnt/sdcard 하면 디렉토리의 파일들이 /mnt/sdcard에 올라간다. 38 | 39 | 바로 실행하기 40 | 이게 되려면 Manifest 파일에 등록되어 있어야만 한다. 41 | adb shell am start -n com.nhn.android.search/com.nhn.android.alarmclock.AlarmClock 42 | %http://www.xinotes.org/notes/note/1441/ 43 | %http://en.androidwiki.com/wiki/ADB_Shell_Command_Reference 참고하자! 44 | Intent Filter가 없는 경우에는 android:exported="true"가 있어야만, 위 명령어를 쓸 수가 있다. 45 | 아니면, java.lang.SecurityException을 발생시킨다. 46 | 47 | %http://developer.android.com/guide/topics/manifest/activity-element.html#exported 48 | 49 | Shell에 들어가면 /system/bin 아래 사용가능한 메소드들이 보인다. 실제 디바이스에서는 잘 쓰지 못한다. 50 | 51 | \section{DDMS} 52 | unKnownHost 문제가 자꾸 나면 emulator를 재시작해보도록 한다. 53 | unknownHostException 나는 경우는 Internet 퍼미션 없을때도 생겨난다. 54 | 55 | System.out.println으로 출력하면 Logcat에서는 Tag에 system.out 으로 표시된다. 56 | System.err.println은system.err로 표시된다. 57 | 58 | \section{monkey} 59 | 크래쉬나, Exception, ANR을 만나면, Monkey가 끝나고 error를 리포트 한다. 60 | % http://blog.daum.net/whisperlip/7287317 61 | 파일로 결과를 확인하도록 하자. 62 | \begin{verbatim} 63 | monkey -p com.suribada.notepad -s 269 --pct-touch 50 --pct-nav 5 64 | --pct-majornav 25 --pct-appswitch 10 --pct-anyevent 10 -v 20000 > /mnt/sdcard/monkey.log 65 | \end{verbatim} 66 | 67 | --------------------------------------------------------------------------------