Вы находитесь на странице: 1из 592

. , .

Android.
.





.
.
.
.
.
.

32.973.2-018.2
004.451

. , .
20  Android. . .: , 2014.
592.: . ( ).

ISBN 978-5-496-00502-9
, Android.
IT- Big Nerd Ranch, Android, API .
Android-
, Flickr, , , , .
,
Android .

12+ ( 12 . 29 2010 . 436-.)

ISBN 978-0321804334 .

A
 uthorized translation from the English language edition,
entitled Android Programming: The Big Nerd Ranch Guide;
ISBN 0321804334; by Hardy, Brian; and by Phillips, Bill;
published by The Big Nerd Ranch Guide, Inc.

The Big Nerd Ranch, Inc., 2013
ISBN 978-5-496-00502-9
, 2014

,
, 2014
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or
mechanical, including photocopying, recording or by any information storage retrieval system, without permission from
Pearson Education, Inc.
The Big Nerd Ranch, Inc. .

.
, , , . , ,

, .
, 192102, -, . (. ), . 3, , . 7.
005-93, 2; 95 3005 .
30.10.13. 70100/16. . . . 47,730. 1700.

. 432049, , . , .27.

............................................................................................................. 16
Android......................................................................................................... 18
1. Android........................................................................ 24
.................................................................................................. 24
Android............................................................................................................ 25
Eclipse...................................................................................................................... 28
...................................................................... 29
.............................................................................................................. 33
...................................................................................................................... 34
android:layout_width android:layout_height.............................................................................. 34
android:orientation..................................................................................................................... 34
android:text............................................................................................................................... 35
...................................................................................................... 35
.............................................................................................. 36
XML View................................................................................................ 36
........................................................................................... 37
............................................................................................ 40
................................................................................................................... 40
..................................................................................................... 41
............................................................................................................... 41
..................................................................................................... 42
................................................................................................................................ 43
........................................................................................................................... 44
.............................................................................................................. 45
: Android................................................... 47
Android........................................................................................ 49

2. Android MVC................................................................................................ 51
............................................................................................................... 52
get- set-................................................................................................ 53
-- Android...................................................... 55
MVC...................................................................................................................... 56
.............................................................................................. 57
................................................................................................. 59
.................................................................................................................... 63
.......................................................................................................... 63
...................................................................................... 64

...................................................................................................................... 64
..................................................................................................... 65
XML.......................................................................................................... 66
................................................................................................................................. 67
. TextView.................................................................... 68
. ............................................................................... 68
. Button ImageButton....................................................................................... 68

3. Activity.............................................................................. 71
Activity........................................................................... 72
................................................................................................ 72
LogCat.................................................................................................................. 74
...................................................................................... 78
.................................................................... 78
............................................................................... 79
........................................................................................ 82
onSaveInstanceState(Bundle)............................................................................. 83
Activity................................................................................................. 84
: onSaveInstanceState(Bundle)................................................. 85
: ................................................................... 87

4. Android...................................................................... 89
DDMS....................................................................................................................... 90
.................................................................................................. 91
................................................................................................... 92
................................................................................................. 94
........................................................................................................ 95
.................................................................................................... 99
File Explorer.................................................................................................................................100
Android......................................................................................................101
Android Lint..............................................................................................................................101
R.............................................................................................................103

5. ...................................................................................... 104
.......................................................................................................105
..........................................................................................................106
.......................................................................................109
.....................................................................................110
Cheat QuizActivity.......................................................................................111
.......................................................................................................................113
.........................................................................................113
.......................................................................................................115
........................................................................................115
..............................................................................................................116
.......................................................................118
...............................................................................................................119
..............................................................................................................120
..............................................................................................................121
Android.....................................................................................123
................................................................................................................................125

6. Android SDK ........................................................ 126


Android SDK.....................................................................................................................126
Android...............................................................................127
Honeycomb...........................................................................................................128
SDK (Minimum Required SDK)...................................................................129
SDK (Target SDK).............................................................................................130
SDK (Compile With)..............................................................................130
API....................................................130
Lint...................................................................................133
Android...........................................................................................134
. .......................................................................................136

7. UI- FragmentManager............................................................ 138


.....................................................................................139
..........................................................................................................140
CriminalIntent................................................................................................141
............................................................................................................143
...........................................................................................144
Crime.............................................................................................................146
UI-...............................................................................................................147
....................................................................................................148
..........................................................................................149
............................................................................149
UI-...........................................................................................................151
CrimeFragment........................................................................................151
CrimeFragment...............................................................................................152
..................................................................153
.......................................................................................155
UI- FragmentManager.........................................................................156
...........................................................................................................157
FragmentManager ......................................................................159
..................................................................161
: Honeycomb,ICS, Jelly Bean .......................................161

8. ...................................................................................... 163
Crime.......................................................................................................................163
..................................................................................................................164
...............................................................................................................166
XML......................................................................................167
, ....................................................................................................167
, dp sp....................................................................................................168
Android.........................................................169
...................................................................................................................170
........................................................................................................................170
.................................................................................171
....................................................................................................173
....................................................................................174
............................................................................175
...............................................................................176
android:layout_weight..........................................................................................177

..............................................................................................178
.............................................................178
. ............................................................................................179

9. ListFragment................................................................... 180
CriminalIntent...................................................................................181
.........................................................................182
ListFragment.................................................................................................................184
........................................................................185
............................................................................185
Activity.......................................................................................................186
........................................................................................188
CrimeListActivity....................................................................................................189
ListFragment, ListView ArrayAdapter...........................................................................................191
ArrayAdapter<T>..........................................................................................................193
...................................................................................................195
.......................................................................................................196
...........................................................................................196
..................................................................................................198

10. ............................................................................ 202


.................................................................................................202
...........................................................................................................203
..................................................................................................................204
CrimeFragment Crime......................................................205
....................................................................................................206
............................................................................................................206
.................................................................................207
.............................................................................................................208
................................................................................................................208
.................................................................210

11. ViewPager................................................................................................... 212


CrimePagerActivity.........................................................................................................213
............................................................................214
...................................................................................214
ViewPager PagerAdapter.........................................................................................................215
CrimePagerActivity.....................................................................................................216
FragmentStatePagerAdapter FragmentPagerAdapter.................................................................219
: ViewPager.............................................................................220

12. ...................................................................................... 223


DialogFragment.............................................................................................................225
DialogFragment...................................................................................................226
............................................................................228
.....................................................................................229
DatePickerFragment......................................................................................230
CrimeFragment........................................................................................232
.............................................................................................233
....................................................................................234
DialogFragment...................................................................236
. .........................................................................................238

13. MediaPlayer.................................................... 239


..................................................................................................................240
HelloMoonFragment.....................................................................................242
..........................................................................................................243
HelloMoonFragment........................................................................................244
............................................................................................244
...............................................................................................................246
................................................................248
. ..............................................................................248
: ............................................................................249
. HelloMoon.......................................................................249

14. .......................................................................... 250


................................................................................................................250
..........................................................................................251
: ?.................................................................253
onSaveInstanceState(Bundle).....................................................................................254
: .........................................................256

15. .............................................................................................. 257


.................................................................................................................258
............................................................................................................258
..........................................................................259
............................................................................................259
........................................................................................260
..............................................................................................261
........................................................................................262
....................................................................................262
................................................................................................263
.....................................................................263
.......................................................................................................................264
.................................................................................................264
......................................................................................264

16. ....................................................................................... 266


.........................................................................................................................267
XML.....................................................................................268
..........................................................................................270
.....................................................................................................270
............................................................................................................273
.........................................................................................275
................................................................................................275
Up...............................................................................................................277
....................................................................................................279
..................................................................................280
................................................................................................281
, .....................................................................................................................281
. ..........................................................................283

17. ............................................ 285


CriminalIntent............................................................................285
JSON.................................................................................287

10

CriminalIntentJSONSerializer............................................................................287
JSON Crime........................................................................288
Crime CrimeLab.......................................................................................289
onPause()...........................................................................290
....................................................................................291
. ......................................................................293
: Android - Java.........................293
...........................................................................................294

18. ............................. 295


....................................................................................296
...................................................................................................296
...................................................................................................297
..............................................................................................297
................................................................................................................299
..................................................................................300
......................................................................................................301
......................................301
.................................................................................304
..........................................305
: ?...................................................................................306
. CrimeFragment....................................................................................307
: ActionBarSherlock......................................................................................307
. ActionBarSherlock............................................................................310
ABS CriminalIntent..................................................................................310
.............................................................................................311
......................................................................................311

19. I: Viewfinder................................................................................. 313


........................................................................................................315
CrimeCameraFragment.......................................................................................316
CrimeCameraActivity...........................................................................................317
...........................................................317
API ..........................................................................................................318
...........................................................................................318
SurfaceView, SurfaceHolder Surface.........................................................................................320
...................................................323
CrimeCameraActivity CrimeFragment...........................................................................325
.........................................................................328
: ..................................................329

20. II: ........................................ 331


.......................................................................................................................332
........................................................................................333
...........................................................................................336
CrimeFragment.................................................................................................337
CrimeCameraActivity ...........................................................337
CrimeCameraFragment.......................................................................338
CrimeFragment................................................................................339
.........................................................................................................340
Photo.........................................................................................................340

11

Crime ..........................................................341
.........................................................................................342
CrimeFragment..............................................................................342
ImageView............................................................................................................343
..........................................................................................................345
ImageView.......................................................345
............................................................................................................347
DialogFragment....................................................349
. Crime...........................................................................351
. ............................................................................................351
: Android...........................................................352

21. ...................................................................................... 354


.....................................................................................................................355
..........................................................................................357
.......................................................................................................................357
...............................................................................................359
.................................................................................................359
......................................................................................................................360
Android........................................................................................................362
...................................................................................364
.............................................................................................................365
........................................................................................366
. ..........................................................................................366

22. ................................................................... 367


..........................................................................................................................368
SingleFragmentActivity............................................................................................369
...................................................................370
-........................................................................................371
.....................................................................................372
: .........................................................................................373
.............................................................................374
CrimeFragment.Callbacks........................................................................................378
: ..............................................381

23. ........................................................... 383


NerdLauncher............................................................................................383
.......................................................................................................385
........................................................................387
...............................................................................................................389
NerdLauncher ........................................................391
. , ..........................................................................392
: ....................................................................................392

24. ................................................................................... 394


RemoteControl.................................................................................................395
RemoteControlActivity................................................................................................395
RemoteControlFragment.............................................................................................396
..................................................................................................399
..................................................................................................................401

12

: include merge.........................................................................................404
. .............................................................................................405

25. .............................................................................. 406


XML..........................................................................................................407
........................................................................................................................409
............................................................................................................411
9- ................................................................................................................413

26. HTTP ........................................................................... 420


PhotoGallery..............................................................................................421
.........................................................................................................424
.................................................................................................425
AsyncTask .....................................................426
.......................................................................................................427
............................................................................................................428
XML Flickr..................................................................................................................429
XmlPullParser....................................................................................................433
AsyncTask ..................................................................................................435
: AsyncTask............................................................................438
AsyncTask...............................................................................................................439
. ............................................................................................439

27. Looper, Handler HandlerThread.............................................................. 440


GridView ................................................................................440
............................................................................................................443
............................................................................................443
......................................................................................................444
.........................................................................................446
................................................................................................................447
.............................................................................................................447
..................................................................................................448
Handler.....................................................................................................................451
: AsyncTask ...................................................................................455
. .............................................................455

28. .......................................................................................................... 457


Flickr...............................................................................................................................457
..............................................................................................................459
............................................................................................459
.............................................................................................................462
.......................................................................................................464
..................................................................................................................465
............................................................................................465
.........................................467
SearchView Android 3.0.................................................................470
................................................................................................................................472

29. ...................................................................................... 474


IntentService.................................................................................................................474
..................................................................................................................477
...................................................................477

13

............................................................................................................478
AlarmManager....................................................................................480
PendingIntent...........................................................................................................................482
PendingIntent...........................................................482
..................................................................................................................483
..................................................................................484
............................................................................................................................486
: ................................................................................488
( ) ...................................................................................488
.........................................................................................................488
.........................................................................................................488
.............................................................................................................489
.................................................................................................................489
................................................................................................490
..................................................................................................491

30. ................................................................ 492


..........................................................................................................493
.............................................................................493
.....................................................................................................495
..................................................................................496
.................................................................................496
.......................................................................497
.............................................................................................................500
.................................................................................................502
...............................503
.......................................................................................507

31. - WebView........................................................... 508


Flickr....................................................................................................508
: ..............................................................................................510
: WebView.............................................................................................510
WebChromeClient............................................................................................................514
WebView...................................................................................................................516
: JavaScript..................................................................517

32. .......................... 519


DragAndDraw...................................................................................................520
DragAndDrawActivity.......................................................................................520
DragAndDrawFragment...................................................................................521
....................................................................................522
BoxDrawingView.............................................................................................522
........................................................................................................524
.......................................................................525
onDraw()...................................................................................................527
: ................................................................................................................530

33. .......................................... 531


RunTracker................................................................................................531
RunActivity.........................................................................................................533
RunFragment......................................................................................................................533

14

....................................................................................................................534
............................................................................................................................534
RunFragment..................................................................................................534
LocationManager............................................................................................535
....................................................537
......................................539
: .........................................................543
...............................................................544

34. SQLite........................................................... 547


...................................................................................547
...........................................................................................554
CursorAdapter..................................................................556
.................................................................................................................559
...............................................................................................561
: ......................................................................................567

35. ................................................................. 568


Loader LoaderManager...............................................................................................................568
RunTracker.....................................................................................570
.................................................................................................................570
..................................................................................................................574
...........................................................................................577

36. ......................................................................................................... 579


Maps API RunTracker..........................................................................579
.....................................................................................579
Google Play services SDK................................................................579
Google Maps API...........................................................................................580
RunTracker..........................................................................................580
...........................................................................581
..........................................................................................................................585
.........................................................................589
: ........................................................................................590

37. .............................................................................................. 591


...............................................................................................................591
..........................................................................................................592
.......................................................................................................................................592

. ,

,
.
. .

, .
, .
.
(Chris Stewart) (Owen Matthews)
.
-
(Christopher Moor)
,
.
(Bolot Kerimbaev) (Andrew
Lunsford)
.
(Frank Robles), (Jim Steele),
(Laura Cassell), (Mark Dalrymple)
(Magnus Dahl) .
(Aaron Hillegass).
, . . ( ,
.)
(Susan Loper)
,
. .
, .
NASA.
.

17

( Ellie Volckhausen), .
IntelligentEnglish.com , EPUB Kindle. DocBook
.
, Facebook
.
. , ,
,
. , . .

Android

Android .
Android : , .
, - ,
. , ,
.
Android . c
Java, Java . Android,
. , .
. , Big Nerd Ranch, ,
Android :
Android;
, .
. Android.
Android,
. - , - , ,
.

, ,
. , , Android.

19


, Java,
, , , , , , .
, 2. Java
. ;
.
- , Java , .

Java (, ).
Java ,
.


Big Nerd Ranch.
, .
, . , .
,
.
,
,
, , .
, . ,
.
:
.
,
.
forums.bignerdranch.com.
Android, .


Android.
, .
,

20

Android

. ,
.
GeoQuiz
Android, , .
CriminalIntent
.
, - ,
, , , .
HelloMoon .
,
, .
NerdLauncher .
RemoteControl
, ,
.
PhotoGallery Flickr Flickr. , , - ..
DragAndDraw .
RunTracker
. ,
SQLite, .

.
,
.
.
.
,
forums.bignerdranch.com.

?
. ,
. , , .

21


, Android.
. .
, . , .
, .
7 . .
Android- ,
. ,
. , ,
.
, Gingerbread Froyo. Android Ice Cream Sandwich Jelly Bean,
Key Lime Pie. ,
Froyo Gingerbread. (
Android 6.)
, , Froyo Gingerbread.
, ,
Android . ,
, Gingerbread
40% .


ADT Bundle
ADT (Android Developer Tools) Bundle.
:
Eclipse Android.
Eclipse Java, PC, Mac Linux. Eclipse
,
, .
Android Developer Tools Eclipse. ADT
21.1. ,
.
Android SDK Android SDK.

22

Android

Android SDK
.
Android
.


ADT Bundle Android zip-.
1. http://developer.android.com/sdk/index.html.
2. zip- , Eclipse
.
3. eclipse Eclipse.
Windows Eclipse , ,
Java Development Kit (JDK6),
www.oracle.com.
, http://developer.
android.com/sdk/index.html .

SDK
ADT Bundle SDK
. Android
.
Android SDK Manager.
Eclipse WindowAndroid SDK Manager.
, Android 2.2 (Froyo),
:
SDK Platform;
;
Google API.
, .
Android SDK Manager Android , .

23

. Android SDK Manager


.
Android . , .


, ,
comp@piter.com ( , ).
!
- http://www.piter.com
.


Android

, Android. ,
- .

.
, ,
GeoQuiz. ,
. , True False,
GeoQuiz .
. 1.1
False.

. 1.1. ,

GeoQuiz
(activity) (layout):
Activity Android SDK.

.
, ,

Android

25

Activity.
; .
GeoQuiz ,
Activity QuizActivity. QuizActivity , .1.1.
. ,
XML. ,
(, ).
GeoQuiz activity_quiz.xml.
XML ,
.1.1.
QuizActivity activity_quiz.xml .1.2.

. 1.2. QuizActivity , activity_quiz.xml

, .

Android
Android. Android ,
. , Eclipse
FileNewAndroid Application Project,
.
GeoQuiz (.1.3).
. Package
Name com.bignerdranch.android.geoquiz.
:
DNS, .

Google Play.

Android. GeoQuiz ,

26

1. Android

.
Android 6.

. 1.3.

Android ,
.
; . (
, ,
. . forums.bignerdranch.com,
.)
Next.
Create custom launcher icon (.1.4). GeoQuiz .
, Create activity .

Android

27

. 1.4.

Next.
(. 1.5)
. BlankActivity ( ).

. 1.5.

28

1. Android

Next.
QuizActivity (.1.6). Activity .
, ,
.

. 1.6.

activity_quiz . , ;
,
.
, , .
Navigation Type None Finish.
Eclipse .

Eclipse
Eclipse (workbench window),
.1.7. ( Eclipse, Eclipse, .)

29

Package Explorer. ,
.
. , Eclipse activity_quiz.xml.
.
, X
(. 1.7). .
,
.

. 1.7.

,
Eclipse.
, ;
, .

Eclipse activity_quiz.xml Android,


.
, XML, ,
.

30

1. Android

XML, activity_quiz.xml
.
activity_quiz.xml
. , XML
, 1.1.
1.1.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".QuizActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" />
</RelativeLayout>

, activity_quiz.xml
:
<?xml version="1.0" encoding="utf-8"?>

ADT 21 Android.
, .
(widgets): RelativeLayout
TextView.
,
. , . ,
, .
Android SDK , .
View (, TextView Button).
. 1.8 , RelativeLayout TextView,
1.1.
, . QuizActivity
:
LinearLayout;
TextView;
LinearLayout;
Button.
.1.9 , QuizActivity.

. 1.8.

()

()

. 1.9.

31

32

1. Android

activity_quiz.xml.
activity_quiz.xml , 1.2. XML, , ,
XML . .
, ;
, . : ,
.
,
android:text, . .
.
1.2. XML (activity_quiz.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".QuizActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" />
</RelativeLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/question_text" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"

33

android:text="@string/false_button" />
</LinearLayout>
</LinearLayout>

XML , .1.9. XML.


.
XML. .
, ,
.


View, .
. 1.10 XML 1.2.

. 1.10.

LinearLayout. XML Android


http://schemas.android.com/apk/res/android.

34

1. Android

LinearLayout View ViewGroup. ViewGroup

.
, .
ViewGroup FrameLayout, TableLayout RelativeLayout.
ViewGroup, (child) ViewGroup.
LinearLayout : TextView
LinearLayout. LinearLayout Button.


, .

android:layout_width android:layout_height
android:layout_width android:layout_height,
, . ,
match_parent wrap_content:
match_parent .
wrap_content .
( fill_parent.
match_parent.)
LinearLayout match_parent.
LinearLayout , , Android
.
wrap_content.
.1.9 , .
TextView -
android:padding="24dp".
, . (, dp? ,
(density-independent pixels),
8.)

android:orientation
android:orientation LinearLayout ,
. LinearLayout ; LinearLayout .
.
LinearLayout , , . LinearLayout

35

. ( , ;
.)

android:text
TextView Button android:text.
,
: , .
, XML, .
(, android:text="True"), .
, . 15 ,
.
, activity_quiz.xml, .
.


strings.xml.
Package Explorer res/values,
strings.xml.
; strings.xml .
.
hello_world .
1.3. (strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">GeoQuiz</string>
<string name="hello_world">Hello, world!</string>
<string name="question_text">Constantinople is the largest city in Turkey.</
string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="menu_settings">Settings</string>
</resources>

( menu_settings .
menu_settings , .)
@string/false_button XML GeoQuiz
"False" .

36

1. Android

strings.xml. activity_quiz.xml , , . (
, , - .)
strings.xml,
.
. res/values/,
resources string,
.


.
, . activity_quiz.xml Graphical Layout
.

. 1.11.
(activity_quiz.xml)

XML View
XML activity_quiz.xml View?
QuizActivity.

37

GeoQuiz Activity QuizActivity. QuizActivity src (


Java- ).
Package Explorer src, com.
bignerdranch.android.geoquiz. QuizActivity.java (1.4).
1.4. QuizActivity (QuizActivity.java)
package com.bignerdranch.android.geoquiz;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
public class QuizActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_quiz, menu);
return true;
}

( import,
import, .)
Activity: onCreate(Bundle) onCreateOptionsMenu(Menu).
onCreateOptionsMenu(Menu).
16.
onCreate(Bundle) . , . ,
Activity:
public void setContentView(int layoutResID)

(inflates) .
, . , ,
.


. ,
, , XML ..

38

1. Android

res. Package Explorer


, activity_quiz.xml res/layout/. ,
, res/values/.
.
R.layout.activity_quiz.
GeoQuiz,
Package Explorer gen. R.java.
Android,
, .
1.5. GeoQuiz
/* AUTO-GENERATED FILE.
...
*/

DO NOT MODIFY.

package com.bignerdranch.android.geoquiz;
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int ic_launcher=0x7f020000;
}
public static final class id {
public static final int menu_settings=0x7f070003;
}
public static final class layout {
public static final int activity_quiz=0x7f030000;
}
public static final class menu {
public static final int activity_quiz=0x7f060000;
}
public static final class string {
public static final int app_name=0x7f040000;
public static final int false_button=0x7f040003;
public static final int menu_settings=0x7f040006;
public static final int question_text=0x7f040001;
public static final int true_button=0x7f040002;
}
...
}

, R.layout.activity_quiz
activity_quiz layout R.
.
, :
setTitle(R.string.app_name);

Android ,
activity_quiz.xml.

39

.
, .
,
android:id. activity_quiz.xml android:id
.
1.6. (activity_quiz.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
... >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/question_text" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/true_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button" />
<Button
android:id="@+id/false_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/false_button" />
</LinearLayout>
</LinearLayout>

: + android:id, android:text. , ,
.
activity_quiz.xml. R.java ,
R.id .
1.7. (R.java)
public final class R {
...
public static final class id {
public static final int false_button=0x7f070001;
public static final int menu_settings=0x7f070002;
public static final int true_button=0x7f070000;
}
...

40

1. Android


, , QuizActivity. .
QuizActivity.java. ( ;
.) .
1.8. (QuizActivity.java)
public class QuizActivity extends Activity {
private Button mTrueButton;
private Button mFalseButton;

...
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
}

, m
( ).
Android, .
. : Button (Button cannot be resolved
to a type).
, android.widget.Button
QuizActivity.java. :
import android.widget.Button;


Eclipse
, Java Android SDK.
,
.
, :
Command+Shift+O Mac;
Ctrl+Shift+O Windows Linux.
. ( , XML.)
-.
:

41

View;
, .


,
Activity:
public View findViewById(int id)

View.
QuizActivity.java . ,
View Button.
1.9. (QuizActivity.java)
public class QuizActivity extends Activity {
private Button mTrueButton;
private Button mFalseButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);

...
}

mTrueButton = (Button)findViewById(R.id.true_button);
mFalseButton = (Button)findViewById(R.id.false_button);


Android (event-driven).
,
, . ( ,
, , .)
, ,
. , ,
(listener).
.
Android SDK , . ,
View.OnClickListener.

42

1. Android

True. QuizActivity.java
onCreate() .
1.10. True (QuizActivity.java)
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
mTrueButton = (Button)findViewById(R.id.true_button);
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// , !
}
});

mFalseButton = (Button)findViewById(R.id.false_button);

( View cannot be resolved to a type, Command+Shift+O Ctrl+Shift+O


View.)
1.10 , Button mTrueButton. setOnClickListener(OnClickListener)
, OnClickListener.


. , ; : ,
, setOnClickListener(OnClickListener) .
,
.
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// , !
}
});

. ,
,
, .
OnClickListener, onClick(View).

43

onClick(View) , . ,
onClick(View) ,

, .
( ,
, Java ,
, .)
False.
1.11. False (QuizActivity.java)
...

mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// , !
}
});
mFalseButton = (Button)findViewById(R.id.false_button);
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// , !
}
});

- . (toast) ,
- ,
, . ,
(.1.12).
strings.xml ,
.
1.12. (strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">GeoQuiz</string>
<string name="question_text">Constantinople is the largest city
in Turkey.</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="correct_toast">Correct!</string>
<string name="incorrect_toast">Incorrect!</string>
<string name="menu_settings">Settings</string>
</resources>

44

1. Android

. 1.12.

Toast:
public static Toast makeText(Context context, int resId, int duration)

Context Activity (Activity Context). ,


. Context
Toast .
Toast,
.
, Toast.show(),
.
QuizActivity makeText() (1.13). ,
Eclipse.

, .
1.13.
Toast, Toast.

45

, Tab,
. ( , . Tab
.)
makeText(Context, int, int).
.
;
QuizActivity.this. Tab, , , 1.13.
1.13. (QuizActivity.java)
...
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(QuizActivity.this,
R.string.incorrect_toast,
Toast.LENGTH_SHORT).show();
}
});
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(QuizActivity.this,
R.string.correct_toast,
Toast.LENGTH_SHORT).show();
}
});

makeText() QuizActivity Context.


, this . , this View.OnClickListener.

, Toast . , .


Android . Android,
.
Android (AVD, Android Virtual Device),
WindowAndroid Virtual Device Manager.
AVD Manager, New... .
. Galaxy Nexus Google APIs

46

1. Android

(Google Inc.) API Level 17, . 1.13.

Windows, , AVD
(RAM) 1024 512. OK.


Windows
512
1024

. 1.13. Android

,
GeoQuiz. Package Explorer
GeoQuiz. Run AsAndroid Application. Eclipse
, . Eclipse ,
LogCat .
, , GeoQuiz .
. ( ,
, , AVD
. AVD .)

: Android

47

GeoQuiz ,
LogCat. ;
. Text
, .

. 1.14. NullException 21

.
.
; , , .
Back (U- ),
Eclipse, .
, . 2 GeoQuiz ,
.

:
Android
, , Android. , Eclipse
, . Android , AndroidManifest.xml
( ) .apk.
, .
( .apk ,
.
Android http://http://developer.
android.com/tools/publishing/preparing.html.)
activity_quiz.xml View ? aapt (Android Asset Packaging Tool)
. .apk. , setContentView()
onCreate() QuizActivity, QuizActivity LayoutInflater
View, .

48

1. Android

(
XML.
, , SDK;
3.)
, XML,
, 8.

Java

-
Java (.class)

dalvik

-
dalvik (.dex)


apk

Android
(.apk)

. 1.15. GeoQuiz

Android

49

. 1.16. activity_quiz.xml

Android
, ,
Eclipse. ADT Android (, aapt),
Eclipse.
, - Eclipse. . maven
ant. Ant , .
:
, ant .
, tools/ platform-tools/ Android SDK
.
:
$ android update project -p .

50

1. Android

Eclipse build.xml ant. build.xml ; .


. .apk,
:
$ ant debug

. .apk, bin/--debug.apk. .apk ,


:
$ adb install bin/--debug.apk

,
.

Android MVC

GeoQuiz
.

. 2.1. !

GeoQuiz TrueFalse.
/.
TrueFalse,
QuizActivity.

52

2. Android MVC


Package Explorer com.bignerdranch.android.geoquiz NewClass. TrueFalse,
java.lang.Object Finish.

. 2.2. TrueFalse

TrueFalse.java :
2.1. TrueFalse
public class TrueFalse {
private int mQuestion;
private boolean mTrueQuestion;

public TrueFalse(int question, boolean trueQuestion) {


mQuestion = question;
mTrueQuestion = trueQuestion;
}

get- set-

53

mQuestion int, String?


( int) .
.
mTrueQuestion , .
get- set-. Eclipse .

get- set-
Eclipse m is get .
Eclipse ( Eclipse Mac, WindowsPreferences
Windows). Java Code Style.
Conventions for variable names: Fields (.2.3).
Edit m . s . ( GeoQuiz s ,
.)
, Use 'is' prefix for getters that return boolean . OK.

. 2.3. Java

54

2. Android MVC

? Eclipse
get- mQuestion, getQuestion()
getMQuestion() isTrueQuestion() isMTrueQuestion().
TrueFalse.java,
SourceGenerate Getters And Setters... Select
All, get- set- .

()
. 2.4. GeoQuiz

OK. Eclipse .
2.2. get- set-
public class TrueFalse {
private int mQuestion;
private boolean mTrueQuestion;
public TrueFalse(int question, boolean trueQuestion) {
mQuestion = question;
mTrueQuestion = trueQuestion;
}
public int getQuestion() {
return mQuestion;
}
public void setQuestion(int question) {
mQuestion = question;
}

-- Android

55

public boolean isTrueQuestion() {


return mTrueQuestion;
}

public void setTrueQuestion(boolean trueQuestion) {


mTrueQuestion = trueQuestion;
}

TrueFalse . QuizActivity
TrueFalse, , GeoQuiz
.
QuizActivity TrueFalse.
TextView Button
.

- Android
, , .2.4 : , . Android
, --,
MVC (Model-View-Controller). MVC, ,
.
-. ,
, , ,
/ ..
; .
Android
.
.
GeoQuiz TrueFalse.

, . :
- , .
Android .
. .
GeoQuiz , activity_quiz.xml.
; . ,

56

2. Android MVC

,
.
Android Activity, Fragment
Service. ( 7, 29.)
GeoQuiz
QuizActivity.
.2.5 , . :
;
-,
.

. 2.5. MVC

MVC
, .
; ,
.
, ;
, .
GeoQuiz ,
. GeoQuiz Next.
TrueFalse.

57

MVC .
, ,
.
, TrueFalse ,
/. TrueFalse
. , , ,
.


MVC
GeoQuiz Next.
Android
XML . GeoQuiz activity_quiz.xml.
, .2.6. (
, .)

. 2.6.

, :
android:text TextView.
.
TextView android:id.
, QuizActivity.
Button LinearLayout.
activity_quiz.xml .
2.3. TextView (activity_quiz.xml)
<LinearLayout
... >
<TextView
android:id="@+id/question_text_view"

58

2. Android MVC

2.3 ()
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/question_text"
/>
<LinearLayout
... >
...
</LinearLayout>
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_button" />
</LinearLayout>

activity_quiz.xml. ,
.
res/values/strings.xml.
.
2.4. (strings.xml)
...
<string name="app_name">GeoQuiz</string>
<string name="question_text">Constantinople is the largest city
in Turkey.</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="next_button">Next</string>
<string name="correct_toast">Correct!</string>
...

strings.xml , ,
.
2.5. (strings.xml)
...
<string name="incorrect_toast">Incorrect!</string>
<string name="menu_settings">Settings</string>
<string name="question_oceans">The Pacific Ocean is larger than
the Atlantic Ocean.</string>
<string name="question_mideast">The Suez Canal connects the Red Sea
and the Indian Ocean.</string>
<string name="question_africa">The source of the Nile River is in Egypt.</string>
<string name="question_americas">The Amazon River is the longest river
in the Americas.</string>
<string name="question_asia">Lake Baikal is the world\'s oldest and deepest
freshwater lake.</string>
...

59

\' .
, \n
.
. activity_quiz.xml
.
, GeoQuiz.
QuizActivity.


GeoQuiz QuizActivity
. , activity_quiz.
xml, .
, , QuizActivity

GeoQuiz.
QuizActivity.java. TextView
Button . TrueFalse
.
2.6. TrueFalse (QuizActivity.java)
public class QuizActivity extends Activity {
private
private
private
private

Button mTrueButton;
Button mFalseButton;
Button mNextButton;
TextView mQuestionTextView;

private
new
new
new
new
new
};

TrueFalse[] mQuestionBank = new TrueFalse[] {


TrueFalse(R.string.question_oceans, true),
TrueFalse(R.string.question_mideast, false),
TrueFalse(R.string.question_africa, false)
TrueFalse(R.string.question_americas, true),
TrueFalse(R.string.question_asia, true),

private int mCurrentIndex = 0;


...

TrueFalse TrueFalse.
( .
.
.)
mQuestionBank, mCurrentIndex
TrueFalse .

60

2. Android MVC

TextView .
2.7. TextView (QuizActivity.java)
public class QuizActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);

mTrueButton = (Button)findViewById(R.id.true_button);
...

. GeoQuiz.
TextView.
Next. , View.OnClickListener.
TextView.
2.8. (QuizActivity.java)
public class QuizActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
...
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(QuizActivity.this,
R.string.correct_toast,
Toast.LENGTH_SHORT).show();
}
});
mNextButton = (Button)findViewById(R.id.next_button);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;

});

61

int question = mQuestionBank[mCurrentIndex].getQuestion();


mQuestionTextView.setText(question);

mQuestionTextView .
, 2.9. mNextButton onCreate(Bundle)
.
2.9. updateQuestion() (QuizActivity.java)
public class QuizActivity extends Activity {
...
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
updateQuestion();
}
});

updateQuestion();

GeoQuiz Next.
, . ,
.
, QuizActivity, :
private void checkAnswer(boolean userPressedTrue)

, ,
: True False.
TrueFalse. ,
.

62

2. Android MVC

QuizActivity.java checkAnswer(boolean),
2.10.
2.10. checkAnswer(boolean) (QuizActivity.java)
public class QuizActivity extends Activity {
...
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
}
private void checkAnswer(boolean userPressedTrue) {
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isTrueQuestion();
int messageResId = 0;
if (userPressedTrue == answerIsTrue) {
messageResId = R.string.correct_toast;
} else {
messageResId = R.string.incorrect_toast;
}

Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)


.show();

@Override
protected void onCreate(Bundle savedInstanceState) {
...
}

checkAnswer(boolean), 2.11.
2.11. checkAnswer(boolean) (QuizActivity.java)
public class QuizActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mTrueButton = (Button)findViewById(R.id.true_button);
mTrueButton.setOnClickListener(new View.OnClickListener() {

});

@Override
public void onClick(View v) {
Toast.makeText(QuizActivity.this,
R.string.incorrect_toast,
Toast.LENGTH_SHORT).show();
checkAnswer(true);
}

63

mFalseButton = (Button)findViewById(R.id.false_button);
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(QuizActivity.this,
R.string.correct_toast,
Toast.LENGTH_SHORT).show();
checkAnswer(false);
}
});

mNextButton = (Button)findViewById(R.id.next_button);
...

GeoQuiz .
.


,
GeoQuiz .


. Mac,
. Windows
adb (Android Debug Bridge). Windows
adb, .
, ,
Devices. DDMS,
DDMS Eclipse. Devices
.
AVD, .
, Java
.
, adb. Devices
, , .
. Reset adb. , .
, Android. http://developer.android.com/tools/device.
html forums.bignerdranch.com
.

64

2. Android MVC


, Google Play.
Android 4.1 Settings
Applications. , Unknown sources
.
Android 4.2 SettingsSecurity
Unknown sources.
USB.
Android 4.0 SettingsApplications
Development USB debugging.
Android 4.0 4.1 SettingsDeveloper options.
Android 4.2 Developer options .
, SettingsAbout Tablet/Phone Build Number
7 . Settings, Developer options
USB debugging.
, .
,
http://developer.android.com/tools/device.html.
GeoQuiz , . Eclipse
,
. ; GeoQuiz
. ( , GeoQuiz
, , .)


GeoQuiz , , Next ,
.
(http://www.
bignerdranch.com/solutions/AndroidProgramming.zip).
Eclipse .
02_MVC/GeoQuiz/res.
drawable-hdpi, res/drawable-mdpi drawable-xhdpi.
.
mdpi

(~160 dpi)

hdpi

(~240 dpi)

xhdpi

(~320 dpi)

65

( ldpi,
.)
arrow_right.png arrow_left.png.
, .
, . ,
.



GeoQuiz.
Package Explorer res , .
Package Explorer.

. 2.7. drawable GeoQuiz

, , .2.8. Copy Files,


.

66

2. Android MVC

. 2.8. ( )

. .png,
.jpg .gif, res/drawable, . (,
.)
, , gen/R.java
R.drawable. : R.drawable.arrow_left R.drawable.arrow_right.
,
; .
, .
Android 3 .
.

XML
. Next , .
XML?
, .
activity_quiz.xml Button.
2.12. Next (activity_quiz.xml)
<LinearLayout
... >
...
<LinearLayout
... >
...
</LinearLayout>
<Button
android:id="@+id/next_button"

67

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_question_button"
android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp"
/>
</LinearLayout>

XML .
@string/.
@drawable/.
res , 3.
GeoQuiz .
, , .
, GeoQuiz . GeoQuiz
Next, ,
. ( , Control+F12/
Ctrl+F12.)
. ?
3.

. ,
. ,
.
. ,
Android
Android.
- , ,
. ,
forums.bignerdranch.com. , ,
.
,
Eclipse .
Package Explorer,
Copy, .

68

2. Android MVC

,
Package Explorer .

. TextView
Next , ,
TextView.
. TextView View.OnClickListener,
Button, TextView View.

.
.
, .2.9.

. 2.9. !

. ,
.

. Button ImageButton
, ,
.

69

. 2.10.

ImageButton (
Button).
ImageButton ImageView
Button, TextView.

. 2.11. ImageButton Button

text drawable Next ImageView:


<Button ImageButton
android:id="@+id/next_button"

70

2. Android MVC

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_question_button"
android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp"
android:src="@drawable/arrow_right"
/>

, QuizActivity,
ImageButton.
ImageButton, Eclipse android:contentDescription.
. ,
, ( ).
, android:contentDescription ImageButton.

Activity

Activity . : , .
Activity ,
(
. .3.1
)
, .

Activity ,
.3.1,


( )
onCreate(Bundle).
, .


, onCreate()

(
setContentView(int));

( )
;

;

.
, onCre
ate() Activity

. , Android
. 3.1.
.
Activity

72

3. Activity

Activity
,
QuizActivity.
.


Android android.util.Log
. Log
. :
public static int d(String tag, String msg)

d debug () .
( Log .)
, .
TAG,
. .
QuizActivity.java TAG QuizActivity.
3.1. TAG (QuizActivity.java)
public class QuizActivity extends Activity {
private static final String TAG = "QuizActivity";
}

...

onCreate() Log.d() .
3.2. OnCreate() (QuizActivity.java)
public class QuizActivity extends Activity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate(Bundle) called");
setContentView(R.layout.activity_quiz);
...

Eclipse , Log, Command+Shift+O


(Ctrl+Shift+O) . Eclipse
; android.util.Log.
QuizActivity.

Activity

73

3.3. (QuizActivity.java)
} // onCreate(Bundle)
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart() called");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause() called");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume() called");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop() called");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() called");
}
}

. ,
- onCreate();
.
, @Override.
, .
, :
public class QuizActivity extends Activity {
@Override
public void onCreat(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
}
...

74

3. Activity

Activity onCreat(Bundle),
. ,
QuizActivity.onCreat(Bundle).

LogCat

,
LogCat , Android SDK.
LogCat, WindowShow ViewOther...
Android LogCat,
OK (. 3.2).
LogCat
,
. ,
LogCat
.
,
LogCat (. 3.3).
LogCat ,
.
. 3.2. LogCat
; LogCat
.
Eclipse ,
.3.4. LogCat,
(
Eclipse).
GeoQuiz; LogCat .
.
. TAG LogCat TAG,
QuizActivity.
( , , LogCat
. WindowShow ViewOther... Devices. , , LogCat.)
, TAG.
LogCat + ;
. QuizActivity
QuizActivity by Log Tag: (.3.5).

LogCat

75

. 3.3. LogCat

. 3.4. Eclipse LogCat

76

3. Activity

. 3.5. LogCat

OK; ,
QuizActivity (.3.6). GeoQuiz
QuizActivity.

. 3.6. GeoQuiz ,

( , QuizActivity
LogCat.)
. Back, LogCat. onPause(),
onStop() onDestroy().

. 3.7. Back

Back, Android: ,
. Android ,
.

LogCat

77

GeoQuiz. Home
LogCat. onPause() onStop(),
onDestroy().

. 3.8. Home

.
Recents Home (. 3.9).
Recents Home.

Home

Back

Recents

. 3.9. Home, Back Recents

GeoQuiz LogCat.
, .
Home Android: ,
. Android ,
.

78

3. Activity

.
, .
, , .
.
, .

.
.


, 2. GeoQuiz,
Next ,
. ( , Control+F12/Ctrl+F12).
GeoQuiz . ,
, LogCat.

. 3.10. QuizActivity

, QuizActivity, ,
, . .
- . QuizActivity mCurrentIndex 0,
. , , .

. ,
. , ,

79

, , , ,
, .
,
. ,

.

; . ,
(, ) .
, , .
, , ,
Android .


LogCat. ( LogCat,
WindowShow View...)
Package Explorer res
. layout-land.

. 3.11.

80

3. Activity

activity_quiz.xml res/layout/ res/layout-land/.


: .
.
, .
-land . res Android ,
.
, Android,
http://developer.
android.com/guide/topics/resources/providing-resources.html. 15.
, Android res/layout-land.
res/layout/.
,
. .3.12.

. 3.12.

LinearLayout FrameLayout ViewGroup .



android:layout_gravity.
android:layout_gravity TextView, LinearLayout
Button. Button LinearLayout .
layout-land/activity_quiz.xml , .3.12. 3.4.

81

3.4. (layout-land/activity_quiz.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/question_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:orientation="horizontal" >
...
</LinearLayout>
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:text="@string/next_button"
android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp"
/>
</LinearLayout>
</FrameLayout>

GeoQuiz. ,
. , ,
QuizActivity.
, .
QuizActivity.
Android ,
. QuizActivity , setContentView(R.layout.activity_quiz).
QuizActivity.onCreate().

82

3. Activity

Android QuizActivity
, .

. 3.13. QuizActivity

, Android
.
(,
), .


Android .
, ,
GeoQuiz.
, QuizActivity, ,
mCurrentIndex.
(, ). Activity
protected void onSaveInstanceState(Bundle outState)

onPause(), onStop() onDestroy().


onSaveInstanceState() Bundle ,
.
Bundle. onCreate(Bundle):
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...

onSaveInstanceState(Bundle)

83

onCreate() onCreate() Bundle.



.

onSaveInstanceState(Bundle)
onSaveInstanceState() , Bundle, onCreate().
mCurrentIndex .
QuizActivity.java , -.
3.5. (QuizActivity.java)
public class QuizActivity extends Activity {
private static final String TAG = "QuizActivity";
private static final String KEY_INDEX = "index";
Button mTrueButton;
...

onSaveInstanceState() mCurrentIndex
Bundle .
3.6. onSaveInstanceState() (QuizActivity.java)
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length;
updateQuestion();
}
});
}

updateQuestion();

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i(TAG, "onSaveInstanceState");
savedInstanceState.putInt(KEY_INDEX, mCurrentIndex);
}

, onCreate() , mCurrentIndex.

84

3. Activity

3.7. onCreate() (QuizActivity.java)


...
if (savedInstanceState != null) {
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0);

updateQuestion();

GeoQuiz Next.
, QuizActivity
.
, Bundle
, Serializable.
, onSaveInstanceState(),
Serializable.
onSaveInstanceState()
. ; .
,
Android .

Activity
onSaveInstanceState(Bundle) . ,
, Android .
Android .
,
. ,
, onSaveInstanceState().
onSaveInstanceState() Bundle. Bundle (activity record).
, ,
(.3.14).
, Activity ,
. .
,
onDestroy(). , onPause()
onSaveInstanceState() . onSaveInstanceState()
Bundle, onPause()
.

: onSaveInstanceState(Bundle)

85

( ;
)

Android

( )

(
)

. 3.14.

Android ,
.
, , .
.
? Back,
. .
;
, .

:
onSaveInstanceState(Bundle)
onSaveInstanceState(Bundle), ,
, .
.

86

3. Activity

.
Settings. , .

. 3.15. Settings

Settings Development options.


; Dont keep activities.
Home.
. ,
Android .
, , .
Back ( Home)
, . Back , .
,
Dev Tools.
http://developer.android.com/tools/debugging/debugging-devtools.html.

87

. 3.16.

android.util.Log , , , . Android
(.3.1). Log.
Log.
3.1.

ERROR

Log.e()

WARNING

Log.w()

INFO

Log.i()

DEBUG

Log.d()

( )

VERBOSE

Log.v()

88

3. Activity

:
, Throwable,
,
. 3.8
.
Java String.format, .
3.8. Android
// "debug"
Log.d(TAG, "Current question index: " + mCurrentIndex);
TrueFalse question;
try {
question = mAnswerKey[mCurrentIndex];
} catch (ArrayIndexOutOfBoundsException ex) {
// "error"
//
Log.e(TAG, "Index was out of bounds", ex);
}


Android

, , . , LogCat, Android Lint Eclipse.


, - .
QuizActivity.java onCreate(Bundle), mQuestionTextView.
4.1. (QuizActivity.java)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate() called");
setContentView(R.layout.activity_quiz);
mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
//mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
mTrueButton = (Button)findViewById(R.id.true_button);
mTrueButton.setOnClickListener(new View.OnClickListener() {
...
});
}

...

GeoQuiz , .
. 4.1 , .
Android , . , , ,
.

90

4. Android

. 4.1. GeoQuiz

DDMS
Eclipse WindowOpen PerspectiveDDMS.

. 4.2. DDMS

91

(perspective) Eclipse .
,
; Eclipse .
, .
, Eclipse . -
,
WindowReset Perspective...
, , Java. , ,
Eclipse.
.
.4.2 DDMS (Dalvik Debug Monitor Service). DDMS Android. DDMS LogCat Devices.
Devices , . ,
.
, ,
? Reset adb. adb
. , LogCat ?
, , LogCat
.


. , , LogCat. ,
. Android.
;
, , ;
, , .
,
. java.lang.NullPointerException.

. , ,
. ; Eclipse
.
mQuestionTextView updateQuestion(). NullPointerException : .
mQuestionTextView,
.

92

4. Android

. 4.3. LogCat

: ,
LogCat
. ,
.
, .
, . , ,
. , DDMS Eclipse Devices.
LogCat .



. , Next, .
.

93

QuizActivity.java mNextButton , mCurrentIndex.


4.2. (QuizActivity.java)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

...
mNextButton = (Button)findViewById(R.id.next_button);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length;
//mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length;
updateQuestion();
}
});
...

GeoQuiz Next. .

. 4.4. Next

. , .
, :
, updateQuestion().

94

4. Android

, .
:
.


updateQuestion()
QuizActivity:
4.3. Exception
public class QuizActivity extends Activity {
...
public void updateQuestion() {
Log.d(TAG, "Updating question text for question #" + mCurrentIndex,
new Exception());
int question = mAnswerKey[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
}

Log.d Log.d(String, String, Throwable)


AndroidRuntime. ,
updateQuestion().
Log.d() .
Exception() . ,
.
GeoQuiz, Next LogCat.

. 4.5.

95

,
Exception. , set- onTextChanged().
; ,
. ;
.
, , , ,
.
, LogCat .
, , ,
.
, , , .
http://stackoverflow.com forums.bignerdranch.com, .
LogCat, (
LogCat).
, TAG log
QuizActivity.java.
4.4. , (QuizActivity.java)
public class QuizActivity extends Activity {
...
public void updateQuestion() {
Log.d(TAG, "Updating question text for question #" + mCurrentIndex,
new Exception());
int question = mAnswerKey[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
}

TAG
. , ,
.


Eclipse.
updateQuestion(), , .
,
, .
QuizActivity.java updateQuestion().
.
; .

96

4. Android

. 4.6.

,
( ).
GeoQuiz
Debug AsAndroid Application.
, ,
, .
, . GeoQuiz QuizActivity.
onCreate(Bundle), updateQuestion(),
.
,
Debug. Yes,
Debug.

. 4.7. Debug

Eclipse Debug.
. QuizActivity.java,
, .
Debug .

. , updateQuestion()
onCreate(Bundle). Next,
Resume, .
Next, , (
).
, ,
. Variables. .

97

, : this ( QuizActivity). this


. , .4.9.

- -

. 4.8. Debug

. 4.9.

:
(public) ;
(
);
(protected) ;
(private) .

98

4. Android

this .
, QuizActivity, ,
, Activity, Activity, ..
: mCurrentIndex.
mCurrentIndex. , 0.
. ,
. , Step Over
( , Step Return). (-
access$1(QuizActivity) Step Return
.)
OnClickListener
mNextButton, updateQuestion().
, .
, - , . :
, .
, DDMS Devices
-.
: Disconnect (. .4.8).
Java OnClickListener .
4.5. (QuizActivity.java)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...

mNextButton = (Button)findViewById(R.id.next_button);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length;
mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length;
updateQuestion();
}
});
...

. Breakpoints
Variables. ( ,
WindowShow ViewBreakpoints.) X .
: .
? , ,
.

99

,
. ,
, , , .
.
( Debug AsAndroid Application),
, .


,
.
mQuestionTextView. RunAdd Java
Exception Breakpoint..., .

. 4.10.

,
, .

, .
Android ,
. ,

Suspend on caught exceptions.

100

4. Android

, . RuntimeException . RuntimeException
NullPointerException, ClassCastException
, ,
.
, ,
-. Debug Breakpoints.
RuntimeException.
Subclasses of this exception,
NullPointerException.
GeoQuiz. , , .
, ,
,
. , .

File Explorer
DDMS , .
File Explorer .

. 4.11.

, .
/data, , . Android
/data/data/[ ].

Android

101

. 4.12. GeoQuiz ( )

, 17 , .

Android
Android Java.
, Android
(, ), Java .

Android Lint
Android Lint Android.
, .
Android Lint Android
, . ,
Android Lint .
6 , Android Lint . , Android Line ,
XML. QuizActivity
.
4.6. (QuizActivity.java)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate() called");
setContentView(R.layout.activity_quiz);
mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
mTrueButton = (Button)findViewById(R.id.true_button);

102

4. Android

4.6 ()
mTrueButton = (Button)findViewById(R.id.question_text_view);
}

...

-
TextView Button ,
. Java , Android
Lint .
Package Explorer GeoQuiz
Android ToolsRun Lint: Check for Common Errors;
Lint Warnings (.4.13).

. 4.13. Lint

Android Lint .
:
Button . onCreate(Bundle).
4.7. (QuizActivity.java)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate() called");
setContentView(R.layout.activity_quiz);
mQuestionTextView = (TextView)findViewById(R.id.question_text_view);
mTrueButton = (Button)findViewById(R.id.question_text_view);
mTrueButton = (Button)findViewById(R.id.true_button);
}

...

Android Lint .
merge layout-land/activity_quiz.xml. , Android Lint
. FrameLayout merge, FrameLayout
.

Android

103

R
, -
( , ).
, Eclipse , .
.
, .
Android Lint
WindowRun Android Lint. Line .

ProjectClean. Eclipse , .
XML
R.java ,
.
XML. ,
.
, R.java .
gen
Eclipse R.java,
gen. Eclipse
gen R.
( ), .
.
Android Lint. .
, Eclipse,
http://stackoverflow.com
http://forums.bignerdranch.com.

GeoQuiz .
, ;
,
.

. 5.1. CheatActivity

, QuizActivity
, .

105

. 5.2. QuizActivity ,

Android?
:
;
( onCreate(Bundle));
() () .


.
CheatActivity, CheatActivity.
strings.xml , .
5.1. (strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<string name="question_asia">Lake Baikal is the world\'s oldest and deepest
freshwater lake.</string>
<string name="cheat_button">Cheat!</string>
<string name="warning_text">Are you sure you want to do this?</string>
<string name="show_answer_button">Show Answer</string>
<string name="judgment_toast">Cheating is wrong.</string>
</resources>

106

5.


.5.1 , CheatActivity.
.5.3.

. 5.3. CheatActivity

, res/layout
Package Explorer NewOther... Android
Android XML Layout File (.5.4). Next.

. 5.4.

107

activity_cheat.xml LinearLayout. Finish.

. 5.5.

, XML. :

<?xml version="1.0" encoding="utf-8"?>

XML,
, ( ).
( , ,
. fragment_crime.xml res/layout res/layout, Eclipse .
Eclipse;
XML Java , .
, , Android).
LinearLayout . android:gravity .

108

5.

XML activity_cheat.xml . 5.3.


8 XML
.5.3, XML
. 5.2.

. 5.6. activity_cheat.xml
5.2. (activity_cheat.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/warning_text_view" />
<TextView
android:id="@+id/answerTextView"

109

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp" />
<Button
android:id="@+id/showAnswerButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_answer_button" />
</LinearLayout>

, ,
.
activity_cheat.xml
, ,
.
,
. , .
,
.


Package Explorer com.bignerdranch.
android.geoquiz NewClass.
CheatActivity. Superclass: android.app.Activity (.5.7).
Finish; Eclipse CheatActivity.java .
onCreate(), ,
activity_cheat.xml, setContentView().
5.3. onCreate() (CheatActivity.java)
public class CheatActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cheat);
}
}

onCreate() CheatActivity . : CheatActivity


.

110

5.

. 5.7. CheatActivity


(manifest) XML ,
Android. AndroidManifest.xml .
Package Explorer AndroidManifest.xml . ,
AndroidManifest.xml .
,
.
QuizActivity, . CheatActivity
.
AndroidManifest.xml CheatActivity (5.4).
5.4. CheatActivity (AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.geoquiz"

Cheat QuizActivity

111

android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.bignerdranch.android.geoquiz.QuizActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".CheatActivity"
android:label="@string/app_name" />
</application>
</manifest>

android:name .
, ,
package manifest .
,
CheatActivity. .

Cheat QuizActivity
, QuizActivity,
CheatActivity. ,
layout/activity_quiz.xml layout-land/activity_quiz.xml.

LinearLayout.
Next.
5.5. Cheat! (layout/activity_quiz.xml)
...
</LinearLayout>
<Button
android:id="@+id/cheat_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

112

5.

5.5 ()
android:text="@string/cheat_button" />
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_button" />
</LinearLayout>

FrameLayout.
5.6. Cheat! (layout-land/activity_quiz.xml)
...
</LinearLayout>
<Button
android:id="@+id/cheat_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:text="@string/cheat_button" />
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:text="@string/next_button"
android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp" />
</FrameLayout>

QuizActivity.java. , View.onClickListener Cheat!.


5.7. Cheat! (QuizActivity.java)
public class QuizActivity extends Activity {
...
private Button mNextButton;
private Button mCheatButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mCheatButton = (Button)findViewById(R.id.cheat_button);
mCheatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

});
}

113

// CheatActivity

updateQuestion();

...

CheatActivity.


,
Activity:
public void startActivity(Intent intent)

, startActivity() ,
Activity. ,
. startActivity(), .
, , ActivityManager.
ActivityManager Activity onCreate().

Android

. 5.8.

ActivityManager , ? Intent.


(intent) , . ,
(services), (broadcast
receivers) (content providers).

114

5.

,
Intent ,
.
ActivityManager, , :
public Intent(Context packageContext, Class<?> cls)

Class , ActivityManager.
Context ActivityManager, Class.
Android

. 5.9. : ActivityManager ,

mCheatButton Intent, CheatActivity, startActivity(Intent) ( 5.8).


5.8. CheatActivity (QuizActivity.java)
...
mCheatButton = (Button)findViewById(R.id.cheat_button);
mCheatButton.setOnClickListener(new View.OnClickListener() {

});
}

@Override
public void onClick(View v) {
Intent i = new Intent(QuizActivity.this, CheatActivity.class);
startActivity(i);
}

updateQuestion();

, ActivityManager , Class. , .
, ActivityNotFoundException .
.

115


Intent Class Context (explicit)
. .
, ActivityManager, .

.
, (implicit) .
21.
GeoQuiz. Cheat!;
.
Back. CheatActivity , QuizActivity.


, QuizActivity CheatActivity,
. . 5.10,
.
True?

. 5.10. QuizActivity CheatActivity

QuizActivity CheatActivity
CheatActivity.

Back, QuizActivity, CheatActivity . QuizActivity


, .
QuizActivity CheatActivity.

116

5.


CheatActivity ,

mAnswerKey[mCurrentIndex].isTrueQuestion();

(extra) Intent, startActivity(Intent).


,
.
, .
Android

. 5.11. :

- , mCurrentIndex QuizActivity.
onSaveInstanceState(Bundle).
Intent.putExtra()
,
public Intent putExtra(String name, boolean value)

Intent.putExtra() ,
. String,
.
CheatActivity.java .
5.9. (CheatActivity.java)
public class CheatActivity extends Activity {
public static final String EXTRA_ANSWER_IS_TRUE =
"com.bignerdranch.android.geoquiz.answer_is_true";
...

117

, , .
5.9,
.
QuizActivity .
5.10. (QuizActivity.java)
...
mCheatButton.setOnClickListener(new View.OnClickListener() {

});
}

@Override
public void onClick(View v) {
Intent i = new Intent(QuizActivity.this, CheatActivity.class);
boolean answerIsTrue = mAnswerKey[mCurrentIndex].isTrueQuestion();
i.putExtra(CheatActivity.EXTRA_ANSWER_IS_TRUE, answerIsTrue);
startActivity(i);
}

updateQuestion();

,
Intent .

public boolean getBooleanExtra(String name, boolean defaultValue)

getBooleanExtra() ,
, .
CheatActivity .
5.11. (CheatActivity.java)
public class CheatActivity extends Activity {
...
private boolean mAnswerIsTrue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cheat);

mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);

: Activity.getIntent() Intent,
startActivity(Intent).
, CheatActivity , TextView Show Answer.

118

5.

5.12. (CheatActivity.java)
public class CheatActivity extends Activity {
...
private boolean mAnswerIsTrue;
private TextView mAnswerTextView;
private Button mShowAnswer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cheat);
mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);
mAnswerTextView = (TextView)findViewById(R.id.answerTextView);

mShowAnswer = (Button)findViewById(R.id.showAnswerButton);
mShowAnswer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mAnswerIsTrue) {
mAnswerTextView.setText(R.string.true_button);
} else {
mAnswerTextView.setText(R.string.false_button);
}
}
});

: TextView TextView.
setText(int). TextView.setText() ;
, .
GeoQuiz. Cheat!, CheatActivity. Show Answer, .


.
; CheatActivity
QuizActivity, .
,
Activity:
public void startActivityForResult(Intent intent, int requestCode)

, . ,
, .
,
, .

119

QuizActivity mCheatButton
startActivityForResult(Intent, int).
5.13. startActivityForResult() (QuizActivity.java)
...
mCheatButton.setOnClickListener(new View.OnClickListener() {

});
}

@Override
public void onClick(View v) {
Intent i = new Intent(QuizActivity.this, CheatActivity.class);
boolean answerIsTrue = mAnswerKey[mCurrentIndex].isTrueQuestion();
i.putExtra(CheatActivity.EXTRA_ANSWER_IS_TRUE, answerIsTrue);
startActivity(i);
startActivityForResult(i, 0);
}

updateQuestion();

QuizActivity .

,
0.


,
:
public final void setResult(int resultCode)
public final void setResult(int resultCode, Intent data)

, resultCode : Activity.RESULT_OK Activity.RESULT_CANCELED. (


RESULT_FIRST_USER
.)
, , .
, OK Cancel,
,
.
.
setResult() .

, .
,
startActivityForResult() . setResult()
, Back
Activity.RESULT_CANCELED.

120

5.


QuizActivity
. , Intent, , Activity.setResult(int, Intent)
QuizActivity.
,
CheatActivity CheatActivity. , CheatActivity QuizActivity. ,
CheatActivity.
CheatActivity ,
, CheatActivity.
CheatActivity ,
, .
Show Answer.
5.14. (CheatActivity.java)
public class CheatActivity extends Activity {
public static final String EXTRA_ANSWER_IS_TRUE =
"com.bignerdranch.android.geoquiz.answer_is_true";
public static final String EXTRA_ANSWER_SHOWN =
"com.bignerdranch.android.geoquiz.answer_shown";
...
private void setAnswerShownResult(boolean isAnswerShown) {
Intent data = new Intent();
data.putExtra(EXTRA_ANSWER_SHOWN, isAnswerShown);
setResult(RESULT_OK, data);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...

// ,
//
setAnswerShownResult(false);
...
mShowAnswer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mAnswerIsTrue) {
mAnswerTextView.setText(R.string.true_button);
} else {
mAnswerTextView.setText(R.string.false_button);
}
setAnswerShownResult(true);
}
});

121

Show Answer, CheatActivity


setResult(int, Intent).
, Back QuizActivity,
ActivityManager :
protected void onActivityResult(int requestCode, int resultCode, Intent data)

QuizActivity,
, setResult().
.5.12.
Android

Cheat!,
onClick(View)


Show Answer,

setResult()


Back

. 5.12. GeoQuiz

: onActivityResult(int, int, Intent)


QuizActivity .


QuizActivity.java , CheatActivity. onActivityResult()
.

122

5.

5.15. onactivityResult() (QuizActivity.java)


public class QuizActivity extends Activity {
...
private int mCurrentIndex = 0;
private boolean mIsCheater;
...

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data == null) {
return;
}
mIsCheater = data.getBooleanExtra(CheatActivity.EXTRA_ANSWER_SHOWN, false);
}
...

onActivityResult() QuizActivity ,
.
, .
, checkAnswer(boolean) QuizActivity. , , .
5.16. mIsCheater (QuizActivity.java)
private void checkAnswer(boolean userPressedTrue) {
boolean answerIsTrue = mAnswerKey[mCurrentIndex].isTrueQuestion();
int messageResId = 0;
if (mIsCheater) {
messageResId = R.string.judgment_toast;
} else {
if (userPressedTrue == answerIsTrue) {
messageResId = R.string.correct_toast;
} else {
messageResId = R.string.incorrect_toast;
}
}

Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)


.show();

@Override
protected void onCreate(Bundle savedInstanceState) {
...
mNextButton = (Button)findViewById(R.id.next_button);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length;

Android

});
...

123

mIsCheater = false;
updateQuestion();

GeoQuiz. ,
.

Android
,
. , GeoQuiz ,
, . ,
. GeoQuiz
QuizActivity.
GeoQuiz, QuizActivity
. intent-filter QuizActivity (5.17).
5.17. QuizActivity (AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
... >
...
<application
... >
<activity
android:name="com.bignerdranch.android.geoquiz.QuizActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".CheatActivity"
android:label="@string/app_name" />
</application>
</manifest>

QuizActivity ,
Cheat!. CheatActivity QuizActivity.
(.5.13).
Back CheatActivity ,
QuizActivity .

124

5.


Cheat!

Back

. 5.13. GeoQuiz

Activity.finish() CheatActivity CheatActivity .


GeoQuiz Eclipse Back QuizActivity, QuizActivity ,
GeoQuiz.

GeoQuiz
Eclipse


Back

. 5.14. Home Eclipse

GeoQuiz (launcher), Back


QuizActivity .

GeoQuiz

125


Back

. 5.15. GeoQuiz

Back ,
.
, ActivityManager (back stack)
.
; ,
ActivityManager
, .
, .

, ,
. , ,
.
GeoQuiz ,
. .
,
:
, CheatActivity, .
QuizActivity, mIsCheater.
Next , ,
, .
!

Android SDK

, GeoQuiz, Android.
,
.

Android SDK
6.1 SDK, And
roid, , , 2013 .
6.1. API Android,

API

17

Jelly Bean

4.2

1,6

4.1

14,9

16
15

Ice Cream Sandwich (ICS)

4.0.3, 4.0.4

28,6

13

Honeycomb ( )

3.2

0,9

3.1.x

0,3

2.3.32.3.7

43,9

2.3.3, 2.3.1, 2.3

0,2

12
10

Gingerbread

9
8

Froyo

2.2.x

7,5

Eclair

2.1.x

1,9

Android

127

. , Ice Cream Sandwich was Android 4.0


(API 14) , Android 4.0.3 4.0.4 (API 15).
, . 6.1 , : Android .
2013 Froyo Gingerbread
SDK. Android 2.3.7 ( Gingerbread)
2011 . , Android 4.2,
2012, 1,6% .
( http://developer.android.com/
resources/dashboard/platform-versions.html.)
Android?
- Android
. , .
,
.
Android.
Android
, Google. ,
Google
, .

Android.

Android
- Android.
, Android , Android: Froyo,
Gingerbread, Honeycomb, Ice Cream Sandwich Jelly Bean,
-.
, . , Android
. ,
( 22).
, Google TV ( Android)
, .

128

6. Android SDK

.
,
.

Honeycomb
Android Honeycomb. Honeycomb
Android, . Honeycomb
Google TV, ,
Ice
Cream Sandwich. , , .
,
Gingerbread ,
, .
, , .
, Android Gingerbread (API 10)
Honeycomb (API 11). Android
; , .
Android.

, . ,
, (
) .
( , ).
Android,
Gingerbread . ,
.
GeoQuiz
SDK. (, Android SDK API .)
, , .
SDK (Minimum Required SDK)
SDK (Target SDK) . AndroidManifest.xml
Package Explorer. uses-sdk android:minSdkVersion
android:targetSdkVersion.
6.1. minSdkVersion (AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.geoquiz"

Android

129

android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
...
</manifest>

. 6.1. ?

SDK (Minimum Required SDK)


, , . minSdkVersion ,
.
API 8 (Froyo), Android GeoQuiz
Froyo . Android GeoQuiz
, , Eclair.
.6.1, , Froyo
SDK: 95% .

130

6. Android SDK

SDK (Target SDK)


targetSdkVersion Android, API . Android.
SDK? SDK
. , ,
, .
http://developer.android.com/
reference/android/os/Build.VERSION_CODES.html. , ,
SDK. SDK ,
, .
.

SDK (Compile With)


SDK (CompileWith .6.1) SDK
. .
SDK , SDK
, .
Android SDK.
SDK, , (build
target), ,
. Eclipse ,
, SDK , SDK .
,
GeoQuiz Package Explorer Properties. Android; .
Google APIs Android Open Source Project? Google
APIs Android API Google API
Google Maps.
GeoQuiz . Cancel,
.


API
SDK SDK , . ,
GeoQuiz , SDK
Froyo (API 8)?
Froyo, .

Android

131

. 6.2.

. Android Lint ,
, .
, SDK, Android Lint
.
GeoQuiz API 8 .
API 11 , .
QuizActivity.java. onCreate(Bundle)
, .
6.2. (QuizActivity.java)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate() called");
setContentView(R.layout.activity_quiz);

132

6. Android SDK

6.2 ()
ActionBar actionBar = getActionBar();
actionBar.setSubtitle("Bodies of Water");
mIsCheater = false;

. (
), .
, ActionBar.
API 11,
. ActionBar
16, , Froyo.
GeoQuiz Package Explorer
Android ToolsRun Lint: Check for Common Errors. API 17, .
, Android Lint SDK .
: Class requires API level11
(current min is8). Android Lint ,
.
? SDK
11. , .
Gingerbread , .
, ActionBar , Android .
6.3. Android
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate() called");
setContentView(R.layout.activity_quiz);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {


ActionBar actionBar = getActionBar();
actionBar.setSubtitle("Bodies of Water");
}

Build.VERSION.SDK_INT Android .
, Honeycomb. (
http://developer.android.com/reference/android/os/Build.VERSION_CODES.
html.)
ActionBar ,
Honeycomb .

Android

133

Froyo, Android Lint .


, .

Lint
, Android Lint , , .
onCreate(Bundle).
6.4. Android Lint
@TargetApi(11)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate() called");
setContentView(R.layout.activity_quiz);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ActionBar actionBar = getActionBar();
actionBar.setSubtitle("TFFTT");
}
}

. 6.3.

, if ,
Android .

. Android Lint . ,
, .

134

6. Android SDK

6.4
. if : getActionBar(),
if, ,
. @TargetApi(11) (Android
Lint), : .
, , .
, @TargetApi, ,
SDK_INT. ,
.
GeoQuiz Honeycomb
.
GeoQuiz Froyo Gingerbread ( ). ,
, .

Android
Android Lint , API .
, API
, Android.
. Android
SDK , , , ,
.
Android . http://developer.android.com/.
: (Design), (Develop)
(Distribute).
.
.
, Google
Play . ,
.
.
Android Training

API Guides

Reference

, , , .. SDK,

Tools

Android

135

.
, SDK;
docs .
, API getActionBar(),
.
API .
.
, Reference .
;
Activity. .
Methods, Activity.

. 6.4. Activity

, getActionBar() ,
. , getActionBar() API 11.
, Activity , , API 8,
API. ,

136

6. Android SDK

, API level:17.
8. , Android
API 8, .

. 6.5. Activity, API 8

, .
,
,
, .. Android
, - .

.
GeoQuiz TextView API ,
.
TextView , . TextView
TextView Android. ,
( CharSequence).

137

. 6.6.

XML, TextView.

UI-
FragmentManager

CriminalIntent.
: , , .

. 7.1. CriminalIntent

139

CriminalIntent , .
, Twitter,
Facebook . , .
CriminalIntent ,
13. /: .

.


, /
:
. .
Back
, .
, ,
?
, CriminalIntent .

.

. 7.2. /

. ,
,
. .
:
, .

140

7. UI- FragmentManager

.
. , setContentView(), .
( )
.
Android.


Android , ,
(fragments).
,
.
.
, , UI. UI- , .
, .
,
, .
, ,
.
, Android .
, /.
.
.
. ; (. 7.3).
.
UI-
, /.
, .
, :
, , .
11 22,
.

CriminalIntent

141

. 7.3.

CriminalIntent
CriminalIntent. .7.4
, CriminalIntent .

. 7.4. CriminalIntent

. ,
.
, .7.4, UI- CrimeFragment. (host) CrimeFragment
CrimeActivity.
, , (.7.5).

142

7. UI- FragmentManager

.
.

. 7.5. CrimeActivity CrimeFragment

CriminalIntent ;
. .7.6 CriminalIntent.
, ,
, .
, CrimeFragment , GeoQuiz
: ,
.

()

. 7.6. CriminalIntent ( )

143

, .7.6: Crime, CrimeFragment Crime-

Activity.

Crime .
. (,
- !),
Crime.
Crime.
CrimeFragment (mCrime) .
CrimeActivity FrameLayout,
, CrimeFragment.
CrimeFragment LinearLayout EditText.
CrimeFragment EditText (mTitleField)
, .


; . Android (NewAndroid Application Project). CriminalIntent com.bignerdranch.android.criminalintent,
. 7.7. API ,
Froyo.

. 7.7. CriminalIntent

144

7. UI- FragmentManager

,
, Next.
Next.
, CrimeActivity Finish
(.7.8).

. 7.8. CrimeActivity


API 11 Android
. CriminalIntent SDK 8,
Android.
, :
Android.
(support library) .
libs/android-support-v4.jar, . Fragment (android.support.v4.app.Fragment),
Android API 4 .
; .
FragmentActivity (android.
support.v4.app.FragmentActivity). , . Honeycomb

145

Android Activity .
Activity
Activity. API FragmentActivity Activity,
Activity Android.
Android 4.2

Android 2.3


Android 4.2


Android 2.3

. 7.9.

. 7.9 .
( android.support.v4.app.Fragment)
, ,
.
Package Explorer CrimeActivity.java. CrimeActivity FragmentActivity.
onCreateOptionsMenu(Menu). ( CriminalIntent 16.)
7.1. (CrimeActivity.java)
public class CrimeActivity extends Activity FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {

146

7. UI- FragmentManager

7.1 ()

getMenuInflater().inflate(R.menu.activity_crime, menu);
return true;

CrimeActivity, CriminalIntent. Crime.

Crime
Package Explorer com.bignerdranch.android.criminalintent NewClass.
Crime, java.lang.Object Finish.
Crime.java .
7.2. Crime (Crime.java)
public class Crime {
private UUID mId;
private String mTitle;

public Crime() {
//
mId = UUID.randomUUID();
}

mId, ,
get-, mTitle get- set-.

SourceGenerate Getters and Setters. get- mId,
, ,
getId(), .7.10.
7.3. get- set- (Crime.java)
public class Crime {
private UUID mId;
private String mTitle;
public Crime() {
mId = UUID.randomUUID();
}
public UUID getId() {
return mId;
}
public String getTitle() {

UI-

147

return mTitle;

public void setTitle(String title) {


mTitle = title;
}

. 7.10. get- set-

, Crime CriminalIntent
.
, , Froyo Gingerbread.
, .

UI-
UI-, :
;
.

148

7. UI- FragmentManager


. 7.11 . : ,
, ,
,
.

. 7.11.

.
, . ,
.

, -, . , ;
.

UI-

149

,
CriminalIntent.


UI- :
;
.

, .
,

.
, , 13.
,
. , . ,
, .
, .
, CrimeActivity CrimeFragment. ,
CrimeActivity.


UI- -,
.
CrimeActivity FrameLayout, .7.12.

. 7.12. CrimeActivity

FrameLayout CrimeFragment.
: ;
CrimeFragment. ( )
.

150

7. UI- FragmentManager

CrimeActivity res/layout/activity_crime.xml. FrameLayout, .7.12.


XML 7.4.
7.4. (activity_crime.xml)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

activity_crime.xml , ;
, .
CriminalIntent, .
FrameLayout, CrimeActivity
.

. 7.13. FrameLayout

,
FrameLayout. .

UI-

151

UI-
UI- :
;
, , ;
, .

CrimeFragment
CrimeFragment , Crime. Crime CrimeFragment
,
.
. 7.14 CrimeFragment. LinearLayout, EditText
.

. 7.14. CrimeFragment

, res/layout
Package Explorer NewAndroid XML File. ,
Layout,
fragment_crime.xml. LinearLayout
Finish.
, XML.
LinearLayout . .7.14,
fragment_crime.xml. 7.5.

152

7. UI- FragmentManager

7.5. (fragment_crime.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<EditText android:id="@+id/crime_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/crime_title_hint"
/>
</LinearLayout>

res/values/strings.xml, crime_title_hint
hello_world menu_settings, .
7.6. (res/values/strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CriminalIntent</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_crime">CrimeActivity</string>
<string name="crime_title_hint">Enter a title for the crime.</string>
</resources>

. menu_settings
. , Package Explorer
res/menu/activity_crime.xml. ,
, menu_settings.
CriminalIntent,
Package Explorer.
Eclipse .
. , fragment_crime.xml.

CrimeFragment
com.bignerdranch.android.criminalintent
NewClass. CrimeFragment
Browse, .
Fragment. . android.
support.v4.app.Fragment Fragment .
OK.
( android.support.v4.app.Fragment, ,
CriminalIntent CriminalIntent/libs/.)

UI-

153

. 7.15. Fragment


CrimeFragment , -

.
.
GeoQuiz . CriminalIntent
.
Activity,
onCreate(Bundle).
CrimeFragment.java Crime
Fragment.onCreate(Bundle).
7.7. Fragment.onCreate(Bundle) (CrimeFragment.java)
public class CrimeFragment extends Fragment {
private Crime mCrime;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCrime = new Crime();
}

154

7. UI- FragmentManager

. -, Fragment.onCreate(Bundle) , Activity.
onCreate(Bundle) . Fragment.onCreate()
Fragment , , .
-, , Bundle
. Fragment.on
SaveI nstanceState(Bundle) , Activity.onSave
InstanceState(Bundle).
, Fragment.onCreate(): . Fragment.
onCreate(),
:
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState)

,
View . LayoutInflater ViewGroup
. Bundle , .
CrimeFragment.java onCreateView(),
fragment_crime.xml.
7.8. onCreateView() (CrimeFragment.java)
public class CrimeFragment extends Fragment {
private Crime mCrime;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCrime = new Crime();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
return v;
}
}

onCreateView() ,
LayoutInflater.inflate() . ,
. ,
. false,
.

UI-

155


onCreateView() EditText
. ,
EditText .
7.9. EditText (CrimeFragment.java)
public class CrimeFragment extends Fragment {
private Crime mCrime;
private EditText mTitleField;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
mTitleField = (EditText)v.findViewById(R.id.crime_title);
mTitleField.addTextChangedListener(new TextWatcher() {
public void onTextChanged(
CharSequence c, int start, int before, int count) {
mCrime.setTitle(c.toString());
}
public void beforeTextChanged(
CharSequence c, int start, int count, int after) {
//
}

});

public void afterTextChanged(Editable c) {


//
}

return v;

Fragment.onCreateView() ,
Activity.onCreate(). , View.findViewById(int). Activity.
findViewById(int), ,
, View.findViewById(int) .
Fragment ,
.
, . 7.9 ,
TextWatcher. , :
onTextChanged().
onTextChanged() toString() CharSequence,
. ,
Crime.

156

7. UI- FragmentManager

CrimeFragment . ,
CriminalIntent . ,
.
, CrimeFragment
CrimeActivity.

UI- FragmentManager
Honeycomb Fragment, Activity
: , FragmentManager.
.
FragmentManager :
( ).

. 7.16. FragmentManager

CriminalIntent FragmentManager.
, FragmentManager .
FragmentManager. CrimeActivity.java
onCreate().
7.10. FragmentManager (CrimeActivity.java)
public class CrimeActivity extends FragmentActivity {
/** . */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime);

FragmentManager fm = getSupportFragmentManager();

UI-

157

getSupportFragmentManager(),
FragmentActivity. , Honeycomb,
Activity getFragmentManager().


FragmentManager ,
. (
, .)
7.11. CrimeFragment (CrimeActivity.java)
public class CrimeActivity extends FragmentActivity {
/** . */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

, 7.11, .
add() .
.
7.12. (CrimeActivity.java)
if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();

, , ,
.
. FragmentManager ,
.
FragmentManager.beginTransaction() FragmentTransaction . FragmentTransaction , FragmentTransaction ,

158

7. UI- FragmentManager

FragmentTransaction void, .

, 7.12 : , add, .
add() .
:
CrimeFragment.
: FrameLayout,
activity_crime.xml.
:
FragmentManager, .
FragmentManager.
CrimeFragment FragmentManager, .
7.13.

(CrimeActivity.java)
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

, FragmentManager CrimeFragment
FrameLayout. UI-

FragmentManager.
, 7.11,
.
FragmentManager R.id.fragmentContainer.
, FragmentManager .
? CrimeActivity.onCreate() CrimeActivity
- .
FragmentManager . FragmentManager , , .
, , fragment null.

UI-

159

CrimeFragment ,
.
CrimeActivity CrimeFragment. ,
CriminalIntent. ,
fragment_crime.xml (. 7.17).

. 7.17. CrimeActivity CrimeFragment

,
, . ,
CriminalIntent .

FragmentManager
FragmentManager
.
FragmentManager
. onAttach(Activity), onCreate(Bundle) onCreateView() FragmentManager.
onActivityCreated() onCreate()
-. CrimeFragment CrimeActivity.onCreate(),
.

160

7. UI- FragmentManager

. 7.18. ()

, ,
, ? FragmentManager ,
. ,
, ,
onAttach(Activity), onCreate(Bundle), onCreateView(), onActivityCreated(Bundle),
onStart() onResume().
,
FragmentManager -

.
,
.
: Activity.onCreate(),
onActivityCreated() Activity.onCreate().

: Honeycomb,ICS, Jelly Bean ..

161

Activity.onStart(). ? SDK
Honeycomb onActivityCreated()
FragmentActivity,
. ; onStart()
Activity.onCreate().

. :
, ,
.
; , .
, ,
.
- , ,
, ( ).
YAGNI.
YAGNI You Arent Gonna Need It ( );
, , , . ? YAGNI. YAGNI
.
, . UI-
, .
, ,
, .
, ,
.
, , , : AUF,
Always Use Fragments ( ). , ,
. AUF!

:
Honeycomb,ICS, Jelly Bean ..
,
SDK API 11.
SDK,

162

7. UI- FragmentManager

.
.
,
:
SDK API
11 .
Activity (android.app.Activity) FragmentActivity. API 11
.
android.app.Fragment android.support.v4.app.Fragment.
FragmentManager, getFragmentManager() getSupportFragmentManager().

,
CriminalIntent.

Crime
Crime.java . Date
, mSolved , .
8.1. Crime (Crime.java)
public class Crime {
private UUID mId;
private String mTitle;
private Date mDate;
private boolean mSolved;

public Crime() {
mId = UUID.randomUUID();
mDate = new Date();
}
...

Date Date
mDate .
get- set- (SourceGenerate
Getters and Setters...).
8.2. get- set- (Crime.java)
public class Crime {
...
public void setTitle(String title) {
mTitle = title;
}

164

8.

8.2 ()

public Date getDate() {


return mDate;
}
public void setDate(Date date) {
mDate = date;
}
public boolean isSolved() {
return mSolved;
}
public void setSolved(boolean solved) {
mSolved = solved;
}

fragment_crime.xml CrimeFragment.java.


CrimeFragment .

. 8.1. CriminalIntent, 2

, CrimeFragment : TextView, Button CheckBox.


fragment_crime.xml , 8.3. ,
.

Crime

165

8.3. (fragment_crime.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_title_label"
style="?android:listSeparatorTextViewStyle"
/>
<EditText android:id="@+id/crime_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/crime_title_hint"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_details_label"
style="?android:listSeparatorTextViewStyle"
/>
<Button android:id="@+id/crime_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/>
<CheckBox android:id="@+id/crime_solved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="@string/crime_solved_label"
/>
</LinearLayout>

: Button android:text.
, Crime,
.
Button? .
.
12 ,
DatePicker, .
, ,
style margin. CriminalIntent
.
res/values/strings.xml .

166

8.

8.4. (strings.xml)
<resources>
<string name="app_name">CriminalIntent</string>
<string name="title_activity_crime">CrimeActivity</string>
<string name="crime_title_hint">Enter a title for this crime.</string>
<string name="crime_title_label">Title</string>
<string name="crime_details_label">Details</string>
<string name="crime_solved_label">Solved?</string>
</resources>


CheckBox , . CheckBox mSolved Crime.
Button mDate.
CrimeFragment.java .
8.5. (CrimeFragment.java)
public class CrimeFragment extends Fragment {
private Crime mCrime;
private EditText mTitleField;
private Button mDateButton;
private CheckBox mSolvedCheckBox;
@Override
public void onCreate(Bundle savedInstanceState) {
...

, DatePicker CheckBox.
onCreateView() ,
.
8.6. Button (CrimeFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
...
mTitleField.addTextChangedListener(new TextWatcher() {
...
});
mDateButton = (Button)v.findViewById(R.id.crime_date);
mDateButton.setText(mCrime.getDate().toString());
mDateButton.setEnabled(false);
}

return v;

167

, .
, . 12
.
CheckBox: ,
mSolved Crime.
8.7. CheckBox (CrimeFragment.java)
...
mDateButton = (Button)v.findViewById(R.id.crime_date);
mDateButton.setText(mCrime.getDate().toString());
mDateButton.setEnabled(false);

mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved);
mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)

});
}

//
mCrime.setSolved(isChecked);

return v;

OnCheckedChangeListener Eclipse , CompoundButton, ,


RadioGroup. CompoundButton; CheckBox
CompoundButton.
, onCheckedChanged()
@Override, 8.7. . , ,
@Override .
CriminalIntent. CheckBox
, .

XML
, fragment_crime.xml,
.

,
(style) XML, ,
. ,
, .

168

8.

<style name="BigTextStyle">
<item name="android:textSize">20sp</item>
<item name="android:layout_margin">3dp</item>
</style>

( 24). res/values/,
: @style/my_own_style.
TextView fragment_crime.xml;
style, , Android. TextView ,
. (theme) .
,
.
Android ,
. CriminalIntent
Holo Light with Dark Action Bar, .

(theme attribute reference). fragment_crime.xml, ?android:listSeparatorTextViewStyle.
Android:
listSeparatorTextViewStyle.
. .
Android listSeparatorTextViewStyle,
.
, TextView .
, , 24.

, dp sp
fragment_crime.xml margin dp.
; , .
(
, , ).
, . .
,
.
Android
, drawable-ldpi, drawable-mdpi drawable-hdpi.
, , ?
?

169

. 8.2. TextView (: MDPI;


: HDPI; : HDPI )

Android ,
; ,
. Android , :
dp ( dip) density-independent pixel (, ); .
, ,
. dp . dp
1/160 .
.
sp scale-independent pixel (, ). , ,
. sp
.
pt, mm, in ( dp),
(1/72 ), .
:
.

dp sp. Android .

Android
8.3 16dp .
Android,

170

8.

48dp. http://developer.
android.com/design/index.html.
Android , .
Android SDK,
.
,
, ActionBarSherlock, 16.


, ,
layout_ (android:layout_marginLeft),
(android:text).
, layout_,
. .
layout_,
. , ,
,
.
(, LinearLayout) ,
, , .
LinearLayout fragment_crime.xml,
android:layout_width android:layout_height.
LinearLayout .
LinearLayout FrameLayout
CrimeActivity.


fragment_crime.xml margin padding. ,
. , , ,
, . margin
; .
, .
, . android:padding ,
. ,
. Button ,
.

171

8.8.
<Button android:id="@+id/crime_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:padding="80dp"
/>

. 8.3.

, .


, XML.
CrimeFragment.
, LinearLayout .

.
CriminalIntent ,
CrimeFragment .

172

8.

. 8.4. CrimeFragment

; ,
.
res/layout-land ( res/
Package Explorer NewFolder), fragment_crime.xml res/layout-land/.
, res/
layout/fragment_crime.xml, . res/layout-land/
fragment_crime.xml Graphical Layout.
. .
, .

. 8.5.

173

.
.
.
, .
? .8.6.

. 8.6. CrimeFragment

:
LinearLayout .
LinearLayout.
Button CheckBox LinearLayout.
Button CheckBox.


,
. Layouts . LinearLayout
(Horizontal) .
LinearLayout LinearLayout, LinearLayout.
. Layout
, ,
.

174

8.

. 8.7. LinearLayout fragment_crime.xml


LinearLayout ,
. Layout Parameters, Margins.
, LinearLayout .
Left
16dp. (Right)
.
(
.
, ,
. , , XML
margin EditText
LinearLayout).
. 8.8.

XML fragment_crime.
xml
. LinearLayout .

175


Button CheckBox
LinearLayout. , Button LinearLayout.
,
Button LinearLayout .
CheckBox.
,
.
, :
.

CheckBox Button. LinearLayout (match_par. 8.9. Button
ent) (Button)
CheckBox
, Check LinearLayout
Box (. 8.10).

. 8.10. Button, , CheckBox

176

8.

LinearLayout,
.


. Width wrap_content.
16dp . ,
LinearLayout,
.
Weight Layout Parameters
1. android:layout_weight .8.6.
CheckBox
: Width wrap_content, Weight 1,
.
, .
XML, . 8.11.
. 8.9
wrap_content
XML.
8.9.
 XML ,
(layout-land/fragment_crime.xml)
...
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_details_label"
style="?android:listSeparatorTextViewStyle"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" >
<Button
android:id="@+id/crime_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<CheckBox
android:id="@+id/crime_solved"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/crime_solved_label" />
</LinearLayout>
</LinearLayout>

177

android:layout_weight
android:layout_weight LinearLayout,
. , ,
. LinearLayout
layout_width layout_weight.
LinearLayout .
LinearLayout layout_width ( layout_height ). layout_width Button, CheckBox
wrap_content, ,
(. 8.12).
( , , .
, LinearLayout,
.)

. 8.12. 1: layout_width

LinearLayout
layout_weight (. 8.13).

. 8.13. 2:
layout_weight

Button CheckBox layout_weight, 50/50.


Button 2, 2/3 , CheckBox 1/3 (.8.14).

. 8.14.
layout_weight 2:1


. .

178

8.

fragment_crime.xml . -

, 1.0 100;
0.66 66 .
, LinearLayout 50% ? ,
layout_width 0dp wrap_content.
LinearLayout layout_weight
(. 8.15).

. 8.15. layout_width="0dp" layout_weight


, Android
ADT. XML.
XML (
).

. , ,
.8.6. ,
XML, .


, CriminalIntent, , .
,
.
,

null:

Button landscapeOnlyButton = (Button)v.findViewById(R.id.landscapeOnlyButton);


if (landscapeOnlyButton != null) {
//

, , , android:id ,
.

179

.
Date (timestamp),
. toString() Date , . ,
,
(, Oct 12, 2012).
android.text.format.DateFormat. Android.
DateFormat
.
, (Tuesday, Oct 12, 2012).


ListFragment

CriminalIntent Crime. CriminalIntent,


.
, .

. 9.1.

CriminalIntent

181

. 9.2 CriminalIntent .

. 9.2. CriminalIntent

CrimeLab,
Crime.
CriminalIntent
: CrimeListActivity CrimeListFragment.
CrimeListFragment ListFragment Fragment,
. CrimeLab .
( CrimeActivity CrimeFragment . 9.2?
, . 10 CriminalIntent.)
. 9.2 , CrimeListActivity
CrimeListFragment. FrameLayout,
. ListView. ListFragment ListView
.

CriminalIntent
CriminalIntent
Crime Crime.
ArrayList<E> Java,
. , .

182

9. ListFragment


-
(singleton) . .
, , ,
, .
,
get(), . ,
get() . , get() .
com.bignerdranch.android.criminalintent NewClass. CrimeLab Finish.
CrimeLab.java CrimeLab get(Context).
9.1. (CrimeLab.java)
public class CrimeLab {
private static CrimeLab sCrimeLab;
private Context mAppContext;
private CrimeLab(Context appContext) {
mAppContext = appContext;
}

public static CrimeLab get(Context c) {


if (sCrimeLab == null) {
sCrimeLab = new CrimeLab(c.getApplicationContext());
}
return sCrimeLab;
}

s sCrimeLab. Android, , sCrimeLab


.
CrimeLab Context. Android
; Context
, , ..
: get(Context) Context .
Context Activity Context ,
Service. , Context , CrimeLab
.
,
Context , getApplicationContext()

183

Context . .
, .
CrimeLab Crime . CrimeLab ArrayList Crime.
: getCrimes() , getCrime(UUID)
Crime ( 9.2).
9.2. ArrayList Crime (CrimeLab.java)
public class CrimeLab {
private ArrayList<Crime> mCrimes;
private static CrimeLab sCrimeLab;
private Context mAppContext;
private CrimeLab(Context appContext) {
mAppContext = appContext;
mCrimes = new ArrayList<Crime>();
}
public static CrimeLab get(Context c) {
...
}
public ArrayList<Crime> getCrimes() {
return mCrimes;
}

public Crime getCrime(UUID id) {


for (Crime c : mCrimes) {
if (c.getId().equals(id))
return c;
}
return null;
}

ArrayList Crime, , .
100 Crime ( 9.3).
9.3. (CrimeLab.java)
private CrimeLab(Context appContext) {
mAppContext = appContext;
mCrimes = new ArrayList<Crime>();
for (int i = 0; i < 100; i++) {
Crime c = new Crime();
c.setTitle("Crime #" + i);
c.setSolved(i % 2 == 0); //
mCrimes.add(c);
}
}

184

9. ListFragment

100
.

ListFragment
CrimeListFragment. Browse,
. ListFragment android.support.v4.app,
Finish, CrimeListFragment.
ListFragment Honeycomb, . , ,
android.support.v4.app.ListFragment.
CrimeListFragment.java onCreate(Bundle),
, .
9.4.
 onCreate(Bundle)
(CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.crimes_title);
}
}

getActivity(). Fragment
-
. Activity.setTitle(int), (
) .
onCreateView() CrimeListFragment. ListFragment , ListView; .
CrimeListFragment.onCreateView()
.
strings.xml .
9.5. (strings.xml)
...
<string name="crime_solved_label">Solved?</string>
<string name="crimes_title">Crimes</string>
</resources

CrimeListFragment ,
CrimeLab. CrimeListFragment.onCreate()
CrimeLab .

185

9.6. CrimeListFragment (CrimeListFragment.java)


public class CrimeListFragment extends ListFragment {
private ArrayList<Crime> mCrimes;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.crimes_title);
mCrimes = CrimeLab.get(getActivity()).getCrimes();
}


CrimeListActivity,
CrimeListFragment.
CrimeListActivity.


CrimeListActivity ,
activity_crime.xml (9.7). FrameLayout
,
.
9.7. activity_crime.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

activity_crime.xml , ,
. activity_fragment.xml, .
activity_crime.xml ( ). Package
Explorer res/layout/activity_crime.xml. (
activity_crime.xml, fragment_crime.xml.)
RefactorRename... activity_fragment.xml. .
ADT
. Eclipse CrimeActivity.java,
CrimeActivity, 9.8.
9.8. CrimeActivity (CrimeActivity.java)
public class CrimeActivity extends FragmentActivity {
/** . */

186

9. ListFragment

9.8 ()
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

Activity
CrimeListActivity CrimeActivity. , CrimeActivity (9.8):
. , -
: CrimeFragment FragmentManager.
9.9. CrimeActivity (CrimeActivity.java)
public class CrimeActivity extends FragmentActivity {
/** . */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

, , . ,
.
SingleFragmentActivity CriminalIntent.
FragmentActivity abstract,
SingleFragmentActivity (. 9.3).

187

. 9.3. SingleFragmentActivity

Finish SingleFragmentActivity.java. , CrimeActivity.


9.10. (SingleFragmentActivity.java)
public abstract class SingleFragmentActivity extends FragmentActivity {
protected abstract Fragment createFragment();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}
}

188

9. ListFragment

activity_fragment.xml.
FragmentManager ,
, .
9.10 CrimeActivity createFragment(), .
SingleFragmentActivity ,
, .


CrimeListActivity.
CrimeListActivity. SingleFragmentActivity .
Browse, SingleFragmentActivity, Eclipse . Finish.

. 9.4. SingleFragmentActivity

Eclipse CrimeListActivity.java,
createFragment(). CrimeListFragment.
9.11. CrimeListActivity (CrimeListActivity.java)
public class CrimeListActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
return new CrimeListFragment();
}
}

189

, CrimeActivity . CrimeActivity.java. CrimeActivity


SingleFragmentActivity, 9.12.
9.12. CrimeListActivity (CrimeListActivity.java)
public class CrimeActivity extends FragmentActivity SingleFragmentActivity {
/** . */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

if (fragment == null) {
fragment = new CrimeFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

@Override
protected Fragment createFragment() {
return new CrimeFragment();
}

SingleFragmentActivity
. ,
.

CrimeListActivity
, CrimeListActivity , .
, ,
CriminalIntent; ,
CrimeListActivity .
CrimeListActivity CrimeActivity CrimeListActivity,
9.13.
9.13. CrimeListActivity (AndroidManifest.xml)
...
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity android:name=".CrimeListActivity">

190

9. ListFragment

9.13 ()
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".CrimeActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

CrimeListActivity . CriminalIntent; FrameLayout CrimeListActivity,


CrimeListFragment.

. 9.5. CrimeListActivity

ListView , ListFragment
. CrimeListFragment Crime, , ListView.
.

ListFragment, ListView ArrayAdapter

191

ListFragment, ListView ArrayAdapter


ListView CrimeListFragment
.
Crime.
ListView ViewGroup,
View ListView. View .
:
Crime,
View TextView.

TextView

. 9.6. ListView TextView

.9.6 11 TextView 12-.


, ListView
TextView Crime #12, Crime #13 ..
View? , ListView
? . View
, . ,

.

192

9. ListFragment

, . ListView ,
.
ListView ? (adapter) , ListView , ListView.
:
;
;
ListView.
, Adapter.
ArrayAdapter<T> ,
( ArrayList) T.
.9.7 ArrayAdapter<T>.
.

. 9.7. ArrayAdapter<T>

ListView ,
. . 9.8
ListView .
ListView ,
getCount() ( ,
).

ArrayAdapter<T>

193

. 9.8. ListView-Adapter

ListView getView(int, View, ViewGroup) .


, ListView.
getView() ListView.
,
.
getView() , .

ArrayAdapter<T>
ArrayAdapter<T> CrimeListFragment :
public ArrayAdapter(Context context, int textViewResourceId, T[] objects)

Context,
.
, ArrayAdapter .
.
CrimeListFragment.java ArrayAdapter<T>
ListView CrimeListFragment (9.14).

194

9. ListFragment

9.14. ArrayAdapter (CrimeListFragment.java)


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.crimes_title);
mCrimes = CrimeLab.get(getActivity()).getCrimes();

ArrayAdapter<Crime> adapter =
new ArrayAdapter<Crime>(getActivity(),
android.R.layout.simple_list_item_1,
mCrimes);
setListAdapter(adapter);

setListAdapter(ListAdapter) ListFragment,
ListView,
CrimeListFragment.

, (android.R.layout.simple_list_item_1),
,
Android SDK. TextView.
9.15. android.R.layout.simple_list_item_1
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/listItemFirstLineStyle"
android:paddingTop="2dip"
android:paddingBottom="3dip"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

,
TextView.
, ListFragment, . ListView ,
.
CriminalIntent. . , , ,
.
ArrayAdapter<T>.getView() toString().
, Crime, toString()
, TextView.
Crime toString() ,
java.lang.Object,
.
Crime,
Crime.java toString() ,
.

ArrayAdapter<T>

195

9.16. Crime.toString() (Crime.java)


...
public Crime() {
mId = UUID.randomUUID();
mDate = new Date();
}
@Override
public String toString() {
return mTitle;
}
...

CriminalIntent. .

. 9.9.

. 9.10.

ListView getView()
, .


,
ListFragment:
public void onListItemClick(ListView l, View v, int position, long id)

196

9. ListFragment

: ,
, onListItemClick().
CrimeListFragment.java onListItemClick().
Crime , ,
Crime.
9.17.
 onListItemClick()
Crime (CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
private static final String TAG = "CrimeListFragment";
...
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Crime c = (Crime)(getListAdapter()).getItem(position);
Log.d(TAG, c.getTitle() + " was clicked");
}

getListAdapter() ListFragment, ,
ListFragment. getItem(int) position
onListItemClick() Crime.

CriminalIntent. ,
Crime.


Crime Crime.toString().
, , .
:
XML , ;
ArrayAdapter<T>, , , .


CriminalIntent
, (. 9.11).
TextView CheckBox.
, . Package Explorer res/
layout NewOther...Android XML File.
Layout, list_item_crime.xml,
RelativeLayout Finish.

RelativeLayout -

197


. , CheckBox
RelativeLayout. TextView
CheckBox.
. 9.11. .9.12
. CheckBox ,
. , TextView CheckBox
.

. 9.12. (list_item_crime.xml)

198

9. ListFragment

TextView
TextView .
, .
:
+. android:id.
android:text. . , ,
.
, .
,
.


, Crime.
Crime, , Crime.
CrimeListFragment.java ArrayAdapter
CrimeListFragment.
9.18.

(CrimeListFragment.java)
public void onListItemClick(ListView l, View v, int position, long id) {
Crime c = (Crime)(getListAdapter()).getItem(position);
Log.d(TAG, c.getTitle() + " was clicked");
}
private class CrimeAdapter extends ArrayAdapter<Crime> {

public CrimeAdapter(ArrayList<Crime> crimes) {


super(getActivity(), 0, crimes);
}

Crime.
, 0.
Array
Adapter<T>:
public View getView(int position, View convertView, ViewGroup parent)

convertView ,
.

199

,
. , ListView, ,

, .
CrimeAdapter getView().
,
Crime ( 9.19).
9.19. getView() (CrimeListFragment.java)
private class CrimeAdapter extends ArrayAdapter<Crime> {
public CrimeAdapter(ArrayList<Crime> crimes) {
super(getActivity(), 0, crimes);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// ,
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.list_item_crime, null);
}
// Crime
Crime c = getItem(position);
TextView titleTextView =
(TextView)convertView.findViewById(R.id.crime_list_item_titleTextView);
titleTextView.setText(c.getTitle());
TextView dateTextView =
(TextView)convertView.findViewById(R.id.crime_list_item_dateTextView);
dateTextView.setText(c.getDate().toString());
CheckBox solvedCheckBox =
(CheckBox)convertView.findViewById(R.id.crime_list_item_solvedCheckBox);
solvedCheckBox.setChecked(c.isSolved());

return convertView;

getView() , , .
.
, getItem(int)
Adapter Crime .
Crime
Crime. ListView.
CrimeListFragment.
CrimeListFragment.java onCreate() onListItemClick()
CrimeAdapter, 9.20.

200

9. ListFragment

9.20. CrimeAdapter (CrimeListFragment.java)

ArrayAdapter<Crime> adapter = new ArrayAdapter<Crime>(this,


android.R.layout.simple_list_item_1,
mCrimes);
CrimeAdapter adapter = new CrimeAdapter(mCrimes);
setListAdapter(adapter);

public void onListItemClick(ListView l, View v, int position, long id) {


Crime c = (Crime)(getListAdapter()).getItem(position);
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
Log.d(TAG, c.getTitle() + " was clicked");
}

CrimeAdapter
. CrimeAdapter Crime, Crime .
. - ,
CheckBox, .
CheckBox .
CheckBox
onListItemClick().

. 9.13.

201

ListView , , (, CheckBox
Button), . ,
, .
CheckBox
, :
list_item_crime.xml CheckBox.
9.21. CheckBox (list_item_crime.xml)
...
<CheckBox android:id="@+id/crime_list_item_solvedCheckBox"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:layout_alignParentRight="true"
android:enabled="false"
android:focusable="false"
android:padding="4dp" />
...

.
, CrimeAdapter .
, ,
list_item_crime.xml .

10


CriminalIntent. ,
CrimeActivity,
CrimeFragment
Crime.

. 10.1. CrimeActivity CrimeListActivity

GeoQuiz (QuizActivity) (CheatActivity). CriminalIntent CrimeActivity


CrimeListFragment.


, . Fragment.startActivity(Intent),
Activity .
onListItemClick() CrimeListFragment
, CrimeActivity. (
Crime;
.)

203

10.1. CrimeActivity (CrimeListFragment.java)


public void onListItemClick(ListView l, View v, int position, long id) {
// Crime
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
Log.d(TAG, c.getTitle() + " was clicked");

// CrimeActivity
Intent i = new Intent(getActivity(), CrimeActivity.class);
startActivity(i);

CrimeListFragment CrimeActivity. CrimeListFragment getActivity() - Context, Intent.


CriminalIntent. ; CrimeActivity, CrimeFragment.

. 10.2. CrimeFragment

CrimeFragment Crime,
, Crime .


CrimeFragment, Crime ,
mCrimeId (extra) Intent. onListItemClick()

204

10.

mCrimeId Crime , CrimeActivity.


Eclipse CrimeFragment.EXTRA_
CRIME_ID . , .
10.2. CrimeActivity (CrimeListFragment.java)
public void onListItemClick(ListView l, View v, int position, long id) {
// Crime
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);

// CrimeActivity
Intent i = new Intent(getActivity(), CrimeActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
startActivity(i);

putExtra(),
(mCrimeId).
putExtra(String, Serializable), UUID Serializable.


mCrimeId , CrimeActivity, CrimeFragment.
, : ,
. ,
.
CrimeFragment getActivity() CrimeActivity. CrimeFragment
, onCreate(Bundle)
CrimeActivity Crime.
10.3. Crime (CrimeFragment.java)
public class CrimeFragment extends Fragment {
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
private Crime mCrime;
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UUID crimeId = (UUID)getActivity().getIntent()
.getSerializableExtra(EXTRA_CRIME_ID);
}

mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);

205

getActivity(), 10.3
. getIntent() Intent , CrimeActivity .
getSerializableExtra(String) Intent, UUID .
Crime
CrimeLab. CrimeLab.get() Context,
CrimeFragment CrimeActivity.

CrimeFragment Crime
, CrimeFragment Crime,
Crime. onCreateView(), (
).

. 10.3. ,
10.4. (CrimeFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
mTitleField = (EditText)v.findViewById(R.id.crime_title);

206

10.

10.4 ()
mTitleField.setText(mCrime.getTitle());
mTitleField.addTextChangedListener(new TextWatcher() {
...
});
...
mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved);
mSolvedCheckBox.setChecked(mCrime.isSolved());
mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
...
});
...
}

return v;

CriminalIntent. Crime #4 ,
CrimeFragment .


, -,
. , . CrimeFragment
, , ,
Intent, EXTRA_CRIME_ID.
, CrimeFragment , ,
CrimeFragment .
, mCrimeId , CrimeFragment ( CrimeActivity). CrimeFragment ,
. ,
, (arguments bundle).


Bundle.
-, ,
Activity. (argument).
, Bundle,
put- Bundle (
Intent) .
Bundle args = new Bundle();
args.putSerializable(EXTRA_MY_OBJECT, myObject);
args.putInt(EXTRA_MY_INT, myInt);
args.putCharSequence(EXTRA_MY_STRING, myString);

207


, Fragment.
setArguments(Bundle).
, .
Android Fragment
newInstance(). , .
- ,
newInstance().
newInstance() ,
.
CrimeFragment newInstance(UUID), UUID,
, ,
.
10.5. newInstance(UUID) (CrimeFragment.java)
public static CrimeFragment newInstance(UUID crimeId) {
Bundle args = new Bundle();
args.putSerializable(EXTRA_CRIME_ID, crimeId);
CrimeFragment fragment = new CrimeFragment();
fragment.setArguments(args);
return fragment;
}

CrimeActivity CrimeFragment.newInstance(UUID)
, CrimeFragment.
UUID, . CrimeActivity,
createFragment() CrimeActivity CrimeFragment.newInstance(UUID).
10.6. newInstance(UUID) (CrimeActivity.java)
@Override
protected Fragment createFragment() {
return new CrimeFragment();
UUID crimeId = (UUID)getIntent()
.getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
}

return CrimeFragment.newInstance(crimeId);

, . CrimeActivity CrimeFragment , ,
newInstance(UUID). ; -
, ,
(
).

208

10.


,
getArguments() Fragment, get- Bundle .
CrimeFragment.onCreate()
UUID .
10.7. (CrimeFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UUID crimeId = (UUID)getActivity().getIntent()
.getSerializableExtra(EXTRA_CRIME_ID);
UUID crimeId = (UUID)getArguments().getSerializable(EXTRA_CRIME_ID);
}

mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);

CriminalIntent. ,
CrimeFragment .
, ,
CriminalIntent.


, .
CriminalIntent,
. ,
.
, ( ),
. ActivityManager,
.
CrimeListFragment CrimeActivity,
. CrimeActivity,
, .
Back ,
CrimeActivity . CrimeListActivity .
CrimeListActivity ,
onResume() . CrimeListActivity
FragmentManager onResume() ,

209

. CrimeListFragment.

Back

. 10.4. CriminalIntent

CrimeListFragment onResume() .
10.8. onResume() (CrimeListFragment.java)
@Override
public void onResume() {
super.onResume();
((CrimeAdapter)getListAdapter()).notifyDataSetChanged();
}


onResume(), onStart()? , .
, .
, onStart(),
. , ,
onResume().
CriminalIntent.
. ,
.
CriminalIntent .
.

210

10.

. 10.5. CriminalIntent

, . ? ,
GeoQuiz. startActivityForResult() Activity
Fragment.startActivityForResult(). Activity.onActivityResult() Fragment.onActivityResult().
public class CrimeListFragment extends ListFragment {
private static final int REQUEST_CRIME = 1;
...
public void onListItemClick(ListView l, View v, int position, long id) {
// Crime
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
Log.d(TAG, c.getTitle() + " was clicked");
// CrimeActivity
Intent i = new Intent(getActivity(), CrimeActivity.class);
startActivityForResult(i, REQUEST_CRIME);
}

211

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CRIME) {
//
}
}

Fragment.startActivityForResult(Intent,int)
Activity.
-.
.
,
. , Fragment
startActivityForResult() onActivityResult(), setResult().
- ;
:
public class CrimeFragment extends Fragment {
...

public void returnResult() {


getActivity().setResult(Activity.RESULT_OK, null);
}


CriminalIntent 20.

11

ViewPager

, CrimeFragment. ViewPager.
ViewPager
, .

. 11.1.

. 11.2 CriminalIntent. CrimePagerActivity CrimeActivity.


ViewPager.
, , .
CriminalIntent . ,
CrimeFragment
CrimeFragment, 10.

CrimePagerActivity

213

. 11.2. CrimePagerActivity

:
CrimePagerActivity;
, ViewPager;
ViewPager CrimePagerActivity;
CrimeListFragment.onListItemClick() ,
CrimePagerActivity CrimeActivity.

CrimePagerActivity
CrimePagerActivity FragmentActivity. ViewPager.
CrimePagerActivity.
FragmentActivity. onCreate(Bundle)
ViewPager .
11.1. ViewPager (CrimePagerActivity.java)
public class CrimePagerActivity extends FragmentActivity {
private ViewPager mViewPager;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

214

11. ViewPager


XML-
. , Android ,
.
.
XML . , .
;
. , XML .
;
ViewPager . FragmentManager , ,
, .
ViewPager ,
.
:
ViewPager;
ViewPager mViewPager;
, ;
ViewPager .


: XML res/values.
Android XML res/values/ids.xml
viewPager.
11.2. (res/values/ids.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item type="id" name="viewPager" />
</resources>

, ViewPager. ViewPager
.
11.3. (CrimePagerActivity.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewPager);
setContentView(mViewPager);
}

215

ViewPager . Fragment
ViewPager ; SDK ViewPager.

ViewPager PagerAdapter
ViewPager - AdapterView ( ListView).
AdapterView , Adapter.
ViewPager PagerAdapter.
ViewPager PagerAdapter AdapterView Adapter. ,
FragmentStatePagerAdapter PagerAdapter,
.
FragmentStatePagerAdapter : getCount() getItem(int). getItem(int)
CrimeFragment,
.
CrimePagerActivity PagerAdapter
ViewPager getCount() getItem(int).
11.4. PagerAdapter (CrimePagerActivity.java)
public class CrimePagerActivity extends FragmentActivity {
private ViewPager mViewPager;
private ArrayList<Crime> mCrimes;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewPager);
setContentView(mViewPager);
mCrimes = CrimeLab.get(this).getCrimes();
FragmentManager fm = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
@Override
public int getCount() {
return mCrimes.size();
}

});

@Override
public Fragment getItem(int pos) {
Crime crime = mCrimes.get(pos);
return CrimeFragment.newInstance(crime.getId());
}

216

11. ViewPager

. CrimeLab ArrayList Crime. FragmentManager .


FragmentStatePagerAdapter. FragmentStatePagerAdapter
FragmentManager. , FragmentStatePagerAdapter ,
ViewPager.
, getItem(int),
. FragmentManager.
( ? ViewPager
.
).
PagerAdapter . getCount() . getItem(int).
Crime ,
CrimeFragment.

CrimePagerActivity
CrimeActivity
CrimePagerActivity.
CrimeListFragment
CrimePagerActivity CrimeActivity.
CrimeListFragment.java onListItemClick() ,
CrimePagerActivity.
11.5. (CrimeListFragment.java)
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Crime
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
// CrimeActivity
Intent i = new Intent(getActivity(), CrimeActivity.class);
// CrimePagerActivity rime
Intent i = new Intent(getActivity(), CrimePagerActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
startActivity(i);
}

CrimePagerActivity , . ,
CrimeActivity.

CrimePagerActivity

217

11.6. CrimePagerActivity (AndroidManifest.xml)


<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
...
<application ...>
...
<activity
android:name=".CrimeActivity"
android:label="@string/app_name" >
</activity>
<activity android:name=".CrimePagerActivity"
android:label="@string/app_name">
</activity>
</application>
</manifest>

, , CrimeActivity.java Package
Explorer.
CriminalIntent. Crime #0, . ,
. :
. ViewPager ,
, , .
setOffscreenPageLimit(int).
ViewPager .
Back . , .
ViewPager PagerAdapter
. , , ViewPager .
CrimePagerActivity.onCreate() ; .
Crime, mId crimeId
, Crime.
11.7. (CrimePagerActivity.java)
public class CrimePagerActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
...
FragmentManager fm = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
...
});

218

11. ViewPager

11.7 ()

UUID crimeId = (UUID)getIntent()


.getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
for (int i = 0; i < mCrimes.size(); i++) {
if (mCrimes.get(i).getId().equals(crimeId)) {
mViewPager.setCurrentItem(i);
break;
}
}

CriminalIntent.
Crime.
:
, ( ), Crime.
ViewPager.OnPageChangeListener.
11.8. OnPageListener (CrimePagerActivity.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewPager);
setContentView(mViewPager);
mCrimes = CrimeLab.get(this).getCrimes();
FragmentManager fm = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
...
});

mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageScrollStateChanged(int state) { }
public void onPageScrolled(int pos, float posOffset, int posOffsetPixels) {

});
...

public void onPageSelected(int pos) {


Crime crime = mCrimes.get(pos);
if (crime.getTitle() != null) {
setTitle(crime.getTitle());
}
}

onPageChangeListener ,
ViewPager.
CrimePagerActivity Crime.
, ,
onPageSelected(). onPageScrolled() ,

CrimePagerActivity

219

, onPageScrollStateChanged() ,
.
CriminalIntent ,
mTitle
Crime. ! ViewPager .

FragmentStatePagerAdapter FragmentPagerAdapter
PagerAdapter, ; FragmentPagerAdapter.
FragmentPagerAdapter , FragmentStatePagerAdapter,
.


Fragment
Item1

Fragment
Item2

Fragment
Item3


Fragment
Item1

Fragment
Item2

Fragment
Item3

. 11.3. FragmentStatePagerAdapter

FragmentStatePagerAdapter
. FragmentManager . FragmentStatePagerAdapter ,
Bundle onSaveInstanceState(Bundle).
,
.
, FragmentPagerAdapter .
, FragmentPagerAdapter
detach(Fragment) remove(Fragment).

220

11. ViewPager

, FragmentManager. , , FragmentPagerAdapter,
.


Fragment
Item1

Fragment
Item2

Fragment
Item3


Fragment
Item1

Fragment
Item2

Fragment
Item3

. 11.4. FragmentPagerAdapter

. , FragmentStatePagerAdapter . CriminalIntent

, ,
. , FragmentStatePagerAdapter.
,
, FragmentPagerAdapter .
. ,
. ViewPager
.
, ,
.

: ViewPager
ViewPager PagerAdapter
. ,
.

: ViewPager

221

, : -,
ViewPager ,
. -, .
PagerAdapter ,
, ViewPager-PagerAdapter
AdapterView-Adapter.
ViewPager, AdapterView? AdapterView Gallery . ?
AdapterView ,
Fragment.
Adapter , View .
, FragmentManager, .
, Gallery Adapter ,
.
ViewPager. Adapter PagerAdapter. Adapter,
.
.
getView(), , PagerAdapter
:
public Object instantiateItem(ViewGroup container, int position)
public void destroyItem(ViewGroup container, int position, Object object)
public abstract boolean isViewFromObject(View view, Object object)

pagerAdapter.instantiateItem(ViewGroup, int)

ViewGroup; destroyItem(ViewGroup, int, Object)
. : instantiateItem(ViewGroup,
int) . PagerAdapter
.
, ViewPager - . , , ViewPager isViewFromObject(View, Object). Object ,
instantiateItem(ViewGroup, int). ,
ViewPager instantiateItem(ViewGroup, 5) A,
isViewFromObject(View, A) true, View
5, false .
ViewPager, PagerAdapter, ,
, . PagerAdapter
instantiateItem(ViewGroup, int)
Object. isViewFromObject(View, Object) :

222

11. ViewPager

@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}

PagerAdapter , ViewPager, . ,
FragmentPagerAdapter FragmentStatePagerAdapter.

12

. .
,
.
CrimeFragment , . 12.1.

. 12.1.

224

12.

.12.1 AlertDialog Dialog. Dialog


.
( AlertDialog DatePickerDialog,
. DatePickerDialog ; AlertDialog ,
.)
AlertDialog . 12.1 DialogFragment
Fragment. , AlertDialog
DialogFragment, Android .
FragmentManager
.
, AlertDialog . , AlertDialog ,
.
CriminalIntent DialogFragment
DatePickerFragment. DatePickerFragment
AlertDialog, DatePicker. DatePickerFragment CrimePagerActivity.
.12.2 .

. 12.2. CrimePagerActivity

DialogFragment

225

:
DatePickerFragment;
AlertDialog;
FragmentManager.
DatePicker
CrimeFragment DatePickerFragment.
, (12.1).
12.1. (values/strings.xml)
<resources>
...
<string name="crime_solved_label">Solved?</string>
<string name="crimes_title">Crimes</string>
<string name="date_picker_title">Date of crime:</string>
</resources>

DialogFragment
DatePickerFragment
DialogFragment : android.support.v4.app.DialogFragment.
DialogFragment :
public Dialog onCreateDialog(Bundle savedInstanceState)

FragmentManager - DialogFragment .
DatePickerFragment.java onCreateDialog(),
AlertDialog OK. ( DatePicker
.)
12.2. DialogFragment (DatePickerFragment.java)
public class DatePickerFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.date_picker_title)
.setPositiveButton(android.R.string.ok, null)
.create();
}
}

AlertDialog.Builder,
AlertDialog.
Context AlertDialog.Builder,
AlertDialog.Builder.

226

12.

AlertDialog.Builder :
public AlertDialog.Builder setTitle(int titleId)
public AlertDialog.Builder setPositiveButton(int textId,
DialogInterface.OnClickListener listener)

setPositiveButton() ,
DialogInterface.OnClickListener. 12.2 Android
OK null .
.
(Positive)
. AlertDialog
: (Negative) (Neutral).
( ). Froyo Gingerbread .
, ).
AlertDialog.Builder.create(),
AlertDialog.
AlertDialog AlertDialog.Builder ; .
.

DialogFragment
, DialogFragment
FragmentManager -.
DialogFragment FragmentManager
:
public void show(FragmentManager manager, String tag)
public void show(FragmentTransaction transaction, String tag)

DialogFragment FragmentManager. ( FragmentManager FragmentTransaction)


FragmentManager,
. FragmentManager.
CrimeFragment DatePickerFragment.
onCreateView() , ,
View.OnClickListener, DatePickerFragment
.
12.3. DialogFragment (CrimeFragment.java)
public class CrimeFragment extends Fragment {
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
private static final String DIALOG_DATE = "date";
...

DialogFragment

227

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
mDateButton = (Button)v.findViewById(R.id.crime_date);
mDateButton.setText(mCrime.getDate().toString());
mDateButton.setEnabled(false);
mDateButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FragmentManager fm = getActivity()
.getSupportFragmentManager();
DatePickerFragment dialog = new DatePickerFragment();
dialog.show(fm, DIALOG_DATE);
}
});
mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved);
...
}
}

return v;

...

CriminalIntent ,
. OK (.12.3).

. 12.3. AlertDialog

228

12.


AlertDialog DatePicker AlertDialog.Builder:
public AlertDialog.Builder setView(View view)

View
(-).
Package Explorer dialog_date.xml
DatePicker. View
(DatePicker), setView().
DatePicker , .12.4.

. 12.4. DatePicker (layout/dialog_date.xml)

DatePickerFragment.onCreateDialog() .
12.4. DatePicker AlertDialog (DatePickerFragment.java)
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View v = getActivity().getLayoutInflater()
.inflate(R.layout.dialog_date, null);

return new AlertDialog.Builder(getActivity())


.setView(v)
.setTitle(R.string.date_picker_title)
.setPositiveButton(android.R.string.ok, null)
.create();

CriminalIntent. ,
DatePicker.
, DatePicker
, ?
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DatePicker dp = new DatePicker(getActivity());

return new AlertDialog.Builder(getActivity())


.setView(dp)
...
.create();

DialogFragment

229

. 12.5. AlertDialog DatePicker

.
, , DatePicker TimePicker.
, .
, .
Crime , .


;
. , CrimeFragment
DatePickerFragment (. . 5.10).
DatePickerFragment ,
newInstance(Date) Date .
CrimeFragment
, Intent
Intent CrimeFragment.onActivityResult().
Fragment.onActivityResult() ,
- Activity.onActivityResult()
. , onActivityResult()
, .

230

12.

. 12.6. CrimeFragment DatePickerFragment

. 12.7.
CrimeFragment DatePickerFragment

DatePickerFragment
DatePickerFragment, DatePickerFragment, DatePickerFragment .
newInstance(), .
DatePickerFragment.java newInstance(Date).
12.5. newInstance(Date) (DatePickerFragment.java)
public class DatePickerFragment extends DialogFragment {
public static final String EXTRA_DATE =
"com.bignerdranch.android.criminalintent.date";
private Date mDate;
public static DatePickerFragment newInstance(Date date) {
Bundle args = new Bundle();
args.putSerializable(EXTRA_DATE, date);

DialogFragment

231

DatePickerFragment fragment = new DatePickerFragment();


fragment.setArguments(args);
}
}

return fragment;

...

CrimeFragment DatePickerFragment
DatePickerFragment.newInstance(Date).
12.6. newInstance() (CrimeFragment.java)
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup parent, Bundle savedInstanceState) {
...
mDateButton = (Button)v.findViewById(R.id.crime_date);
updateDate();
mDateButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FragmentManager fm = getActivity()
.getSupportFragmentManager();
DatePickerFragment dialog = new DatePickerFragment();
DatePickerFragment dialog = DatePickerFragment
.newInstance(mCrime.getDate());
dialog.show(fm, DIALOG_DATE);
}
});
}

return v;

DatePickerFragment DatePicker , Date. DatePicker


, . Date
.
, Calendar
Date .
Calendar.
onCreateDialog() Date
Calendar DatePicker.
12.7. Date DatePicker (DatePickerFragment.java)
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mDate = (Date)getArguments().getSerializable(EXTRA_DATE);
// Calendar ,
Calendar calendar = Calendar.getInstance();

232

12.

12.7 ()
calendar.setTime(mDate);
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
View v = getActivity().getLayoutInflater()
.inflate(R.layout.dialog_date, null);
DatePicker datePicker = (DatePicker)v.findViewById(R.id.dialog_date_datePicker);
datePicker.init(year, month, day, new OnDateChangedListener() {
public void onDateChanged(DatePicker view, int year, int month, int day) {
// , Date
mDate = new GregorianCalendar(year, month, day).getTime();

});
...

//
//
getArguments().putSerializable(EXTRA_DATE, mDate);

DatePicker OnDateChangedListener. DatePicker,


Date . Date
CrimeFragment.
onDateChanged() Date . , mDate .
DatePickerFragment , FragmentManager .
FragmentManager onCreateDialog(),
.
, onSaveInstanceState().
( , ,
: DatePickerFragment?
, 14. , DialogFragment
, - ,
DatePickerFragment .)
CrimeFragment DatePickerFragment,
. CriminalIntent ,
, .

CrimeFragment
CrimeFragment DatePickerFragment, - .

DialogFragment

233

startActivityForResult(), ActivityManager .
, ActivityManager ,
.


CrimeFragment (target fragment) DatePickerFragment.
Fragment:
public void setTargetFragment(Fragment fragment, int requestCode)

, , ,
startActivityForResult().
, .
FragmentManager . ,
getTargetFragment() getTargetRequestCode() , .
CrimeFragment.java ,
CrimeFragment DatePickerFragment.
12.8. (CrimeFragment.java)
public class CrimeFragment extends Fragment {
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
private static final String DIALOG_DATE = "date";
private static final int REQUEST_DATE = 0;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
mDateButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FragmentManager fm = getActivity()
.getSupportFragmentManager();
DatePickerFragment dialog = DatePickerFragment
.newInstance(mCrime.getDate());
dialog.setTargetFragment(CrimeFragment.this, REQUEST_DATE);
dialog.show(fm, DIALOG_DATE);
}
});
}
}

...

return v;

234

12.


, CrimeFragment DatePickerFragment ,
CrimeFragment. Intent .
?
, DatePickerFragment CrimeFragment.
onActivityResult(int, int, Intent).
Activity.onActivityResult() ActivityManager
.
Activity.onActivityResult() ; ActivityManager. , FragmentManager Fragment.onActivityResult() .
, Fragment.onActivityResult()
. :
, , setTargetFragment(),
, ;
;
Intent, .
DatePickerFragment , ,
, CrimeFragment.onActivityResult(). onCreateDialog() null setPositiveButton() DialogInterface.OnClickListener,
.
12.9. (DatePickerFragment.java)
private void sendResult(int resultCode) {
if (getTargetFragment() == null)
return;
Intent i = new Intent();
i.putExtra(EXTRA_DATE, mDate);

getTargetFragment()
.onActivityResult(getTargetRequestCode(), resultCode, i);

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
...
return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.date_picker_title)
.setPositiveButton(android.R.string.ok, null)
.setPositiveButton(
android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {

DialogFragment

235

sendResult(Activity.RESULT_OK);

})
.create();

CrimeFragment onActivityResult(), , Crime .


12.10. (CrimeFragment.java)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;
if (requestCode == REQUEST_DATE) {
Date date = (Date)data
.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date);
mDateButton.setText(mCrime.getDate().toString());
}
}

, , onCreateView().
, updateDate(), onCreateView() onActivityResult().
12.11. updateDate() (CrimeFragment.java)
public class CrimeFragment extends Fragment {
...
public void updateDate() {
mDateButton.setText(mCrime.getDate().toString());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
...
mDateButton = (Button)v.findViewById(R.id.crime_date);
mDateButton.setText(mCrime.getDate().toString());
updateDate();
...

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;
if (requestCode == REQUEST_DATE) {
Date date = (Date)data
.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date);
mDateButton.setText(mCrime.getDate().toString());
updateDate();
}
}

236

12.

.
CriminalIntent ,
. Crime; CrimeFragment. Crime
, .

DialogFragment
onActivityResult()
,
.
.
, , , .
startActivityForResult()
.
onActivityResult(), , .

. 12.8.

DialogFragment

237

, ,
DialogFragment . show() .
onActivityResult().

B (
)

B (
)

. 12.9.

onActivityResult() ,
, . ,
.
, ,
onCreateDialog() DialogFragment.onCreateView().

238

12.

.
TimePickerFragment . TimePicker , CrimeFragment
TimePickerFragment.
,
. , , .
.

13


MediaPlayer

CriminalIntent
. MediaPlayer.

. 13.1. , !

240

13. MediaPlayer

MediaPlayer Android - .

(,
, ) (WAV, MP3, Ogg Vorbis,
MPEG-4, 3GPP ..).
HelloMoon.
Holo Dark.

. 13.2. HelloMoon Holo Dark

Next. ,
. HelloMoonActivity.


HelloMoon ( ) (http://www.bignerdranch.com/solutions/AndroidProgramming.zip).
:
13_Audio/HelloMoon/res/drawable-mdpi/armstrong_on_moon.jpg
13_Audio/HelloMoon/res/raw/one_small_step.wav

241

armstrong_on_moon.jpg
(~160 dpi), Android
. armstrong_on_moon.jpg drawable-mdpi.
res/raw. raw
,
Android.
res/raw ,
. ( res
NewFolder.) one_small_step.wav .
( res/raw/ 13_Audio/HelloMoon/res/raw/apollo_17_stroll.
mpg.
.)
res/values/strings.xml ,
HelloMoon:
13.1. (strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string
<string
<string
<string
<string
<string

name="app_name">HelloMoon</string>
name="hello_world">Hello world!</string>
name="menu_settings">Settings</string>
name="hellomoon_play">Play</string>
name="hellomoon_stop">Stop</string>
name="hellomoon_description">Neil Armstrong stepping
onto the moon</string>

</resources>

( HelloMoon
Play Stop? 15 ,
.)
, ;
HelloMoon.
HelloMoon HelloMoonActivity, HelloMoonFragment.
AudioPlayer , MediaPlayer.
, MediaPlayer ; HelloMoonFragment
MediaPlayer .
.
AudioPlayer,
.
:
;
;
.

242

13. MediaPlayer

. 13.3. HelloMoon

HelloMoonFragment
XML- Android fragment_hello_moon.xml. TableLayout.
fragment_hello_moon.xml .13.4.

. 13.4. HelloMoon

HelloMoonFragment

243

TableLayout , LinearLayout. LinearLayout TableRow.


TableLayout TableRow -

.
ImageView TableRow? TableRow
. , ImageView .
TableRow, TableLayout
.
TableLayout, , , Button
, .
, TableRow .
, TableLayout.
, LinearLayout
.
. ?
HelloMoon Holo Dark.

. , . ( ,
, , .)


application :
...
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
...
</application>
</manifest>

android:theme ; , .
, @style/
AppTheme. Package Explorer res/values/styles.xml.
style AppBaseTheme parent android:Theme.
13.2. (res/values/styles.xml)
<style name="AppBaseTheme" parent="android:Theme.Light">
<style name="AppBaseTheme" parent="android:Theme">

res values ;
styles.xml. API.
res/values-11/styles.xml API 1113,
res/values-14/styles.xml API 14 .

244

13. MediaPlayer

res/values-11/styles.xml parent AppBaseTheme android:Theme.Holo.


API 11 , res/values-14/ . HelloMoon.
. ,
.

HelloMoonFragment
HelloMoonFragment
android.support.v4.app.Fragment.
HelloMoonFragment.onCreateView(),
.
13.3. HelloMoonFragment (HelloMoonFragment.java)
public class HelloMoonFragment extends Fragment {
private Button mPlayButton;
private Button mStopButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_hello_moon, parent, false);
mPlayButton = (Button)v.findViewById(R.id.hellomoon_playButton);
mStopButton = (Button)v.findViewById(R.id.hellomoon_stopButton);

return v;


CriminalIntent
. HelloMoon
,
fragment.
activity_hello_moon.xml fragment, 13.4.
13.4. (activity_hello_moon.xml)
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/helloMoonFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.bignerdranch.android.hellomoon.HelloMoonFragment">
</fragment>

HelloMoonFragment

245

,
HelloMoonActivity. HelloMoonActivity
FragmentActivity:
13.5. HelloMoonActivity FragmentActivity (HelloMoonActivity.java)
public class HelloMoonActivity extends Activity FragmentActivity {
/** . */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello_moon);
}
}

HelloMoon. HelloMoonActivity
HelloMoonFragment.
, .
, .
: .setContentView()
activity_hello_moon.xml, fragment.
FragmentManager HelloMoonFragment .
HelloMoonFragment onCreateView() ,
, , fragment.

(/

(/

)

(
)

( setContentView()
)

. 13.5.

?
, FragmentManager:

246

13. MediaPlayer

, , , .
, , . ,
.
.
FragmentManager. .

.
, HelloMoonFragment, .


com.bignerdranch.android.hellomoon
AudioPlayer. java.lang.Object.
AudioPlayer.java MediaPlayer
.
13.6.

MediaPlayer (AudioPlayer.java)
public class AudioPlayer {
private MediaPlayer mPlayer;
public void stop() {
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}

public void play(Context c) {


mPlayer = MediaPlayer.create(c, R.raw.one_small_step);
mPlayer.start();
}

play(Context) MediaPlayer.create(Context, int).


Context MediaPlayer
. ( MediaPlayer.create(),
, URI.)
AudioPlayer.stop() MediaPlayer , mPlayer
null. MediaPlayer.release() .

247

,
. MediaPlayer
release() . . MediaPlayer stop()
MediaPlayer ,
.
release(), .
: MediaPlayer
, - .
play(Context).
stop() stop()
.
13.7. (AudioPlayer.java)
...
public void play(Context c) {
stop();
mPlayer = MediaPlayer.create(c, R.raw.one_small_step);
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
stop();
}
});

mPlayer.start();

stop() play(Context) MediaPlayer, Play .


stop()
MediaPlayer, .
AudioPlayer.stop() HelloMoonFragment,
MediaPlayer . HelloMoonFragment onDestroy()
AudioPlayer.stop().
13.8. onDestroy() (HelloMoonFragment.java)
...

@Override
public void onDestroy() {
super.onDestroy();
mPlayer.stop();
}

248

13. MediaPlayer

MediaPlayer HelloMoonFragment, MediaPlayer


(thread). HelloMoon.
26.


HelloMoonFragment.java. :
AudioPlayer .
13.9. Play (HelloMoonFragment.java)
public class HelloMoonFragment extends Fragment {
private AudioPlayer mPlayer = new AudioPlayer();
private Button mPlayButton;
private Button mStopButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_hello_moon, parent, false);
mPlayButton = (Button)v.findViewById(R.id.hellomoon_playButton);
mPlayButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mPlayer.play(getActivity());
}
});

mStopButton = (Button)v.findViewById(R.id.hellomoon_stopButton);
mStopButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mPlayer.stop();
}
});
return v;

HelloMoon, Play
.
MediaPlayer.
MediaPlayer Android MediaPlayer
Developer Guide http://developer.android.com/guide/topics/media/mediaplayer.html.

.
.
MediaPlayer.

. HelloMoon

249

:
, , . MediaPlayer, .
.
( ) Android
SurfaceView. , Surface, SurfaceView. Surface,
SurfaceHolder SurfaceView. 19.
, SurfaceHolder MediaPlayer MediaPlayer.setDisplay(SurfaceHolder).
VideoView .
VideoView MediaPlayer, SurfaceView.
MediaController,
.
VideoView ,

Uri. Uri, Android,
:
Uri resourceUri = Uri.parse("android.resource://" +
"com.bignerdranch.android.hellomoon/raw/apollo_17_stroll");

URI android.resource, , . VideoView.

. HelloMoon
HelloMoon ,
. apollo_17_stroll.mpg ,
res/raw.
.

14

HelloMoon .
, . .
, HelloMoonActivity . , FragmentManager HelloMoonFragment. FragmentManager
: onPause(), onStop()
onDestroy(). HelloMoonFragment.onDestroy() MediaPlayer, .
3 GeoQuiz Activity.onSaveInstanceState(Bundle) . , . Fragment
onSaveInstanceState(Bundle), .
MediaPlayer
.


, , MediaPlayer .
HelloMoonFragment.onCreate() .

251

14.1. setRetainInstance(true) (HelloMoonFragment.java)


...
private Button mPlayButton;
private Button mStopButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...

retainInstance false. ,
,
-. setRetainInstance(true) ,
,
.
, (
mPlayButton, mPlayer mStopButton) . , .
HelloMoon. ,
, .


, .
,
.
FragmentManager .
,
:
. ,
, .
FragmentManager retainInstance .
false ( ), FragmentManager
. FragmentManager .

252

14.

. 14.1. UI-

, retainInstance true, , .
FragmentManager
.

. 14.2. UI-

, (detached) . ,
-.

: ?

253

. 14.3.

:
setRetainInstance(true);
- ( ).
,
.

:
?
: , ? ! .
, . ,
.

254

14.

? , Android
. , ,
Android ,
.
, .
- , ,
.

onSaveInstanceState(Bundle)
onSaveInstanceState(Bundle) ,
. , , onSaveInstanceState() .
CriminalIntent. CrimeFragment
,
, View
. onSaveInstanceState()
.
Fragment.onSaveInstanceState() .
,
.
; ,
Serializable .
, .
, , .
, GeoQuiz.
, .
,
. ,
, .
GeoQuiz , GeoQuiz QuizFragment,
QuizActivity. Fragment.onSaveInstanceState()
QuizFragment ?
.14.4 ,
: ( ),
.

onSaveInstanceState(Bundle)

255

( )

. 14.4.

;
. .
QuizFragment,
. GeoQuiz ,
QuizFragment . , setRetainInstance(true) QuizFragment.
onCreate().
14.2. QuizFragment
public class QuizFragment extends Fragment {
...
private int mCurrentIndex = 0;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}

...

256

14.


.
.14.4, QuizFragment , ,
,
.
, . GeoQuiz 100 ? ()
. . onSaveInstanceState(). ,
,
.
, ,
,
, onSaveInstanceState(Bundle)
.

Honeycomb
Ice Cream Sandwich. ,
.
,
, .
? onRetainNonConfigurationInstance().
onRetainNonConfigurationInstance(),
. , getLa
stNonConfigurationInstance().
? , .
. ,
.
(, MediaPlayer),
? , onRetainNonConfigurationInstance() ,
onDestroy().
onRetainNonConfigurationInstance() ,

.
, , .

15

(localization) . HelloMoon ,
. , Android
.

. 15.1. iHola, Luna!

258

15.


. Android ,
:
.
Android .
ISO 639-1.
-es. HelloMoon res: res/raw-es/ res/values-es/.
(http://www.
bignerdranch.com/solutions/AndroidProgramming.zip). :
15_Localization/HelloMoon/res/raw-es/one_small_step.wav
15_Localization/HelloMoon/res/values-es/strings.xml
.
, ,
. ,
( Settings ;
Android Language and input, Language and Keyboard
- .
, (Espaol).
(Espaa Estados Unidos) ,
-es, .
( +
. , -es-rES, r , ES
ISO 3166-1-alpha-2. :
,
Android: ,
r ( ).
HelloMoon, Tocar , .


-en. , raw values raw-en/ values-en/.
. .
.
: Android , , .

259

, strings.xml values-en/ values-es/,


values/, HelloMoon
, , .
.



. drawable
-mdpi, -hdpi -xhdpi.
drawable Android

.
, Android

.
http://developer.android.com/guide/practices/screens_support.
html, ,
res/drawable/ .



: (, values-es/), (, layout-land/), (, drawable-mdpi/) API
(, values-v11/).
.15.1 , Android
.
15.1.
(MCC) (MNC)







260

15.

15.1 ()


(dpi)





API

http://developer.android.com/guide/topics/resources/
providing-resources.html#AlternativeResources.



,
. , .15.1.
, , HelloMoon
app_name
. app_name
.
, .
values-land .
,
, app_name. app_name
, 15.1.
15.1.

(values-land/strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hello, Moon! How are you? Is it cold up there?</string>
<string name="app_name">HelloMoon</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="hellomoon_play">Play</string>
<string name="hellomoon_stop">Stop</string>
<string name="hellomoon_image_description">Neil Armstrong stepping

261

onto the moon</string>


</resources>

( values)
,
.
.
app_name: values/
strings.xml, values-es/strings.xml
values-land/strings.xml.
, HelloMoon
. , values-es/strings.xml.

. 15.2. Android

, , ,
( ).
, Ajustes Configuracin
idioma ().


. ,
HelloMoon
, values-es-land.
,
. , values-es-land ,
values-land-es .
(, - , -es-rES
, . .)

262

15.

values-land/strings.xml values-es-land/ ,
15.2 (, , res/values-esland/strings.xml ).
15.2.

(values-es-land/strings.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hello, Moon! How are you? Is it cold up there?</string>
<string name="app_name">Hola, Luna! Como ests? Hace fro ah arriba?</string>
</resources>

, HelloMoon ,
.

. 15.3.


, Android , app_name
. Android
app_name:
values-es/strings.xml
values-land/strings.xml
values-es-land/strings.xml
.
.


Android , .

263

.
, ,
values-land/ values-es-land/
.
. , API .
-v11 API 11 .
API. , API 17;
-ldltr ( ) -ldrtl ( ).
API,
-ldltr -ldltr-v17. ( .)
, . Android
, ;
( ) ,
.


Android
(. .15.1),
MCC. MCC
, MCC, .
, Android
, .
MCC ,
, Android .
(values-es/ values-es-land/) .
(values-land/) , .
Android .
, Android
. values-es/ , values-es-land/.
, Android values-es-land/.

, Android,
, .

264

15.



: one_small_step.wav, app_name, armstrong_on_moon.jpg.
XML . ,
@drawable/armstrong_on_moon,
R.drawable.armstrong_on_moon. , , , .


res/.
res/ , .
res Android
. drawable/, layout/, menu/, raw/ values/.
res ( ) http://developer.android.com/guide/topics/resources/providing-resources.
html#ResourceTypes.
, res/, .
res/my_stuff , Android
.
, res/ .
.
;
. . ,
, ,
.
Android. ,
(, activity_, dialog_ list_item_). , res/layout/
CriminalIntent activity_crime_pager, activity_fragment,
dialog_date, fragment_crime list_item_crime. , .


. ,
, ..
, , .
.
, , API, ..

265

, fragment_hello_moon.xml . ,
.

API

. 15.4.

, ,
, . , .
, LogCat Resource not
found... .
,
. Ajustes Configuracin idioma ().

16

(action bar) Honeycomb.


, . ,
.
CriminalIntent.
. ,
Up.

Up

. 16.1.

267


(options menu).
.
. ,
,
, .
18.
.
18.
strings.xml ( 16.1).
, . , , .
16.1. (res/values/strings.xml)
...
<string name="crimes_title">Crimes</string>
<string name="crime_date_label">Date:</string>
<string name="date_picker_title">Date of crime:</string>
<string name="new_crime">New Crime</string>
<string name="show_subtitle">Show Subtitle</string>
<string name="hide_subtitle">Hide Subtitle</string>
<string name="subtitle">If you see something, say something.</string>
<string name="delete_crime">Delete</string>
</resources>

. 16.2. Honeycomb

268

16.


, Android.
, .
,
Android. , .

XML
, .
XML res/menu . Android ,
.
Package Explorer res/ menu.
NewAndroid XML File.
, Menu,
fragment_crime_list.xml.

. 16.3.

fragment_crime_list.xml XML. item,


16.2.

269

16.2. CrimeListFragment (fragment_crime_list.xml)


<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_new_crime"
android:icon="@android:drawable/ic_menu_add"
android:title="@string/new_crime"
android:showAsAction="ifRoom|withText"/>
</menu>

showAsAction ,
(overflow menu).
ifRoom withText, .
, . , ,
.
. ,
.
,
(. 16.4).

. 16.4.

270

16.

showAsAction always never.


always ; ifRoom .
never . ,
,
.
, Android Lint android:showAsAction, API 11. XML , Java,
. XML API.
android:icon @android:drawable/ic_menu_add
(system icon). ,
.


. , , ,
.
, .
. , , .
Androids Icon Design Guidelines http://developer.android.com/guide/practices/
ui_guidelines/icon_design.html.
, ,
. .
, Android SDK
--android-SDK/platforms/-API/data/res.
, Android 4.2 Mac /Developer/androidsdk-mac_86/platforms/android-17/data/res.
SDK
ic_menu_add. drawable . icon android:icon="@
drawable/ic_menu_add", , .



Activity. , Android Activity onCreateOptionsMenu(Menu).

271

,
, . Fragment
, CrimeListFragment.
:
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
public boolean onOptionsItemSelected(MenuItem item)

CrimeListFragment.java onCreateOptionsMenu(Menu, MenuInflater) , , fragment_crime_list.xml.


16.3. (CrimeListFragment.java)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
((CrimeAdapter)getListAdapter()).notifyDataSetChanged();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_crime_list, menu);
}

MenuInflater.inflate(int, Menu)
. Menu , .
onCreateOptionsMenu() .
, ,
, . , Fragment .
FragmentManager Fragment.onCreateOptionsMenu(Menu, MenuInflater) onCreateOptionsMenu()
. FragmentManager,
onCreateOptionsMenu(). :
public void setHasOptionsMenu(boolean hasMenu)

CrimeListFragment.onCreate() FragmentManager,
CrimeListFragment .
16.4. hasOptionsMenu (CrimeListFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
getActivity().setTitle(R.string.crimes_title);
...

272

16.

CriminalIntent (. 16.5).
?
.
.

. 16.5.

. 16.6.

,
(. 16.7).

. 16.7.

273

CriminalIntent Honeycomb, .
. .16.8
Gingerbread.

. 16.8. Gingerbread

, ;
. SDK.
Honeycomb onCreateOptionsMenu()
.
,
. onCreateOptionsMenu()
.
(, .
ActionBarSherlock
API.
ActionBarSherlock 18.)


New Crime,
Crime . CrimeLab.
java .

274

16.

16.5. Crime (CrimeLab.java)


...
public void addCrime(Crime c) {
mCrimes.add(c);
}
public ArrayList<Crime> getCrimes() {
return mCrimes;
}
...

, , 100 . CrimeLab.java
, .
16.6. ! (CrimeLab.java)
public CrimeLab(Context appContext) {
mAppContext = appContext;
mCrimes = new ArrayList<Crime>();
for (int i = 0; i < 100; i++) {
Crime c = new Crime();
c.setTitle("Crime #" + i);
c.setDate(new Date());
c.setSolved(i % 2 == 0); //
mCrimes.add(c);
}
}

,
onOptionsItemSelected(MenuItem).
MenuItem, .
, .
, ,
.
, .
CrimeListFragment.java onOptionsItemSelected(MenuItem),
. Crime,
CrimeLab CrimePagerActivity Crime.
16.7. (CrimeListFragment.java)
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_crime_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

275

case R.id.menu_item_new_crime:
Crime crime = new Crime();
CrimeLab.get(getActivity()).addCrime(crime);
Intent i = new Intent(getActivity(), CrimePagerActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, crime.getId());
startActivityForResult(i, 0);
return true;
default:
return super.onOptionsItemSelected(item);

. , true; ,
. default ,
.
CriminalIntent . .
( .
.)


CriminalIntent Back
. Back
. ,
.
Android
.
( Home
). Android
,
. Up).
Up
CrimePagerActivity. , .


, Up, ,
, .16.9.
, ,
:
public abstract void setDisplayHomeAsUpEnabled(boolean showHomeAsUp)

276

16.

API 11, , Froyo Gingerbread.


, Android Lint.

. 16.9. Up

CrimeFragment.onCreateView() setDisplayHomeAsUpEnabled(true).
16.8. Up (CrimeFragment.java)
@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}
}

...

:
Up.
. . , API 1113, ,
setDisplayHomeAsUpEnabled(true).

277

16.8 onCreateView() @TargetApi.


setDisplayHomeAsUpEnabled(true),
onCreateView() ,
API, .
CriminalIntent,
, .

Up
,
onOptionsItemSelected(MenuIt
em). , FragmentManager, CrimeFragment .
CrimeFragment.onCreate() setHasOptionsMenu(true),
CrimeListFragment.
16.9. (CrimeFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
...
setHasOptionsMenu(true);
}


XML. : android.R.id.home.
CrimeFragment.java onOptionsItemSelected(MenuItem),
.
16.10.
 (Home)
(CrimeFragment.java)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
//
return true;
default:
return super.onOptionsItemSelected(item);
}
}

. ,
CrimePagerActivity:
Intent intent = new Intent(getActivity(), CrimeListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();

FLAG_ACTIVITY_CLEAR_TOP Android ,
, .

278

16.

CriminalListActivity

. 16.10. FLAG_ACTIVITY_CLEAR_TOP

,
, NavUtils
.
. AndroidManifest.xml.
CrimePagerActivity , CrimeListActivity
.
16.11. (AndroidManifest.xml)
<activity android:name=".CrimePagerActivity"
android:label="@string/app_name">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".CrimeListActivity"/>
</activity>
...

, .
PackageManager,
, .
- .
NavUtils,
.
NavUtils:
public static void navigateUpFromSameTask(Activity sourceActivity)

CrimeFragment.onOptionsItemSelected() ,
, , NavUtils.getParentActivityName(Activity). , navigat
eUpFromSameTask(Activity) .
16.12. NavUtils (CrimeFragment.java)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (NavUtils.getParentActivityName(getActivity()) != null) {

279

NavUtils.navigateUpFromSameTask(getActivity());
}
return true;
default:
return super.onOptionsItemSelected(item);

,
. onCreateView()
setDisplayHomeAsUpEnabled(true).
16.13. (CrimeFragment.java)
@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (NavUtils.getParentActivityName(getActivity()) != null) {
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
}

...

NavUtils ? , NavUtils . , NavUtils


.
, ,
Java.
,
. CrimeFragment ,
; CrimeFragment
.
CriminalIntent.
, . , CriminalIntent , navigateUpFromS
ameTask(Activity)
CrimePagerActivity.


, , , , CrimeListActivity.

280

16.


, , . ,
. res menu-v11.
fragment_crime_list.xml .
res/menu-v11/fragment_crime_list.xml Show Subtitle,
.
16.14. Show Subtitle (res/menu-v11/fragment_crime_list.xml)
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_new_crime"
android:icon="@android:drawable/ic_menu_add"
android:title="@string/new_crime"
android:showAsAction="ifRoom|withText"/>
<item android:id="@+id/menu_item_show_subtitle"
android:title="@string/show_subtitle"
android:showAsAction="ifRoom"/>
</menu>

onOptionsItemSelected()
.
16.15. Show Subtitle (CrimeListFragment.java)
@TargetApi(11)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_crime:
...
return true;
case R.id.menu_item_show_subtitle:
getActivity().getActionBar().setSubtitle(R.string.subtitle);
return true;
default:
return super.onOptionsItemSelected(item);
}
}

: Android Lint, .
, R.id.
menu_item_show_subtitle .
CriminalIntent . Froyo Gingerbread (
). , Show Subtitle
. ,
, .

281


, :
Show Subtitle. ,
.
onOptionsItemSelected()
.
16.16. (CrimeListFragment.java)
@TargetApi(11)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_crime:
...
return true;
case R.id.menu_item_show_subtitle:
if (getActivity().getActionBar().getSubtitle() == null) {
getActivity().getActionBar().setSubtitle(R.string.subtitle);
item.setTitle(R.string.hide_subtitle);
} else {
getActivity().getActionBar().setSubtitle(null);
item.setTitle(R.string.show_subtitle);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}

, Hide Subtitle. ,
, Show Subtitle.
CriminalIntent ,
.

,
Android
. , .
Android : ,
. ,
, . ,
CrimeListFragment,
.
CrimeListFragment.java , onCreate() CrimeListFragment .

282

16.

16.17.
 CrimeListFragment
(CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
mCrimes;
private boolean mSubtitleVisible;
private final String TAG = "CrimeListFragment";

private ArrayList<Crime>

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
setRetainInstance(true);
mSubtitleVisible = false;
}

onOptionsItemSelected()
.
16.18.
 subtitleVisible
(CrimeListFragment.java)
@TargetApi(11)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_crime:
...
return true;
case R.id.menu_item_show_subtitle:
if (getActivity().getActionBar().getSubtitle() == null) {
getActivity().getActionBar().setSubtitle(R.string.subtitle);
mSubtitleVisible = true;
item.setTitle(R.string.hide_subtitle);
}
else {
getActivity().getActionBar().setSubtitle(null);
mSubtitleVisible = false;
item.setTitle(R.string.show_subtitle);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}

, . CrimeListFragment.java onCreateView() ,
mSubtitleVisible true.
16.19.
 , mSubtitleVisible
(CrimeListFragment.java)
@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,

283

Bundle savedInstanceState) {
View v = super.onCreateView(inflater, parent, savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (mSubtitleVisible) {
getActivity().getActionBar().setSubtitle(R.string.subtitle);
}
}
}

return v;

onCreateOptionsMenu()
, .
16.20.
 mSubtitleVisible
(CrimeListFragment.java)
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_crime_list, menu);
MenuItem showSubtitle = menu.findItem(R.id.menu_item_show_subtitle);
if (mSubtitleVisible && showSubtitle != null) {
showSubtitle.setTitle(R.string.hide_subtitle);
}
}

CriminalIntent. ,
. ,
.

.
CriminalIntent
. - .
ListView AdapterView, View,
. , ListView
, .

AdapterView:
public void setEmptyView(View emptyView)

XML , ListView,
. @android:id/
list @android:id/empty , .

284

16.

CrimeListFragment onCreateView(),
.
CrimeListFragment XML- , FrameLayout
, ListView View,
.
(, ).
,
,
.

17

.
CriminalIntent
JSON, .
Android (sandbox).
(
, ).
/data/data,
. CriminalIntent
/data/data/com.bignerdranch.android.criminalintent.
, ; ,
API.
, .
, SD-, ( )
. SD- ,
. ,

, .
() , API
. (,
.)

CriminalIntent
:
.

286

17.

. , .
: ,
, .
CriminalIntent JSON,
- Android Context.
.17.1
CriminalIntent.

Crime
CrimeLab

JSON
Crime


Crime JSON


( -)

(
-)

crimes.json
( )

. 17.1. CriminalIntent

JSON (JavaScript Object Notation) ,


-. Android org.json,

JSON. Android org.json,
JSON http://json.org.
(XML . Android
XML.
XML 26.)
, , -, Context (
: Application, Activity Service,

CriminalIntent

287

).
Java , java.io.File java.io.FileInputStream.

JSON
CriminalIntent CrimeLab
,
JSON CriminalIntentJSONSerializer
Crime.

CriminalIntentJSONSerializer
ArrayList Crime
JSON CriminalIntentJSONSerializer.
com.bignerdranch.android.criminalintent.
java.lang.Object.
, 17.1.
import , , Eclipse Organize Imports.
17.1. CriminalIntentJSONSerializer
public class CriminalIntentJSONSerializer {
private Context mContext;
private String mFilename;
public CriminalIntentJSONSerializer(Context c, String f) {
mContext = c;
mFilename = f;
}
public void saveCrimes(ArrayList<Crime> crimes)
throws JSONException, IOException {
// JSON
JSONArray array = new JSONArray();
for (Crime c : crimes)
array.put(c.toJSON());

//
Writer writer = null;
try {
OutputStream out = mContext
.openFileOutput(mFilename, Context.MODE_PRIVATE);
writer = new OutputStreamWriter(out);
writer.write(array.toString());
} finally {
if (writer != null)
writer.close();
}

CrimeLab, JSON

288

17.

. (unit-testing) ,
.
, ,
Context . ,
, Context.
saveCrimes(ArrayList<Crime>) JSONArray,
toJSON() JSONArray. ( toJSON() ,
Crime. .)
, Context.openFileOutput(). .
,
.
, Context.getFilesDir(), openFileOutput()
.
,
Java. Writer, OutputStream OutputStreamWriter
java.io. OutputStream, openFileOutput(), OutputStreamWriter,
Java String
, OutputStream .

JSON Crime
mCrimes JSON,
Crime JSON. Crime.java
toJSON(), Crime
JSON JSONObject JSONArray.
17.2. JSON (Crime.java)
public class Crime {
private
private
private
private

static
static
static
static

final
final
final
final

String
String
String
String

JSON_ID = "id";
JSON_TITLE = "title";
JSON_SOLVED = "solved";
JSON_DATE = "date";

private
private
private
private

UUID mId;
String mTitle;
boolean mSolved;
Date mDate = new Date();

public Crime() {
mId = UUID.randomUUID();
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put(JSON_ID, mId.toString());

Crime CrimeLab

289

json.put(JSON_TITLE, mTitle);
json.put(JSON_SOLVED, mSolved);
json.put(JSON_DATE, mDate.getTime());
return json;

@Override
public String toString() {
return mTitle;
}

JSONObject Crime , JSON.

Crime CrimeLab
CriminalIntentJSONSerializer Crime
JSON
.
? : ,
.
CrimeLab, .
.
, - , . CriminalIntent ,
.
, SQLite.
SQLite Android 34.
CrimeLab.java CriminalIntentJSONSerializer onCreate(). saveCrimes(),
. ,
.
17.3. CrimeLab (CrimeLab.java)
public class CrimeLab {
private static final String TAG = "CrimeLab";
private static final String FILENAME = "crimes.json";
private ArrayList<Crime> mCrimes;
private CriminalIntentJSONSerializer mSerializer;
private static CrimeLab sCrimeLab;
private Context mAppContext;
private CrimeLab(Context appContext) {
mAppContext = appContext;
mCrimes = new ArrayList<Crime>();
}
...

290

17.

17.3 ()
public void addCrime(Crime c) {
mCrimes.add(c);
}

public boolean saveCrimes() {


try {
mSerializer.saveCrimes(mCrimes);
Log.d(TAG, "crimes saved to file");
return true;
} catch (Exception e) {
Log.e(TAG, "Error saving crimes: ", e);
return false;
}
}

.

, Toast .

onPause()
saveCrimes()? onPause(). onStop() onDestroy(),
. ,
, onStop()
onDestroy() .
17.4.
 onPause()
(CrimeFragment.java)
...
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(getActivity());
return true;
default:
return super.onOptionsItemSelected(item);
}
}

@Override
public void onPause() {
super.onPause();
CrimeLab.get(getActivity()).saveCrimes();
}

CriminalIntent.
Home ; ,
. LogCat.

Crime CrimeLab

291


CriminalIntent
.
Crime.java , JSONObject.
17.5. Crime(JSONObject) (Crime.java)
public class Crime {
...
public Crime() {
mId = UUID.randomUUID();
}
public Crime(JSONObject json) throws JSONException {
mId = UUID.fromString(json.getString(JSON_ID));
mTitle = json.getString(JSON_TITLE);
mSolved = json.getBoolean(JSON_SOLVED);
mDate = new Date(json.getLong(JSON_DATE));
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put(JSON_ID, mId.toString());
json.put(JSON_TITLE, mTitle);
json.put(JSON_SOLVED, mSolved);
json.put(JSON_DATE, mDate.getTime());
return json;
}

CriminalIntentJSONSerializer.java CriminalIntentJSONSerializer .
17.6. loadCrimes() (CriminalIntentJSONSerializer.java)
public CriminalIntentJSONSerializer(Context c, String f) {
mContext = c;
mFilename = f;
}
public ArrayList<Crime> loadCrimes() throws IOException, JSONException {
ArrayList<Crime> crimes = new ArrayList<Crime>();
BufferedReader reader = null;
try {
// StringBuilder
InputStream in = mContext.openFileInput(mFilename);
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder jsonString = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
// Line breaks are omitted and irrelevant
jsonString.append(line);
}
// JSON JSONTokener
JSONArray array = (JSONArray) new JSONTokener(jsonString.toString())
.nextValue();

292

17.

17.6 ()

// Crime JSONObject
for (int i = 0; i < array.length(); i++) {
crimes.add(new Crime(array.getJSONObject(i)));
}
} catch (FileNotFoundException e) {
// " ";
} finally {
if (reader != null)
reader.close();
}
return crimes;

public void saveCrimes(ArrayList<Crime> crimes) throws JSONException, IOException {


// JSON
JSONArray array = new JSONArray();
for (Crime c : crimes)
array.put(c.toJSON());
...

Java JSON openFileInput() Context


, JSONObject, JSONArray, ArrayList, .
reader.close() finally. ,

.
, CrimeLab
ArrayList ( ,
). CrimeLab.java .
17.7. (CrimeLab.java)
public CrimeLab(Context appContext) {
mAppContext = appContext;
mSerializer = new CriminalIntentJSONSerializer(mAppContext, FILENAME);
mCrimes = new ArrayList<Crime>();

try {
mCrimes = mSerializer.loadCrimes();
} catch (Exception e) {
mCrimes = new ArrayList<Crime>();
Log.e(TAG, "Error loading crimes: ", e);
}

public static CrimeLab get(Context c) {


...
}
...

; , .

: Android - Java

293

CriminalIntent .
.
, . (, ); , CriminalIntent.
, .
Eclipse.
,
. ,

.

,
. ,
.
, (, ,
) .
, ,
(, ).
.
, . android.os.Environment
, . ( Context,
). ,
CriminalIntentJSONSerializer.

:
Android - Java
Android Linux.
Android Linux Unix.
(/),
, .
,
Linux.
Android
Java.
APK, /data/app com.bignerdranch.
android.criminalintent-1.apk.

294

17.

,
.


, , Context
: Application, Activity Service, .

, .17.1.
17.1. Context

File getFilesDir()

FileInputStream

(
)

openFileInput(String name)
FileOutputStream
openFileOutput(String name,

,
( )

int mode)
File getDir(String name,
int mode)

( , )

String[] fileList()

(, openFileInput(String))

File getCacheDir()

,
-. ,

Java
, java.io.File java.io.FileInputStream.
API, , ,
Java. Android java.nio.*.

18


. .
:
( ), .

. 18.1.

296

18.

Honeycomb .
.
.
API : .
, .
. - ,
.
API 11
Froyo Gingerbread.
(,
. ,
ActionBarSherlock
API. ActionBarSherlock .)


res/menu/ XML crime_list_item_context.xml,
menu. item, 18.1.
18.1. (crime_list_item_context.xml)
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_delete_crime"
android:icon="@android:drawable/ic_menu_delete"
android:title="@string/delete_crime" />
</menu>


. Fragment
, 16.
:
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo)


Fragment:
public boolean onContextItemSelected(MenuItem item)

297


CrimeListFragment.java onCreateContextMenu() .
18.2. (CrimeListFragment.java)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
...
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
getActivity().getMenuInflater().inflate(R.menu.crime_list_item_context, menu);
}

onCreateOptionsMenu() MenuInflater, MenuInflater,


CrimeListActivity. MenuInflater.inflate()
ContextMenu
, .
,
,
. ,
View, onCreateContextMenu().


.
Fragment,
:
public void registerForContextMenu(View view)


. , . ListView,
.
CrimeListFragment.onCreateView() ListView .
18.3. ListView (CrimeListFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,

298

18.

18.3 ()
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, parent, savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (mSubtitleVisible) {
getActivity().getActionBar().setSubtitle(R.string.subtitle);
}
}
ListView listView = (ListView)v.findViewById(android.R.id.list);
registerForContextMenu(listView);
}

return v;

android.R.id.list ListView, ListFragment. ListFragment


getListView(), onCreateView() , getListView() null
onCreateView().
CriminalIntent,
, Delete
Crime.

. 18.2.

299


, . CrimeLab.java deleteCrime(Crime).
18.4. (CrimeLab.java)
public void addCrime(Crime c) {
mCrimes.add(c);
}
public void deleteCrime(Crime c) {
mCrimes.remove(c);
}

onContextItemSelec
ted(MenuItem). MenuItem ,
. , ,
. , .
getMenuInfo() MenuItem.
, ContextMenu.ContextMenuInfo.
CrimeListFragment onContextItemSelected(MenuItem),
,
Crime . Crime
.
18.5. (CrimeListFragment.java)
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
getActivity().getMenuInflater().inflate(R.menu.crime_list_item_context, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
int position = info.position;
CrimeAdapter adapter = (CrimeAdapter)getListAdapter();
Crime crime = adapter.getItem(position);

switch (item.getItemId()) {
case R.id.menu_item_delete_crime:
CrimeLab.get(getActivity()).deleteCrime(crime);
adapter.notifyDataSetChanged();
return true;
}
return super.onContextItemSelected(item);

18.5 getMenuInfo() AdapterView.AdapterContextMenuInfo, ListView AdapterView.

300

18.

getMenuInfo()
, .
Crime.
CriminalIntent,
. ( ,
.)


, ,
Android. , .18.2
Jelly Bean.
,
.
, , , , .
, .

. 18.3.

, , . ,

301

, Froyo Gingerbread,
, .


,
. ,
, .
CrimeListFragment.onCreateView()
CHOICE_MODE_MULTIPLE_MODAL. , ListView, , .
18.6.
@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, parent, savedInstanceState);
...
ListView listView = (ListView)v.findViewById(android.R.id.list);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Froyo Gingerbread
registerForContextMenu(listView);
} else {
// Honeycomb
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
}
}

return v;



ListView ,
AbsListView.MultiChoiceModeListener. ,
.
public abstract void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked)

MultiChoiceModeListener ActionMode.
Callback. , ActionMode, ActionMode.Callback
ActionMode. ActionMode.Callback
:
public abstract boolean onCreateActionMode(ActionMode mode, Menu menu)

302

18.

ActionMode. , .
public abstract boolean onPrepareActionMode(ActionMode mode, Menu menu)

onCreateActionMode() ,
.
public abstract boolean onActionItemClicked(ActionMode mode, MenuItem item)

, . , .
public abstract void onDestroyActionMode(ActionMode mode)

ActionMode - ,
. (-).
,
.
CrimeListFragment.onCreateView() ,
MultiChoiceModeListener . -
onCreateActionMode() onActionItemClicked(ActionMode,
MenuItem).
18.7. MultiChoiceModeListener (CrimeListFragment.java)
...
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
public void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked) {
// ,
//
}
// ActionMode.Callback
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.crime_list_item_context, menu);
return true;
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
// ,
//
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_delete_crime:
CrimeAdapter adapter = (CrimeAdapter)getListAdapter();
CrimeLab crimeLab = CrimeLab.get(getActivity());
for (int i = adapter.getCount() - 1; i >= 0; i--) {

303

if (getListView().isItemChecked(i)) {
crimeLab.deleteCrime(adapter.getItem(i));
}

}
mode.finish();
adapter.notifyDataSetChanged();
return true;
default:
return false;

}
}
public void onDestroyActionMode(ActionMode mode) {
// ,
//
}

});
return v;

Eclipse , : , onCreateActionMode(),
false. true; false .
onCreateActionMode() ,
MenuInflater ActionMode, .
.
, ActionMode.setTitle()
. MenuInflater
.

. 18.4.

304

18.

onActionItemClicked() Crime
CrimeLab, . ActionMode.finish() .
CriminalIntent. , .
() .
. .

.

. ,
- .
, ,
.
.


.
, .

.
XML. ( ) ,
. ( , ,
StateListDrawable.)
,
, ,
drawable . res/drawable XML
res/drawable/background_activated.xml. selector
, 18.8.
18.8.

(res/drawable/background_activated.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:state_activated="true"
android:drawable="@android:color/darker_gray"
/>
</selector>

: ,
, , android:drawable.
. android:state_activated

305

false, ,
.
res/layout/list_item_crime.xml
.
18.9. (res/layout/list_item_crime.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_activated">
...
</RelativeLayout>

CriminalIntent.
.

. 18.5. ,

25.



, , ListView. GridView

306

18.

AdapterView, 26. , -

,
ListView, GridView?
, View.OnLongClickListener.
ActionMode Activity.
startActionMode(). ( MultiChoiceModeListener
ActionMode .)
startActionMode() ,
ActionMode.Callback. ActionMode.Callback,
ActionMode, :
public
public
public
public

abstract
abstract
abstract
abstract

boolean onCreateActionMode(ActionMode mode, Menu menu)


boolean onPrepareActionMode(ActionMode mode, Menu menu)
boolean onActionItemClicked(ActionMode mode, MenuItem item)
void onDestroyActionMode(ActionMode mode)

, ActionMode.Callback, startActionMode(), startActionMode().

: ?
,
(gracious fallback):
.
SDK .
. ,
. (duplication).
:
.
, .
,
.
. android.support.v4.app.Fragment ,
android.app.Fragment .
,
,
.
Gingerbread, ,
. ActionBarSherlock (Jake Wharton) . http://www.actionbarsherlock.com.
ActionBarSherlock Android

: ActionBarSherlock

307

Android.
ActionBarSherlock .
( ,
. .)
? .
,
Android.
, Android
. ,

. , . ,
ICS Jelly Bean
, .
, . -,
,
. , . -,
.
, ,
.
,
, .

. CrimeFragment
, .
,
. CrimeFragment.

: ActionBarSherlock
Android ,
. , ,
16.
ActionBarSherlock ( ABS, )
.
, ,

308

18.

.
http://www.actionbarsherlock.com. Android, .
, ABS
, .
, ABS .
, jar- ABS
Android. Android,
,
. Android Android, . ,
Android,
, jar-.
ABS , ABS
:
;
Eclipse ActionBarSherlock;
ActionBarSherlock CriminalIntent;
CriminalIntent ActionBarSherlock.
( ActionBarSherlock, CriminalIntent. CriminalIntent ABS ).

. 18.6. Android

: ActionBarSherlock

309

ABS, http://www.actionbarsherlock.com/download.
html, zip tgz ( )
.
, Eclipse,
Package Explorer NewProject....
Android ,
; Android Project From Existing Code (. 18.6).

. Browse ,
ABS.
: library, samples website. library,
Open, Finish.

. 18.7. ABS

Eclipse library ; , RefactorRename...


ActionBarSherlock.

CriminalIntent.

CriminalIntent
Package Explorer Properties...
Android
, . 18.8. Android
.

310

18.

Android.
, . Add

. 18.9. ActionBarSherlock

, ActionBarSherlock,
.

. ActionBarSherlock
, ActionBarSherlock,
CriminalIntent. ABS ,
Android, Activity, Fragment ActionBar. (
) ABS Sherlock,
.
, .
Sherlock-, .

ABS CriminalIntent
ABS :
SingleFragmentActivity CrimePagerActivity , SherlockFragmentActivity (
FragmentActivity).
,
SherlockFragment, SherlockDialogFragment SherlockListFragment
( ).
Menu, MenuItem MenuInflater com.actionbarsherlock.view.
.
, CrimeFragment CrimeListFragment

311

. , ,
.
CrimeFragment : import ,
Command+Shift+O/Ctrl+Shift+O com.actionbarsherlock.view.
CrimeListFragment, . , CrimeListFragment
MultiChoiceModeListener,
Android.
?
MenuItem, Menu MenuInflater onCreateOptionsMenu()
onOptionsItemSelected(). :
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
...
}

:
@Override
public void onCreateOptionsMenu(com.actionbarsherlock.view.Menu menu,
com.actionbarsherlock.view.MenuInflater inflater) {
...
}


, , ABS
. . getSherlockActivity().
getSupportActionBar() getActivity().getActionBar().
SherlockActivity ,
. res/menu-v11/
fragment_crime_list.xml res/menu,
.


?
CriminalIntent MultiChoiceModeListener . ,
MultiChoiceModeListener .
?
ListView: ListView.CHOICE_MODE_MULTIPLE.
ListView.CHOICE_MODE_MULTIPLE_MODAL,

312

18.

Android. ListView.CHOICE_MODE_MULTIPLE , :
Android. ListView.CHOICE_MODE_MULTIPLE
ListView .
, ListView.CHOICE_MODE_NONE.
, CHOICE_MODE_MULTIPLE_MODAL. ,
, getSherlockActivity().startActionMode(). , com.actionbarsherlock.view.ActionMode.Callback, Android.
, . OnItemLongClickListener ListView.
setOnItemLongClickListener().

19

I: Viewfinder

.
API
.
API . ,
, . ,
- : .
?
. . Android ,
, MediaStore.ACTION_IMAGE_CAPTURE.
21.
, , -
. ,
. CriminalIntent , API .
,
SurfaceView
.

314

19. I: Viewfinder

. 19.1.

. 19.2 , .

. 19.2. CriminalIntent

Camera . ;
.

315

SurfaceView . SurfaceView
.
CrimeCameraFragment,
CrimeCameraFragment CrimeCameraActivity.
CrimeCameraFragment. , CrimeFragment
CrimeCameraActivity.


Android XML Layout fragment_crime_camera.xml
FrameLayout. , .19.3.

. 19.3. CrimeCameraFragment (fragment_crime_camera.xml)

FrameLayout LinearLayout, - LinearLayout.


; FrameLayout
20.
LinearLayout
layout_width layout_weight. ,
Button, - android:layout_width="wrap_
content", SurfaceView (android:layout_width="0dp").
SurfaceView
layout_weight, SurfaceView.
. 19.4 , .

316

19. I: Viewfinder

. 19.4.

strings.xml .
19.1. (strings.xml)
...
<string name="show_subtitle">Show Subtitle</string>
<string name="subtitle">Sometimes tolerance is not a virtue.</string>
<string name="take">Take!</string>
</resources>

CrimeCameraFragment
CrimeCameraFragment
android.support.v4.app.Fragment. CrimeCameraFragment.java
, 19.2. onCreateView() ,
.
, -;
.
19.2. (CrimeCameraFragment.java)
public class CrimeCameraFragment extends Fragment {
private static final String TAG = "CrimeCameraFragment";
private Camera mCamera;
private SurfaceView mSurfaceView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime_camera, parent, false);
Button takePictureButton = (Button)v
.findViewById(R.id.crime_camera_takePictureButton);

317

takePictureButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
getActivity().finish();
}
});
mSurfaceView = (SurfaceView)v.findViewById(R.id.crime_camera_surfaceView);

return v;

CrimeCameraActivity
SingleFragmentActivity CrimeCameraActivity.
createFragment(),
CrimeCameraFragment.
19.3. (CrimeCameraActivity.java)
public class CrimeCameraActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
return new CrimeCameraFragment();
}
}

CrimeCameraActivity .
, ,
uses-permission.
AndroidManifest.xml , 19.4.
19.4. (AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.criminalintent"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<application
...

318

19. I: Viewfinder

19.4 ()
<activity android:name=".CrimeCameraActivity"
android:screenOrientation="landscape"
android:label="@string/app_name">
</activity>
</application>
</manifest>

uses-feature ,
. android.hardware.camera ,
Google Play, ,
.
,
android:screenOrientation.
,
.
screenOrientation . , , ,
.
<activity>.

API
.
,
.


. CrimeCameraFragment
Camera. ,
, ,
.
.
Camera:
public static Camera open(int cameraId)
public static Camera open()
public final void release()

open(int) API 9, API 8


open() .
CrimeCameraFragment,
, onResume() onPause().

API

319

,
. ( : onResume()
, .)
CrimeCameraFragment.onResume()
Camera.open(int) 0, ,
. ,
(, , Nexus 7), .
API 8 Camera.open() .
Froyo, Camera.open() API
8.
19.5. onResume() (CrimeCameraFragment.java)
@TargetApi(9)
@Override
public void onResume() {
super.onResume();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
mCamera = Camera.open(0);
} else {
mCamera = Camera.open();
}
}

(Android Lint
. ,
, 26.)
, ,
. onPause() .
19.6. (CrimeCameraFragment.java)
public void onResume() {
super.onResume();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
mCamera = Camera.open(0);
} else {
mCamera = Camera.open();
}
}
@Override
public void onPause() {
super.onPause();
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}


release(). .

320

19. I: Viewfinder

, ,
,
. - , null .

SurfaceView, SurfaceHolder Surface


SurfaceView SurfaceHolder. CrimeCameraFragment.java , SurfaceHolder
SurfaceView.
19.7. SurfaceHolder (CrimeCameraFragment.java)
@Override
@SuppressWarnings("deprecation")
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
mSurfaceView = (SurfaceView)v.findViewById(R.id.crime_camera_surfaceView);
SurfaceHolder holder = mSurfaceView.getHolder();
// setType() SURFACE_TYPE_PUSH_BUFFERS
// , ,
//
// Camera 3.0.
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

return v;

setType() SURFACE_TYPE_PUSH_BUFFERS ,
. Eclipse
.
? setType() SURFACE_TYPE_PUSH_BUFFERS
Honeycomb.
19.7 @SuppressWarnings.
, Android.
Android 20.
SurfaceHolder Surface.
Surface .
Surface :
SurfaceView , SurfaceView . , , Surface ,
.
View, SurfaceView,
. (client) Surface ,

API

321

- . CrimeCameraFragment
Camera.


. 19.5.SurfaceView, SurfaceHolder Surface

: , Surface , . (.19.6),
Camera SurfaceHolder Surface
Surface.


. 19.6.

322

19. I: Viewfinder

SurfaceHolder , SurfaceHolder.Callback.
Surface Surface .
SurfaceHolder.Callback :
public abstract void surfaceCreated(SurfaceHolder holder)

, SurfaceView. Surface .
public abstract void surfaceChanged(SurfaceHolder holder, int format, int width,
int height)

, surfaceChanged().
, .
Surface .
public abstract void surfaceDestroyed(SurfaceHolder holder)

SurfaceView Surface .
Surface Surface.
Camera,
Surface:
public final void setPreviewDisplay(SurfaceHolder holder)

Surface. surfaceCreated().
public final void startPreview()

Surface . surfaceChanged().
public final void stopPreview()

Surface. surfaceDestroyed().
CrimeCameraFragment.java SurfaceHolder.Callback
Surface .
19.8. SurfaceHolder.Callback (CrimeCameraFragment.java)
...
SurfaceHolder holder = mSurfaceView.getHolder();
// setType() SURFACE_TYPE_PUSH_BUFFERS
// , ,
//
// Camera 3.0.
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
//
//
try {

API

if (mCamera != null) {
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "Error setting up preview display", exception);
}

public
//
//
if
}

void surfaceDestroyed(SurfaceHolder holder) {


,
.
(mCamera != null) {
mCamera.stopPreview();

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)

if (mCamera == null) return;

});
}

323

// ;
//
Camera.Parameters parameters = mCamera.getParameters();
Size s = null; //
parameters.setPreviewSize(s.width, s.height);
mCamera.setParameters(parameters);
try {
mCamera.startPreview();
} catch (Exception e) {
Log.e(TAG, "Could not start preview", e);
mCamera.release();
mCamera = null;
}

return v;

, .

.
surfaceChanged()
null. ,
.
, .

Camera.Parameters,
:
public List<Camera.Size> getSupportedPreviewSizes()

324

19. I: Viewfinder

android.hardware.Camera.Size,
.
Surface,
surfaceChanged(), ,
Surface.
CrimeCameraFragment , .
, .
19.9. (CrimeCameraFragment.java)
/** .
* CameraPreview.java
* - ApiDemos Android. */
private Size getBestSupportedSize(List<Size> sizes, int width, int height) {
Size bestSize = sizes.get(0);
int largestArea = bestSize.width * bestSize.height;
for (Size s : sizes) {
int area = s.width * s.height;
if (area > largestArea) {
bestSize = s;
largestArea = area;
}
}
return bestSize;
}


surfaceChanged().
19.10. getBestSupportedSize() (CrimeCameraFragment.java)
...
holder.addCallback(new SurfaceHolder.Callback() {
...

});

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {


// ;
//
Camera.Parameters parameters = mCamera.getParameters();
Size s = null;
Size s = getBestSupportedSize(parameters.getSupportedPreviewSizes(), w, h);
parameters.setPreviewSize(s.width, s.height);
mCamera.setParameters(parameters);
try {
mCamera.startPreview();
} catch (Exception e) {
Log.e(TAG, "Could not start preview", e);
mCamera.release();
mCamera = null;
}
}

API

325

CrimeCameraActivity
CrimeFragment
, CrimeFragment . CrimeFragment CrimeCameraActivity.

CrimeFragment, . 19.7.
, .19.7,
LinearLayout ImageButton.
.19.8 , CrimeFragment .
. 19.7. CrimeFragment

. 19.8.
(layout/fragment_crime.xml)

326

19. I: Viewfinder

. 19.9.
(layout-land/fragment_crime.xml)

CrimeFragment ImageButton,
OnClickListener, CrimeCameraActivity.
19.11. CrimeCameraActivity (CrimeFragment.java)
public class CrimeFragment extends Fragment {
...
private ImageButton mPhotoButton;
...
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
...
mPhotoButton = (ImageButton)v.findViewById(R.id.crime_imageButton);
mPhotoButton.setOnClickListener(new View.OnClickListener() {

API

});
}

327

@Override
public void onClick(View v) {
Intent i = new Intent(getActivity(), CrimeCameraActivity.class);
startActivity(i);
}

return v;

, mPhotoButton .
PackageManager.
onCreateView(), ImageButton
.
19.12. (CrimeFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
...
mPhotoButton = (ImageButton)v.findViewById(R.id.crime_imageButton);
mPhotoButton.setOnClickListener(new View.OnClickListener() {
...
});
// ,
//
PackageManager pm = getActivity().getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) &&
!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
mPhotoButton.setEnabled(false);
}
}

...

PackageManager hasSystemFeature(String),
. :
FEATURE_CAMERA , FEATURE_CAMERA_FRONT . ,
ImageButton .
CriminalIntent.
. .
Take! CrimeFragment.

328

19. I: Viewfinder

. 19.10.

. ,
.


. 19.10
.
,
. .
, CrimeCameraFragment
; CrimeCameraActivity. CrimeCameraActivity.java onCreate(Bundle).
19.13. (CrimeCameraActivity.java)
public class CrimeCameraActivity extends SingleFragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
// .
requestWindowFeature(Window.FEATURE_NO_TITLE);
//
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
}

@Override
protected Fragment createFragment() {
return new CrimeCameraFragment();
}

? requestWindowFeature()
addFlags()

329

Activity.setContentView(), CrimeCameraActivity onCreate(Bundle) .


-. ,
.
CriminalIntent .

. 19.11.

API ,
CrimeFragment.

:

,
CrimeFragment CrimeCameraActivity. ,
.


. . ,
, .
,
.
: adb.
.
CrimeCameraActivity AndroidManifest.xml:

330

19. I: Viewfinder

<activity android:name=".CrimeCameraActivity"
android:exported="true"
android:screenOrientation="landscape"
android:label="@string/app_name">
</activity>

.
exported true, Android,
. (
exported true.)
adb platform-tools Android SDK.
platform-tools tools
.
adb (Android Debug Bridge). adb ,
Eclipse. LogCat,
, , , ,
.
adb , .
CriminalIntent
, . CrimeCameraActivity :
$ adb shell am start -n com.bignerdranch.android.criminalintent/.
CrimeCameraActivity
Starting: Intent { cmp=com.bignerdranch.android.criminalintent/.CrimeCameraActivity
}

, CrimeCameraActivity
.

. :
$ adb shell
shell@android:/ $ am start -n com.bignerdranch.android.criminalintent/.
CrimeCameraAct\
ivity

am ,
(Activity Manager). Android, .
am adb shell am.

20

II:

JPEG
, JPEG Crime CrimeFragment.

DialogFragment.

. 20.1.

332

20. II:


CrimeCameraFragment . , ,
.
layout/fragment_crime_camera.xml FrameLayout ProgressBar,
.20.2.

. 20.2. FrameLayout ProgressBar (fragment_crime_camera.xml)

@android:style/Widget.ProgressBar.Large .

. 20.3.

333

FrameLayout ( ProgressBar ) .
, Take!
.
: FrameLayout match_
parent. FrameLayout
. , FrameLayout,
ProgressBar, LinearLayout.
FrameLayout , - LinearLayout. ProgressBar
. FrameLayout
match_parent android:clickable="true" ,
FrameLayout (
).
LinearLayout, Take!.
CrimeCameraFragment.java. FrameLayout,
.
20.1. FrameLayout (CrimeCameraFragment.java)
public class CrimeCameraFragment extends Fragment {
...
private View mProgressContainer;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime_camera, parent, false);
mProgressContainer = v.findViewById(R.id.crime_camera_progressContainer);
mProgressContainer.setVisibility(View.INVISIBLE);
...

...
}

return v;


, ;
JPEG.
Camera:
public final void takePicture(Camera.ShutterCallback shutter,
Camera.PictureCallback raw,
Camera.PictureCallback jpeg)

ShutterCallback , ,
.

334

20. II:

PictureCallback

.
PictureCallback JPEG-
..
takePicture().
- , null takePicture().
CriminalIntent ShutterCallback
JPEG.
.20.4 .

. 20.4. CrimeCameraFragment

, .
public static interface Camera.ShutterCallback {
public abstract void onShutter();
}
public static interface Camera.PictureCallback {
public abstract void onPictureTaken (byte[] data, Camera camera);
}

CrimeCameraFragment.java Camera.ShutterCallback,
, Camera.PictureCallback,
JPEG.
20.2. takePicture() (CrimeCameraFragment.java)
...
private View mProgressContainer;
private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
public void onShutter() {

};

335

//
mProgressContainer.setVisibility(View.VISIBLE);

private Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {


public void onPictureTaken(byte[] data, Camera camera) {
//
String filename = UUID.randomUUID().toString() + ".jpg";
// jpeg
FileOutputStream os = null;
boolean success = true;
try {
os = getActivity().openFileOutput(filename, Context.MODE_PRIVATE);
os.write(data);
} catch (Exception e) {
Log.e(TAG, "Error writing to file " + filename, e);
success = false;
} finally {
try {
if (os != null)
os.close();
} catch (Exception e) {
Log.e(TAG, "Error closing file " + filename, e);
success = false;
}
}

};
...

if (success) {
Log.i(TAG, "JPEG saved at " + filename);
}
getActivity().finish();

onPictureTaken() ,
. - Java
JPEG, Camera.
, .
: mProgressContainer .
, PictureCallback , .
Take!,
takePicture(). null
, .
20.3. takePicture() (CrimeCameraFragment.java)
@Override
@SuppressWarnings("deprecation")
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...

336

20. II:

20.3 ()
takePictureButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
getActivity().finish();
if (mCamera != null) {
mCamera.takePicture(mShutterCallback, null, mJpegCallback);
}
}
});
...
}

return v;


. , .
Camera.Parameters:
public List<Camera.Size> getSupportedPictureSizes()

surfaceChanged() getBestSupportedSize()
,
Surface. .
20.4.
 getBestSupportedSize()
(CrimeCameraFragment.java)
...
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mCamera == null) return;
// ;
//
Camera.Parameters parameters = mCamera.getParameters();
Size s = getBestSupportedSize(parameters.getSupportedPreviewSizes(), w, h);
parameters.setPreviewSize(s.width, s.height);
s = getBestSupportedSize(parameters.getSupportedPictureSizes(), w, h);
parameters.setPictureSize(s.width, s.height);
mCamera.setParameters(parameters);

});

...

CriminalIntent Take!. ,
, LogCat CrimeCameraFragment.
CrimeCameraFragment , . API . CrimeFragment
.

CrimeFragment

337

CrimeFragment
CrimeFragment , CrimeCameraFragment
. .20.5
CrimeFragment CrimeCameraFragment.
Android

. 20.5. CrimeCameraActivity

CrimeFragment CrimeCameraActivity .
CrimeCameraFragment , , setResult(). ActivityManager
CrimePagerActivity onActivityResult(). FragmentManager, CrimePagerActivity, CrimeFragment
CrimeFragment.onActivityResult().

CrimeCameraActivity
CrimeFragment CrimeCameraActivity.
CrimeFragment.java ,
, CrimeCameraActivity .
20.5. CrimeCameraActivity (CrimeFragment.java)
public class CrimeFragment extends Fragment {
...
private static final int REQUEST_DATE = 0;
private static final int REQUEST_PHOTO = 1;
...

338

20. II:

20.5 ()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...

mPhotoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//
Intent i = new Intent(getActivity(), CrimeCameraActivity.class);
startActivity(i);
startActivityForResult(i, REQUEST_PHOTO);
}
});
...

CrimeCameraFragment
CrimeCameraFragment
CrimeCameraActivity.setResult(int, Intent). CrimeCameraFragment.
java ,

RESULT_OK, , RESULT_CANCELED, - .
20.6.

(CrimeCameraFragment.java)
public class CrimeCameraFragment extends Fragment {
private static final String TAG = "CrimeCameraFragment";
public static final String EXTRA_PHOTO_FILENAME =
"com.bignerdranch.android.criminalintent.photo_filename";
...
private Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
...
try {
...
} catch (Exception e) {
...
} finally {
...
}
Log.i(TAG, "JPEG saved at " + filename);
//
if (success) {
Intent i = new Intent();
i.putExtra(EXTRA_PHOTO_FILENAME, filename);
getActivity().setResult(Activity.RESULT_OK, i);
} else {

CrimeFragment

};

339

getActivity().setResult(Activity.RESULT_CANCELED);
}
getActivity().finish();

CrimeFragment
CrimeFragment
CriminalIntent.
CrimeFragment.java onActivityResult(),
, .
TAG CrimeFragment, .
20.7. (CrimeFragment.java)
public class CrimeFragment extends Fragment {
private static final String TAG = "CrimeFragment"
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
...
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;

if (requestCode == REQUEST_DATE) {
Date date = (Date)data
.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date);
updateDate();
} else if (requestCode == REQUEST_PHOTO) {
// Photo Crime
String filename = data
.getStringExtra(CrimeCameraFragment.EXTRA_PHOTO_FILENAME);
if (filename != null) {
Log.i(TAG, "filename: " + filename);
}
}

CriminalIntent CrimeCameraActivity.
LogCat , CrimeFragment .
, CrimeFragment ,
:
: Photo,
. Crime mPhoto
Photo. CrimeFragment Photo mPhoto Crime.

340

20. II:

CrimeFragment: CrimeFragment
ImageView Crime.
DialogFragment ImageFragment
.


. 20.6 CrimeFragment, Crime Photo.

. 20.6. CrimeFragment

Photo
com.bignerdranch.android.criminalintent. Photo java.lang.Object.
Photo.java , 20.8.
20.8. Photo (Photo.java)
...
public class Photo {
private static final String JSON_FILENAME = "filename";
private String mFilename;
/** Photo, */
public Photo(String filename) {
mFilename = filename;
}

341

public Photo(JSONObject json) throws JSONException {


mFilename = json.getString(JSON_FILENAME);
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put(JSON_FILENAME, mFilename);
return json;
}
public String getFilename() {
return mFilename;
}

: Photo .
Photo , JSON, Crime
Photo.

Crime
Crime Photo,
JSON ( 20.9).
20.9. Photo Crime (Crime.java)
public class Crime {
...
private static final String JSON_DATE = "date";
private static final String JSON_PHOTO = "photo";
...
private Date mDate = new Date();
private Photo mPhoto;
...
public Crime(JSONObject json) throws JSONException {
...
mDate = new Date(json.getLong(JSON_DATE));
if (json.has(JSON_PHOTO))
mPhoto = new Photo(json.getJSONObject(JSON_PHOTO));
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
...
json.put(JSON_DATE, mDate.getTime());
if (mPhoto != null)
json.put(JSON_PHOTO, mPhoto.toJSON());
return json;
}

342

20. II:

20.9 ()
...
public Photo getPhoto() {
return mPhoto;
}

public void setPhoto(Photo p) {


mPhoto = p;
}


CrimeFragment.java onActivityResult(),
Photo Crime.
20.10. Photo
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;
if (requestCode == REQUEST_DATE) {
Date date = (Date)data
.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date);
updateDate();
} else if (requestCode == REQUEST_PHOTO) {
// Photo Crime
String filename = data
.getStringExtra(CrimeCameraFragment.EXTRA_PHOTO_FILENAME);
if (filename != null) {
Log.i(TAG, "filename: " + filename);

Photo p = new Photo(filename);


mCrime.setPhoto(p);
Log.i(TAG, "Crime: " + mCrime.getTitle() + " has a photo");

CriminalIntent . LogCat , Crime .


Photo , Crime
? , ,
Photo - ,
. .

CrimeFragment
.
CrimeFragment ImageView.

343

. 20.7. CrimeFragment ImageView

ImageView
layout/fragment_crime.xml ImageView,
. 20.8.

. 20.8. CrimeFragment ImageView (layout/fragment_crime.xml)

344

20. II:

ImageView (. 20.9).

. 20.9. ImageView (layout-land/fragment_crime.xml)

CrimeFragment.java ImageView
onCreateView().
20.11. ImageView (CrimeFragment.java)
public class CrimeFragment extends Fragment {
...
private ImageButton mPhotoButton;
private ImageView mPhotoView;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
mPhotoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Launch the camera activity
Intent i = new Intent(getActivity(), CrimeCameraActivity.class);
startActivityForResult(i, REQUEST_PHOTO);
}
});

mPhotoView = (ImageView)v.findViewById(R.id.crime_imageView);
...

, ImageView ,
CriminalIntent.

345


ImageView , , ,
. - . , .
Android
8- .
, ,
,
.


ImageView
com.bignerdranch.android.criminalintent
PictureUtils. PictureUtils.java ,
.
20.12. PictureUtils (PictureUtils.java)
public class PictureUtils {
/**
* BitmapDrawable ,
* .
*/
@SuppressWarnings("deprecation")
public static BitmapDrawable getScaledDrawable(Activity a, String path) {
Display display = a.getWindowManager().getDefaultDisplay();
float destWidth = display.getWidth();
float destHeight = display.getHeight();
//
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > destHeight || srcWidth > destWidth) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / destHeight);
} else {
inSampleSize = Math.round(srcWidth / destWidth);
}
}
options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;

346

20. II:

20.12 ()

Bitmap bitmap = BitmapFactory.decodeFile(path, options);


return new BitmapDrawable(a.getResources(), bitmap);

Display.getWidth() Display.getHeight() (deprecated). .


, ImageView. ,
, . ,
onCreateView() ImageView. ,
. , ,
, .
CrimeFragment , ImageView.
20.13. showPhoto() (CrimeFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
}
private void showPhoto() {
// ,
Photo p = mCrime.getPhoto();
BitmapDrawable b = null;
if (p != null) {
String path = getActivity()
.getFileStreamPath(p.getFilename()).getAbsolutePath();
b = PictureUtils.getScaledDrawable(getActivity(), path);
}
mPhotoView.setImageDrawable(b);
}

CrimeFragment.java onStart() showPhoto(), , CrimeFragment.


20.14. (CrimeFragment.java)
...
private void showPhoto() {
// ,
Photo p = mCrime.getPhoto();
BitmapDrawable b = null;
if (p != null) {
String path = getActivity()
.getFileStreamPath(p.getFilename()).getAbsolutePath();
b = PictureUtils.getScaledDrawable(getActivity(), path);
}

347

mPhotoButton.setImageDrawable(b);

@Override
public void onStart() {
super.onStart();
showPhoto();
}

CrimeFragment.onActivityResult() showPhoto(),
CrimeCameraActivity.
20.15. showPhoto() onActivityResult() (CrimeFragment.java)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;
if (requestCode == REQUEST_PHOTO) {
// Photo Crime
String filename = data
.getStringExtra(CrimeCameraFragment.EXTRA_PHOTO_FILENAME);
if (filename != null) {
Photo p = new Photo(filename);
mCrime.setPhoto(p);
showPhoto();
Log.i(TAG, "Crime: " + mCrime.getTitle() + "has a photo");
}
}
}


PictureUtils BitmapDrawable,
ImageView ( ).
20.16. (PictureUtils.java)
public class PictureUtils {
/**
* ...
*/
@SuppressWarnings("deprecation")
public static BitmapDrawable getScaledDrawable(Activity a, String path) {
...
}
public static void cleanImageView(ImageView imageView) {
if (!(imageView.getDrawable() instanceof BitmapDrawable))
return;

//
BitmapDrawable b = (BitmapDrawable)imageView.getDrawable();
b.getBitmap().recycle();
imageView.setImageDrawable(null);

348

20. II:

Bitmap.recycle() . ,
, . Bitmap.recycle()
(native) , .
. ( Android.
Honeycomb Java Bitmap.)
recycle(),
. -
(finalizer),
.
.
,
. ,
( ) recycle()
.
CrimeFragment onStop() cleanImageView().
20.17. (CrimeFragment.java)
@Override
public void onStart() {
super.onStart();
showPhoto();
}
@Override
public void onStop() {
super.onStop();
PictureUtils.cleanImageView(mPhotoView);
}

onStart() onStop() .
, .
onResume() onPause(),
.
,
, , .
onResume() onPause(), . ,
, ,
.
CriminalIntent. ,
ImageView. CriminalIntent .
,
, .
CrimeCameraActivity ,
. , .
.

DialogFragment

349


DialogFragment

Crime.

. 20.10. DialogFragment

com.bignerdranch.android.criminalintent. ImageFragment; DialogFragment.


ImageFragment Crime. ImageFragment.java newInstance(String),
,
20.18.
20.18. ImageFragment (ImageFragment.java)
public class ImageFragment extends DialogFragment {
public static final String EXTRA_IMAGE_PATH =
"com.bignerdranch.android.criminalintent.image_path";
public static ImageFragment newInstance(String imagePath) {
Bundle args = new Bundle();

350

20. II:

20.18 ()

args.putSerializable(EXTRA_IMAGE_PATH, imagePath);
ImageFragment fragment = new ImageFragment();
fragment.setArguments(args);
fragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);

return fragment;

DialogFragment.STYLE_NO_TITLE,
, .20.10.
ImageFragment , AlertDialog.
, , onCreateView()
View ( onCreateDialog() Dialog).
ImageFragment.java onCreateView(), ImageView .
ImageView.
onDestroyView(), ,
.
20.19. ImageFragment (ImageFragment.java)

public class ImageFragment extends DialogFragment {


public static final String EXTRA_IMAGE_PATH =
"com.bignerdranch.android.criminalintent.image_path";
public static ImageFragment newInstance(String imagePath) {
...
}
private ImageView mImageView;
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup parent, Bundle savedInstanceState) {
mImageView = new ImageView(getActivity());
String path = (String)getArguments().getSerializable(EXTRA_IMAGE_PATH);
BitmapDrawable image = PictureUtils.getScaledDrawable(getActivity(), path);
mImageView.setImageDrawable(image);
}

return mImageView;

@Override
public void onDestroyView() {
super.onDestroyView();
PictureUtils.cleanImageView(mImageView);
}

351

, CrimeFragment.
CrimeFragment.java mPhotoView.
ImageFragment FragmentManager
CrimePagerActivity show() ImageFragment.
ImageFragment FragmentManager.
20.20. ImageFragment (CrimeFragment.java)
public class CrimeFragment extends Fragment {
...
private static final String DIALOG_IMAGE = "image";
...
@Override
@TargetApi(11)
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
...
mPhotoView = (ImageView)v.findViewById(R.id.crime_imageView);
mPhotoView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Photo p = mCrime.getPhoto();
if (p == null)
return;

});
...

FragmentManager fm = getActivity()
.getSupportFragmentManager();
String path = getActivity()
.getFileStreamPath(p.getFilename()).getAbsolutePath();
ImageFragment.newInstance(path)
.show(fm, DIALOG_IMAGE);

CriminalIntent. ,

.

. Crime
. API , . Photo
CrimeFragment ImageFragment.

.
Crime, . onActivityResult(int,

352

20. II:

int, Intent) CrimeFragment -

.

. CrimeFragment
/ , . Delete Photo ,
ImageView.

:
Android
19,
. .
: ?
, . API
, , .
- ,
SurfaceHolder.setType(int) SurfaceHolder.
SURFACE_TYPE_PUSH_BUFFERS, 19.
Android SurfaceHolder
. , setType() .
, ,
- . , BitmapDrawable
BitmapDrawable(Bitmap),
. ,
, , , View.setBackgroundDrawable(Drawable).
Display.getWidth() Display.getHeight(), . getSize(Point),
, getWidth() getHeight().
- ,
. , ,
, Microsoft Apple.
Microsoft API , .
, Microsoft . Microsoft
API, .
, . - Microsoft .
, Apple API ,
. Apple ,

: Android

353

. , API
. Apple ,
.
Apple , :
float destWidth;
float destHeight;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2) {
Point size = display.getSize();
destWidth = size.x;
destHeight = size.y;
} else {
destWidth = display.getWidth();
destHeight = display.getHeight();
}

, Apple getWidth() getHeight(), ,


. , .
. Android Microsoft,
. Android SDK
, ,
API . ,
. ,
, .
API ,
.

21

Android
(implicit intent).
, .
, , .
CriminalIntent

. ,
, .

. 21.1.

355

, .
,
, .
, CriminalIntent
:
CrimeFragment
;
Crime mSuspect, ;
.


CrimeFragment .
, .
21.1. (strings.xml)
<string name="take">Take!</string>
<string name="crime_suspect_text">Choose Suspect</string>
<string name="crime_report_text">Send Crime Report</string>
</resources>

layout/fragment_crime.xml Button,
.21.2. : LinearLayout,
.

. 21.2.
(layout/fragment_crime.xml)

LinearLayout,
.

356

21.

. 21.3.

.21.3, .
, ScrollView.
. 21.4.
ScrollView,
ScrollView.

. 21.4.
(layout_land/fragment_crime.xml)

357

CriminalIntent,
.


Crime.java, JSON
. JSON / JSON .
21.2. (Crime.java)
public class Crime {
...
private static final String JSON_PHOTO = "photo";
private static final String JSON_SUSPECT = "suspect";
...
private Photo mPhoto;
private String mSuspect;
public Crime(JSONObject json) throws JSONException {
mId = UUID.fromString(json.getString(JSON_ID));
...
if (json.has(JSON_PHOTO))
mPhoto = new Photo(json.getJSONObject(JSON_PHOTO));
if (json.has(JSON_SUSPECT))
mSuspect = json.getString(JSON_SUSPECT);
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
...
if (mPhoto != null)
json.put(JSON_PHOTO, mPhoto.toJSON());
json.put(JSON_SUSPECT, mSuspect);
return json;
}
public void setPhoto(Photo p) {
mPhoto = p;
}

public String getSuspect() {


return mSuspect;
}
public void setSuspect(String suspect) {
mSuspect = suspect;
}


, .

358

21.

, ,
. :
<string name="crime_report">%1$s! The crime was discovered on %2$s. %3$s, and %4$s

%1$s, %2$s .. . getString()


, .
strings.xml 21.3.
21.3. (strings.xml)
<string name="crime_suspect_text">Choose Suspect</string>
<string name="crime_report_text">Send Crime Report</string>
<string name="crime_report">%1$s!
The crime was discovered on %2$s. %3$s, and %4$s
</string>
<string name="crime_report_solved">The case is solved</string>
<string name="crime_report_unsolved">The case is not solved</string>
<string name="crime_report_no_suspect">There is no suspect.</string>
<string name="crime_report_suspect">The suspect is %s.</string>
<string name="crime_report_subject">CriminalIntent Crime Report</string>
<string name="send_report">Send crime report via</string>
</resources>

CrimeFragment.java , ,
.
21.4. getCrimeReport() (CrimeFragment.java)
private String getCrimeReport() {
String solvedString = null;
if (mCrime.isSolved()) {
solvedString = getString(R.string.crime_report_solved);
} else {
solvedString = getString(R.string.crime_report_unsolved);
}
String dateFormat = "EEE, MMM dd";
String dateString = DateFormat.format(dateFormat, mCrime.getDate()).toString();

String suspect = mCrime.getSuspect();


if (suspect == null) {
suspect = getString(R.string.crime_report_no_suspect);
} else {
suspect = getString(R.string.crime_report_suspect, suspect);
}
String report = getString(R.string.crime_report,
mCrime.getTitle(), dateString, solvedString, suspect);
return report;

,
.

359


Intent , .
, ,
, .
Intent i = new Intent(getActivity(), CrimeCameraActivity.class);
startActivity(i);

, , ,
. , .


, .
(action)
Intent. , URL- Intent.ACTION_VIEW,
Intent.ACTION_SEND.
,
(, URL -), URI URI
, ContentProvider.
, , MIME (, text/html
audio/mpeg3). ,
.
, , ,
. Android android.intent.category.
LAUNCHER ,
. , android.intent.category.INFO ,
, .
- Intent.ACTION_VIEW Uri URL- .
. ( ,
.)
ACTION_VIEW
. , -,
, ACTION_VIEW.
<activity
android:name=".BrowserActivity"
android:label="@string/app_name" >

360

21.

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" android:host="www.bignerdranch.com" />
</intent-filter>
</activity>

DEFAULT . action
, ,
DEFAULT . DEFAULT
. ( LAUNCHER,
23.)
, , .
.
, . ,
.


, ,
CriminalIntent. ,
, ;
. ,
ACTION_SEND. , text/plain.
CrimeFragment.onCreateView() Send Crime
Report .
startActivity(Intent).
21.5. (CrimeFragment.java)
...
Button reportButton = (Button)v.findViewById(R.id.crime_reportButton);
reportButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_TEXT, getCrimeReport());
i.putExtra(Intent.EXTRA_SUBJECT,
getString(R.string.crime_report_subject));
startActivity(i);
}
});
}

return v;

361

Intent, , .
,
.
Intent. , , ,
.
.
, Intent.
, ,
, .
CriminalIntent Send Crime Report.

, ,
:
, . , - . 21.5. ,

.

.
, : ,
,
.
,
. , CriminalIntent :
,
.
, .
, , Intent
:
public static Intent createChooser(Intent target, String title)

, createChooser(), startActivity().
CrimeFragment.java ,
.

362

21.

21.6. (CrimeFragment.java)
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_TEXT, getCrimeReport());
i.putExtra(Intent.EXTRA_SUBJECT,
getString(R.string.crime_report_subject));
i = Intent.createChooser(i, getString(R.string.send_report));
startActivity(i);
}

CriminalIntent Send Crime Report.


, ,
.

. 21.6.

Android
, . .
Intent.ACTION_PICK, ContactsContract.

363

Contacts.CONTENT_URI. , Android

.
,
startActivityForResult() . CrimeFragment.java .
21.7. (CrimeFragment.java)
...
private static final int REQUEST_PHOTO = 1;
private static final int REQUEST_CONTACT = 2;
...
private ImageButton mPhotoButton;
private Button mSuspectButton;
...

onCreateView() .
startActivityForResult(). (
Crime).
21.8. (CrimeFragment.java)
...
mSuspectButton = (Button)v.findViewById(R.id.crime_suspectButton);
mSuspectButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_PICK,
ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(i, REQUEST_CONTACT);
}
});
if (mCrime.getSuspect() != null) {
mSuspectButton.setText(mCrime.getSuspect());
}
}

return v;

CriminalIntent Choose Suspect.


.
,
. : , .
,
, .

364

21.

. 21.7.


.
, Android
API
ContentProvider. . ContentProvider ContentResolver.

ACTION_PICK, onActivityResult().
URI , .
CrimeFragment.java onActivityResult() CrimeFragment.
21.9. (CrimeFragment.java)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;
if (requestCode == REQUEST_DATE) {
...
} else if (requestCode == REQUEST_PHOTO) {

365

...
} else if (requestCode == REQUEST_CONTACT) {
Uri contactUri = data.getData();
// ,
// .
String[] queryFields = new String[] {
ContactsContract.Contacts.DISPLAY_NAME
};
// - contactUri
// "where"
Cursor c = getActivity().getContentResolver()
.query(contactUri, queryFields, null, null, null);
//
if (c.getCount() == 0) {
c.close();
return;
}

// - .
c.moveToFirst();
String suspect = c.getString(0);
mCrime.setSuspect(suspect);
mSuspectButton.setText(suspect);
c.close();

21.9 ,
. Cursor . ,
,
. ,
Crime Choose Suspect.
( .
. , Contacts Provider API: http://developer.android.com/guide/topics/providers/
contacts-provider.html.)


? . .
URI , Intent.
FLAG_GRANT_READ_URI_PERMISSION. Android,
CriminalIntent
. ,
, .

366

21.


, , - .
Android
.
, ?
, .
,
PackageManager. :
PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(yourIntent, 0);
boolean isIntentSafe = activities.size() > 0;

queryIntentActivities() PackageManager.
, , . ,
,
.
onCreateView() ,
.

.
, . .
,
URI :
Uri number = Uri.parse("tel:5551234");

Intent.ACTION_DIAL Intent.ACTION_
CALL. ACTION_CALL
, ; ACTION_DIAL
, .
ACTION_DIAL. ACTION_CALL , . ,
.

22

CriminalIntent ,
. .22.1
-.

. 22.1.

368

22.

AVD. AVD,
WindowAndroid Virtual Device Manager. New
(Device) AVD , .22.2.
AVD API 17.

. 22.2. AVD


CrimeListActivity
, . ,
.
CrimeListActivity CrimeListFragment, CrimeFragment, . 22.3.

. 22.3.

SingleFragmentActivity

369

:
SingleFragmentActivity,
;
, ;
CrimeListActivity,
, .

SingleFragmentActivity
CrimeListActivity SingleFragmentActivity.
SingleFragmentActivity ,
activity_fragment.xml.

SingleFragmentActivity , ,
.
SingleFragmentActivity.java ,
, .
22.1. SingleFragmentActivity (SingleFragmentActivity.java)
public abstract class SingleFragmentActivity extends FragmentActivity {
protected abstract Fragment createFragment();
protected int getLayoutResId() {
return R.layout.activity_fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

if (fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

SingleFragmentActivity ,
, getLayoutResId()
, activity_fragment.xml.

370

22.

Package Explorer res/layout/


Android XML. ,
Layout, activity_twopane.xml
LinearLayout.
XML , .22.4.

. 22.4. (layout/activity_twopane.xml)

: FrameLayout fragmentContainer, SingleFragmentActivity.onCreate()


, . ,
createFragment(), .
CrimeListActivity; getLayoutResId() , R.layout.activity_twopane.
22.2. (CrimeListActivity.java)
public class CrimeListActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
return new CrimeListFragment();
}

@Override
protected int getLayoutResId() {
return R.layout.activity_twopane;
}

CriminalIntent ,
(.22.5).

371

, .
.

. 22.5.

CrimeListActivity .
-.

-
- (alias resource) , . - res/values/
refs.xml.
-,
activity_fragment.xml, activity_twopane.xml.
Package Explorer res/layout/
Android XML. ,
Values, refs.xml,
resources Finish. , 22.3.
22.3. - (res/values/refs.xml)
<resources>
<item name="activity_masterdetail" type="layout">@layout/activity_fragment</item>
</resources>

.
: R.layout.activity_masterdetail. :

372

22.

type .
res/values/, R.layout.
R.layout.activi
ty_fragment. CrimeListActivity.
22.4. (CrimeListActivity.java)
@Override
protected int getLayoutResId() {
return R.layout.activity_twopane;
return R.layout.activity_masterdetail;
}

CriminalIntent ,
. CrimeListActivity
.


res/values/, . , CrimeListActivity .
, activity_masterdetail
activity_twopane.xml.
Package Explorer res/ values-sw600dp. res/values/refs.xml res/
values-sw600dp/ , .
22.5.

(res/values-sw600dp/refs.xml)
<resources>
<item name="activity_masterdetail" type="layout">@layout/activity_fragment</item>
<item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>
</resources>

-sw600dp ? sw
Smallest Width ( ),
, , .
-sw600dp, : , 600dp .
.
: sw Android 3.2. ,
Android 3.0 3.1 .
, -xlarge.
res/
values-xlarge. res/values-sw600dp/refs.xml res/values-xlarge/.

373

, ,
22.6.
22.6.
 3.2
(res/values-xlarge/refs.xml)
<resources>
<item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>
</resources>

-xlarge 720960dp. , Android 3.2. -sw600dp.


CriminalIntent . ,
- , .

:
, , ,
CrimeFragment CrimeListActivity.
,
CrimeListFragment.onListItemClick() . CrimePagerActivity onListItemClick()
FragmentManager, CrimeListActivity,
, CrimeFragment .
:
public void onListItemClick(ListView l, View v, int position, long id) {
// Crime
Crime crime = ((CrimeAdapter)getListAdapter()).getItem(position);
// CrimeFragment
Fragment fragment = CrimeFragment.newInstance(crime.getId());
FragmentManager fm = getActivity().getSupportFragmentManager();
fm.beginTransaction()
.add(R.id.detailFragmentContainer, fragment)
.commit();
}

, Android. ,
.
FragmentManager , , ,
-, .
, CrimeListFragment CrimeFragment
CrimeListActivity , CrimeListActivity detailFragmentContainer.
- CrimeListFragment, CrimeListFragment.

374

22.


-, .
- - .


-, Callbacks.
, -. , ,
.
, .
CrimeListFragment.Callbacks
Callbacks,
, Callbacks. - Callbacks, .
Fragment:
public void onAttach(Activity activity)

(
, ).
null :
public void onDetach()

null, , .
CrimeListFragment.java CrimeListFragment
Callb acks . mCallbacks
onAttach(Activity) onDetach(), .
22.7. (CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
private ArrayList<Crime> mCrimes;
private boolean mSubtitleVisible;
private Callbacks mCallbacks;
/**
* -.
*/
public interface Callbacks {
void onCrimeSelected(Crime crime);
}
@Override

375

public void onAttach(Activity activity) {


super.onAttach(activity);
mCallbacks = (Callbacks)activity;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}

CrimeListFragment -. , CrimeListFragment.
Callbacks, CrimeListFragment .
, CrimeListFragment CrimeListFragment.Callbacks. ,
- CrimeListFragment.Callbacks. , .
CrimeListActivity CrimeListFragment.Callbacks.
onCrimeSelected(Crime) .
22.8. (CrimeListActivity.java)
public class CrimeListActivity extends SingleFragmentActivity
implements CrimeListFragment.Callbacks {
@Override
protected Fragment createFragment() {
return new CrimeListFragment();
}
@Override
protected int getLayoutResId() {
return R.layout.activity_twopane;
}

public void onCrimeSelected(Crime crime) {


}

CrimeListFragment onListItemClick(), ,
.
, CrimeListActivity.
onCrimeSelected(Crime).
onCrimeSelected(Crime) CrimeListActivity
:

CrimePagerActivity;
CrimeFragment
detailFragmentContainer.

376

22.

, , . , detailFragmentContainer. .
, ,
; ,
detailFragmentContainer CrimeFragment.
detailFragmentContainer, , CrimeFragment detail
FragmentContainer ( ) CrimeFragment,
.
CrimeListActivity.java onCrimeSelected(Crime),
.
22.9. CrimeFragment (CrimeListActivity.java)
public void onCrimeSelected(Crime crime) {
if (findViewById(R.id.detailFragmentContainer) == null) {
// CrimePagerActivity
Intent i = new Intent(this, CrimePagerActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, crime.getId());
startActivity(i);
} else {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment oldDetail = fm.findFragmentById(R.id.detailFragmentContainer);
Fragment newDetail = CrimeFragment.newInstance(crime.getId());
if (oldDetail != null) {
ft.remove(oldDetail);
}
ft.add(R.id.detailFragmentContainer, newDetail);
ft.commit();
}
}

, CrimeListFragment onCrimeSelected(Crime)
, CrimePagerActivity.
CrimeListFragment.java onListItemClick() onOptions
ItemSelected(MenuItem) , Callbacks.
onCrimeSelected(Crime).
22.10. (CrimeListFragment.java)
public void onListItemClick(ListView l, View v, int position, long id) {
// Crime
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
// CrimePagerActivity
Intent i = new Intent(getActivity(), CrimePagerActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
startActivity(i);
mCallbacks.onCrimeSelected(c);
}

377

...
@TargetApi(11)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_crime:
Crime crime = new Crime();
CrimeLab.get(getActivity()).addCrime(crime);
Intent i = new Intent(getActivity(), CrimePagerActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, crime.getId());
startActivity(i);
((CrimeAdapter)getListAdapter()).notifyDataSetChanged();
mCallbacks.onCrimeSelected(crime);
return true;
...
}
}

onOptionsItemSelected() . ,

( ).
CriminalIntent . ;
CrimeFragment detailFragmentContainer.
- , CrimeFragment .

. 22.6.

.
CrimeListFragment.onResume().

378

22.

CrimeListFragment CrimeFragment. CrimeListFragment


CrimeFragment,

.

CrimeFragment.

CrimeFragment.Callbacks
CrimeFragment :
public interface Callbacks {
void onCrimeUpdated(Crime crime);
}

CrimeFragment onCrimeUpdated(Crime) -
Crime. onCrimeUpdated(Crime)
CrimeListActivity CrimeListFragment.

CrimeFragment, CrimeListFragment
, CrimeListFragment.
22.11. updateUI() (CrimeListFragment.java)
public class CrimeListFragment extends ListFragment {
...
public void updateUI() {
((CrimeAdapter)getListAdapter()).notifyDataSetChanged();
}

CrimeFragment.java ,
mCallbacks onAttach() onDetach().
22.12. CrimeFragment (CrimeFragment.java)
...
private ImageView mPhotoView;
private Button mSuspectButton;
private Callbacks mCallbacks;
/**
* -
*/
public interface Callbacks {
void onCrimeUpdated(Crime crime);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (Callbacks)activity;
}
@Override
public void onDetach() {
super.onDetach();

379

mCallbacks = null;

public static CrimeFragment newInstance(UUID crimeId) {


...
}

CrimeFragment.Callbacks CrimeListActivity,
onCrimeUpdated(Crime).
22.13. (CrimeListActivity.java)
public void onCrimeUpdated(Crime crime) {
FragmentManager fm = getSupportFragmentManager();
CrimeListFragment listFragment = (CrimeListFragment)
fm.findFragmentById(R.id.fragmentContainer);
listFragment.updateUI();
}

CrimeFragment.java onCrimeUpdated(Crime)
Crime.
22.14. onCrimeUpdated(Crime) (CrimeFragment.java)
@Override
@TargetApi(11)
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, parent, false);
...

mTitleField = (EditText)v.findViewById(R.id.crime_title);
mTitleField.setText(mCrime.getTitle());
mTitleField.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence c, int start, int before, int count)

});

mSolvedCheckBox = (CheckBox)v.findViewById(R.id.crime_solved);
mSolvedCheckBox.setChecked(mCrime.isSolved());
mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)

});
...
}

}
...

mCrime.setTitle(c.toString());
mCallbacks.onCrimeUpdated(mCrime);
getActivity().setTitle(mCrime.getTitle());

//
mCrime.setSolved(isChecked);
mCallbacks.onCrimeUpdated(mCrime);

return v;

380

22.

onCrimeUpdated(Crime) onActivityResult(),
, .
, CrimeFragment
.
22.15. onCrimeUpdated(Crime) (2) (CrimeFragment.java)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) return;
if (requestCode == REQUEST_DATE) {
Date date = (Date)data.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date);
mCallbacks.onCrimeUpdated(mCrime);
updateDate();
} else if (requestCode == REQUEST_PHOTO) {
// Photo Crime
String filename = data
.getStringExtra(CrimeCameraFragment.EXTRA_PHOTO_FILENAME);
if (filename != null) {
Photo p = new Photo(filename);
mCrime.setPhoto(p);
mCallbacks.onCrimeUpdated(mCrime);
showPhoto();
}
} else if (requestCode == REQUEST_CONTACT) {
...

c.moveToFirst();
String suspect = c.getString(0);
mCrime.setSuspect(suspect);
mCallbacks.onCrimeUpdated(mCrime);
mSuspectButton.setText(suspect);
c.close();

CrimeListActivity CrimeFragment.Callbacks.

CriminalIntent . :
, CrimeFragment, CrimeFragment.
Callbacks. CrimeFragment.Callbacks CrimePagerActivity.
CrimePagerActivity , onCrime
Updated(Crime) . CrimePagerActivity
CrimeFragment, onResume().
22.16. CrimeFragment.Callbacks (CrimePagerActivity.java)
public class CrimePagerActivity extends FragmentActivity
implements CrimeFragment.Callbacks {
...

public void onCrimeUpdated(Crime crime) {


}

381

CriminalIntent , ListView CrimeFragment.


, , .

. 22.7. , ,

CriminalIntent . 13
, ,
, . ?
, CriminalIntent.

:

Android 3.2
.
small, normal, large xlarge.
.22.1 .
22.1.

small

320 x 426dp

normal

320 x 470dp

large

480 x 640dp

xlarge

720 x 960dp

382

22.

Android3.2.
, .
.22.2 .
22.2.

wXXXdp

: XXX dp

hXXXdp

: XXX dp

swXXXdp

: ( )
XXX dp

, ,
, 300dp.
res/layout-w300dp (w width,
). h
(Height, ).
,
.
sw (Smallest Width, ).
,
, . 1024 800, sw
800. 800 1024, sw 800.

23

-,
Android. , ,
Android.

NerdLauncher
(NewAndroid Application Project) ,
CriminalIntent (. 23.1).
NerdLauncher com.bignerdranch.android.nerdlauncher.
, . . NerdLauncherActivity
Finish.
NerdLauncherActivity SingleFragmentActivity, . Package Explorer
SingleFragmentActivity.java CriminalIntent. com.
bignerdranch.android.nerdlauncher. Eclipse .
activity_fragment.xml. res/layout/activity_fragment.xml res/layout NerdLauncher.
NerdLauncher .
, .
, .

384

23.

. 23.1. NerdLauncher

NerdLauncherFragment ListFragment ,
ListView,
ListFragment.
NerdLauncherFragment
android.support.v4.app.ListFragment. .
NerdLauncherActivity.java NerdLauncherActivity
SingleFragmentActivity. createFragment() , NerdLauncherFragment.
23.1. SingleFragmentActivity (NerdLauncherActivity.java)
public class NerdLauncherActivity extends Activity SingleFragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nerd_launcher);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_nerd_launcher, menu);
return true;
}

@Override
public Fragment createFragment() {
return new NerdLauncherFragment();
}

385


NerdLauncher . NerdLauncher ,
. MAIN
LAUNCHER. :
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

NerdLauncherFragment.java onCreate(Bundle)
. PackageManager , .
, PackageManager.
23.2. PackageManager (NerdLauncherFragment.java)
public class NerdLauncherFragment extends ListFragment {
private static final String TAG = "NerdLauncherFragment";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent startupIntent = new Intent(Intent.ACTION_MAIN);
startupIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(startupIntent, 0);

Log.i(TAG, "I've found " + activities.size() + " activities.");

NerdLauncher LogCat, PackageManager.


CriminalIntent .
, ,
startActivity(Intent):
Intent i = new Intent(Intent.ACTION_SEND);
... //
i = Intent.createChooser(i, getString(R.string.send_report));
startActivity(i);

? ,
MAIN/LAUNCHER
MAIN/LAUNCHER, startActivity().
, startActivity(Intent) ,
.
.

386

23.

startActivity() ( startActivityForResult()), Intent.CATEGORY_DEFAULT.


, ,
, startActivity(),
DEFAULT.
MAIN/LAUNCHER
, . , ,
,
CATEGORY_DEFAULT.
MAIN/LAUNCHER CATEGORY_DEFAULT,
, startActivity(), .
PackageManager MAIN/LAUNCHER.
ListView NerdLauncherFragment. (label) , . ,
, , ,
.
ResolveInfo, PackageManager.
ResolveInfo , PackageManager, ,
ResolveInfo.loadLabel().
23.3. (NerdLauncherFragment.java)
...
Log.i("NerdLauncher", "I've found " + activities.size() + " activities.");
Collections.sort(activities, new Comparator<ResolveInfo>() {
public int compare(ResolveInfo a, ResolveInfo b) {
PackageManager pm = getActivity().getPackageManager();
return String.CASE_INSENSITIVE_ORDER.compare(
a.loadLabel(pm).toString(),
b.loadLabel(pm).toString());
}
});

ArrayAdapter, , ListView.
23.4. (NerdLauncherFragment.java)
...
Collections.sort(activities, new Comparator<ResolveInfo></ResolveInfo>() {
...
}
});

387

ArrayAdapter<ResolveInfo> adapter = new ArrayAdapter<ResolveInfo>(


getActivity(), android.R.layout.simple_list_item_1, activities) {
public View getView(int pos, View convertView, ViewGroup parent) {
View v = super.getView(pos, convertView, parent);
// , simple_list_item_1
// TextView; .
TextView tv = (TextView)v;
ResolveInfo ri = getItem(pos);
tv.setText(ri.loadLabel(pm));
return v;
}
};
setListAdapter(adapter);

NerdLauncher; ListView,
.

. 23.2.



. .
.

388

23.


ResolveInfo , . ResolveInfo ActivityInfo. ( ,
ResolveInfo, .)
NerdLauncherFragment.java onListItemClick() ActivityInfo .
, .
23.5. onListItemClick() (NerdLauncherFragment.java)
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
ResolveInfo resolveInfo = (ResolveInfo)l.getAdapter().getItem(position);
ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) return;
Intent i = new Intent(Intent.ACTION_MAIN);
i.setClassName(activityInfo.applicationInfo.packageName, activityInfo.name);
}

startActivity(i);

: . ,
, .

, .
, .
23.5
Intent:
public Intent setClassName(String packageName, String className)

,
. Intent,
Context Class:
public Intent(Context packageContext, Class<?> cls)

ComponentName
, . Activity Class
Intent, Activity.
ComponentName
Intent :
public Intent setComponent(ComponentName component)

setClassName(), , .
NerdLauncher , .

389


Android
. (task) , .
, . Back .
Back , .

. , , .

.
.
,
.

Back


( )

. 23.3.

390

23.

.
CriminalIntent .
, CriminalIntent (, ).
, , .
, NerdLauncher,
NerdLaucher. , CriminalIntent NerdLauncher . ( Recents,
; Home.)
CriminalIntent. CrimeListActivity NerdLauncher. NerdLauncher
CriminalIntent,
.

. 23.4. CriminalIntent

, NerdLauncher .

, .
, .
NerdLauncher CriminalIntent.
, CriminalIntent .

NerdLauncher

391

23.6. (NerdLauncherFragment.java)
Intent i = new Intent(Intent.ACTION_MAIN);
i.setClassName(activityInfo.applicationInfo.packageName, activityInfo.name);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);

. 23.5. CriminalIntent

CriminalIntent NerdLauncher
CriminalIntent. FLAG_ACTIVITY_NEW_TASK
. CrimeListActivity , Android
.

NerdLauncher

, ?
NerdLauncher
. AndroidManifest.xml NerdLauncher
.
23.7. NerdLauncher (AndroidManifest.xml)
<intent-filter>
<action android:name="android.intent.action.MAIN" />

392

23.

<category android:name="android.intent.category.LAUNCHER" />


<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

HOME DEFAULT , NerdLauncher . Home


NerdLauncher.
( NerdLauncher ,
, SettingsApplicationsManage Applications. All,
NerdLauncher Launch by default.
Home
.)

. ,
ResolveInfo.loadLabel()
. ResolveInfo
loadIcon() , .
: NerdLauncher .
, NerdLauncher
.
ActivityManager, , , . PackageManager
Activity getActivityManager()
.
ActivityManager Activity.
getSystemService() Activity.ACTIVITY_SERVICE.
getRunningTasks() , ( ).
, moveTaskToFront().
Android .

:
.
, ,
.
, , , ..
( , ) (thread). Android
Dalvik.

393

, Android ( ).
,
.
,
. , -
,
(multi-threading),
Android , .
. , .
. ,
.
, ,
. ,
CriminalIntent NerdLauncher CriminalIntent ,
CrimeListActivity .
CriminalIntent.
, , ,
.
CriminalIntent, CriminalIntent
.
CriminalIntent

CriminalIntent)

. 23.6.

, Back , .
. Android? ,
Android . Home ,
. , , . ,
Google Play ,
.

24


,
, . ,

.
,
. , .
, , .
,
. ,
;
.

, .24.1.
. 24.1.
RemoteControl
RemoteControl
, .24.1.
.
, .
Delete . Enter
.

RemoteControl

395

RemoteControl
Android Application ,
.24.2.

. 24.2. RemoteControl

RemoteControlActivity.

RemoteControlActivity
RemoteControlActivity SingleFragmentActivity,
SingleFragmentActivity.java CriminalIntent com.
bignerdranch.android.remotecontrol. activity_fragment.xml
res/layout RemoteControl.
RemoteControlActivity.java. RemoteControlActivity
SingleFragmentActivity; RemoteControlFragment. ( ). ,
RemoteControlActivity.onCreate(),
.
24.1. RemoteControlActivity (RemoteControlActivity.java)
public class RemoteControlActivity extends Activity SingleFragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

396

24.

24.1 ()
}

setContentView(R.layout.activity_remote_control);

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_remote_control, menu);
return true;
}
@Override
protected Fragment createFragment() {
return new RemoteControlFragment();
}

@Override
public void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
}

AndroidManifest.xml .
24.2. (AndroidManifest.xml)
<activity
android:name="com.bignerdranch.android.remotecontrol.RemoteControlActivity"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

RemoteControlFragment
Package Explorer activity_remote_control.xml fragment_remote_control.xml. , .
fragment_remote_control.xml XML 24.3.
24.3. (layout/fragment_remote_control.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RemoteControlActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"

RemoteControl

397

android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" />
</RelativeLayout>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_remote_control_tableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="*" >
<TextView
android:id="@+id/fragment_remote_control_selectedTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="center"
android:text="0"
android:textSize="50dp" />
<TextView
android:id="@+id/fragment_remote_control_workingTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="15dp"
android:background="#555555"
android:gravity="center"
android:text="0"
android:textColor="#cccccc"
android:textSize="20dp" />
<TableRow android:layout_weight="1" >
<Button
android:id="@+id/fragment_remote_control_zeroButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="0" />
<Button
android:id="@+id/fragment_remote_control_oneButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="1" />
<Button
android:id="@+id/fragment_remote_control_enterButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="Enter" />
</TableRow>
</TableLayout>

android:stretchColumns="*" ,
. ,
dp sp. , .
, RemoteControlFragment.
android.support.v4.app.Fragment. RemoteControlFragment.
java onCreateView() .

398

24.

24.4. RemoteControlFragment (RemoteControlFragment.java)


public class RemoteControlFragment extends Fragment {
private TextView mSelectedTextView;
private TextView mWorkingTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_remote_control, parent, false);
mSelectedTextView = (TextView)v
.findViewById(R.id.fragment_remote_control_selectedTextView);
mWorkingTextView = (TextView)v
.findViewById(R.id.fragment_remote_control_workingTextView);
View.OnClickListener numberButtonListener = new View.OnClickListener() {
public void onClick(View v) {
TextView textView = (TextView)v;
String working = mWorkingTextView.getText().toString();
String text = textView.getText().toString();
if (working.equals("0")) {
mWorkingTextView.setText(text);
} else {
mWorkingTextView.setText(working + text);
}
}
};
Button zeroButton = (Button)v
.findViewById(R.id.fragment_remote_control_zeroButton);
zeroButton.setOnClickListener(numberButtonListener);
Button oneButton = (Button)v
.findViewById(R.id.fragment_remote_control_oneButton);
oneButton.setOnClickListener(numberButtonListener);
Button enterButton = (Button) v
.findViewById(R.id.fragment_remote_control_enterButton);
enterButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
CharSequence working = mWorkingTextView.getText();
if (working.length() > 0)
mSelectedTextView.setText(working);
mWorkingTextView.setText("0");
}
});

return v;

, .
, .
, ,

399

, 0.
Enter
.
RemoteControl. . , ?

. 24.3.


, , .
. ,
, . ,
?
, Android ,
.
CSS. XML -. : ,
, .
, , <resources> XML
res/values. , , ,
styles.xml.

400

24.

Android styles.xml. ( :
RemoteControl
Android; ,
.)
, ,
RemoteButton. , .
24.5. RemoteControl (values/styles.xml)
<resources>
<style name="AppTheme" parent="android:Theme.Light" />
<style name="RemoteButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
</style>
</resources>

<style> <item>. ,
XML, , .
RemoteButton,
. ,
.
, style .
fragment_remote_control.xml:
.
24.6. (layout/activity_remote.xml)
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
>
...
<TableRow android:layout_weight="1" >
<Button
android:id="@+id/fragment_remote_control_zeroButton"
android:layout_width="0dp"
android:layout_height="match_parent"
style="@style/RemoteButton"
android:text="0" />
<Button
android:id="@+id/fragment_remote_control_oneButton"
android:layout_width="0dp"
android:layout_height="match_parent"
style="@style/RemoteButton"
android:text="1" />
<Button

401

android:id="@+id/fragment_remote_control_enterButton"
android:layout_width="0dp"
android:layout_height="match_parent"
style="@style/RemoteButton"
android:text="Enter" />
</TableRow>
</TableLayout>

RemoteControl. , ,
.


.
12 ,
.
34 .
. res/layout/
button_row.xml .
24.7. (layout/button_row.xml)
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android" >
<Button style="@style/RemoteButton" />
<Button style="@style/RemoteButton" />
<Button style="@style/RemoteButton" />
</TableRow>

. ?
include.
24.8. (layout/fragment_remote_control.xml)
<TableRow android:layout_weight="1" >
<Button
android:id="@+id/fragment_remote_control_zeroButton"
style="@style/RemoteButton"
android:text="0" />
<Button
android:id="@+id/fragment_remote_control_oneButton"
style="@style/RemoteButton"
android:text="1" />
<Button
android:id="@+id/fragment_remote_control_enterButton"
style="@style/RemoteButton"
android:text="Enter" />
</TableRow>
<include
android:layout_weight="1"
layout="@layout/button_row" />

402

24.

24.8 ()
<include
android:layout_weight="1"
layout="@layout/button_row" />
<include
android:layout_weight="1"
layout="@layout/button_row" />
<include
android:layout_weight="1"
layout="@layout/button_row" />

, , , layout/button_row.xml,
. ,
, .
,
. .
24.9. (RemoteControlFragment.java)
View.OnClickListener numberButtonListener = new View.OnClickListener() {
...
};
Button zeroButton = (Button)v.findViewById(R.id.fragment_remote_control_zeroButton);
zeroButton.setOnClickListener(numberButtonListener);
Button oneButton = (Button)v.findViewById(R.id.fragment_remote_control_oneButton);
oneButton.setOnClickListener(numberButtonListener);
TableLayout tableLayout = (TableLayout)v
.findViewById(R.id.fragment_remote_control_tableLayout);
int number = 1;
for (int i = 2; i < tableLayout.getChildCount() - 1; i++) {
TableRow row = (TableRow)tableLayout.getChildAt(i);
for (int j = 0; j < row.getChildCount(); j++) {
Button button = (Button)row.getChildAt(j);
button.setText("" + number);
button.setOnClickListener(numberButtonListener);
number++;
}
}

for 2, ,
.
numberButtonListener, , .
. :
Delete Enter.
24.10. (RemoteControlFragment.java)
for (int i = 2; i < tableLayout.getChildCount() - 1; i++) {
...
}
TableRow bottomRow = (TableRow)tableLayout
.getChildAt(tableLayout.getChildCount() - 1);

403

Button deleteButton = (Button)bottomRow.getChildAt(0);


deleteButton.setText("Delete");
deleteButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mWorkingTextView.setText("0");
}
});
Button zeroButton = (Button)bottomRow.getChildAt(1);
zeroButton.setText("0");
zeroButton.setOnClickListener(numberButtonListener);
Button enterButton = (Button)
v.findViewById(R.id.fragment_remote_control_enterButton);
Button enterButton = (Button)bottomRow.getChildAt(2);
enterButton.setText("Enter");
enterButton.setOnClickListener(new View.OnClickListener() {
...
});

.
, .
.

. 24.4. ,

, ,
. RemoteButton.

404

24.

24.11. (values/styles.xml)
<style name="RemoteButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:textColor">#556699</item>
<item name="android:textSize">20dp</item>
<item name="android:layout_margin">3dp</item>
</style>

. 24.5. !

: include merge
include RemoteButton ( ). :
<include layout="@layout/some_partial_layout"/>


@layout/some_partial_layout.
include
, ,
.

405

, , . ,
.
include , . -,

;
, . -,
android:id android:layout_*
, include.
,
.
merge include.
.
merge, merge
include,
merge .
, .
, , merge .
: XML ,
.

.
Delete Enter , , .
, .
, RemoteButton, .
.
, , .
. parent ,
. ,
(, ParentStyleName.
MyStyleName).

25


, .
, , Android .
, .

. 25.1. RemoteControl

(drawables). Android ,

XML

407

, , , Drawable,
. : BitmapDrawable,
.
:
, , 9- .
XML,
XML.

XML
XML, : android:background .
25.1. (values/styles.xml)
<style name="RemoteButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:textColor">#556699</item>
<item name="android:textSize">20dp</item>
<item name="android:layout_margin">3dp</item>
<item name="android:background">#ccd7ee</item>
</style>

. 25.2. ?

408

25.

. ,
.
- ?
Button , View .
, Drawable.
.
,
.
XML.
XML , drawable ( ).
Package Explorer res/drawable.
XML button_shape_normal.xml.
shape. ( normal, ?
, .)
25.2.

(drawable/button_shape_normal.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners android:radius="3dp" />
<gradient
android:angle="90"
android:endColor="#cccccc"
android:startColor="#acacac" />
</shape>

. corners
, gradient /
.
shape : ,
, .., .
http://developer.android.com/guide/topics/resources/
drawable-resource.html.
styles.xml Button, Drawable .
25.3. (values/styles.xml)
<style name="RemoteButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:textColor">#556699</item>
<item name="android:textSize">20dp</item>
<item name="android:layout_margin">3dp</item>

409

<item name="android:background">#ccd7ee</item>
<item name="android:background">@drawable/button_shape_normal</item>
</style>

RemoteControl , .

. 25.3.


, .
Button
(state list).
,
View. (
18.) ,
, .
.
, , .
Package Explorer button_shape_normal.xml
button_shape_pressed.xml. button_shape_pressed.xml
180 , .

410

25.

25.4. (drawable/button_shape_pressed.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners android:radius="3dp" />
<gradient
android:angle="90"
android:angle="270"
android:endColor="#cccccc"
android:startColor="#acacac" />
</shape>

.
selector
item, .
res/drawable/, XML button_shape.xml selector.
25.5. (drawable/button_shape.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_shape_normal"
android:state_pressed="false"/>
<item android:drawable="@drawable/button_shape_pressed"
android:state_pressed="true"/>
</selector>

,
. , ,
,
. ,
, .
res/drawable/ button_text_color.xml.
25.6.

(drawable/button_text_color.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" android:color="#ffffff"/>
<item android:state_pressed="true" android:color="#556699"/>
</selector>

styles.xml Button, .

411

25.7. (values/styles.xml)
<style name="RemoteButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:textColor">#556699</item>
<item name="android:textSize">20dp</item>
<item name="android:layout_margin">3dp</item>
<item name="android:background">@drawable/button_shape_normal</item>
<item name="android:background">@drawable/button_shape</item>
<item name="android:textColor">@drawable/button_text_color</item>
</style>

RemoteControl. , .

. 25.4.


Android, ,
. , ,

XML: .

412

25.

, .
layer-list,
inset, .
XML res/drawable/. button_shape_
shadowed.xml layer-list.
25.8. (drawable/button_shape_shadowed.xml)
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle" >
<corners android:radius="5dp" />
<gradient
android:angle="90"
android:centerColor="#303339"
android:centerY="0.05"
android:endColor="#000000"
android:startColor="#00000000" />
</shape>
</item>
<item>
<inset
android:drawable="@drawable/button_shape"
android:insetBottom="5dp" />
</item>
</layer-list>

,
( ).
inset,
5dp, .
,
.
, ,
.
, , .
, drawable/folder . ,
.
styles.xml
.
25.9. (values/styles.xml)
<style name="RemoteButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:textSize">20dp</item>
<item name="android:layout_margin">3dp</item>
<item name="android:background">@drawable/button_shape_normal</item>

9-

413

<item name="android:background">@drawable/button_shape_shadowed</item>
<item name="android:textColor">@drawable/button_text_color</item>
</style>

:
, .
remote_background.xml shape.
(: ,
).
25.10.

(drawable/remote_background.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:centerY="0.05"
android:endColor="#dbdbdb"
android:gradientRadius="500"
android:startColor="#f4f4e9"
android:type="radial" />
</shape>

fragment_remote_control.xml.
25.11. (layout/fragment_remote_control.xml)
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_remote_control_tableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/remote_background"
android:stretchColumns="*" >

.
( , ,
TableLayout.) , ,
. , .

9-
,
XML.
. (stretchable)
, Android
9- (9-patch).
TextView
. ,

414

25.

. TextView,
. window.png
TextView , .

. 25.5.

bar.png .
; .

. 25.6.

, 25_XMLDrawables/RemoteControl/res/drawable-hdpi. /res/drawable-hdpi RemoteControl.


fragment_remote_control.xml
. ,
, , ( ) . TextView
, .
25.12. (layout/fragment_remote_control.xml)
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
... >
<TextView

9-

android:id="@+id/fragment_remote_control_selectedTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:background="@drawable/window"
android:gravity="center"
android:text="0"
android:textColor="#ffffff"
android:textSize="50dp" />
<TextView
android:id="@+id/fragment_remote_control_workingTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="15dp"
android:layout_weight="1"
android:background="#555555"
android:background="@drawable/bar"
android:gravity="center"
android:text="0"
android:textColor="#cccccc"
android:textStyle="italic"
android:textSize="20dp" />
...
</TableLayout>

, .

. 25.7.

415

416

25.

. ,
. , TextView
.
9- .
9- , Android
, .
, , .
9-?
33 9 , (patches).
, , .

. 25.8. 9-

9- png, : .9.png,
. . ,
.
9- ,
draw9patch, Android SDK.
tools SDK. , File.
, ,
. window.
png, . 25.9.
.
?
, (
). , , . ,
.

9-

417

. 25.9. 9-

, window_
patch.9.png. , 9-
;
window.9.png window.png, .
9- bar.png. ,
4 .

. 25.10. 9-

bar_patch.9.png.

418

25.

res/ Refresh. Eclipse


, Refresh .
, TextView 9- .

. 25.11. RemoteControl
25.13. 9- (layout/fragment_remote_control.xml)
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
... >
<TextView
android:id="@+id/fragment_remote_control_selectedTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:background="@drawable/window"
android:background="@drawable/window_patch"
android:gravity="center"
android:text="0"
android:textSize="50dp" />
<TextView
android:id="@+id/fragment_remote_control_workingTextView"
android:layout_width="match_parent"
android:layout_height="0dp"

9-

419

android:layout_margin="15dp"
android:layout_weight="1"
android:background="@drawable/bar"
android:background="@drawable/bar_patch"
android:gravity="center"
android:text="0"
android:textColor="#cccccc"
android:textSize="20dp" />
...
</TableLayout>

. ! . , ?
, . ,
.
,
.

26

HTTP

. ,
?
,
.
Android PhotoGallery. Flickr,
, Flickr.
26.1 .

. 26.1. PhotoGallery

PhotoGallery

421

( .26.1
Flickr. Flickr , , .
, Flickr, http://pressroom.yahoo.net/pr/ycorp/
permissions.aspx.)
PhotoGallery .
XML, . : , ,
, -.
HTTP
Android. -
HTTP.
Flickr, (
27).

. 26.2. PhotoGallery

PhotoGallery
Android. , .26.3.

422

26. HTTP

. 26.3. PhotoGallery

PhotoGalleryActivity.
PhotoGallery ,
. PhotoGalleryActivity
SingleFragmentActivity, ,
activity_fragment.xml. ,
PhotoGalleryFragment, .
SingleFragmentActivity.java activity_fragment.xml
.
PhotoGalleryActivity.java PhotoGalleryActivity SingleFragmentActivity ; , ,
createFragment(). createFragment()
PhotoGalleryFragment.
( , .
, PhotoGalleryFragment.)
26.1. (PhotoGalleryActivity.java)
public class PhotoGalleryActivity extends Activity {
public class PhotoGalleryActivity extends SingleFragmentActivity {
/* , */

@Override
public Fragment createFragment() {
return new PhotoGalleryFragment();
}

PhotoGallery

423

PhotoGallery GridView,
PhotoGalleryFragment.
GridView AdapterView, , ListView. ListView, GridView
GridFragment, . , PhotoGalleryFragment.
PhotoGalleryFragment ,
GridView .
, layout/activity_photo_gallery.xml
layout/fragment_photo_gallery.xml.
GridView, . 26.4.

. 26.4. GridView (layout/fragment_photo_gallery.xml)

120dp GridView , .
120dp, stretchMode GridView
.
, PhotoGalleryFragment. ,
GridView ( 26.2).
26.2. (PhotoGalleryFragment.java)
package com.bignerdranch.android.photogallery;
...
public class PhotoGalleryFragment extends Fragment {
GridView mGridView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

setRetainInstance(true);

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

424

26. HTTP

26.2 ()
false);

View v = inflater.inflate(R.layout.fragment_photo_gallery, container,


mGridView = (GridView)v.findViewById(R.id.gridView);

return v;

, PhotoGallery , (
).


PhotoGallery . Java.
Flickr, FlickrFetchr.
F l i c k r F e t c h r :
getUrlBytes(String) getUrl(String) . getUrlBytes(String)
URL .
getUrl(String) getUrlBytes(String) String.
FlickrFetchr.java getUrlBytes(String) getUrl(String)
( 26.3).
26.3. (FlickrFetchr.java)
package com.bignerdranch.android.photogallery;
...
public class FlickrFetchr {
byte[] getUrlBytes(String urlSpec) throws IOException {
URL url = new URL(urlSpec);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return null;
}

int bytesRead = 0;
byte[] buffer = new byte[1024];
while ((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
out.close();
return out.toByteArray();
} finally {
connection.disconnect();
}

425

public String getUrl(String urlSpec) throws IOException {


return new String(getUrlBytes(urlSpec));
}

URL , http://www.google.com.
openConnection()
URL-. URL.openConnection() URLConnection,
HTTP,
HttpURLConnection. HTTP- , , ..
HttpURLConnection ,
getInputStream() ( getOutputStream()
POST-).
.
URL
read(), . InputStream
. , ByteArrayOutputStream.
getUrlBytes(String),
getUrl(String). ,
getUrlBytes(String), String.
?
, .


: . ,
.
,
AndroidManifest.xml.
26.4. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.photogallery"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>

426

26. HTTP

AsyncTask


. FlickrFetchr.getURL(String)
PhotoGalleryFragment. .

AsyncTask. AsyncTask
, doInBackground().
PhotoGalleryFragment.java PhotoGalleryFragment FetchItemsTask. AsyncTask.doInBackground() .
PhotoGalleryFragment.onCreate().
26.5. AsyncTask (PhotoGalleryFragment.java)
public class PhotoGalleryFragment extends Fragment {
private static final String TAG = "PhotoGalleryFragment";
GridView mGridView;
...

private class FetchItemsTask extends AsyncTask<Void,Void,Void> {


@Override
protected Void doInBackground(Void... params) {
try {
String result = new FlickrFetchr().getUrl("http://www.google.com");
Log.i(TAG, "Fetched contents of URL: " + result);
} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch URL: ", ioe);
}
return null;
}
}

PhotoGalleryFragment.onCreate() execute()
FetchItemsTask.
26.6. AsyncTask (PhotoGalleryFragment.java)
public class PhotoGalleryFragment extends Fragment {
private static final String TAG = "PhotoGalleryFragment";
GridView mGridView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);

}
...

427

new FetchItemsTask().execute();

execute() AsyncTask,
doInBackground(). , LogCat
HTML Google Javascriptlicious , . 26.5.

. 26.5. HTML Google LogCat

, ,
Android.


. - - ,
. - Android , Honeycomb
Android. , Android
NetworkOnMainThreadException. ? ,
, (thread),
.
(thread) . Android
. . , ,
.
, ,
: 1. ,
1

, . . .

428

26. HTTP

, ,
. , ,
, .


( Android
)

( )

( )

. 26.6.

,
- . , ? -
, .
, .
. , . , ,
.. (
,
, UI-.)

. , ,
. ,
( , AsyncTask) .


:
. ,
ANR (Application Not Responding). ,
Android ,
, Back. .

XML Flickr

429

. 26.7.

Android, Honeycomb, .
,
. Android
.
? AsyncTask.
, AsyncTask.
, -
.

XML Flickr
Flickr XML API. www.flickr.com/services/api/.
Request Formats.
REST, API http://api.flickr.com/
services/rest/. , Flickr
.
API API Methods. photos flickr.photos.getRecent.
; ,

430

26. HTTP

, flickr. ,
PhotoGallery.
getRecent API.
API, http://www.flickr.com/services/
api/ API keys.
Yahoo. API;
. API
: 4f721bgafa75bf6d2cb9af54f937bb70.
- Flickr.
GET- http://api.flickr.com/services/rest/?method=flickr.
photos.getRecent&api_key=xxx.
.
FlickrFetchr.
26.7. (FlickrFetchr.java)
public class FlickrFetchr {
public static final String TAG = "FlickrFetchr";
private
private
private
private

static
static
static
static

final
final
final
final

String
String
String
String

ENDPOINT = "http://api.flickr.com/services/rest/";
API_KEY = "yourApiKeyHere";
METHOD_GET_RECENT = "flickr.photos.getRecent";
PARAM_EXTRAS = "extras";

private static final String EXTRA_SMALL_URL = "url_s";

, , API extras url_s.


url_s Flickr URL ,
.
, URL- .
26.8. fetchItems() (FlickrFetchr.java)
public class FlickrFetchr {
...
String getUrl(String urlSpec) throws IOException {
return new String(getUrlBytes(urlSpec));
}
public void fetchItems() {
try {
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_GET_RECENT)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.build().toString();
String xmlString = getUrl(url);

XML Flickr

431

Log.i(TAG, "Received xml: " + xmlString);


} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch items", ioe);
}

Uri.Builder URL-
API- Flickr. Uri.Builder URL- . Uri.
Builder.appendQueryParameter(String,String)
.
, AsyncTask PhotoGalleryFragment
fetchItems().
26.9. fetchItems() (PhotoGalleryFragment.java)
private class FetchItemsTask extends AsyncTask<Void,Void,Void> {
@Override
protected Void doInBackground(Void... params) {
try {
String result = new FlickrFetchr().getUrl("http://www.google.com");
Log.i(TAG, "Fetched contents of URL: " + result);
} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch URL: ", ioe);
}
new FlickrFetchr().fetchItems();
return null;
}
}

PhotoGallery. LogCat , XML.

. 26.8. XML Flickr

432

26. HTTP

, XML Flickr ; ? , .
, PhotoGallery, GalleryItem. .26.9
PhotoGallery.

. 26.9. XML Flickr

: .26.9 -,
.
GalleryItem .
26.10. (GalleryItem.java)
package com.bignerdranch.android.photogallery;
public class GalleryItem {
private String mCaption;
private String mId;
private String mUrl;

public String toString() {


return mCaption;
}

Eclipse get- set- mId, mCaption mUrl.

XML Flickr

433

,
XML, Flickr. XML
XmlPullParser.

XmlPullParser
XmlPullParser , XML. XmlPullParser
Android .
GalleryItem.
FlickrFetchr , XML, . , XmlPullParser
XML. GalleryItem
ArrayList.
26.11. Flickr (FlickrFetchr.java)
public class FlickrFetchr {
public static final String TAG = "FlickrFetchr";
private static final String ENDPOINT = "http://api.flickr.com/services/rest/";
private static final String API_KEY = "your API key";
private static final String METHOD_GET_RECENT = "flickr.photos.getRecent";
private static final String XML_PHOTO = "photo";
...
public void fetchItems() {
...
}
void parseItems(ArrayList<GalleryItem> items, XmlPullParser parser)
throws XmlPullParserException, IOException {
int eventType = parser.next();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG &&
XML_PHOTO.equals(parser.getName())) {
String id = parser.getAttributeValue(null, "id");
String caption = parser.getAttributeValue(null, "title");
String smallUrl = parser.getAttributeValue(null, EXTRA_SMALL_URL);

GalleryItem item = new GalleryItem();


item.setId(id);
item.setCaption(caption);
item.setUrl(smallUrl);
items.add(item);

eventType = parser.next();

434

26. HTTP

, XmlPullParser XML,
, START_TAG, END_TAG END_DOCUMENT.
, getText(), getName() getAttributeValue(), ,
XmlPullParser. XmlPullParser
XML, next(). ,
, .

. 26.10. XmlPullParser

parseItems() XmlPullParser ArrayList.


xmlString, Flickr.
parseItems() .
26.12. parseItems() (FlickrFetchr.java)
public void fetchItems() {
public ArrayList<GalleryItem> fetchItems() {
ArrayList<GalleryItem> items = new ArrayList<GalleryItem>();
try {
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_GET_RECENT)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.build().toString();
String xmlString = getUrl(url);
Log.i(TAG, "Received xml: " + xmlString);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
parser.setInput(new StringReader(xmlString));
parseItems(items, parser);
} catch (IOException ioe) {

AsyncTask

435

Log.e(TAG, "Failed to fetch items", ioe);


} catch (XmlPullParserException xppe) {
Log.e(TAG, "Failed to parse items", xppe);
}
return items;

PhotoGallery XML. PhotoGallery ArrayList, , ,


.

AsyncTask

GridView PhotoGalleryFragment.
GridView, ListView, AdapterView,
, .
PhotoGalleryFragment.java ArrayList GalleryItems, ArrayAdapter , Android.
26.13. setupAdapter() (PhotoGalleryFragment.java)
public class PhotoGalleryFragment extends Fragment {
private static final String TAG = "PhotoGalleryFragment";
GridView mGridView;
ArrayList<GalleryItem> mItems;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);
mGridView = (GridView)v.findViewById(R.id.gridView);
setupAdapter();
}

return v;

void setupAdapter() {
if (getActivity() == null || mGridView == null) return;

if (mItems != null) {
mGridView.setAdapter(new ArrayAdapter<GalleryItem>(getActivity(),
android.R.layout.simple_gallery_item, mItems));
} else {
mGridView.setAdapter(null);
}

436

26. HTTP

GridView GridFragment,
. ,
setupAdapter().
GridView. onCreateView(),
GridView .
.
android.R.layout.simple_gallery_item TextView. , GalleryItem toString()
mCaption . ,
GridView GalleryItem .
: getActivity()
null. , , - .
,
. , . .
, AsyncTask, , , .
, , ; .
Flickr setupAdapter(). ,
, setupAdapter() doInBackground()
FetchItemsTask. . ,
,
Flickr. , , ,
? ,
.
.
, .
? AsyncTask onPostExecute(), . onPostExecute() doInBackground(). , onPostExecute() ,
, .
FetchItemsTask, mItems setupAdapter() .
26.14. (PhotoGalleryFragment.java)
private class
private class
@Override
protected
protected

FetchItemsTask extends AsyncTask<Void,Void,Void> {


FetchItemsTask extends AsyncTask<Void,Void,ArrayList<GalleryItem>> {
Void doInBackground(Void... params) {
ArrayList<GalleryItem> doInBackground(Void... params) {

AsyncTask

437

new FlickrFetchr().fetchItems();
return new FlickrFetchr().fetchItems();
return null;

@Override
protected void onPostExecute(ArrayList<GalleryItem> items) {
mItems = items;
setupAdapter();
}

. -,
FetchItemsTask. , AsyncTask; , doInBackground(),
onPostExecute(). -, doInBackground() , GalleryItem.
.
,
onPostExecute(). -, onPostExecute().
, doInBackground(), mItems
GridView.
. ,
, GalleryItem.

. 26.11. Flickr

438

26. HTTP

: AsyncTask
- AsyncTask,
. ?
- .
:
AsyncTask<String,Void,Void> task = new AsyncTask<String,Void,Void>() {
public Void doInBackground(String... params) {
for (String parameter : params) {
Log.i(TAG, "Received parameter: " + parameter);
}

};

return null;

task.execute("First parameter", "Second parameter", "Etc.");

execute(), . doInBackground().
- . :
final ProgressBar progressBar = /* */;
progressBar.setMax(100);
AsyncTask<Integer,Integer,Void> task = new AsyncTask<Integer,Integer,Void>() {
public Void doInBackground(Integer... params) {
for (Integer progress : params) {
publishProgress(progress);
Thread.sleep(1000);
}
}
public void onProgressUpdate(Integer... params) {
int progress = params[0];
progressBar.setProgress(progress);
}

};
task.execute(25, 50, 75, 100);

.
, , AsyncTask
publishProgress() onProgressUpdate().
: doInBackground()
publishProgress(). onProgressUpdate() . , onProgressUpdate(), publishProgress() doInBackground().

439

AsyncTask
AsyncTask , .
AsyncTask
.
AsyncTask , AsyncTask.cancel(boolean)
AsyncTask.
AsyncTask.cancel(boolean)
. cancel(false), true isCancelled(). AsyncTask isCancelled()
doInBackground() .
cancel(true)
, doInBackground(). AsyncTask.cancel(true)
AsyncTask.
.

.
getRecent 100.
page ,
.
,
.
,
.

27

Looper, Handler
HandlerThread

XML Flickr
.
Looper, Handler HandlerThread
PhotoGallery.

GridView
PhotoGalleryFragment TextView
GridView. TextView
GalleryItem.
, ,
ImageView. ImageView , mUrl GalleryItem.
gallery_item.xml. ImageView (. 27.1).

. 27.1. (res/layout/gallery_item.xml)

GridView

441

ImageView GridView, ,
.
.
ImageView, scaleType centerCrop. ,
, .
ImageView,
.
brian_up_close.jpg res/drawable-hdpi.
PhotoGalleryFragment ArrayAdapter , getView()
ImageView .
27.1. GalleryItemAdapter (PhotoGalleryFragment.java)
public class PhotoGalleryFragment extends Fragment {
...
void setupAdapter() {
if (getActivity() == null || mGridView == null) return;

if (mItems != null) {
mGridView.setAdapter(new ArrayAdapter<GalleryItem>(getActivity(),
android.R.layout.simple_gallery_item, mItems));
mGridView.setAdapter(new GalleryItemAdapter(mItems));
} else {
mGridView.setAdapter(null);
}

private class FetchItemsTask extends AsyncTask<Void,Void,ArrayList<GalleryItem>> {


...
}
private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
public GalleryItemAdapter(ArrayList<GalleryItem> items) {
super(getActivity(), 0, items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater()
.inflate(R.layout.gallery_item, parent, false);
}
ImageView imageView = (ImageView)convertView
.findViewById(R.id.gallery_item_imageView);
imageView.setImageResource(R.drawable.brian_up_close);

return convertView;

442

27. Looper, Handler HandlerThread

, AdapterView (GridView ) getView()


.

. 27.2. AdapterView-ArrayAdapter

PhotoGallery, .

. 27.3.

443


PhotoGallery : PhotoGalleryFragment AsyncTask, XML Flickr
, XML GalleryItem.
GalleryItem URL,
.
. , doInBackground()
FetchItemsTask. GalleryItem 100 URL-
. ,
100. onPostExecute() GridView.
. -,
, .
.
-, . . , 1000? ,
? .
, .
GridView . getView() .
AsyncTask ,
. ( , ,
.)
AsyncTask . .


, GridView ,
?
-.

, .
, .
. ,
.

444

27. Looper, Handler HandlerThread

. , , .
, , -
.
. , , , .
.
Android , ,
(message queue). , , (message loop);
, (. 27.4).

. 27.4.

Looper, .

, ,
.
, .
HandlerThread,
Looper.


ThumbnailDownloader, HandlerThread.
ThumbnailDownloader

445

; Token,
ThumbnailDownloader<Token> .
queueThumbnail()
(27.2).
27.2. (ThumbnailDownloader.java)
public class ThumbnailDownloader<Token> extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
public ThumbnailDownloader() {
super(TAG);
}

public void queueThumbnail(Token token, String url) {


Log.i(TAG, "Got an URL: " + url);
}

: queueThumbnail() Token String.


GalleryItemAdapter getView().
PhotoGalleryFragment.java. PhotoGalleryFragment
ThumbnailDownloader. (token) ThumbnailDownloader
.
ImageView,
. onCreate() .
onDestroy() .
27.3. ThumbnailDownloader (PhotoGalleryFragment.java)
public class PhotoGalleryFragment extends Fragment {
private static final String TAG = "PhotoGalleryFragment";
GridView mGridView;
ArrayList<GalleryItem> mItems;
ThumbnailDownloader<ImageView> mThumbnailThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();

mThumbnailThread = new ThumbnailDownloader<ImageView>();


mThumbnailThread.start();
mThumbnailThread.getLooper();
Log.i(TAG, "Background thread started");

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
}

446

27. Looper, Handler HandlerThread

27.3 ()

@Override
public void onDestroy() {
super.onDestroy();
mThumbnailThread.quit();
Log.i(TAG, "Background thread destroyed");
}
...

: -, , getLooper()
start() ThumbnailDownloader. ,
(
Looper ). -, quit()
onDestroy(). . HandlerThread,
, ).
, GalleryItemAdapter.getView()
GalleryItem position, queueThumbnail()
ImageView URL- .
27.4. ThumbnailDownloader (PhotoGalleryFragment.java)
private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
ImageView imageView = (ImageView)convertView
.findViewById(R.id.gallery_item_imageView);
imageView.setImageResource(R.drawable.brian_up_close);
GalleryItem item = getItem(position);
mThumbnailThread.queueThumbnail(imageView, item.getUrl());

return convertView;

PhotoGallery LogCat.
GridView LogCat , , ThumbnailDownloader
.
, HandlerThread , , queueThumbnail(),
ThumbnailDownloader.


, , (message
handler).

447


. , (
),
, ,
.
Message .
:
what int, ;
obj , ;
target , Handler, .
Message Handler.
, Handler. , Handler ,
.


, Handler. Handler ;
.

. 27.5. Looper, Handler, HandlerThread Message

Message Looper,
Looper Message.
Handler Looper.

448

27. Looper, Handler HandlerThread

Handler Looper, Message Handler, .


Looper Message.

. 27.6. Handler, Looper

Looper Handler.
, Message Handler Handler.


Handler . Handler.obtainMessage().
, .
Handler.obtainMessage() ,
Message, ,
.
Message , sendToTarget(),
.
Looper.

queueThumbnail(). what , MESSAGE_DOWNLOAD. obj Token
ImageView, queueThumbnail().
Looper ,
. , Handler.handleMessage() .
handleMessage() FlickrFetchr
URL- .
, 27.5.

449


Handler


Handler


Handler

. 27.7.
27.5. , (ThumbnailDownloader.java)
public class ThumbnailDownloader<Token> extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
Handler mHandler;
Map<Token, String> requestMap =
Collections.synchronizedMap(new HashMap<Token, String>());
public ThumbnailDownloader() {
super(TAG);
}
@SuppressLint("HandlerLeak")
@Override
protected void onLooperPrepared() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_DOWNLOAD) {
@SuppressWarnings("unchecked")
Token token = (Token)msg.obj;
Log.i(TAG, "Got a request for url: " + requestMap.get(token));
handleRequest(token);
}
}
};
}
public void queueThumbnail(Token token, String url) {
Log.i(TAG, "Got a URL: " + url");
requestMap.put(token, url);

mHandler
.obtainMessage(MESSAGE_DOWNLOAD, token)
.sendToTarget();

450

27. Looper, Handler HandlerThread

27.5 ()
private void handleRequest(final Token token) {
try {
final String url = requestMap.get(token);
if (url == null)
return;
byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
final Bitmap bitmap = BitmapFactory
.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
Log.i(TAG, "Bitmap created");

} catch (IOException ioe) {


Log.e(TAG, "Error downloading image", ioe);

onLooperPrepared()
@SuppressLint("HandlerLeak"). Android Lint Handler. Handler Looper. , Handler
, .
HandlerThread, .
@SuppressWarnings("unchecked") . , Token , Message.
obj Object. -
. ,
(type erasure) Android.
requestMap HashMap.
, Token , URL-, Token.
queueThumbnail() - Token-URL.
, Token obj
.
Handler.handleMessage() Handler onLooperPrepared(). HandlerThread.onLooperPrepared() ,
Looper ,
Handler.
Handler.handleMessage() , Token
handleRequest().
handleRequest(). URL-,
FlickrFetchr. FlickrFetchr.getUrlBytes(),
.

451

, BitmapFactory , getUrlBytes().
PhotoGallery LogCat .
, ImageView, GalleryItemAdapter.
, .
.
, ThumbnailDownloader Handler
.

Handler
, HandlerThread , Handler HandlerThread.
Looper.
Handler
Looper . Handler
. Handler Looper -.
, Handler ,
. , Handler
ThumbnailDownloader.

. 27.8. ThumbnailDownloader


Handler, .

452

27. Looper, Handler HandlerThread

!


ImageView.

. 27.9. ThumbnailDownloader

ThumbnailDownloader.java mRes
ponseHandler Handler, .
, Handler , .
27.6. (ThumbnailDownloader.java)
public class ThumbnailDownloader extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
Handler mHandler;
Map<Token,String> requestMap =
Collections.synchronizedMap(new HashMap<Token,String>());
Handler mResponseHandler;
Listener<Token> mListener;
public interface Listener<Token> {
void onThumbnailDownloaded(Token token, Bitmap thumbnail);
}
public void setListener(Listener<Token> listener) {
mListener = listener;
}
public ThumbnailDownloader() {
super(TAG);
public ThumbnailDownloader(Handler responseHandler) {
super(TAG);
mResponseHandler = responseHandler;
}

PhotoGalleryFragment , Handler
ThumbnailDownloader, Listener

453

Bitmap ImageView. , Handler Looper . Handler


onCreate(), Looper .
27.7. (PhotoGalleryFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();

mThumbnailThread = new ThumbnailDownloader();


mThumbnailThread = new ThumbnailDownloader(new Handler());
mThumbnailThread.setListener(new ThumbnailDownloader.Listener<ImageView>() {
public void onThumbnailDownloaded(ImageView imageView, Bitmap thumbnail) {
if (isVisible()) {
imageView.setImageBitmap(thumbnail);
}
}
});
mThumbnailThread.start();
mThumbnailThread.getLooper();
Log.i(TAG, "Background thread started");

ThumbnailDownloader Handler, Looper , mResponseHandler. , Listener Bitmap. : imageView.setImageBitmap(Bitmap)


Fragment.isVisible().
ImageView.

Messsage. Handler
handleMessage().
Handler post(Runnable).
Handler.post(Runnable) :
Runnable myRunnable = new Runnable() {
public void run() {
/* */
}
};
Message m = mHandler.obtainMessage();
m.callback = myRunnable;

Message callback, Handler


Runnable callback.
ThumbnailDownloader.handleRequest() .

454

27. Looper, Handler HandlerThread

27.8. (ThumbnailDownloader.java)
...
private void handleRequest(final Token token) {
try {
final String url = requestMap.get(token);
if (url == null)
return;
byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
final Bitmap bitmap = BitmapFactory
.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
Log.i(TAG, "Bitmap created");
mResponseHandler.post(new Runnable() {
public void run() {
if (requestMap.get(token) != url)
return;
requestMap.remove(token);
mListener.onThumbnailDownloaded(token, bitmap);

}
});
} catch (IOException ioe) {
Log.e(TAG, "Error downloading image", ioe);
}

mResponseHandler Looper , .
? requestMap. ,
GridView . ,
ThumbnailDownloader Bitmap, , GridView ImageView URL. , Token
, .
, Token requestMap Token.
,
, .
, ThumbnailDownloader
ImageView. .
.
27.9. (ThumbnailDownloader.java)
public void clearQueue() {
mHandler.removeMessages(MESSAGE_DOWNLOAD);
requestMap.clear();
}

PhotoGalleryFragment .

455

27.10. (PhotoGalleryFragment.java)
@Override
public void onDestroyView() {
super.onDestroyView();
mThumbnailThread.clearQueue();
}

. PhotoGallery. ,
.
PhotoGallery Flickr. : Flickr
-.

: AsyncTask
, Handler Looper, AsyncTask
.
, . AsyncTask
HandlerThread?
. ,
AsyncTask .
, . AsyncTask
, . AsyncTask , ,
.
, : Android3.2
AsyncTask . Android 3.2, AsyncTask
AsyncTask. Executor
AsyncTask . , AsyncTask ,
AsyncTask AsyncTask
.
AsyncTask
, . ,
, Handler ,
.

, (
). , .

456

27. Looper, Handler HandlerThread


:
;
.

Bitmap, .
,
.
LRU (Least Recently Used): , .
Android LruCache,
LRU. LruCache ThumbnailDownloader. , URL-
Bitmap, . , , ,
.
, , , .
Bitmap .
,
. , GalleryItem
10 10 GalleryItem.

28

PhotoGallery Flickr. ,
Android.
, , .
Android , ( ) .
, API. , ,
Jelly Bean.

Flickr
, Flickr. Flickr
flickr.photos.search. flickr.
photos.search red:
http://api.flickr.com/services/rest/?method=flickr.photos.search
&api_key=XXX&extras=url_s&text=red

, , . , XML,
GalleryItem, .
, 28.1, FlickrFetchr
. search getRecent GalleryItem , fetchItems()
, downloadGalleryItems(String).
fetchItems() fetchItems(),
.

458

28.

28.1. Flickr (FlickrFetchr.java)


public class FlickrFetchr {
public static final String TAG = "PhotoFetcher";
private
private
private
private
private
private
...

static
static
static
static
static
static

final
final
final
final
final
final

String
String
String
String
String
String

ENDPOINT = "http://api.flickr.com/services/rest/";
API_KEY = "4f721bbafa75bf6d2cb5af54f937bb70";
METHOD_GET_RECENT = "flickr.photos.getRecent";
METHOD_SEARCH = "flickr.photos.search";
PARAM_EXTRAS = "extras";
PARAM_TEXT = "text";

public ArrayList<GalleryItem> fetchItems() {


public ArrayList<GalleryItem> downloadGalleryItems(String url) {
ArrayList<GalleryItem> items = new ArrayList<GalleryItem>();
try {
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_GET_RECENT)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.build().toString();
String xmlString = getUrl(url);
Log.i(TAG, "Received xml: " + xmlString);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
parser.setInput(new StringReader(xmlString));

parseItems(items, parser);
} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch items", ioe);
} catch (XmlPullParserException xppe) {
Log.e(TAG, "Failed to parse items", xppe);
}
return items;

public ArrayList<GalleryItem> fetchItems() {


// ,
String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_GET_RECENT)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.build().toString();
return downloadGalleryItems(url);
}

public ArrayList<GalleryItem> search(String query) {


String url = Uri.parse(ENDPOINT).buildUpon()
.appendQueryParameter("method", METHOD_SEARCH)
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter(PARAM_EXTRAS, EXTRA_SMALL_URL)
.appendQueryParameter(PARAM_TEXT, query)
.build().toString();
return downloadGalleryItems(url);
}

459

downloadGalleryItems(String) , XML search getRecent.


flickr.photos.search
text.
PhotoGalleryFragment.
FetchItemsTask. ,
.
28.2. (PhotoGalleryFragment.java)
private class FetchItemsTask extends AsyncTask<Void,Void,ArrayList<GalleryItem>> {
@Override
protected ArrayList<GalleryItem> doInBackground(Void... params) {
String query = "android"; //

}
}

if (query != null) {
return new FlickrFetchr().search(query);
} else {
return new FlickrFetchr().fetchItems();
}

@Override
protected void onPostExecute(ArrayList<GalleryItem> items) {
...
}

...

getRecent. null ( ), FetchItemsTask


.
PhotoGallery . ,
.


PhotoGallery Android.
.


Honeycomb Android . ,
.
Android ,
3.0.
Activity.onSearchRequested(). ,
.

460

28.

XML- PhotoGallery res/menu/fragment_photo_gallery.xml.


,
.
28.3. (res/menu/fragment_photo_gallery.xml)
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_search"
android:title="@string/search"
android:icon="@android:drawable/ic_menu_search"
android:showAsAction="ifRoom"
/>
<item android:id="@+id/menu_item_clear"
android:title="@string/clear_search"
android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:showAsAction="ifRoom"
/>
</menu>

; strings.xml. ( , .)
28.4. (res/values/strings.xml)
<resources>
...
<string
<string
<string
<string

name="title_activity_photo_gallery">PhotoGalleryActivity</string>
name="search_hint">Search Flickr</string>
name="search">Search</string>
name="clear_search">Clear Search</string>

</resources>

. ,
onSearchRequested().
.
28.5. (PhotoGalleryFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setRetainInstance(true);
setHasOptionsMenu(true);
...

...
@Override
public void onDestroyView() {
...
}

461

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_photo_gallery, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_search:
getActivity().onSearchRequested();
return true;
case R.id.menu_item_clear:
return true;
default:
return super.onOptionsItemSelected(item);
}
}

, .

. 28.1.

, .
onSearchRequested() , PhotoGalleryActivity (searchable activity).

462

28.


. XML.
searchable, , . res/xml,
XML searchable.xml. searchable.
28.6. (res/xml/searchable.xml)
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/search_hint"
/>

XML (search configuration).


.
. , , , ,
.
.
AndroidManifest.xml.
,
PhotoGalleryActivity.
,
XML .
Android,
,
. (SearchManager) ,
.
AndroidManifest.xml .
android:launchMode, 28.7.
28.7. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
... >
...
<application
... >
<activity
android:name=".PhotoGalleryActivity"
android:launchMode="singleTop"
android:label="@string/title_activity_photo_gallery" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>

463

<action android:name="android.intent.action.SEARCH" />


</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
</application>
</manifest>

.
. startActivity() , action.
intent.action.SEARCH. .
, ,
, action.intent.action.SEARCH.
. 16, . android:value
android:resource .
. ,
:
<meta-data android:name="metadata.value"
android:value="@string/app_name" />
<meta-data android:name="metadata.resource"
android:resource="@string/app_name" />

. 28.2.

464

28.

metadata.value, , PhotoGallery , @string/app_name.


metadata.resource .
, metadata.resource R.string.
app_name .
. SearchManager
searchable.xml, XML; android:resource SearchManager
.
android:launchMode activity?
. ,
.

. PhotoGallery .


, onSearchRequested(),
.
,
3.0 ;
, .28.3.

. 28.3.

465


Android
. :
.

. AndroidManifest.xml , .
,
. , .
Android


,
ACTION_SEARCH

. 28.4.

, . . ?
android:launchMode="singleTop" ( 28.7),
.


(launch mode)? ,
,

.

466

28.

28.1.

standard

singleTop

singleTask

. , , ,
,

singleInstance

.
,
. ,

, , . :
,
.
PhotoGalleryActivity ( , SearchView Honeycomb).
singleTop.
,
PhotoGalleryActivity
.
onNewIntent(Intent)
Activity.
PhotoGalleryFragment.
PhotoGalleryFragment updateItems(),
FetchItemsTask .
28.8. (PhotoGalleryFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();
updateItems();

mThumbnailThread = new ThumbnailDownloader<ImageView>(new Handler());


mThumbnailThread.setListener(new ThumbnailDownloader.Listener<ImageView>() {
...
});
mThumbnailThread.start();
mThumbnailThread.getLooper();

467

public void updateItems() {


new FetchItemsTask().execute();
}

PhotoGalleryActivity onNewIntent(Intent),
PhotoGalleryFragment:
28.9. onNewIntent() (PhotoGalleryActivity.java)
public class PhotoGalleryActivity extends SingleFragmentActivity {
private static final String TAG = "PhotoGalleryActivity";
@Override
public Fragment createFragment() {
return new PhotoGalleryFragment();
}
@Override
public void onNewIntent(Intent intent) {
PhotoGalleryFragment fragment = (PhotoGalleryFragment)
getSupportFragmentManager().findFragmentById(R.id.fragmentContainer);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
Log.i(TAG, "Received a new search query: " + query);
}

fragment.updateItems();

LogCat PhotoGalleryActivity . PhotoGallery


, .
onNewIntent(Intent):
, -. ,
getIntent(), , . ,
getIntent() , ,
.
.
.

, 17.
.

468

28.

(shared preferences) , SharedPreferences. SharedPreferences , Bundle,


. , .
, XML, SharedPreferences
.
, , .
FlickrFetchr.
28.10. (FlickrFetchr.java)
public class FlickrFetchr {
public static final String TAG = "FlickrFetchr";
public static final String PREF_SEARCH_QUERY = "searchQuery";
private static final String ENDPOINT = "http://api.flickr.com/services/rest/";
...

SharedPreferences
Context.getSharedPreferences(String,int).
,
. PreferenceManager.ge
tDefaultSharedPreferences(Context),
(private) .
PhotoGalleryActivity SharedPreferences
.
28.11. (PhotoGalleryActivity.java)
@Override
public void onNewIntent(Intent intent) {
PhotoGalleryFragment fragment = (PhotoGalleryFragment)
getSupportFragmentManager()
.findFragmentById(R.id.fragmentContainer);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
Log.i(TAG, "Received a new search query: " + query);
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putString(FlickrFetchr.PREF_SEARCH_QUERY, query)
.commit();

}
fragment.updateItems();

SharedPreferences.edit()
SharedPreferences.Editor.

469

SharedPreferences. , , FragmentTransaction:
.
, commit() Editor
SharedPreferences.
SharedPreferences.getString(), getInt() , . PhotoGalleryFragment
SharedPreferences .
28.12. (PhotoGalleryFragment.java)
private class FetchItemsTask extends AsyncTask<Void,Void,ArrayList<GalleryItem>> {
@Override
protected ArrayList<GalleryItem> doInBackground(Void... params) {
String query = "android"; // just for testing
Activity activity = getActivity();
if (activity == null)
return new ArrayList<GalleryItem>();

String query = PreferenceManager.getDefaultSharedPreferences(activity)


.getString(FlickrFetchr.PREF_SEARCH_QUERY, null);
if (query != null) {
return new FlickrFetchr().search(query);
} else {
return new FlickrFetchr().fetchItems();
}

@Override
protected void onPostExecute(ArrayList<GalleryItem> items) {
...
}

,
JSON, ?
. PhotoGallery,
- , .
,
updateItems().
28.13. (PhotoGalleryFragment.java)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
...
case R.id.menu_item_clear:
PreferenceManager.getDefaultSharedPreferences(getActivity())
.edit()
.putString(FlickrFetchr.PREF_SEARCH_QUERY, null)
.commit();

470

28.

updateItems();
return true;
default:
return super.onOptionsItemSelected(item);

SearchView Android
3.0
.
Honeycomb.
Honeycomb SearchView. SearchView
(action view) ,
. SearchView
( ,
).
, , .

android:actionViewClass .
28.14. (res/menu/fragment_photo_
gallery.xml)
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_search"
android:title="@string/search"
android:icon="@android:drawable/ic_menu_search"
android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
/>
<item android:id="@+id/menu_item_clear"
...
/>
</menu>

, : Android,

. .
, SearchView onOptionsItemSelected().
,
, .
( , , SearchViewCompat . , SearchViewCompat SearchView,
. ,
SearchView , .
.)

SearchView Android 3.0

471

, PhotoGallery ,
SearchView. , . SearchView
, .
onCreateOptionsMenu() , SearchView.
SearchManager , . SearchManager getSearchableInfo(ComponentName) ,
, SearchableInfo.
SearchableInfo SearchView. ,
28.15.
28.15. SearchView (PhotoGalleryFragment.java)
@Override
@TargetApi(11)
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_photo_gallery, menu);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// SearchView
MenuItem searchItem = menu.findItem(R.id.menu_item_search);
SearchView searchView = (SearchView)searchItem.getActionView();
// searchable.xml
// SearchableInfo
SearchManager searchManager = (SearchManager)getActivity()
.getSystemService(Context.SEARCH_SERVICE);
ComponentName name = getActivity().getComponentName();
SearchableInfo searchInfo = searchManager.getSearchableInfo(name);

searchView.setSearchableInfo(searchInfo);

SearchView. MenuItem ,
getActionView().
SearchManager . SearchManager , , .
SearchManager ,
. ,
, , searchable.xml,
SearchableInfo, getSearchabl
eInfo(ComponentName).
SearchableInfo SearchView
setSearchableInfo(SearchableInfo). SearchView
. 3.0
, .

472

28.

. 28.5.

SearchView , .
: , , .
, SearchView .
.
.
, singleTop. ,
,
. , - ,
.

.
Activity.startSearch().
onSearchRequested() Activity.startSearch() .

473

startSearch() ,
EditText, Bundle , -, ,
- ( , ).
Activity.startSearch()
.
,
Toast.
XML, Flickr.
.

29

, , ;
, ,
.
? ,
, ,
RSS?
(service).
PhotoGallery
. ,
, .

IntentService
. IntentService. , , , . IntentService PollService.

.
onHandleIntent(Intent) PollService
. onHandleIntent(Intent) ,
.
29.1. PollService (PollService.java)
public class PollService extends IntentService {
private static final String TAG = "PollService";
public PollService() {
super(TAG);
}

IntentService

475

@Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "Received an intent: " + intent);
}

IntentService. ? - . (Service Context)


( onHandleIntent(Intent)).
(commands). .
.
1. 1.
.

2. 2.

3. 3.

4. 1.

5. 2 .

6. 3 .
.

. 29.1. IntentService

IntentService , .
IntentService , -

.
IntentService ,
onHandleIntent(Intent) .

476

29.

.
, .
IntentService. .
, IntentService, ,
. ! ,
, ,
AndroidManifest.xml. PollService.
29.2. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
... >
...
<application
... >
<activity
android:name=".PhotoGalleryActivity"
... >
...
</activity>
<service android:name=".PollService" />
</application>
</manifest>

PhotoGalleryFragment.
29.3. (PhotoGalleryFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
updateItems();
Intent i = new Intent(getActivity(), PollService.class);
getActivity().startService(i);

mThumbnailThread = new ThumbnailDownloader<ImageView>(new Handler());


...

, (.29.2).

. 29.2.

477


, : LogCat . -
! ? ?
, , -.
: , , , .
.
.
Android. ,
.
Android. ,
. ,
, .
, . ,
? , ,
.


Flickr .
, .
Android
. , , .
,
ConnectivityManager , .
API , , .
ConnectivityManager.getBackgroundDataSetting(),
ConnectivityManager.getActiveNetworkInfo() null.
29.4 ,
.
29.4. (PollService.java)
@Override
public void onHandleIntent(Intent intent) {
ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
@SuppressWarnings("deprecation")
boolean isNetworkAvailable = cm.getBackgroundDataSetting() &&
cm.getActiveNetworkInfo() != null;
if (!isNetworkAvailable) return;
}

Log.i(TAG, "Received an intent: " + intent);

478

29.

? Android
getBackgroundDataSetting() , false.
,
. :
, .
Android 4.0, Ice Cream Sandwich, :
. ,
getActiveNetworkInfo() null. ,
.
, .
getActiveNetworkInfo(),
ACCESS_NETWORK_STATE.
29.5. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.photogallery"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
...
</application>
</manifest>


Flickr ,
. SharedPreferences. FlickrFetchr
.
29.6. (FlickrFetchr.java)
public class FlickrFetchr {
public static final String TAG = "PhotoFetcher";
public static final String PREF_SEARCH_QUERY = "searchQuery";
public static final String PREF_LAST_RESULT_ID = "lastResultId";

479

private static final String ENDPOINT = "http://api.flickr.com/services/rest/";


private static final String API_KEY = "xxx";
...

. :
1. SharedPreferences.
2. FlickrFetchr.
3. , .
4. ,
.
5. SharedPreferences.
PollService.java . 29.7 , ,
.
29.7. (PollService.java)
@Override
protected void onHandleIntent(Intent intent) {
...
if (!isNetworkAvailable) return;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String query = prefs.getString(FlickrFetchr.PREF_SEARCH_QUERY, null);
String lastResultId = prefs.getString(FlickrFetchr.PREF_LAST_RESULT_ID, null);
ArrayList<GalleryItem> items;
if (query != null) {
items = new FlickrFetchr().search(query);
} else {
items = new FlickrFetchr().fetchItems();
}
if (items.size() == 0)
return;
String resultId = items.get(0).getId();
if (!resultId.equals(lastResultId)) {
Log.i(TAG, "Got a new result: " + resultId);
} else {
Log.i(TAG, "Got an old result: " + resultId);
}

prefs.edit()
.putString(FlickrFetchr.PREF_LAST_RESULT_ID, resultId)
.commit();

480

29.

? .
PhotoGallery , .
, , .

AlarmManager
, -
, , , - .
Handler, Handler.sendMessageDelayed() Handler.postDelayed(). ,
, .
, Handler.
Handler AlarmManager
, .
AlarmManager, ?
PendingIntent. , PendingIntent :
PollService.
, AlarmManager.
PollService setServiceAlarm(Context,boole
an), . ; ,
PollService, , .

.
29.8. (PollService.java)
public class PollService extends IntentService {
private static final String TAG = "PollService";
private static final int POLL_INTERVAL = 1000 * 15; // 15
public PollService() {
super(TAG);
}
@Override
public void onHandleIntent(Intent intent) {
...
}
public static void setServiceAlarm(Context context, boolean isOn) {
Intent i = new Intent(context, PollService.class);
PendingIntent pi = PendingIntent.getService(
context, 0, i, 0);
AlarmManager alarmManager = (AlarmManager)

AlarmManager

481

context.getSystemService(Context.ALARM_SERVICE);

if (isOn) {
alarmManager.setRepeating(AlarmManager.RTC,
System.currentTimeMillis(), POLL_INTERVAL, pi);
} else {
alarmManager.cancel(pi);
pi.cancel();
}

PendingIntent, PollService.
PendingIntent.getService(), Context.startService(Intent). :
Context ; , PendingIntent
; Intent; , , PendingIntent ( ).
, .
, AlarmManager.setRepeating().
: (
), , , ,
PendingIntent, .
AlarmManager.cancel(PendingIntent).
PendingIntent. ,
PendingIntent .
PhotoGalleryFragment.
29.9. (PhotoGalleryFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
updateItems();
Intent i = new Intent(getActivity(), PollService.class);
getActivity().startService(i);
PollService.setServiceAlarm(getActivity(), true);

mThumbnailThread = new ThumbnailDownloader<ImageView>(new Handler());


...

PhotoGallery. Back
.
- LogCat? PollService , 15 . AlarmManager.

482

29.

, AlarmManager ,
PollService.
(, . ,
, .)

PendingIntent
PendingIntent. PendingIntent
-. PendingIntent.getService(), : , ,
startService(Intent). send()
PendingIntent, ,
.
, PendingIntent
, . PendingIntent ,
. , ( ) - PendingIntent , send()
.
PendingIntent ,
PendingIntent. ,
PendingIntent PendingIntent.

PendingIntent
PendingIntent .
setServiceAlarm(boolean) isOn:
AlarmManager.cancel(PendingIntent) ,
PendingIntent, PendingIntent.
PendingIntent , ,
PendingIntent, , .
PendingIntent.FLAG_NO_CREATE PendingIntent.getService(). , PendingIntent ,
null.
isServiceAlarmOn(Context), PendingIntent.FLAG_NO_CREATE .
29.10. isServiceAlarmOn() (PollService.java)
public static void setServiceAlarm(Context context, boolean isOn) {
...
}
public static boolean isServiceAlarmOn(Context context) {
Intent i = new Intent(context, PollService.class);
PendingIntent pi = PendingIntent.getService(

483

context, 0, i, PendingIntent.FLAG_NO_CREATE);
return pi != null;

PendingIntent ,
null PendingIntent , .


, ( ,
), .
menu/fragment_photo_gallery.xml .
29.11. (menu/fragment_photo_gallery.xml)
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_search"
android:title="@string/search"
android:icon="@android:drawable/ic_menu_search"
android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
/>
<item android:id="@+id/menu_item_clear"
android:title="@string/clear_search"
android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:showAsAction="ifRoom"
/>
<item android:id="@+id/menu_item_toggle_polling"
android:title="@string/start_polling"
android:showAsAction="ifRoom"
/>
</menu>

,
. (
; .)
29.12. (res/values/strings.xml)
<resources>
...
<string
<string
<string
<string
<string
<string
string>

name="search">Search</string>
name="clear_search">Clear Search</string>
name="start_polling">Poll for new pictures</string>
name="stop_polling">Stop polling</string>
name="new_pictures_title">New PhotoGallery Pictures</string>
name="new_pictures_text">You have new pictures in PhotoGallery.</

</resources>

484

29.

29.13.

(PhotoGalleryFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
updateItems();
PollService.setServiceAlarm(getActivity(), true);

mThumbnailThread = new ThumbnailDownloader<ImageView>(new Handler());


...

...
@Override
@TargetApi(11)
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_search:
...
case R.id.menu_item_clear:
...
updateItems();
return true;
case R.id.menu_item_toggle_polling:
boolean shouldStartAlarm = !PollService.isServiceAlarmOn(getActivity());
PollService.setServiceAlarm(getActivity(), shouldStartAlarm);

return true;
default:
return super.onOptionsItemSelected(item);

.
?


.
.
, .
, onPrepareOptionsMenu(Menu).
,
.

485

onPrepareOptionsMenu(Menu), ,
, menu_item_toggle_polling, .
29.14. onPrepareOptionsMenu(Menu) (PhotoGalleryFragment.java)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
...
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);

MenuItem toggleItem = menu.findItem(R.id.menu_item_toggle_polling);


if (PollService.isServiceAlarmOn(getActivity())) {
toggleItem.setTitle(R.string.stop_polling);
} else {
toggleItem.setTitle(R.string.start_polling);
}

3.0
. ,
. , PhotoGallery
3.0.
3.0 . .
onPrepareOptionsMenu(Menu) Activity.invalidateOptionsMenu().
onOptionsItemSelected(MenuItem),
3.0 .
29.15. (PhotoGalleryFragment.java)
@Override
@TargetApi(11)
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
...
case R.id.menu_item_toggle_polling:
boolean shouldStartAlarm = !PollService.isServiceAlarmOn(getActivity());
PollService.setServiceAlarm(getActivity(), shouldStartAlarm);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
getActivity().invalidateOptionsMenu();

return true;
default:
return super.onOptionsItemSelected(item);

486

29.

4.2.
- .

.
, .
- ,
(notifications) , , ,
.
, Notification.
Notification - , AlertDialog 12. Notification :
, ;
, ;
,
;
PendingIntent, .
Notification ,
notify(int, Notification) NotificationManager.
PollService ,
29.16. Notification
NotificationManager.notify(int, Notification).
29.16. (PollService.java)
@Override
public void onHandleIntent(Intent intent) {
...
String resultId = items.get(0).getId();
if (!resultId.equals(lastResultId)) {
Log.i(TAG, "Got a new result: " + resultId);
Resources r = getResources();
PendingIntent pi = PendingIntent
.getActivity(this, 0, new Intent(this, PhotoGalleryActivity.class), 0);
Notification notification = new NotificationCompat.Builder(this)
.setTicker(r.getString(R.string.new_pictures_title))
.setSmallIcon(android.R.drawable.ic_menu_report_image)
.setContentTitle(r.getString(R.string.new_pictures_title))

487

.setContentText(r.getString(R.string.new_pictures_text))
.setContentIntent(pi)
.setAutoCancel(true)
.build();
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);

notificationManager.notify(0, notification);
}
prefs.edit()
.putString(FlickrFetchr.PREF_LAST_RESULT_ID, resultId)
.commit();

.
setTicker(CharSequence) setSmallIcon(int).
.
,
, .
setSmallIcon(int).
setContentTitle(CharSequence) setContentText(CharSequence) .
, .
AlarmManager, PendingIntent. PendingIntent, setContentIntent(PendingIntent),
.
setAutoCancel(true) :
.
NotificationManager.notify(). ,
. ,
, .
.
, . ! , ,
.
29.17. (PollService.java)
public class PollService extends IntentService {
private static final String TAG = "PollService";
public static final int POLL_INTERVAL = 1000 * 15; // 15
public static final int POLL_INTERVAL = 1000 * 60 * 5; // 5
public PollService() {
super(TAG);
}

488

29.

:
IntentService .
IntentService ,
.
, .

( )
,
( ). - ,
.
.
, IntentService.
, IntentService , .
,
.


, startService(Intent), .
.
onCreate() .
onStartCommand(Intent,int,int) ,
startService(Intent).
. ,
,
.
onStartCommand(Intent,int,int),
.
onDestroy() , . .
: ? . , onStartCommand(); Service.
START_NOT_STICKY, START_REDELIVER_INTENT START_STICKY.


IntentService (non-sticky) ,

. ,

489

. ,

START_NOT_STICKY START_REDELIVER_INTENT.

Android , stopSelf()
stopSelf(int). , stopSelf(), .
, onStartCommand().
IntentService stopSelf(int). , onStartCommand().

,
( IntentService).

START_NOT_STICKY START_REDELIVER_INTENT ? , .
START_NOT_STICKY .
START_REDELIVER_INTENT, , ,
.
START_NOT_STICKY START_REDELIVER_INTENT
. ,
START_NOT_STICKY. PhotoGallery .
, START_NOT_STICKY.
IntentService.
START_REDELIVER_INTENT, IntentService.setIntentRedelivery(true).


(sticky) , -
, Context.stopService(Intent).
, START_STICKY.
,
Context.stopService(Intent). - , onStartCommand()
null-.
(,
), ,
. ,
.
, ,
.


(binding) bi
ndService(Intent,ServiceConnection,int). .

490

29.

bindService(Intent,ServiceConnection,int). ServiceConnection ,
.
:
private ServiceConnection mServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
//
MyBinder binder = (MyBinder)service;
}

};

public void onServiceDisconnected(ComponentName className) {


}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = new Intent(c, MyService.class);
c.bindService(i, mServiceConnection, 0);
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getApplicationContext().unbindService(mServiceConnection);
}


:
onBind(Intent) , .
IBinder, ServiceConnection.onSer
viceConnected(ComponentName,IBinder).
onUnbind(Intent) .


MyBinder? ,
Java, . (handle), :
private class MyBinder extends IBinder {
public MyService getService() {
return MyService.this;
}
}
@Override
public void onBind(Intent intent) {
return new MyBinder();
}

491

Android,
Android .
. ,
.


,
.
,
. AIDL
Messenger.

30

Android - . WiFi
, ,
.
, Android
(broadcast intent).
, , ,
. (broadcast receivers).

( )

( )

Android

(
)

(
)
(
)

(
)
(
)

(
)

. 30.1.

,
,

493

. , ,
.


PhotoGallery , . , .
, ,
. , BOOT_COMPLETED.


.
Java StartupReceiver, android.content.
BroadcastReceiver. Eclipse
onReceive(Context,Intent). .
30.1. (StartupReceiver.java)
package com.bignerdranch.android.photogallery;
...
public class StartupReceiver extends BroadcastReceiver {
private static final String TAG = "StartupReceiver";

@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received broadcast intent: " + intent.getAction());
}

, ,
. ,
. ,
(, ).
,
: receiver . StartupReceiver BOOT_COMPLETED.
,
uses-permission.
AndroidManifest.xml StartupReceiver.

494

30.

30.2. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.photogallery"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
... >
<activity
... >
...
</activity>
<service android:name=".PollService" />
<receiver android:name=".StartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>

,
.
,
.
.

Android

. 30.2. BOOT_COMPLETED

495

,

.
onReceive(Context,Intent) , .
PhotoGallery,
DDMS. LogCat
. Devices, , ,
PhotoGallery. ,
,
.


, , . ,
API , onReceive(Context,Intent).
, onReceive(Context,Intent) ,
.
, , .
.
; ,
.
, . PollService ,
.
30.3. (PollService.java)
public class PollService extends IntentService {
private static final String TAG = "PollService";
private static final int POLL_INTERVAL = 1000 * 60 * 5; // 5 minutes
public static final String PREF_IS_ALARM_ON = "isAlarmOn";
public PollService() {
super(TAG);
}
...
public static void setServiceAlarm(Context context, boolean isOn) {
Intent i = new Intent(context, PollService.class);
PendingIntent pi = PendingIntent.getService(
context, 0, i, 0);
AlarmManager alarmManager = (AlarmManager)

496

30.

30.3 ()
context.getSystemService(Context.ALARM_SERVICE);
if (isOn) {
alarmManager.setRepeating(AlarmManager.RTC,
System.currentTimeMillis(), POLL_INTERVAL, pi);
} else {
alarmManager.cancel(pi);
pi.cancel();
}

PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean(PollService.PREF_IS_ALARM_ON, isOn)
.commit();

StartupReceiver .
30.4. (StartupReceiver.java)
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received broadcast intent: " + intent.getAction());

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);


boolean isOn = prefs.getBoolean(PollService.PREF_IS_ALARM_ON, false);
PollService.setServiceAlarm(context, isOn);

PhotoGallery. , .


, PhotoGallery. , ,
.
, .


:
. ,
sendBroadcast(Intent) .
,
.
PollService.

497

30.5. (PollService.java)
public class PollService extends IntentService {
private static final String TAG = "PollService";
private static final int POLL_INTERVAL = 1000 * 60 * 5; // 5 minutes
public static final String PREF_IS_ALARM_ON = "isAlarmOn";
public static final String ACTION_SHOW_NOTIFICATION =
"com.bignerdranch.android.photogallery.SHOW_NOTIFICATION";
public PollService() {
super(TAG);
}
@Override
public void onHandleIntent(Intent intent) {
...
if (!resultId.equals(lastResultId)) {
...
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(0, notification);
}

sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION));

prefs.edit()
.putString(FlickrFetchr.PREF_LAST_RESULT_ID, resultId)
.commit();


.
, StartupReceiver, .
, PhotoGalleryFragment ,
. , , . -
, PhotoGalleryFragment .
.
, .
registerReceiver(BroadcastReceiver, IntentFilter), unregisterReceiver(BroadcastReceiver). ,
. registerReceiver() unregisterReceiver()
, .

498

30.

VisibleFragment, Fragment. ,
( 31).
30.6. VisibleFragment (VisibleFragment.java)
package com.bignerdranch.android.photogallery;
...
public abstract class VisibleFragment extends Fragment {
public static final String TAG = "VisibleFragment";
private BroadcastReceiver mOnShowNotification = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(getActivity(),
"Got a broadcast:" + intent.getAction(),
Toast.LENGTH_LONG)
.show();
}
};
@Override
public void onResume() {
super.onResume();
IntentFilter filter = new
IntentFilter(PollService.ACTION_SHOW_NOTIFICATION);
getActivity().registerReceiver(mOnShowNotification, filter);
}

@Override
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mOnShowNotification);
}

: IntentFilter,
. IntentFilter ,
XML:
<intent-filter>
<action android:name="com.bignerdranch.android.photogallery.SHOW_NOTIFICATION" />
</intent-filter>

IntentFilter, XML,
. addCategory(String),
addAction(String), addDataPath(String) .

. ,
, , Context.unregisterReceiver(Broadcast
Receiver). onResume()

499

onPause(). , onActivityCreated(), onActivityDestroyed().


(, onCreate() onDestroy() . getActivity() onCreate()
onDestroy(), . /
Fragment.onCreate(Bundle) Fragment.onDestroy(),
getActivity().getApplicationContext()).
PhotoGalleryFragment VisibleFragment.
30.7. (PhotoGalleryFragment.java)
public class PhotoGalleryFragment extends Fragment {
public class PhotoGalleryFragment extends VisibleFragment {
GridView mGridView;
ArrayList<GalleryItem> mItems;
ThumbnailDownloader<ImageView> mThumbnailThread;

PhotoGallery . , .

. 30.3.

500

30.



,
. .
. , receiver
android:exported="false".
. , AndroidManifest.xml permission.
XML AndroidManifest.xml,
(private) .
30.8. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.photogallery"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<permission android:name="com.bignerdranch.android.photogallery.PRIVATE"
android:protectionLevel="signature" />
<uses-permission
<uses-permission
<uses-permission
<uses-permission

android:name="android.permission.INTERNET" />
android:name="android.permission.ACCESS_NETWORK_STATE" />
android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
android:name="com.bignerdranch.android.photogallery.PRIVATE" />

<application
... >
...
</application>
</manifest>


signature ( ).
, , .
, .
.
, .
, . , .
,
sendBroadcast().

501

30.9. (PollService.java)
public class PollService extends IntentService {
private static final String TAG = "PollService";
private static final int POLL_INTERVAL = 1000 * 60 * 5; // 5 minutes
public static final String PREF_IS_ALARM_ON = "isAlarmOn";
public static final String ACTION_SHOW_NOTIFICATION =
"com.bignerdranch.android.photogallery.SHOW_NOTIFICATION";
public static final String PERM_PRIVATE =
"com.bignerdranch.android.photogallery.PRIVATE";
public PollService() {
super(TAG);
}
@Override
public void onHandleIntent(Intent intent) {
...
if (!resultId.equals(lastResultId)) {
...
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(0, notification);

sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION));
sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION), PERM_PRIVATE);

prefs.edit()
.putString(FlickrFetchr.PREF_LAST_RESULT_ID, resultId)
.commit();

, sendBroadcast().
, , .
?
, . registerReceiver().
30.10. (VisibleFragment.java)
@Override
public void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter(PollService.ACTION_SHOW_
NOTIFICATION);
getActivity().registerReceiver(mOnShowNotification, filter);
getActivity().registerReceiver(mOnShowNotification, filter,
PollService.PERM_PRIVATE, null);
}

502

30.



android:protectionLevel. Android, . signature.
, ,
,
. , . ,
, .
, , , .
30.1. protectionLevel

normal

, ,

.
, . android.permission.RECEIVE_BOOT_COMPLETED
, . , , ,
,

dangerous

, normal
,
, ,
, ,
. , ,
. Android

signature

, , ,
, .
, . ,

, , ,
.
, ,

signatureOrSystem

signature,
Android. , ,
, ,

503



,
.

. 30.4.

,
. onReceive() , .
, - ,
, .
.

. 30.5.


.
. ,
,
, (result receiver).

504

30.

, .
: ,
. ;
.
setResultCode(int) Activity.
RESULT_CANCELED.
VisibleFragment,
SHOW_NOTIFICATION.
30.11. (VisibleFragment.java)
private BroadcastReceiver mOnShowNotification = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(getActivity(),
"Got a broadcast:" + intent.getAction(),
Toast.LENGTH_LONG)
.show();
Log.i(TAG, "canceling notification");
setResultCode(Activity.RESULT_CANCELED);
}
};

/,
. ,
setResultData(String) setResultExtras(Bundle).
, setResult(int,String,Bundle).
,
.
- ,
. PollService. Notification .
Notification ( ,
).
30.12. (PollService.java)
void showBackgroundNotification(int requestCode, Notification notification) {
Intent i = new Intent(ACTION_SHOW_NOTIFICATION);
i.putExtra("REQUEST_CODE", requestCode);
i.putExtra("NOTIFICATION", notification);

sendOrderedBroadcast(i, PERM_PRIVATE, null, null,


Activity.RESULT_OK, null, null);

Context.sendOrderedBroadcast(Intent,String,BroadcastReceiver,Handle
r,int,String,Bundle) sendBroadcast(Intent,String). , : ,
Handler ,

505

,
.
,
.

.
. PollService.
, .
, .
BroadcastReceiver NotificationReceiver. .
30.13. (NotificationReceiver.java)
public class NotificationReceiver extends BroadcastReceiver {
private static final String TAG = "NotificationReceiver";
@Override
public void onReceive(Context c, Intent i) {
Log.i(TAG, "received result: " + getResultCode());
if (getResultCode() != Activity.RESULT_OK)
//
//
return;
int requestCode = i.getIntExtra("REQUEST_CODE", 0);
Notification notification = (Notification)
i.getParcelableExtra("NOTIFICATION");

NotificationManager notificationManager = (NotificationManager)


c.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(requestCode, notification);

, . ,
,
. , .
, 999 ( 1000 ).
,
. android:exported="false",
.
30.14. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
... >
...
<application

506

30.

30.14 ()

/>

... >
...
<receiver android:name=".StartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".NotificationReceiver"
android:exported="false">
<intent-filter
android:priority="-999">
<action
android:name="com.bignerdranch.android.photogallery.SHOW_NOTIFICATION"
</intent-filter>
</receiver>
</application>

</manifest>

NotificationManager.
30.15. (PollService.java)
@Override
public void onHandleIntent(Intent intent) {
...
if (!resultId.equals(lastResultId)) {
...
Notification notification = new NotificationCompat.Builder(this)
...
.build();
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(0, notification);
sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION), PERM_PRIVATE);
}

showBackgroundNotification(0, notification);

prefs.edit()
.putString(FlickrFetchr.PREF_LAST_RESULT_ID, resultId)
.commit();

PhotoGallery
. , .
, ,

507

PollService.POLL_INTERVAL 5 ,


, ,
, ?
. . .
, .

, .
BroadcastReceiver.goAsync().
BroadcastReceiver.PendingResult,
. ,
PendingResult AsyncTask
, ,
PendingResult.
. -, . -, :
, .
, goAsync() : .
, . , .

31

-
WebView

, Flickr, .
, PhotoGallery . -
. , ,
WebView -.

Flickr
URL- Flickr.
XML,
, , .
<photo id="8232706407" owner="70490293@N03" secret="9662732625"
server="8343" farm="9" title="111_8Q1B2033" ispublic="1"
isfriend="0" isfamily="0"
url_s="http://farm9.staticflickr.com/8343/8232706407_9662732625_m.jpg"
height_s="240" width_s="163" />

, XML? , .
Flickr http://www.flickr.com/services/api/misc.urls.
html, , URL- :
http://www.flickr.com/photos/-/-

id XML.
mId GalleryItem.
? , ,
owner XML . ,
owner, URL- XML :
http://www.flickr.com/photos/owner/id

, GalleryItem.

Flickr

509

31.1. URL (GalleryItem.java)


public class GalleryItem {
private String mCaption;
private String mId;
private String mUrl;
private String mOwner;
...
public void setUrl(String url) {
mUrl = url;
}
public String getOwner() {
return mOwner;
}
public void setOwner(String owner) {
mOwner = owner;
}
public String getPhotoPageUrl() {
return "http://www.flickr.com/photos/" + mOwner + "/" + mId;
}

public String toString() {


return mCaption;
}

mOwner getPhotoPageUrl() URL- , .


parseItems() owner.
31.2. owner (FlickrFetchr.java)
void parseItems(ArrayList<GalleryItem> items, XmlPullParser parser)
throws XmlPullParserException, IOException {
int eventType = parser.next();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG &&
XML_PHOTO.equals(parser.getName())) {
String id = parser.getAttributeValue(null, "id");
String caption = parser.getAttributeValue(null, "title");
String smallUrl = parser.getAttributeValue(null, EXTRA_SMALL_URL);
String owner = parser.getAttributeValue(null, "owner");

GalleryItem item = new GalleryItem();


item.setUrl(smallUrl);
item.setOwner(owner);
items.add(item);

eventType = parser.next();

URL- .

510

31. - WebView

:
URL- . URL-
.
GridView. ,
9 - GridFragment.
onListItemClick() , setOnItemClickListener()
GridView.
. PhotoGalleryFragment.
31.3.
 -
(PhotoGalleryFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);
mGridView = (GridView)v.findViewById(R.id.gridView);
setupAdapter();
mGridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> gridView, View view, int pos,
long id) {
GalleryItem item = mItems.get(pos);
Uri photoPageUri = Uri.parse(item.getPhotoPageUrl());
Intent i = new Intent(Intent.ACTION_VIEW, photoPageUri);

});
}

startActivity(i);

return v;

PhotoGallery . , .

: WebView
- . ,
HTML .
-,

511

.
,
- .
- WebView. ,
(
).
WebView. , , .

. 31.1. (res/layout/fragment_photo_page.xml)

: RelativeLayout
.
.
PhotoPageFragment VisibleFragment,
. , WebView
URL- .
31.4. (PhotoPageFragment.java)
package com.bignerdranch.android.photogallery;
...
public class PhotoPageFragment extends VisibleFragment {
private String mUrl;
private WebView mWebView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mUrl = getActivity().getIntent().getData().toString();
}

512

31. - WebView

31.4 ()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_photo_page, parent, false);
mWebView = (WebView)v.findViewById(R.id.webView);

return v;

.
- PhotoPageActivity SingleFragmentActivity.
31.5. - (PhotoPageActivity.java)
package com.bignerdranch.android.photogallery;
...
public class PhotoPageActivity extends SingleFragmentActivity {
@Override
public Fragment createFragment() {
return new PhotoPageFragment();
}
}

PhotoGalleryFragment,
.
31.6. (PhotoGalleryFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
mGridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> gridView, View view, int pos,
long id) {
GalleryItem item = mItems.get(pos);
Uri photoPageUri = Uri.parse(item.getPhotoPageUrl());
Intent i = new Intent(Intent.ACTION_VIEW, photoPageUri);
Intent i = new Intent(getActivity(), PhotoPageActivity.class);
i.setData(photoPageUri);

});
}

startActivity(i);

return v;

, .

513

31.7. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.photogallery"
android:versionCode="1"
android:versionName="1.0" >
...
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".PhotoGalleryActivity"
android:launchMode="singleTop"
android:label="@string/title_activity_photo_gallery" >
...
</activity>
<activity
android:name=".PhotoPageActivity" />
<service android:name=".PollService" />
<receiver android:name=".StartupReceiver">
...
</receiver>
</application>
</manifest>

PhotoGallery . .
- .
WebView Flickr,
. ,
URL- .
JavaScript.
. , Flickr
. Android Lint (- ), Lint .
, WebViewClient
shouldOverrideUrlLoading(WebView,String) false.
, .
31.8. (PhotoPageFragment.java)
@SuppressLint("SetJavaScriptEnabled")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_photo_page, parent, false);
mWebView = (WebView)v.findViewById(R.id.webView);

514

31. - WebView

31.8 ()
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
});
mWebView.loadUrl(mUrl);
}

return v;

URL- WebView, . JavaScript,


getSettings() WebSettings,
WebSettings.setJavaScriptEnabled(true). WebSettings
WebView. ,
, .
WebViewClient. WebViewClient
. WebViewClient,
. , ,
URL-,
, POST-.
WebViewClient , ; .
shouldOverrideUrlLoading(WebView,String) WebViewClient.
, URL- WebView
(, ). true, : URL-, . false,
: , WebView, URL-, .
URL, ,
. , Flickr
. WebViewClient ; , .
,
false.
PhotoGallery, WebView .

WebChromeClient
WebView,
, .
fragment_photo_page.xml .

515

. 31.2. (fragment_photo_page.xml)

ProgressBar TextView , , WebView: WebChromeClient. WebViewClient , WebChromeClient


,
(chrome) . (alerts)
JavaScript, favicon, ..
onCreateView().

516

31. - WebView

31.9. WebChromeClient (PhotoPageFragment.java)


@SuppressLint("SetJavaScriptEnabled")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_photo_page, parent, false);
final ProgressBar progressBar = (ProgressBar)v.findViewById(R.id.progressBar);
progressBar.setMax(100); // 0-100
final TextView titleTextView = (TextView)v.findViewById(R.id.titleTextView);
mWebView = (WebView)v.findViewById(R.id.webView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
...
});
mWebView.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView webView, int progress) {
if (progress == 100) {
progressBar.setVisibility(View.INVISIBLE);
} else {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(progress);
}
}

});

public void onReceivedTitle(WebView webView, String title) {


titleTextView.setText(title);
}

mWebView.loadUrl(mUrl);
}

return v;

, onProgressChanged(WebView,int) onReceivedTitle(WebView,
String). , onProgressChanged(WebView,int),
0 100. 100, , , ProgressBar,
View.INVISIBLE.
PhotoGallery .

WebView
. , , WebView -. , WebView
, onSaveInstanceState(),

: JavaScript

517

,
.
( VideoView) Android
. ,

. WebView
.
( , ?
. , .)
PhotoPageActivity , AndroidManifest.xml.
31.10. (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.photogallery"
android:versionCode="1"
android:versionName="1.0" >
...
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
...
<activity
android:name=".PhotoPageActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
...
</application>
</manifest>

, - , (
Android 3.2) .
. ;
.

:
JavaScript
, WebViewClient WebChromeClient
, WebView. JavaScript ,

518

31. - WebView

WebView . http://
developer.android.com/reference/android/webkit/WebView.html
addJavascriptInterface(Object,String).
.
mWebView.addJavascriptInterface(new Object() {
public void send(String message) {
Log.i(TAG, "Received message: " + message);
}
}, "androidObject");

:
<input type="button" value="In WebView!"
onClick="sendToAndroid('In Android land')" />
<script type="text/javascript">
function sendToAndroid(message) {
androidObject.send(message);
}
</script>


- . , HTML
.

32

.
View BoxDrawingView.
, .

. 32.1.

520

32.

DragAndDraw
BoxDrawingView DragAndDraw.
NewAndroid Application Project. ,
.32.2, DragAndDrawActivity.

. 32.2. DragAndDraw

DragAndDrawActivity
DragAndDrawActivity SingleFragmentActivity, . Package
Explorer SingleFragmentActivity.java com.bignerdranch.android.
draganddraw. activity_fragment.xml res/layout
DragAndDraw.
DragAndDrawActivity.java DragAndDrawActivity SingleFragmentActivity. DragAndDrawFragment
(, ).
32.1. (DragAndDrawActivity.java)
public class DragAndDrawActivity extends Activity SingleFragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drag_and_draw);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_drag_and_draw, menu);
return true;
}

DragAndDraw

521

@Override
public Fragment createFragment() {
return new DragAndDrawFragment();
}

DragAndDrawFragment
DragAndDrawFragment, activity_drag_
and_draw.xml fragment_drag_and_draw.xml.
DragAndDrawFragment BoxDrawingView
, . BoxDrawingView.
DragAndDrawFragment
android.support.v4.app.ListFragment. onCreateView(),
fragment_drag_and_draw.xml.
32.2. (DragAndDrawFragment.java)
public class DragAndDrawFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_drag_and_draw, parent, false);
return v;
}
}

DragAndDraw , .

. 32.3. DragAndDraw

522

32.


Android ,
, .

:
; , .
;
. ,
. .
:
. View ,
.
.
,
.
.

BoxDrawingView
BoxDrawingView
View.
BoxDrawingView View .
BoxDrawingView.java .
32.3. BoxDrawingView (BoxDrawingView.java)
public class BoxDrawingView extends View {
//
public BoxDrawingView(Context context) {
this(context, null);
}

// XML
public BoxDrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
}

,
, . , ,

523

AttributeSet XML, XML.


, .
fragment_drag_and_draw.xml,
.
32.4. Add BoxDrawingView (fragment_drag_and_draw.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" />
</RelativeLayout>
<com.bignerdranch.android.draganddraw.BoxDrawingView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

. 32.4. BoxDrawingView

524

32.

BoxDrawingView,
. ,
View. ,
android.view android.widget.
, , .
, android.view android.widget,
.
DragAndDraw ,
. , , .
BoxDrawingView

.



View:
public void setOnTouchListener(View.OnTouchListener l)

, setOnClickListener(View.OnClickListener).
View.OnTouchListener,
, .
View, View:
public boolean onTouchEvent(MotionEvent event)

MotionEvent ,
, . .

ACTION_DOWN

ACTION_MOVE

ACTION_UP

ACTION_CANCEL

onTouchEvent() MotionEvent:
public final int getAction()

BoxDrawingView.java onTouch(), .

525

32.5. BoxDrawingView (BoxDrawingView.java)


public class BoxDrawingView extends View {
public static final String TAG = "BoxDrawingView";
...
public boolean onTouchEvent(MotionEvent event) {
PointF curr = new PointF(event.getX(), event.getY());
Log.i(TAG, "Received event at x=" + curr.x +
", y=" + curr.y + ":");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, " ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, " ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, " ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(TAG, " ACTION_CANCEL");
break;
}

return true;

: X Y PointF. . PointF
Android -, .
DragAndDraw LogCat.
. X Y
, BoxDrawingView.


BoxDrawingView , . .
:
( );
( ).
,
MotionEvent. Box.
Box , .

526

32.

32.6. Box (Box.java)


public class Box {
private PointF mOrigin;
private PointF mCurrent;
public Box(PointF origin) {
mOrigin = mCurrent = origin;
}
public void setCurrent(PointF current) {
mCurrent = current;
}

public PointF getOrigin() {


return mOrigin;
}

BoxDrawingView, Box
(. 32.5).

. 32.5. DragAndDraw

BoxDrawingView , Box
.
32.7.

(BoxDrawingView.java)
public class BoxDrawingView extends View {
public static final String TAG = "BoxDrawingView";
private Box mCurrentBox;
private ArrayList<Box> mBoxes = new ArrayList<Box>();
...
public boolean onTouchEvent(MotionEvent event) {
PointF curr = new PointF(event.getX(), event.getY());

527

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, " ACTION_DOWN");
// Reset drawing state
mCurrentBox = new Box(curr);
mBoxes.add(mCurrentBox);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, " ACTION_MOVE");
if (mCurrentBox != null) {
mCurrentBox.setCurrent(curr);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, " ACTION_UP");
mCurrentBox = null;
break;

case MotionEvent.ACTION_CANCEL:
Log.i(TAG, " ACTION_CANCEL");
mCurrentBox = null;
break;

return true;

ACTION_DOWN mCurrentBox
Box , . Box
( ,
, BoxDrawingView Box ).
mCurrentBox.
mCurrent. , , mCurrentBox . Box ;
.
invalidate() ACTION_MOVE.
BoxDrawingView ,
. : .

onDraw()
(invalid).
, . Android
draw() View .
, . .

528

32.

, View
.
,
View:
protected void onDraw(Canvas canvas)

invalidate(), ACTION_MOVE onTouchEvent(), BoxDrawingView .


onDraw().
Canvas. Canvas Paint , Android.
Canvas . ,
Canvas, , , ,
.
Paint , . , Paint,
, ,
..
BoxDrawingView.java Paint BoxDrawingView XML.
32.8. Paint (BoxDrawingView.java)
public class BoxDrawingView extends View {
private static final String TAG = "BoxDrawingView";
private
private
private
private

ArrayList<Box> mBoxen = new ArrayList<Box>();


Box mCurrentBox;
Paint mBoxPaint;
Paint mBackgroundPaint;

...
// XML
public BoxDrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
// (ARGB)
mBoxPaint = new Paint();
mBoxPaint.setColor(0x22ff0000);

// -
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(0xfff8efe0);

Paint
.

529

32.9. onDraw(Canvas) (BoxDrawingView.java)


@Override
protected void onDraw(Canvas canvas) {
//
canvas.drawPaint(mBackgroundPaint);
for (Box box : mBoxes) {
float left = Math.min(box.getOrigin().x, box.getCurrent().x);
float right = Math.max(box.getOrigin().x, box.getCurrent().x);
float top = Math.min(box.getOrigin().y, box.getCurrent().y);
float bottom = Math.max(box.getOrigin().y, box.getCurrent().y);

canvas.drawRect(left, top, right, bottom, mBoxPaint);

: - ,
.
left, right,
top bottom . left top , bottom
right .
Canvas.drawRect()
.
DragAndDraw .

. 32.6.

530

32.

.
View ? ,
View:
protected Parcelable onSaveInstanceState()
protected void onRestoreInstanceState(Parcelable state)

, onSaveInstanceState(Bundle)
Activity Fragment. Bundle , Parcelable. Bundle
Parcelable , Parcelable . ( Parcelable .
, .)
, : .
MotionEvent.
:
,
;

.
, .

MotionEvent:
public
public
public
public
public

final
final
final
final
final

int getActionMasked()
int getActionIndex()
int getPointerId(int pointerIndex)
float getX(int pointerIndex)
float getY(int pointerIndex)

ACTION_POINTER_UP ACTION_

POINTER_DOWN.

33

RunTracker,
GPS :
, . RunTracker
.
RunTracker GPS .
RunTracker , .

RunTracker
Android ,
. 33.1.
. -, SDK API 9. -,
Google API, Android. Google API
.
Google APIs , Android SDK
Manager. WindowAndroid SDK Manager Google
APIs, . 33.2. Install 1 package.

532

33.

. 33.1. RunTracker

. 33.2. Google API SDK 4.2

SDK
Google APIs.
, .
RunActivity.

RunFragment

533

RunActivity
RunActivity ( RunTracker) SingleFragmentActivity. SingleFragmentActivity.java com.bignerdranch.android.runtracker, activity_fragment.xml res/layout/.

RunActivity.java RunActivity SingleFragmentActivity, RunFragment. RunFragment


, .
33.1. RunActivity (RunActivity.java)
public class RunActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
return new RunFragment();
}
}

RunFragment

RunFragment. RunFragment (. 33.3)
.
.

. 33.3. RunTracker

534

33.


, . 33.3.
res/values/strings.xml , ,
.
33.2. RunTracker (strings.xml)
<resources>
<string name="app_name">RunTracker</string>
<string name="started">Started:</string>
<string name="latitude">Latitude:</string>
<string name="longitude">Longitude:</string>
<string name="altitude">Altitude:</string>
<string name="elapsed_time">Elapsed Time:</string>
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="gps_enabled">GPS Enabled</string>
<string name="gps_disabled">GPS Disabled</string>
<string name="cell_text">Run at %1$s</string>
</resources>


TableLayout,
. TableLayout TableRow
LinearLayout. TableRow TextView:
, . LinearLayout Button.
, . , (http://www.
bignerdranch.com/solutions/AndroidProgramming.zip). 33_Location/RunTracker/res/layout/fragment_run.xml res/layout .

RunFragment
RunFragment.
.
33.3. RunFragment (RunFragment.java)
public class RunFragment extends Fragment {
private Button mStartButton, mStopButton;
private TextView mStartedTextView, mLatitudeTextView,
mLongitudeTextView, mAltitudeTextView, mDurationTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}

LocationManager

535

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_run, container, false);
mStartedTextView = (TextView)view.findViewById(R.id.run_startedTextView);
mLatitudeTextView = (TextView)view.findViewById(R.id.run_latitudeTextView);
mLongitudeTextView =
(TextView)view.findViewById(R.id.run_longitudeTextView);
mAltitudeTextView = (TextView)view.findViewById(R.id.run_altitudeTextView);
mDurationTextView = (TextView)view.findViewById(R.id.run_durationTextView);
mStartButton = (Button)view.findViewById(R.id.run_startButton);
mStopButton = (Button)view.findViewById(R.id.run_stopButton);

return view;

, ,
.33.3.

LocationManager
, .
Android LocationManager.
, .
.
( , )
LocationListener. ( onLocationChanged(Location)),
,
.
LocationListener ,
. ,
RunFragment, LocationListener
requestLocationUpdates() requestSingleUpdate()
LocationManager .
. RunTracker ( )
.
, ,
. PendingIntent
API, Android 2.3 (Gingerbread).

536

33.

PendingIntent,
LocationManager
Intent . , ( )
, LocationManager ,
, , . , ,
.
LocationManager,
(. ), RunManager,
33.4.
33.4. RunManager (RunManager.java)
public class RunManager {
private static final String TAG = "RunManager";
public static final String ACTION_LOCATION =
"com.bignerdranch.android.runtracker.ACTION_LOCATION";
private static RunManager sRunManager;
private Context mAppContext;
private LocationManager mLocationManager;
//
// RunManager.get(Context)
private RunManager(Context appContext) {
mAppContext = appContext;
mLocationManager = (LocationManager)mAppContext
.getSystemService(Context.LOCATION_SERVICE);
}
public static RunManager get(Context c) {
if (sRunManager == null) {
//
//
sRunManager = new RunManager(c.getApplicationContext());
}
return sRunManager;
}
private PendingIntent getLocationPendingIntent(boolean shouldCreate) {
Intent broadcast = new Intent(ACTION_LOCATION);
int flags = shouldCreate ? 0 : PendingIntent.FLAG_NO_CREATE;
return PendingIntent.getBroadcast(mAppContext, 0, broadcast, flags);
}
public void startLocationUpdates() {
String provider = LocationManager.GPS_PROVIDER;

// LocationManager
PendingIntent pi = getLocationPendingIntent(true);
mLocationManager.requestLocationUpdates(provider, 0, 0, pi);

537

public void stopLocationUpdates() {


PendingIntent pi = getLocationPendingIntent(false);
if (pi != null) {
mLocationManager.removeUpdates(pi);
pi.cancel();
}
}

public boolean isTrackingRun() {


return getLocationPendingIntent(false) != null;
}

: RunManager ,
API. , , ( ,
LocationManager).
startLocationUpdates() LocationManager GPS .
requestLocationUpdates(String, long, float, PendingIntent)
( ) ( ) .
, . RunTracker
, ,
.
GPS .
, , ,
.
getLocationPendingIntent(boolean) Intent,
.
, shouldCreate PendingIntent.
getBroadcast() ( ),
PendingIntent .
, isTrackingRun() getLocationPen
dingIntent(false) null ,
PendingIntent .

, . .
RunTracker

538

33.

, BroadcastReceiver, .
, LocationReceiver
.
33.5. LocationReceiver (LocationReceiver.java)
public class LocationReceiver extends BroadcastReceiver {
private static final String TAG = "LocationReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// Location,
Location loc = (Location)intent
.getParcelableExtra(LocationManager.KEY_LOCATION_CHANGED);
if (loc != null) {
onLocationReceived(context, loc);
return;
}
// , -
if (intent.hasExtra(LocationManager.KEY_PROVIDER_ENABLED)) {
boolean enabled = intent
.getBooleanExtra(LocationManager.KEY_PROVIDER_ENABLED, false);
onProviderEnabledChanged(enabled);
}
}
protected void onLocationReceived(Context context, Location loc) {
Log.d(TAG, this + " Got location from " + loc.getProvider() + ": "
+ loc.getLatitude() + ", " + loc.getLongitude());
}
protected void onProviderEnabledChanged(boolean enabled) {
Log.d(TAG, "Provider " + (enabled ? "enabled" : "disabled"));
}
}

onReceive(Context, Intent), LocationManager . LocationManager.KEY_LOCATION_CHANGED Location, . ,


onLocationReceived(Context, Location)
, .
LocationManager
KEY_PROVIDER_ENABLED; onProviderEnabled(boolean) . LocationReceiver,
.
LocationReceiver RunTracker.
ACCESS_FINE_LOCATION uses-feature GPS.

539

33.6.

(AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.runtracker"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-feature android:required="true"
android:name="android.hardware.location.gps"/>
<application android:label="@string/app_name"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme">
<activity android:name=".RunActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".LocationReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.bignerdranch.android.runtracker.ACTION_
LOCATION"/>
</intent-filter>
</receiver>
</application>
</manifest>

, .
, .



, , Start Stop
, RunManager.
updateUI().
33.7. (RunFragment.java)
public class RunFragment extends Fragment {
private RunManager mRunManager;
private Button mStartButton, mStopButton;

540

33.

33.7 ()
private TextView mStartedTextView, mLatitudeTextView,
mLongitudeTextView, mAltitudeTextView, mDurationTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRunManager = RunManager.get(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
mStartButton = (Button)view.findViewById(R.id.run_startButton);
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRunManager.startLocationUpdates();
updateUI();
}
});
mStopButton = (Button)view.findViewById(R.id.run_stopButton);
mStopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRunManager.stopLocationUpdates();
updateUI();
}
});
updateUI();
}

return view;

private void updateUI() {


boolean started = mRunManager.isTrackingRun();

mStartButton.setEnabled(!started);
mStopButton.setEnabled(started);

RunTracker , , LogCat
.
Emulator Control DDMS

GPS. , .
,

541


.
LogCat . , RunFragment
LocationReceiver, Location
. , Run,
. Run, ,
.
33.8. Run (Run.java)
public class Run {
private Date mStartDate;
public Run() {
mStartDate = new Date();
}
public Date getStartDate() {
return mStartDate;
}
public void setStartDate(Date startDate) {
mStartDate = startDate;
}
public int getDurationSeconds(long endMillis) {
return (int)((endMillis - mStartDate.getTime()) / 1000);
}
public static String formatDuration(int durationSeconds) {
int seconds = durationSeconds % 60;
int minutes = ((durationSeconds - seconds) / 60) % 60;
int hours = (durationSeconds - (minutes * 60) - seconds) / 3600;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
}

Run RunFragment.
33.9. (RunFragment.java)
public class RunFragment extends Fragment {
private BroadcastReceiver mLocationReceiver = new LocationReceiver() {
@Override
protected void onLocationReceived(Context context, Location loc) {
mLastLocation = loc;
if (isVisible())
updateUI();
}

542

33.

33.9 ()
@Override
protected void onProviderEnabledChanged(boolean enabled) {
int toastText = enabled ? R.string.gps_enabled : R.string.gps_disabled;
Toast.makeText(getActivity(), toastText, Toast.LENGTH_LONG).show();
}
};
private RunManager mRunManager;
private Run mRun;
private Location mLastLocation;
private Button mStartButton, mStopButton;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
mStartButton = (Button)view.findViewById(R.id.run_startButton);
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRunManager.startLocationUpdates();
mRun = new Run();
updateUI();
}
});
}

...

@Override
public void onStart() {
super.onStart();
getActivity().registerReceiver(mLocationReceiver,
new IntentFilter(RunManager.ACTION_LOCATION));
}
@Override
public void onStop() {
getActivity().unregisterReceiver(mLocationReceiver);
super.onStop();
}
private void updateUI() {
boolean started = mRunManager.isTrackingRun();
if (mRun != null)
mStartedTextView.setText(mRun.getStartDate().toString());
int durationSeconds = 0;
if (mRun != null && mLastLocation != null) {
durationSeconds = mRun.getDurationSeconds(mLastLocation.getTime());

543

mLatitudeTextView.setText(Double.toString(mLastLocation.getLatitude()));
mLongitudeTextView.setText(Double.toString(mLastLocation.getLongitude()));
mAltitudeTextView.setText(Double.toString(mLastLocation.getAltitude()));

}
mDurationTextView.setText(Run.formatDuration(durationSeconds));

mStartButton.setEnabled(!started);
mStopButton.setEnabled(started);

Run Location.
, updateUI(). Run
.
LocationReceiver mLocationReceiver, . ,
GPS .
, onStart() onStop()
, . onCreate(Bundle) onDestroy(), mLastLocation
, .
RunTracker.
.


, ,
, . , ,
LocationManager .
GPS, . -
,
Intent LocationManager.
33.10. (RunManager.java)
public void startLocationUpdates() {
String provider = LocationManager.GPS_PROVIDER;
//
// ( ).

544

33.

33.10 ()
Location lastKnown = mLocationManager.getLastKnownLocation(provider);
if (lastKnown != null) {
//
lastKnown.setTime(System.currentTimeMillis());
broadcastLocation(lastKnown);
}

// LocationManager
PendingIntent pi = getLocationPendingIntent(true);
mLocationManager.requestLocationUpdates(provider, 0, 0, pi);

private void broadcastLocation(Location location) {


Intent broadcast = new Intent(ACTION_LOCATION);
broadcast.putExtra(LocationManager.KEY_LOCATION_CHANGED, location);
mAppContext.sendBroadcast(broadcast);
}

,
GPS. , ,
, ; .
LocationManager .
getAllProviders(). ,
,
.
.

, RunTracker,
. ,
, , . .
LocationManager , .
Emulator Control DDMS. ,
( ), GPX
KML , .

, .
.
1. ACCESS_MOCK_LOCATION.

545

2. LocationManager.addTestProvider().
3. setTestProviderEnabled().
4. setTestProviderStatus().
5. setTestProviderLocation().
6. removeTestProvider().
, . Big Nerd Ranch
TestProvider, ,
.
Android Course Resources Github
https://github.com/bignerdranch/AndroidCourseResources
TestProvider Eclipse.
RunTracker GPS
, ,
.
33.11. (RunManager.java)
public class RunManager {
private static final String TAG = "RunManager";
public static final String ACTION_LOCATION =
"com.bignerdranch.android.runtracker.ACTION_LOCATION";
private static final String TEST_PROVIDER = "TEST_PROVIDER";
private static RunManager sRunManager;
private Context mAppContext;
private LocationManager mLocationManager;
...
public void startLocationUpdates() {
String provider = LocationManager.GPS_PROVIDER;
// ,
// .
if (mLocationManager.getProvider(TEST_PROVIDER) != null &&
mLocationManager.isProviderEnabled(TEST_PROVIDER)) {
provider = TEST_PROVIDER;
}
Log.d(TAG, "Using provider " + provider);
//
// , .
Location lastKnown = mLocationManager.getLastKnownLocation(provider);
if (lastKnown != null) {
//
lastKnown.setTime(System.currentTimeMillis());
broadcastLocation(lastKnown);
}

546

33.

, TestProvider ,
Allow mock locations Developer options Settings (. 33.4).
TestProvider ,
.
RunTracker .
, ,
.

. 33.4.

34


SQLite

JSON. , RunTracker
, .
Android SQLite.
SQLite , ,
API
.
Android Java- SQLite SQLiteDatabase, Cursor.
RunTracker ,
. .


- , . Android,
. SQLiteOpenHelper ,
.
RunTracker SQLiteOpenHelper RunDatabaseHelper. RunManager RunDatabaseHelper
API , . RunDatabaseHelper ,
RunManager API.
API SQLiteOpenHelper ,

548

34. SQLite

.
SQKite . , RunTracker, SQLiteOpenHelper,
.
-

. RunTracker
: Run Location.
: run ( )
location ( ). Run
Location, location
run_id, _id run.
.34.1.

. 34.1. RunTracker

RunDatabaseHelper , 34.1.
34.1. RunDatabaseHelper (RunDatabaseHelper.java)
public class RunDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "runs.sqlite";
private static final int VERSION = 1;
private static final String TABLE_RUN = "run";
private static final String COLUMN_RUN_START_DATE = "start_date";
public RunDatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// "run"
db.execSQL("create table run (" +

549

"_id integer primary key autoincrement, start_date integer)");


// "location"
db.execSQL("create table location (" +
" timestamp integer, latitude real, longitude real, altitude real," +
" provider varchar(100), run_id integer references run(_id))");

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//
//
}

public long insertRun(Run run) {


ContentValues cv = new ContentValues();
cv.put(COLUMN_RUN_START_DATE, run.getStartDate().getTime());
return getWritableDatabase().insert(TABLE_RUN, null, cv);
}

SQLiteOpenHelper :
onCreate(SQLiteDatabase) onUpgrade(SQLiteDatabase, int, int). onCreate() , onUpgrade()
.
,
, . , null
CursorFactory .
RunTracker , SQLiteOpenHelper
. , , 1.

onUpgrade()
, .
onCreate() SQL CREATE TABLE. insertRun(Run),
run .
;
long ContentValues,
.
SQLiteOpenHelper , SQLiteDatabase: getWritableDatabase() getReadableDatabase().
, getWritableDatabase(),
getReadableDatabase().
SQLiteDatabase
SQLiteOpenHelper, (, ) , , ,
.

550

34. SQLite


, Run . , 34.2.
34.2. Run
public class Run {
private long mId;
private Date mStartDate;
public Run() {
mId = -1;
mStartDate = new Date();
}
public long getId() {
return mId;
}
public void setId(long id) {
mId = id;
}
public Date getStartDate() {
return mStartDate;
}

RunManager,
. API, .
, Run.
34.3. (RunManager.java)
public class RunManager {
private static final String TAG = "RunManager";
private static final String PREFS_FILE = "runs";
private static final String PREF_CURRENT_RUN_ID = "RunManager.currentRunId";
public static final String ACTION_LOCATION =
"com.bignerdranch.android.runtracker.ACTION_LOCATION";
private static final String TEST_PROVIDER = "TEST_PROVIDER";
private
private
private
private
private
private

static RunManager sRunManager;


Context mAppContext;
LocationManager mLocationManager;
RunDatabaseHelper mHelper;
SharedPreferences mPrefs;
long mCurrentRunId;

private RunManager(Context appContext) {


mAppContext = appContext;
mLocationManager = (LocationManager)mAppContext
.getSystemService(Context.LOCATION_SERVICE);
mHelper = new RunDatabaseHelper(mAppContext);
mPrefs = mAppContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);

551

mCurrentRunId = mPrefs.getLong(PREF_CURRENT_RUN_ID, -1);

...
private void broadcastLocation(Location location) {
Intent broadcast = new Intent(ACTION_LOCATION);
broadcast.putExtra(LocationManager.KEY_LOCATION_CHANGED, location);
mAppContext.sendBroadcast(broadcast);
}
public Run startNewRun() {
// Run
Run run = insertRun();
//
startTrackingRun(run);
return run;
}

public void startTrackingRun(Run run) {


//
mCurrentRunId = run.getId();
//
mPrefs.edit().putLong(PREF_CURRENT_RUN_ID, mCurrentRunId).commit();
//
startLocationUpdates();
}
public void stopRun() {
stopLocationUpdates();
mCurrentRunId = -1;
mPrefs.edit().remove(PREF_CURRENT_RUN_ID).commit();
}
private Run insertRun() {
Run run = new Run();
run.setId(mHelper.insertRun(run));
return run;
}

, RunManager. startNewRun()
insertRun() Run,
startTrackingRun(Run) , ,
.
RunFragment Start .
RunFragment startTrackingRun(Run) .
Run .

;
RunManager.
, stopRun()
. RunFragment Stop.
RunFragment,
RunManager. , 34.4.

552

34. SQLite

34.4. (RunFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_run, container, false);
...
mStartButton = (Button)view.findViewById(R.id.run_startButton);
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRunManager.startLocationUpdates();
mRun = new Run();
mRun = mRunManager.startNewRun();
updateUI();
}
});
mStopButton = (Button)view.findViewById(R.id.run_stopButton);
mStopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRunManager.stopLocationUpdates();
mRunManager.stopRun();
updateUI();
}
});
updateUI();
}

return view;

Location LocationManager. Run,


RunDatabaseHelper RunManager
. Run RunTracker
, .
BroadcastReceiver.
insertLocation(long, Location) RunDatabaseHelper.
34.5. (RunDatabaseHelper.java)
public class RunDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "runs.sqlite";
private static final int VERSION = 1;
private static final String TABLE_RUN = "run";
private static final String COLUMN_RUN_START_DATE = "start_date";
private static final String TABLE_LOCATION = "location";
private static final String COLUMN_LOCATION_LATITUDE = "latitude";
private static final String COLUMN_LOCATION_LONGITUDE = "longitude";

private
private
private
private
...

static
static
static
static

final
final
final
final

String
String
String
String

553

COLUMN_LOCATION_ALTITUDE = "altitude";
COLUMN_LOCATION_TIMESTAMP = "timestamp";
COLUMN_LOCATION_PROVIDER = "provider";
COLUMN_LOCATION_RUN_ID = "run_id";

public long insertLocation(long runId, Location location) {


ContentValues cv = new ContentValues();
cv.put(COLUMN_LOCATION_LATITUDE, location.getLatitude());
cv.put(COLUMN_LOCATION_LONGITUDE, location.getLongitude());
cv.put(COLUMN_LOCATION_ALTITUDE, location.getAltitude());
cv.put(COLUMN_LOCATION_TIMESTAMP, location.getTime());
cv.put(COLUMN_LOCATION_PROVIDER, location.getProvider());
cv.put(COLUMN_LOCATION_RUN_ID, runId);
return getWritableDatabase().insert(TABLE_LOCATION, null, cv);
}

RunManager .
34.6. (RunManager.java)
private Run insertRun() {
Run run = new Run();
run.setId(mHelper.insertRun(run));
return run;
}

public void insertLocation(Location loc) {


if (mCurrentRunId != -1) {
mHelper.insertLocation(mCurrentRunId, loc);
} else {
Log.e(TAG, "Location received with no tracking run; ignoring.");
}
}

,
insertLocation(Location).
BroadcastReceiver , RunTracker.
LocationReceiver TrackingLocationReceiver
.
TrackingLocationReceiver .
34.7.
 TrackingLocationReceiver: ,
(TrackingLocationReceiver.java)
public class TrackingLocationReceiver extends LocationReceiver {

@Override
protected void onLocationReceived(Context c, Location loc) {
RunManager.get(c).insertLocation(loc);
}

554

34. SQLite

, ACTION_LOCATION.
34.8. TrackingLocationReceiver (AndroidManifest.xml)
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
...
<receiver android:name=".LocationReceiver"
<receiver android:name=".TrackingLocationReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.bignerdranch.android.runtracker.ACTION_LOCATION"/>
</intent-filter>
</receiver>
</application>

,
. RunTracker
, ,
. , , ,
onLocationReceived()
TrackingLocationReceiver. ,
, LogCat.


RunTracker
, RunFragment
Start.
.
. ,
CriminalIntent , ,
, , RunTracker
.
SQLiteDatabase Cursor, .
API , .
,
String.
, Java-
, Run Location.
, , ,
Cursor .

555

Cursor CursorWrapper, Cursor .


,

.
RunDatabaseHelper queryRuns(), RunCursor , .
34.9. (RunDatabaseHelper.java)
public class RunDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "runs.sqlite";
private static final int VERSION = 1;
private static final String TABLE_RUN = "run";
private static final String COLUMN_RUN_ID = "_id";
private static final String COLUMN_RUN_START_DATE = "start_date";
...
public RunCursor queryRuns() {
// "select * from run order by start_date asc"
Cursor wrapped = getReadableDatabase().query(TABLE_RUN,
null, null, null, null, null, COLUMN_RUN_START_DATE + " asc");
return new RunCursor(wrapped);
}
/**
* , "run".
* {@link getRun()} Run,
* .
*/
public static class RunCursor extends CursorWrapper {
public RunCursor(Cursor c) {
super(c);
}

/**
* Run, ,
* null, .
*/
public Run getRun() {
if (isBeforeFirst() || isAfterLast())
return null;
Run run = new Run();
long runId = getLong(getColumnIndex(COLUMN_RUN_ID));
run.setId(runId);
long startDate = getLong(getColumnIndex(COLUMN_RUN_START_DATE));
run.setStartDate(new Date(startDate));
return run;
}

RunCursor : getRun().
getRun() , ,

556

34. SQLite

Run . RunCursor getRun()


,
.
RunCursor
run Run,
.
queryRuns() SQL RunCursor, . RunManager,
RunListFragment.
34.10. (RunManager.java)
private Run insertRun() {
Run run = new Run();
run.setId(mHelper.insertRun(run));
return run;
}
public RunCursor queryRuns() {
return mHelper.queryRuns();
}
public void insertLocation(Location loc) {
if (mCurrentRunId != -1) {
mHelper.insertLocation(mCurrentRunId, loc);
} else {
Log.e(TAG, "Location received with no tracking run; ignoring.");
}
}


CursorAdapter
, RunListActivity
. RunListFragment .
34.11. RunListActivity (RunListActivity.java)
public class RunListActivity extends SingleFragmentActivity {
@Override
protected Fragment createFragment() {
return new RunListFragment();
}
}

CursorAdapter

557

34.12. RunListActivity (AndroidManifest.xml)


<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity android:name=".RunActivity"
<activity android:name=".RunListActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".RunActivity"
android:label="@string/app_name" />
<receiver android:name=".TrackingLocationReceiver"
android:exported="false">
<intent-filter>

RunListFragment. onCreate(Bundle) onDestroy(),


,
(UI-),
ANR (Application Not Responding).
, Loader
.
34.13. RunListFragment (RunListFragment.java)
public class RunListFragment extends ListFragment {
private RunCursor mCursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//
mCursor = RunManager.get(getActivity()).queryRuns();
}
@Override
public void onDestroy() {
mCursor.close();
super.onDestroy();
}
}

RunCursor
ListView, RunListFragment. Android API (
) CursorAdapter, , .
.
CursorAdapter
, .

558

34. SQLite

RunListFragment RunCursorAdapter.
34.14. RunCursorAdapter (RunListFragment.java)
public class RunListFragment extends ListFragment {
private RunCursor mCursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//
mCursor = RunManager.get(getActivity()).queryRuns();
// ,
RunCursorAdapter adapter = new RunCursorAdapter(getActivity(), mCursor);
setListAdapter(adapter);
}
@Override
public void onDestroy() {
mCursor.close();
super.onDestroy();
}
private static class RunCursorAdapter extends CursorAdapter {
private RunCursor mRunCursor;
public RunCursorAdapter(Context context, RunCursor cursor) {
super(context, cursor, 0);
mRunCursor = cursor;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
//
//
LayoutInflater inflater = (LayoutInflater)context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater
.inflate(android.R.layout.simple_list_item_1, parent, false);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
//
Run run = mRunCursor.getRun();

//
TextView startDateTextView = (TextView)view;
String cellText =
context.getString(R.string.cell_text, run.getStartDate());
startDateTextView.setText(cellText);

559

CursorAdapter Context, Cursor


. , , .
RunCursor ,
.
newView(Context, Cursor, ViewGroup),
View .
android.R.layout.simple_list_item_1,
TextView. , , .
bindView(View, Context, Cursor) CursorAdapter,

. View,
newView().
bindView() . RunCursor
Run ( CursorAdapter).
, TextView, Run.
RunTracker;
(,
,
). , :
.


, CriminalIntent.
.
34.15. (run_list_options.xml)
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_item_new_run"
android:showAsAction="always"
android:icon="@android:drawable/ic_menu_add"
android:title="@string/new_run"/>
</menu>

; strings.xml.
34.16. New Run (strings.xml)
<string name="stop">Stop</string>
<string name="gps_enabled">GPS Enabled</string>
<string name="gps_disabled">GPS Disabled</string>
<string name="cell_text">Run at %1$s</string>
<string name="new_run">New Run</string>
</resources>

560

34. SQLite

RunListFragment
, .
34.17. (RunListFragment.java)
public class RunListFragment extends ListFragment {
private static final int REQUEST_NEW_RUN = 0;
private RunCursor mCursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
//
mCursor = RunManager.get(getActivity()).queryRuns();
// ,
RunCursorAdapter adapter = new RunCursorAdapter(getActivity(), mCursor);
setListAdapter(adapter);
}
...
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.run_list_options, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_run:
Intent i = new Intent(getActivity(), RunActivity.class);
startActivityForResult(i, REQUEST_NEW_RUN);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (REQUEST_NEW_RUN == requestCode) {
mCursor.requery();
((RunCursorAdapter)getListAdapter()).notifyDataSetChanged();
}
}
private static class RunCursorAdapter extends CursorAdapter {
private RunCursor mRunCursor;


onActivityResult() ,
.

561

, ( ), . Loader.



. , RunFragment
. RunFragement
RunActivity, .
RunFragment newInstance(long), .
34.18. (RunFragment.java)
public class RunFragment extends Fragment {
private static final String TAG = "RunFragment";
private static final String ARG_RUN_ID = "RUN_ID";
...
private TextView mStartedTextView, mLatitudeTextView,
mLongitudeTextView, mAltitudeTextView, mDurationTextView;
public static RunFragment newInstance(long runId) {
Bundle args = new Bundle();
args.putLong(ARG_RUN_ID, runId);
RunFragment rf = new RunFragment();
rf.setArguments(args);
return rf;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

RunActivity.
RUN_ID, RunFragment
newInstance(long) .
, .
34.19. (RunActivity.java)
public class RunActivity extends SingleFragmentActivity {
/** long */
public static final String EXTRA_RUN_ID =
"com.bignerdranch.android.runtracker.run_id";
@Override
protected Fragment createFragment() {
return new RunFragment();
long runId = getIntent().getLongExtra(EXTRA_RUN_ID, -1);
if (runId != -1) {

562

34. SQLite

34.19 ()

return RunFragment.newInstance(runId);
} else {
return new RunFragment();
}

, ,
RunListFragment RunActivity .
34.20.
 onListItemClick()
(RunListFragment.java)
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// id ;
// CursorAdapter .
Intent i = new Intent(getActivity(), RunActivity.class);
i.putExtra(RunActivity.EXTRA_RUN_ID, id);
startActivity(i);
}
private static class RunCursorAdapter extends CursorAdapter {

. run _id, CursorAdapter


id onListItemClick().
RunActivity . !
, . RunFragment
-
. , , .
, , .
RunDatabaseHelper, queryRun(long),
RunCursor .
34.21. (RunDatabaseHelper.java)
public RunCursor queryRun(long id) {
Cursor wrapped = getReadableDatabase().query(TABLE_RUN,
null, //
COLUMN_RUN_ID + " = ?", //
new String[]{ String.valueOf(id) }, //
null, // group by
null, // order by
null, // having
"1"); // 1
return new RunCursor(wrapped);
}

563

query() .
TABLE_RUN ,
WHERE
. ,
RunCursor .
RunManager getRun(long),
queryRun(long) Run
( ).
34.22. getRun(long) (RunManager.java)
public Run getRun(long id) {
Run run = null;
RunCursor cursor = mHelper.queryRun(id);
cursor.moveToFirst();
// ,
if (!cursor.isAfterLast())
run = cursor.getRun();
cursor.close();
return run;
}

Run RunCursor,
queryRun(long). RunCursor
. , isAfterLast()
false, Run .
RunCursor ,
close() ,
.
, RunFragment
.
, 34.23.
34.23. (RunFragment.java)
public class RunFragment extends Fragment {
private static final String TAG = "RunFragment";
private static final String ARG_RUN_ID = "RUN_ID";
private BroadcastReceiver mLocationReceiver = new LocationReceiver() {
@Override
protected void onLocationReceived(Context context, Location loc) {
if (!mRunManager.isTrackingRun(mRun))
return;
mLastLocation = loc;
if (isVisible())
updateUI();
}
@Override

564

34. SQLite

34.23 ()
protected void onProviderEnabledChanged(boolean enabled) {
int toastText = enabled ? R.string.gps_enabled : R.string.gps_disabled;
Toast.makeText(getActivity(), toastText, Toast.LENGTH_LONG).show();
}
};
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRunManager = RunManager.get(getActivity());<

// Run
Bundle args = getArguments();
if (args != null) {
long runId = args.getLong(ARG_RUN_ID, -1);
if (runId != -1) {
mRun = mRunManager.getRun(runId);
}
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_run, container, false);
...
mStartButton = (Button)view.findViewById(R.id.run_startButton);
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRun = mRunManager.startNewRun();
if (mRun == null) {
mRun = mRunManager.startNewRun();
} else {
mRunManager.startTrackingRun(mRun);
}
updateUI();
}
});

...
return view;

...
private void updateUI() {
boolean started = mRunManager.isTrackingRun();
boolean trackingThisRun = mRunManager.isTrackingRun(mRun);

565

if (mRun != null)
mStartedTextView.setText(mRun.getStartDate().toString());
int durationSeconds = 0;
if (mRun != null && mLastLocation != null) {
durationSeconds = mRun.getDurationSeconds(mLastLocation.getTime());
mLatitudeTextView.setText(Double.toString(mLastLocation.
getLatitude()));
mLongitudeTextView.setText(Double.toString(mLastLocation.
getLongitude()))
mAltitudeTextView.setText(Double.toString(mLastLocation.
getAltitude()));
}
mDurationTextView.setText(Run.formatDuration(durationSeconds));

mStartButton.setEnabled(!started);
mStopButton.setEnabled(started);
mStopButton.setEnabled(started && trackingThisRun);

: RunFragment
. Run,
LocationCursor Location.
LocationCursor RunDatabaseHelper.
34.24. (RunDatabaseHelper.java)
public LocationCursor queryLastLocationForRun(long runId) {
Cursor wrapped = getReadableDatabase().query(TABLE_LOCATION,
null, //
COLUMN_LOCATION_RUN_ID + " = ?", //
new String[]{ String.valueOf(runId) },
null, // group by
null, // having
COLUMN_LOCATION_TIMESTAMP + " desc", //
"1"); // limit 1
return new LocationCursor(wrapped);
}
// ... RunCursor ...
public static class LocationCursor extends CursorWrapper {
public LocationCursor(Cursor c) {
super(c);
}
public Location getLocation() {
if (isBeforeFirst() || isAfterLast())
return null;
//

566

34. SQLite

34.24 ()

String provider = getString(getColumnIndex(COLUMN_LOCATION_PROVIDER));


Location loc = new Location(provider);
//
loc.setLongitude(getDouble(getColumnIndex(COLUMN_LOCATION_LONGITUDE)));
loc.setLatitude(getDouble(getColumnIndex(COLUMN_LOCATION_LATITUDE)));
loc.setAltitude(getDouble(getColumnIndex(COLUMN_LOCATION_ALTITUDE)));
loc.setTime(getLong(getColumnIndex(COLUMN_LOCATION_TIMESTAMP)));
return loc;

LocationCursor , RunCursor, location,


Location. :
Location ,
, .
queryLastLocationForRun(long) queryRun(long), , LocationCursor.
queryRun(long), RunManager
Location .
34.25. (RunManager.java)
public Location getLastLocationForRun(long runId) {
Location location = null;
LocationCursor cursor = mHelper.queryLastLocationForRun(runId);
cursor.moveToFirst();
// ,
if (!cursor.isAfterLast())
location = cursor.getLocation();
cursor.close();
return location;
}

RunFragment
.
34.26.

(RunFragment.java)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRunManager = RunManager.get(getActivity());
// Run
Bundle args = getArguments();
if (args != null) {
long runId = args.getLong(ARG_RUN_ID, -1);
if (runId != -1) {

567

mRun = mRunManager.getRun(runId);
mLastLocation = mRunManager.getLastLocationForRun(runId);

RunTracker
, ( ) .
, .
!

.
:
.
,
.
: , (, ).
,
,
RunActivity.

35

34 SQLite Cursor .
;
.
Loader
. Loader Android 3.0 (Honeycomb); ,
.

Loader LoaderManager
(loader) () . , , ContentProvider,
.
, .
: Loader, AsyncTaskLoader CursorLoader (.35.1). Loader , .
API, LoaderManager
.
AsyncTaskLoader Loader, AsyncTask . ,
, AsyncTaskLoader.
, CursorLoader AsyncTaskLoader Cursor ContentProvider ContentResolver. , RunTracker

Loader LoaderManager

569

CursorLoader , SQLiteDatabase.

LoaderManager. ,
, .

Fragment Activity
LoaderManager getLoaderManager().
initLoader(int, Bundle, LoaderCallbacks<D>)
Loader.
, Bundle (
null),
LoaderCallbacks<D>.
,
LoaderCallbacks,
. 35.1.
Fragment.
Loader
restartLoader(int, Bundle, LoaderC all
backs<D>) .
( )
.
LoaderCallbacks<D> : onCreateLoader() ,
onLoadFinished() onLoaderReset().
RunTracker.
, ,
AsyncTask? , LoaderManager

(, ).
AsyncTask ,
, . setRetainInstance(true) Fragment,
,
.
( !) .
,
, ,
. ,
(retained) ; ,
,
.

570

35.

RunTracker
RunTracker : (RunCursor), (Run) (Location).
SQLite,
Loader
.
AsyncTaskLoader.
, SQLiteCursorLoader,
CursorLoader, Cursor,
. , DataLoader<D>, ;
AsyncTaskLoader .


RunListFragment RunManager
RunCursor, , onCreate(Bundle).
,
. RunListFragment LoaderManager ( )
LoaderCallbacks
.
RunListFragment ( , ),
AsyncTaskLoader SQLiteCursorLoader (35.1). CursorLoader,
ContentProvider.
35.1. SQLite (SQLiteCursorLoader.java)
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
private Cursor mCursor;
public SQLiteCursorLoader(Context context) {
super(context);
}
protected abstract Cursor loadCursor();
@Override
public Cursor loadInBackground() {
Cursor cursor = loadCursor();
if (cursor != null) {
// ,
cursor.getCount();
}
return cursor;
}
@Override
public void deliverResult(Cursor data) {

571

Cursor oldCursor = mCursor;


mCursor = data;
if (isStarted()) {
super.deliverResult(data);
}

if (oldCursor != null && oldCursor != data && !oldCursor.isClosed()) {


oldCursor.close();
}

@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
// , .
cancelLoad();
}
@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
@Override
protected void onReset() {
super.onReset();
// ,
onStopLoading();

if (mCursor != null && !mCursor.isClosed()) {


mCursor.close();
}
mCursor = null;

SQLiteCursorLoader AsyncTaskLoader API


Cursor mCursor. loadInBackground()
loadCursor() Cursor, getCount() ,

.
deliverResult(Cursor) . ( , ), deliverResult()

572

35.

. , . , ,
.
RunTracker,
API AsyncTaskLoader.
RunListCursorLoader RunListFragment .
35.2. RunListCursorLoader (RunListFragment.java)
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// id ;
// CursorAdapter .
Intent i = new Intent(getActivity(), RunActivity.class);
i.putExtra(RunActivity.EXTRA_RUN_ID, id);
startActivity(i);
}
private static class RunListCursorLoader extends SQLiteCursorLoader {
public RunListCursorLoader(Context context) {
super(context);
}
@Override
protected Cursor loadCursor() {
//
return RunManager.get(getContext()).queryRuns();
}
}
private static class RunCursorAdapter extends CursorAdapter {

RunListFragment LoaderCallbacks Cursor.


.
35.3. LoaderCallbacks<Cursor> (RunListFragment.java)
public class RunListFragment extends ListFragment implements LoaderCallbacks<Cursor>
{
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new RunListCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// ,
RunCursorAdapter adapter =

573

new RunCursorAdapter(getActivity(), (RunCursor)cursor);


setListAdapter(adapter);

@Override
public void onLoaderReset(Loader<Cursor> loader) {
// ( )
setListAdapter(null);
}

onCreateLoader(int, Bundle) LoaderManager,


. id
, , Bundle
. ; RunListCursorLoader,
Activity .
onLoadFinished(Loader<Cursor>, Cursor)
, . ListView RunCursorAdapter, .
, onLoaderReset(Loader<Cursor>)
. , , null.
, LoaderManager
. mCursor onDestroy(),
.
35.4. Loader (RunListFragment.java)
public class RunListFragment extends ListFragment implements LoaderCallbacks<Cursor>
{
private static final int REQUEST_NEW_RUN = 0;
private RunCursor mCursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
//
mCursor = RunManager.get(getActivity()).queryRuns();
// ,
RunCursorAdapter adapter = new RunCursorAdapter(getActivity(), mCursor);
setListAdapter(adapter);
//
getLoaderManager().initLoader(0, null, this);
}
...
@Override
public void onDestroy() {
mCursor.close();

574

35.

35.4 ()
}

super.onDestroy();

...
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (REQUEST_NEW_RUN == requestCode) {
mCursor.requery();
((RunCursorAdapter)getListAdapter()).notifyDataSetChanged();
//
getLoaderManager().restartLoader(0, null, this);
}
}

, , .
,
. ListFragment
, null.


SQLiteCursorLoader ,
(, ), RunFragment , RunManager.
.
DataLoader,
AsyncTaskLoader. DataLoader ,
AsyncTaskLoader, loadInBackground().
DataLoader 35.5.
35.5. (DataLoader.java)
public abstract class DataLoader<D> extends AsyncTaskLoader<D> {
private D mData;
public DataLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (mData != null) {
deliverResult(mData);
} else {
forceLoad();
}
}

575

@Override
public void deliverResult(D data) {
mData = data;
if (isStarted())
super.deliverResult(data);
}

DataLoader D . onStartLoading() ,
.
forceLoad() .
deliverResult(D) ,
.
, DataLoader RunLoader RunFragment.
35.6. (RunLoader.java)
public class RunLoader extends DataLoader<Run> {
private long mRunId;
public RunLoader(Context context, long runId) {
super(context);
mRunId = runId;
}

@Override
public Run loadInBackground() {
return RunManager.get(getContext()).getRun(mRunId);
}

RunLoader Context (Activity) long,


. loadInBackground()
RunManager .
, RunLoader RunFragment
RunManager . : RunFragment
, , LoaderCallbacks<D>
Java
RunFragment. ,
, LoaderCallbacks<D> Run Location , initLoader()
LoaderManager.
RunLoaderCallbacks
RunFragment.

576

35.

35.7. RunLoaderCallbacks (RunFragment.java)


private class RunLoaderCallbacks implements LoaderCallbacks<Run> {
@Override
public Loader<Run> onCreateLoader(int id, Bundle args) {
return new RunLoader(getActivity(), args.getLong(ARG_RUN_ID));
}
@Override
public void onLoadFinished(Loader<Run> loader, Run run) {
mRun = run;
updateUI();
}

@Override
public void onLoaderReset(Loader<Run> loader) {
//
}

onCreateLoader(int, Bundle) RunLoader, ,


. onCreate(Bundle).
onLoadFinished() mRun updateUI(),
.
onLoaderReset() , Run
.
LoaderManager
onCreate(Bundle) RunFragment. LOAD_RUN, LoaderManager
RunFragment.
35.8. (RunFragment.java)
public class RunFragment
private static final
private static final
private static final

extends Fragment {
String TAG = "RunFragment";
String ARG_RUN_ID = "RUN_ID";
int LOAD_RUN = 0;

...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRunManager = RunManager.get(getActivity());
//
Bundle args = getArguments();
if (args != null) {
long runId = args.getLong(ARG_RUN_ID, -1);

577

if (runId != -1) {
mRun = mRunManager.getRun(runId);
LoaderManager lm = getLoaderManager();
lm.initLoader(LOAD_RUN, args, new RunLoaderCallbacks());
}

RunTracker , ,
, .
( ), , , ,
.


.
,
, .
LastLocationLoader, .
35.9. LastLocationLoader (LastLocationLoader.java)
public class LastLocationLoader extends DataLoader<Location> {
private long mRunId;

public LastLocationLoader(Context context, long runId) {


super(context);
mRunId = runId;
}
@Override
public Location loadInBackground() {
return RunManager.get(getContext()).getLastLocationForRun(mRunId);
}

RunLoader, ,
getLastLocationForRun(long) RunManager .
LocationLoaderCallbacks RunFragment.
35.10. LocationLoaderCallbacks (RunFragment.java)
private class LocationLoaderCallbacks implements LoaderCallbacks<Location> {
@Override
public Loader<Location> onCreateLoader(int id, Bundle args) {
return new LastLocationLoader(getActivity(), args.getLong(ARG_RUN_ID));
}
@Override
public void onLoadFinished(Loader<Location> loader, Location location) {

578

35.

35.10 ()

mLastLocation = location;
updateUI();

@Override
public void onLoaderReset(Loader<Location> loader) {
//
}

RunLoaderCallbacks, , mLastLocation .
RunManager
onCreate(Bundle), ID LOAD_LOCATION.
35.11. (RunFragment.java)
public class RunFragment
private static final
private static final
private static final
private static final
...

extends Fragment {
String TAG = "RunFragment";
String ARG_RUN_ID = "RUN_ID";
int LOAD_RUN = 0;
int LOAD_LOCATION = 1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mRunManager = RunManager.get(getActivity());

//
Bundle args = getArguments();
if (args != null) {
long runId = args.getLong(ARG_RUN_ID, -1);
if (runId != -1) {
LoaderManager lm = getLoaderManager();
lm.initLoader(LOAD_RUN, args, new RunLoaderCallbacks());
mLastLocation = mRunManager.getLastLocationForRun(runId);
lm.initLoader(LOAD_LOCATION, args, new LocationLoaderCallbacks());
}
}

RunTracker . ,
, .

36

RunTracker
. Google Maps API ( 2) .
RunMapFragment,
,
.
, ,
Maps API.

Maps API RunTracker


Maps API ( 2) Google Play services SDK , .


Google Play services SDK ( , Maps API)
Android 2.2
Google Play. .

Google Play services SDK


Maps API ,
Google Play services SDK
. ,
http://developer.android.com/google/
play-services/.

580

36.

1. Android SDK Manager Google Play services


Extras. extras/google/google_play_services
Android SDK.
2. Eclipse
FileImportExisting Android Code Into Workspace. Google Play services libproject/google-play-services_lib.
Copy projects into workspace
, .
3. RunTracker Android, Library. Add
google-play-services_lib.

Google Maps API


Maps API, API . , Google
, .
https://developers.google.com/maps/documentation/android/start .

RunTracker
Google Play services Maps API
(
API). XML
RunTracker. , MAPS_RECEIVE RunTracker.
RunMapActivity.
36.1. Maps API (AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.runtracker"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="15" />
<permission
android:name="com.bignerdranch.android.runtracker.permission.MAPS_RECEIVE"
android:protectionLevel="signature"/>
<uses-permission
android:name="com.bignerdranch.android.runtracker.permission.MAPS_RECEIVE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

581

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-feature android:required="true"
android:name="android.hardware.location.gps" />
<uses-feature
android:required="true"
android:glEsVersion="0x00020000"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity android:name=".RunListActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".RunActivity"
android:label="@string/app_name" />
<activity android:name=".RunMapActivity"
android:label="@string/app_name" />
<receiver android:name=".TrackingLocationReceiver"
android:exported="false">
<intent-filter>
<action
android:name="com.bignerdranch.android.runtracker.ACTION_LOCATION"/>
</intent-filter>
</receiver>
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="your-maps-API-key-here"/>
</application>
</manifest>


. Maps API MapFragment SupportMapFragment,
MapView
GoogleMap.
SupportMapFragment RunMapFragment ,
36.2.
36.2. RunMapFragment (RunMapFragment.java)
public class RunMapFragment extends SupportMapFragment {
private static final String ARG_RUN_ID = "RUN_ID";
private GoogleMap mGoogleMap;

582

36.

36.2 ()
public static RunMapFragment newInstance(long runId) {
Bundle args = new Bundle();
args.putLong(ARG_RUN_ID, runId);
RunMapFragment rf = new RunMapFragment();
rf.setArguments(args);
return rf;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, parent, savedInstanceState);
// GoogleMap
mGoogleMap = getMap();
//
mGoogleMap.setMyLocationEnabled(true);
}

return v;

newInstance(long) RunMapFragment
,
RunFragment. .
onCreateView()
, ,
GoogleMap . GoogleMap
, MapView,
.
setMyLocationEnabled(boolean),
.
RunMapFragment RunMapActivity
. ,
.
36.3. - (RunMapActivity.java)
public class RunMapActivity extends SingleFragmentActivity {
/** long */
public static final String EXTRA_RUN_ID =
"com.bignerdranch.android.runtracker.run_id";

@Override
protected Fragment createFragment() {
long runId = getIntent().getLongExtra(EXTRA_RUN_ID, -1);
if (runId != -1) {
return RunMapFragment.newInstance(runId);
} else {
return new RunMapFragment();
}
}

583

RunMapActivity RunFragment . ,
. , , , ,
.
36.4. (res/values/strings.xml)
<string name="new_run">New Run</string>
<string name="map">Map</string>
<string name="run_start">Run Start</string>
<string name="run_started_at_format">Run started at %s</string>
<string name="run_finish">Run Finish</string>
<string name="run_finished_at_format">Run finished at %s</string>
</resources>

RunFragment Map.
36.5. Map (fragment_run.xml)
<Button android:id="@+id/run_stopButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/stop"
/>
<Button android:id="@+id/run_mapButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/map"
/>
</LinearLayout>
</TableLayout>

RunFragment .
36.6. Map (RunFragment.java)
public class RunFragment extends Fragment {
...
private RunManager mRunManager;
private Run mRun;
private Location mLastLocation;
private Button mStartButton, mStopButton, mMapButton;
private TextView mStartedTextView, mLatitudeTextView,
mLongitudeTextView, mAltitudeTextView, mDurationTextView;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

584

36.

36.6 ()
...
mMapButton = (Button)view.findViewById(R.id.run_mapButton);
mMapButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(getActivity(), RunMapActivity.class);
i.putExtra(RunMapActivity.EXTRA_RUN_ID, mRun.getId());
startActivity(i);
}
});
updateUI();
}

return view;

...
private void updateUI() {
boolean started = mRunManager.isTrackingRun();
boolean trackingThisRun = mRunManager.isTrackingRun(mRun);
if (mRun != null)
mStartedTextView.setText(mRun.getStartDate().toString());
int durationSeconds = 0;
if (mRun != null && mLastLocation != null) {
durationSeconds = mRun.getDurationSeconds(mLastLocation.getTime());
mLatitudeTextView.setText(Double.toString
(mLastLocation.getLatitude()));
mLongitudeTextView.setText(Double.toString
(mLastLocation.getLongitude()));
mAltitudeTextView.setText(Double.toString
(mLastLocation.getAltitude()));
mMapButton.setEnabled(true);
} else {
mMapButton.setEnabled(false);
}
mDurationTextView.setText(Run.formatDuration(durationSeconds));

mStartButton.setEnabled(!started);
mStopButton.setEnabled(started && trackingThisRun);

RunTracker . , Map.
.36.1, ,
- Big Nerd Ranch.

585

. 36.1. RunTracker


, . Maps
API , . RunDatabaseHelper RunManager , LocationCursor .
36.7. (RunDatabaseHelper.java)
public LocationCursor queryLocationsForRun(long runId) {
Cursor wrapped = getReadableDatabase().query(TABLE_LOCATION,
null,
COLUMN_LOCATION_RUN_ID + " = ?", //
new String[]{ String.valueOf(runId) },
null, // group by
null, // having
COLUMN_LOCATION_TIMESTAMP + " asc"); //
return new LocationCursor(wrapped);
//
}

queryLocationsForRun(long) queryLastLocation
ForRun(long) , SQLite,
, .

586

36.

RunManager, (faade)
RunMapFragment.
36.8. , II (RunManager.java)
public LocationCursor queryLocationsForRun(long runId) {
return mHelper.queryLocationsForRun(runId);
}

RunMapFragment . -

,
Loader. LocationListCursorLoader .
36.9. (LocationListCursorLoader.java)
public class LocationListCursorLoader extends SQLiteCursorLoader {
private long mRunId;
public LocationListCursorLoader(Context c, long runId) {
super(c);
mRunId = runId;
}

@Override
protected Cursor loadCursor() {
return RunManager.get(getContext()).queryLocationsForRun(mRunId);
}

RunMapFragment .
36.10. RunMapFragment (RunMapFragment.java)
public class RunMapFragment extends SupportMapFragment
implements LoaderCallbacks<Cursor> {
private static final String ARG_RUN_ID = "RUN_ID";
private static final int LOAD_LOCATIONS = 0;
private GoogleMap mGoogleMap;
private LocationCursor mLocationCursor;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//
Bundle args = getArguments();
if (args != null) {
long runId = args.getLong(ARG_RUN_ID, -1);
if (runId != -1) {
LoaderManager lm = getLoaderManager();
lm.initLoader(LOAD_LOCATIONS, args, this);
}
}

587

...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
long runId = args.getLong(ARG_RUN_ID, -1);
return new LocationListCursorLoader(getActivity(), runId);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mLocationCursor = (LocationCursor)cursor;
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
//
mLocationCursor.close();
mLocationCursor = null;
}

RunMapFragment LocationCursor .
.
onLoaderReset(Loader<Cursor>) ,
.
LoaderManager , .
, , .
:
GoogleMap. updateUI() ,
onLoadFinished().
36.11. (RunMapFragment.java)
private void updateUI() {
if (mGoogleMap == null || mLocationCursor == null)
return;
// .
// .
PolylineOptions line = new PolylineOptions();
// LatLngBounds .
LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();
//
mLocationCursor.moveToFirst();
while (!mLocationCursor.isAfterLast()) {
Location loc = mLocationCursor.getLocation();
LatLng latLng = new LatLng(loc.getLatitude(), loc.getLongitude());
line.add(latLng);
latLngBuilder.include(latLng);
mLocationCursor.moveToNext();
}
//
mGoogleMap.addPolyline(line);
//

588

36.

36.11 ()

//
// .
Display display = getActivity().getWindowManager().getDefaultDisplay();
// .
LatLngBounds latLngBounds = latLngBuilder.build();
CameraUpdate movement = CameraUpdateFactory.newLatLngBounds(latLngBounds,
display.getWidth(), display.getHeight(), 15);
mGoogleMap.moveCamera(movement);
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
long runId = args.getLong(ARG_RUN_ID, -1);
return new LocationListCursorLoader(getActivity(), runId);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mLocationCursor = (LocationCursor)cursor;
updateUI();
}

Maps API . PolylineOptions, ,


, LatLngBounds.Builder .
LocationCursor Location
LatLng . LatLng PolylineOptions,
LatLngBounds
.
addPolyline(PolylineOptions)
GoogleMap, .
,
. , , CameraUpdate
moveCamera(CameraUpdate).
newLatLngBounds(LatLngBounds, int, int, int) CameraUpdateFactory.

, , . newLatLngBounds(LatLngBounds,
int), IllegalStateException,
, MapView
. ,
updateUI(), .
RunTracker
, . ,
PolylineOptions .

589


, , ,
, .
, .
updateUI() , .
36.12. (RunMapFragment.java)
private void updateUI() {
if (mGoogleMap == null || mLocationCursor == null)
return;
// .
// .
PolylineOptions line = new PolylineOptions();
// LatLngBounds .
LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();
//
mLocationCursor.moveToFirst();
while (!mLocationCursor.isAfterLast()) {
Location loc = mLocationCursor.getLocation();
LatLng latLng = new LatLng(loc.getLatitude(), loc.getLongitude());
Resources r = getResources();
// ,
if (mLocationCursor.isFirst()) {
String startDate = new Date(loc.getTime()).toString();
MarkerOptions startMarkerOptions = new MarkerOptions()
.position(latLng)
.title(r.getString(R.string.run_start))
.snippet(r.getString(R.string.run_started_at_format, startDate));
mGoogleMap.addMarker(startMarkerOptions);
} else if (mLocationCursor.isLast()) {
// ,
// ,
String endDate = new Date(loc.getTime()).toString();
MarkerOptions finishMarkerOptions = new MarkerOptions()
.position(latLng)
.title(r.getString(R.string.run_finish))
.snippet(r.getString(R.string.run_finished_at_format, endDate));
mGoogleMap.addMarker(finishMarkerOptions);
}
line.add(latLng);
latLngBuilder.include(latLng);
mLocationCursor.moveToNext();

}
//
mGoogleMap.addPolyline(line);

MarkerOptions , .

590

36.

, .
,
( )
icon(BitmapDescriptor) BitmapDescriptorFactory.
, .
RunTracker
. !

.
RunMapFragment
, .
. LocationReceiver RunMapFragment,
.
(overlay) , .

37

! .
, ; , .
; Android.


, : Android. -,
.
, ? .
. . ,
, . . ,
: .
. . -
?
.
, .
. Android #android-dev irc.freenode.net. Android
Developer Office Hours (https://plus.google.com/+AndroidDevelopers/posts)
Android
.
.
.
Android http://www.github.com. , , .
, .

592

37.


, Big Nerd Ranch
http://www.bignerdranch.com/books. ,
. ,
, .
http://www.bignerdranch.com.

, , .
, .