Pro Android 5 - Lagout Android 5 [MacLean... · Pro Android 5 Dave MacLean Satya Komatineni ... and...
Transcript of Pro Android 5 - Lagout Android 5 [MacLean... · Pro Android 5 Dave MacLean Satya Komatineni ... and...
ProAndroid5
DaveMacLean
SatyaKomatineni
GrantAllen
ProAndroid5
Copyright©2015byDaveMacLean,SatyaKomatineni,andGrantAllen
Thisworkissubjecttocopyright.AllrightsarereservedbythePublisher,whetherthewholeorpartofthematerialisconcerned,specificallytherightsoftranslation,reprinting,reuseofillustrations,recitation,broadcasting,reproductiononmicrofilmsorinanyotherphysicalway,andtransmissionorinformationstorageandretrieval,electronicadaptation,computersoftware,orbysimilarordissimilarmethodologynowknownorhereafterdeveloped.Exemptedfromthislegalreservationarebriefexcerptsinconnectionwithreviewsorscholarlyanalysisormaterialsuppliedspecificallyforthepurposeofbeingenteredandexecutedonacomputersystem,forexclusiveusebythepurchaserofthework.DuplicationofthispublicationorpartsthereofispermittedonlyundertheprovisionsoftheCopyrightLawofthePublisher’slocation,initscurrentversion,andpermissionforusemustalwaysbeobtainedfromSpringer.PermissionsforusemaybeobtainedthroughRightsLinkattheCopyrightClearanceCenter.ViolationsareliabletoprosecutionundertherespectiveCopyrightLaw.
ISBN-13(pbk):978-1-4302-4680-0
ISBN-13(electronic):978-1-4302-4681-7
Trademarkednames,logos,andimagesmayappearinthisbook.Ratherthanuseatrademarksymbolwitheveryoccurrenceofatrademarkedname,logo,orimageweusethenames,logos,andimagesonlyinaneditorialfashionandtothebenefitofthetrademarkowner,withnointentionofinfringementofthetrademark.
TheimagesoftheAndroidRobot(01/AndroidRobot)arereproducedfromworkcreatedandsharedbyGoogleandusedaccordingtotermsdescribedintheCreativeCommons3.0AttributionLicense.AndroidandallAndroidandGoogle-basedmarksaretrademarksorregisteredtrademarksofGoogleInc.intheUnitedStatesandothercountries.ApressMediaLLCisnotaffiliatedwithGoogleInc.,andthisbookwaswrittenwithoutendorsementfromGoogleInc.
Theuseinthispublicationoftradenames,trademarks,servicemarks,andsimilarterms,eveniftheyarenotidentifiedassuch,isnottobetakenasanexpressionofopinionastowhetherornottheyaresubjecttoproprietaryrights.
Whiletheadviceandinformationinthisbookarebelievedtobetrueandaccurateatthedateofpublication,neithertheauthorsnortheeditorsnorthepublishercanacceptanylegalresponsibilityforanyerrorsoromissionsthatmaybemade.Thepublishermakesnowarranty,expressorimplied,withrespecttothematerialcontainedherein.
ManagingDirector:WelmoedSpahr
LeadEditor:SteveAnglin
TechnicalReviewer:ShaneKirk
EditorialBoard:SteveAnglin,LouiseCorrigan,JonathanGennick,RobertHutchinson,MichelleLowman,JamesMarkham,Susan
McDermott,MatthewMoodie,JeffreyPepper,DouglasPundick,BenRenow-Clarke,GwenanSpearing,SteveWeiss
CoordinatingEditor:MarkPowers
CopyEditor:BrendanFrost
Compositor:SPiGlobal
Indexer:SPiGlobal
Artist:SPiGlobal
CoverDesigner:AnnaIshchenko
DistributedtothebooktradeworldwidebySpringerScience+BusinessMediaNewYork,233SpringStreet,6thFloor,NewYork,NY10013.Phone1-800-SPRINGER,fax(201)348-4505,[email protected],orvisitwww.springeronline.com.ApressMedia,LLCisaCaliforniaLLCandthesolemember(owner)isSpringerScience+BusinessMediaFinanceInc(SSBMFinanceInc).SSBMFinanceIncisaDelawarecorporation.
Forinformationontranslations,[email protected],orvisitwww.apress.com.
ApressandfriendsofEDbooksmaybepurchasedinbulkforacademic,corporate,orpromotionaluse.eBookversionsandlicensesarealsoavailableformosttitles.Formoreinformation,referenceourSpecialBulkSales–eBookLicensingwebpageatwww.apress.com/bulk-sales.
Anysourcecodeorothersupplementarymaterialreferencedbytheauthorinthistextisavailabletoreadersatwww.apress.com/9781430246800.Fordetailedinformationabouthowtolocateyourbook’ssourcecode,gotowww.apress.com/source-code/.
TomywifeRosie,Ineverwouldhavemadeitthisfarwithoutyourloveandsupport.Thankyou,youarewonderful,andIloveyou.
—DaveMacLean
TomylateyoungerbrotherSankarKomatineniwhoseindustry,hardship,andzestforlifefillsmewithsadnessandjoy.
—SatyaKomatineni
ToalltheAndroiddevelopersouttheredreamingofthenextgreatAndroidapp!Youareanamazingcommunity,andIcan’twaittoseewhatyou
developnext.
—GrantAllen
ContentsataGlanceAbouttheAuthors
AbouttheTechnicalReviewer
Acknowledgments
Foreword
Introduction
Chapter1:HelloAndroid
Chapter2:IntroductiontoAndroidApplicationArchitecture
Chapter3:BuildingBasicUserInterfacesandUsingControls
Chapter4:AdaptersandListControls
Chapter5:BuildingMoreAdvancedUILayouts
Chapter6:WorkingwithMenusandActionBars
Chapter7:StylesandThemes
Chapter8:Fragments
Chapter9:RespondingtoConfigurationChanges
Chapter10:WorkingwithDialogs
Chapter11:WorkingwithPreferencesandSavingState
Chapter12:UsingtheCompatibilityLibraryforOlderDevices
Chapter13:ExploringPackages,Processes,Threads,andHandlers
Chapter14:BuildingandConsumingServices
Chapter15:AdvancedAsyncTaskandProgressDialogs
Chapter16:BroadcastReceiversandLong-RunningServices
Chapter17:ExploringtheAlarmManager
Chapter18:Exploring2DAnimation
Chapter19:ExploringMapsandLocation-BasedServices
Chapter20:UnderstandingtheMediaFrameworks
Chapter21:HomeScreenWidgets
Chapter22:TouchScreens
Chapter23:ImplementingDragandDrop
Chapter24:UsingSensors
Chapter25:ExploringAndroidPersistenceandContentProviders
Chapter26:UnderstandingLoaders
Chapter27:ExploringtheContactsAPI
Chapter28:ExploringSecurityandPermissions
Chapter29:UsingGoogleCloudMessagingwithAndroid
Chapter30:DeployingYourApplication:GooglePlayStoreandBeyond
Index
ContentsAbouttheAuthors
AbouttheTechnicalReviewer
Acknowledgments
Foreword
Introduction
Chapter1:HelloAndroid
PrerequisitesforAndroidDevelopment
SettingUpYourEclipseEnvironmentDownloadingJDK6
DownloadingEclipse4.2
DownloadingtheAndroidSDK
TheToolsWindow
InstallingADT
SettingUpYourAndroidStudioEnvironmentJavarequirementsforAndroidStudio
DownloadingandInstallingAndroidStudio
LearningAndroid’sFundamentalComponentsView
Activity
Fragment
Intent
ContentProvider
Service
AndroidManifest.xml
AVDs
HelloWorld!
AVDs
RunningonaRealDevice
ExploringtheStructureofanAndroidApplication
ExaminingtheApplicationLifeCycle
SimpleDebugging
LaunchingtheEmulator
References
Summary
Chapter2:IntroductiontoAndroidApplicationArchitecture
ExploringaSimpleAndroidApplication
DefiningUIthroughLayoutFilesSpecifyingCommentsinLayoutFiles
AddingViewsandViewGroupsinLayoutFiles
SpecifyingControlPropertiesinLayoutFiles
IndicatingViewGroupProperties
ControllingWidthandHeightofaControl
IntroducingResourcesandBackgrounds
WorkingwithTextControlsintheLayoutFile
WorkingwithAutogeneratedIDsforControls
ImplementingProgrammingLogicLoadingtheLayoutFileintoanActivity
GatheringControls
SettingUpButtons
RespondingtoButtonClicks:TyingItAllTogether
UpdatingtheAndroidManifest.XML
PlacingtheFilesintheAndroidProject
TestingtheCalculatorApponaRealDevice
AndroidActivityLifeCyclevoidonCreate(BundlesavedInstanceState)
voidonStart()
voidonRestoreInstanceState(BundlesavedInstanceState)
voidonResume()
voidonPause()
voidonStop()
voidonSaveInstanceState(BundlesaveStateBundle)
voidonRestart()
ObjectonRetainNonConfigurationInstance()
voidonDestroy()
GeneralNotesonActivityCallbacks
MoreonResourcesDirectoryStructureofResources
ReadingResourcesfromJavaCode
RuntimeBehaviorofDrawableResources
UsingArbitraryXMLFilesasResources
WorkingwithRawResourceFiles
ReadingFilesfromtheAssetsDirectory
ReadingResourcesandAssetsWithoutanActivityReference
UnderstandingResourceDirectories,Language,andLocale
MoreonIntentsStartingActivitiesforResults
ExercisingtheGET_CONTENTAction
RelatingIntentsandActivities
UnderstandingExplicitandImplicitIntents
SavingStateinAndroid
RoadmapforLearningAndroidandtheRestoftheBookTrack1:UIEssentialsforYourAndroidApplications
Track2:SavingState
Track3:Preparing/TakingYourApplicationtotheMarket
Track4:MakingYourApplicationRobust
Track5:BringingFinessetoYourApps
Track6:IntegratingwithOtherDevicesandtheCloud
FinalTrack:GettingaHelpingHandfromExpertAndroid
AsWeLeaveYouNowwiththeRestoftheBook
References
Summary
Chapter3:BuildingBasicUserInterfacesandUsingControls
UIDevelopmentinAndroidBuildingaUICompletelyinCode
BuildingaUICompletelyinXML
BuildingaUIinXMLwithCode
UnderstandingAndroid’sCommonControlsTextControls
ButtonControls
TheImageViewControl
DateandTimeControls
TheMapViewControl
References
Summary
Chapter4:AdaptersandListControls
UnderstandingAdaptersGettingtoKnowSimpleCursorAdapter
GettingtoKnowArrayAdapter
UsingAdapterswithAdapterViewsTheBasicListControl:ListView
TheGridViewControl
TheSpinnerControl
TheGalleryControl
Summary
Chapter5:BuildingMoreAdvancedUILayouts
CreatingCustomAdaptersOtherControlsinAndroid
StylesandThemesUsingStyles
UsingThemes
UnderstandingLayoutManagersTheLinearLayoutLayoutManager
TheTableLayoutLayoutManager
TheRelativeLayoutLayoutManager
TheFrameLayoutLayoutManager
TheGridLayoutLayoutManager
CustomizingtheLayoutforVariousDeviceConfigurations
Summary
Chapter6:WorkingwithMenusandActionBars
WorkingwithMenusThroughXMLFilesCreatingXMLMenuResourceFiles
PopulatingActivityMenufromMenuXMLFiles
RespondingtoXML-BasedMenuItems
WorkingwithMenusinJavaCodeWorkingwithMenuGroups
UnderstandingExpandedMenus
WorkingwithIconMenus
WorkingwithSubmenus
WorkingwithContextMenusRegisteringaViewforaContextMenu
PopulatingaContextMenu
RespondingtoContextMenuItems
IncorporatingDynamicMenusWorkingwithPop-upMenus
ExploringActionBars
ImplementingaStandardActionBar
ImplementingaTabbedActionBar
ImplementingaList-BasedActionBar
ExploringActionBarandSearchViewDefiningaSearchViewWidgetasaMenuItem
CreatingaSearchResultsActivity
SpecifyingaSearchableXMLFile
DefiningtheSearchResultsActivityintheManifestFile
IdentifyingtheSearchTargetfortheSearchViewWidget
Resources
Summary
Chapter7:StylesandThemes
UsingStyles
UsingThemes
References
Summary
Chapter8:Fragments
WhatIsaFragment?WhentoUseFragments
TheStructureofaFragment
AFragment’sLifeCycle
SampleFragmentAppShowingtheLifeCycle
FragmentTransactionsandtheFragmentBackStack
FragmentTransactionTransitionsandAnimations
TheFragmentManagerCautionWhenReferencingFragments
SavingFragmentState
ListFragmentsand<fragment>
InvokingaSeparateActivityWhenNeeded
PersistenceofFragments
CommunicationswithFragmentsUsingstartActivity()andsetTargetFragment()
References
Summary
Chapter9:RespondingtoConfigurationChanges
TheDefaultConfigurationChangeProcessTheDestroy/CreateCycleofActivities
TheDestroy/CreateCycleofFragments
UsingFragmentManagertoSaveFragmentState
UsingsetRetainInstanceonaFragment
DeprecatedConfigurationChangeMethods
HandlingConfigurationChangesYourself
References
Summary
Chapter10:WorkingwithDialogs
UsingDialogsinAndroid
UnderstandingDialogFragmentsDialogFragmentBasics
DialogFragmentSampleApplication
WorkingwithToast
References
Summary
Chapter11:WorkingwithPreferencesandSavingState
ExploringthePreferencesFrameworkUnderstandingCheckBoxPreferenceandSwitchPreference
AccessingaPreferenceValueinCode
UnderstandingListPreference
UnderstandingEditTextPreference
UnderstandingMultiSelectListPreference
UpdatingAndroidManifest.xml
UsingPreferenceCategory
CreatingChildPreferenceswithDependency
PreferenceswithHeaders
PreferenceScreens
DynamicPreferenceSummaryTextSavingStatewithPreferences
UsingDialogPreference
Reference
Summary
Chapter12:UsingtheCompatibilityLibraryforOlderDevices
ItAllStartedwithTablets
AddingtheLibrarytoYourProjectIncludingthev7SupportLibrary
Includingthev8SupportLibrary
Includingthev13SupportLibrary
Includingthev17SupportLibrary
IncludingJustthev4SupportLibrary
RetrofittinganAppwiththeAndroidSupportLibrary
References
Summary
Chapter13:ExploringPackages,Processes,Threads,andHandlers
UnderstandingPackagesandProcesses
ACodePatternforSharingData
UnderstandingLibraryProjects
UnderstandingComponentsandThreads
UnderstandingHandlers
UsingWorkerThreads
References
Summary
Chapter14:BuildingandConsumingServices
ConsumingHTTPServicesUsingtheHttpClientforHTTPGETRequests
UsingtheHttpClientforHTTPPOSTRequests(aMultipartExample)
SOAP,JSON,andXMLParsers
DealingwithExceptions
AddressingMultithreadingIssues
FunwithTimeouts
UsingtheHttpURLConnection
UsingtheAndroidHttpClient
UsingAndroidServicesUnderstandingServicesinAndroid
UnderstandingLocalServices
UnderstandingAIDLServices
DefiningaServiceInterfaceinAIDL
ImplementinganAIDLInterface
CallingtheServicefromaClientApplication
PassingComplexTypestoServices
MessengersandHandlers
References
Summary
Chapter15:AdvancedAsyncTaskandProgressDialogs
EssentialsofaSimpleAsyncTaskImplementingYourFirstAsyncTask
CallinganAsyncTask
UnderstandingtheonPreExecute()CallbackandProgressDialog
UnderstandingthedoInBackground()Method
TriggeringonProgressUpdate()throughpublishProgress()
UnderstandingtheonPostExecute()Method
UpgradingtoaDeterministicProgressDialog
AsyncTaskandThreadPools
IssuesandSolutionsforCorrectlyShowingtheProgressofanAsyncTaskDealingwithActivityPointersandDeviceRotation
DealingwithManagedDialogs
UsingRetainedObjectsandFragmentDialogs
UsingRetainedFragmentsandFragmentDialogs
UsingRetainedFragmentsandProgressBars
References
Summary
Chapter16:BroadcastReceiversandLong-RunningServices
SendingaBroadcastCodingaSimpleReceiver
RegisteringaReceiverintheManifestFile
AccommodatingMultipleReceivers
WorkingwithOut-of-ProcessReceivers
UsingNotificationsfromaReceiverMonitoringNotificationsThroughtheNotificationManager
SendingaNotification
StartinganActivityinaBroadcastReceiver
ExploringLong-RunningReceiversandServicesUnderstandingLong-RunningBroadcastReceiverProtocol
UnderstandingIntentService
ExtendingIntentServiceforaBroadcastReceiverExploringLong-RunningBroadcastServiceAbstraction
DesigningALong-RunningReceiver
AbstractingaWakeLockwithLightedGreenRoom
ImplementingaLong-RunningServiceUnderstandingaNonstickyService
UnderstandingaStickyService
UnderstandingRedeliverIntentsOption
CodingaLong-RunningService
AdditionalTopicsinBroadcastReceivers
References
Summary
Chapter17:ExploringtheAlarmManager
SettingUpaSimpleAlarmSettingOffanAlarmRepeatedly
CancellinganAlarm
UnderstandingExactnessofAlarms
UnderstandingPersistenceofAlarms
References
Summary
Chapter18:Exploring2DAnimation
ExploringFrame-by-FrameAnimation
ExploringLayoutAnimationUnderstandingInterpolators
ExploringViewAnimationUsingCameratoProvideDepthPerceptionin2D
ExploringtheAnimationListenerClass
NotesonTransformationMatrices
ExploringPropertyAnimations:TheNewAnimationAPIUnderstandingPropertyAnimation
PlanningaTestBedforPropertyAnimation
AnimatingViewswithObjectAnimators
AchievingSequentialAnimationwithAnimatorSet
SettingAnimationRelationshipswithAnimatorSet.Builder
UsingXMLtoLoadAnimators
UsingPropertyValuesHolder
UnderstandingViewPropertiesAnimation
UnderstandingTypeEvaluators
UnderstandingKeyFrames
UnderstandingLayoutTransitions
Resources
Summary
Chapter19:ExploringMapsandLocation-BasedServices
UnderstandingtheMappingPackageObtainingaMapsAPIKeyfromGoogle
AddingtheMapsAPIKeytoYourApplication
UnderstandingMapFragment
AddingMarkerstoMaps
UnderstandingtheLocationPackageGeocodingwithAndroid
UnderstandingLocationServices
UsingProximityAlertsandGeofencing
References
Summary
Chapter20:UnderstandingtheMediaFrameworks
UsingtheMediaAPIsWhitherSDCards?
PlayingMediaPlayingAudioContent
PlayingVideoContent
BonusOnlineChapteronRecordingandAdvancedMediaReferences
Summary
Chapter21:HomeScreenWidgets
UserExperiencewithHomeScreenWidgets
UnderstandingWidgetConfigurationActivity
UnderstandingtheLifeCycleofaWidgetUnderstandingWidgetDefinitionPhase
ImplementingASampleWidgetApplicationDefiningtheWidgetProvider
ImplementingWidgetConfigurationActivity
ImplementingaWidgetProvider
Collection-BasedWidgets
Resources
Summary
Chapter22:TouchScreens
UnderstandingMotionEventsTheMotionEventObject
RecyclingMotionEvents
UsingVelocityTracker
MultitouchTheBasicsofMultitouch
Gestures
ThePinchGesture
GestureDetectorandOnGestureListeners
References
Summary
Chapter23:ImplementingDragandDrop
ExploringDragandDrop
BasicsofDragandDropin3.0+
Drag-and-DropExampleApplicationListofFiles
LayingOuttheExampleDrag-and-DropApplication
RespondingtoonDragintheDropzone
SettingUptheDragSourceViews
TestingtheExampleDrag-and-DropApplication
References
Summary
Chapter24:UsingSensors
WhatIsaSensor?DetectingSensors
WhatCanWeKnowAboutaSensor?
GettingSensorEventsIssueswithGettingSensorData
InterpretingSensorDataLightSensors
ProximitySensors
TemperatureSensors
PressureSensors
GyroscopeSensors
Accelerometers
MagneticFieldSensors
UsingAccelerometersandMagneticFieldSensorsTogether
MagneticDeclinationandGeomagneticField
GravitySensors
LinearAccelerationSensors
RotationVectorSensors
References
Summary
Chapter25:ExploringAndroidPersistenceandContentProvidersSavingStateUsingSharedPreferences
SavingStateUsingInternalFiles
SavingStateUsingExternalFiles
SavingStateUsingSQLite
SavingStateUsingO/RMappingLibraries
SavingStateUsingContentProviders
SavingStateUsingNetworkStorage
StoringDataDirectlyUsingSQLiteSummarizingKeySQLitePackagesandClasses
CreatinganSQLiteDatabase
DefiningaDatabaseThroughDDLs
MigratingaDatabase
InsertingRows
UpdatingRows
DeletingRows
ReadingRows
ApplyingTransactions
SummarizingSQLite
DoingTransactionsThroughDynamicProxies
ExploringDatabasesontheEmulatorandAvailableDevices
ExploringContentProvidersExploringAndroid’sBuilt-inProviders
UnderstandingtheStructureofContentProviderURIs
ImplementingContentProvidersPlanningaDatabase
ExtendingContentProvider
UsingUriMatchertoFigureOuttheURIs
UsingProjectionMaps
FulfillingMIME-TypeContracts
ImplementingtheQueryMethod
ImplementingtheInsertMethod
ImplementingtheUpdateMethod
ImplementingtheDeleteMethod
RegisteringtheProvider
ExercisingtheBookProvider
AddingaBook
RemovingaBook
DisplayingtheListofBooks
Resources
Summary
Chapter26:UnderstandingLoaders
UnderstandingtheArchitectureofLoaders
ListingBasicLoaderAPIClasses
DemonstratingtheLoaders
Step1:PreparingtheActivitytoLoadData
Step2:InitializingtheLoaderDelvingintotheStructureofListActivity
WorkingwithAsynchronousLoadingofData
Step3:ImplementingonCreateLoader()
Step4:ImplementingonLoadFinished()
Step5:ImplementingonLoaderReset()
UsingSearchwithLoaders
UnderstandingtheOrderofLoaderManagerCallbacks
WritingCustomLoaders
Resources
Summary
Chapter27:ExploringtheContactsAPI
UnderstandingAccountsEnumeratingAccounts
UnderstandingContactsExaminingtheContactsSQLiteDatabase
UnderstandingRawContacts
UnderstandingtheContactsDataTable
UnderstandingAggregatedContacts
Exploringview_contacts
Exploringcontact_entities_view
WorkingwiththeContactsAPIExploringAccounts
ExploringAggregatedContacts
ExploringRawContacts
ExploringRawContactData
AddingaContactwithItsDetails
ControllingAggregationofContacts
UnderstandingPersonalProfileReadingProfileRawContacts
ReadingProfileContactData
AddingDatatothePersonalProfile
RoleofSyncAdapters
UsingBatchOperationstoOptimizeContentProviderUpdatesIdeaofBatchingContentProviderUpdates
BatchingCommitsbyYielding
UsingBackReferences
OptimisticLocking
ReusingtheContactProviderUI
UsingGroupFeatures
UsingPhotoFeatures
References
Summary
Chapter28:ExploringSecurityandPermissions
UnderstandingtheAndroidSecurityModelOverviewofSecurityConcepts
SigningApplicationsforDeployment
PerformingRuntimeSecurityChecksUnderstandingSecurityattheProcessBoundary
DeclaringandUsingPermissions
UnderstandingandUsingURIPermissions
References
Summary
Chapter29:UsingGoogleCloudMessagingwithAndroid
WhatIsGoogleCloudMessaging?UnderstandingtheKeyBuildingBlocksofGCM
PreparingtoUseGCMinYourApplication
AuthenticatingGCMCommunication
BuildinganAndroidGCM-EnabledApplicationCodingtheClientComponentforGCM
CodingtheServerComponentforGCM
MovingBeyondtheGCMIntroduction
Chapter30:DeployingYourApplication:GooglePlayStoreandBeyond
BecomingaPublisherFollowingtheRules
DeveloperConsole
PreparingYourApplicationforSaleTestingforDifferentDevices
SupportingDifferentScreenSizes
PreparingAndroidManifest.xmlforUploading
LocalizingYourApplication
PreparingYourApplicationIcon
DirectingUsersBacktothePlayStore
TheAndroidLicensingService
UsingProGuardforOptimization,FightingPiracy
PreparingYour.apkFileforUploading
UploadingYourApplicationGraphics
ListingDetails
PublishingOptions
ContactInformation
Consent
UserExperienceonGooglePlayStore
BeyondGooglePlayStore
References
Summary
Index
AbouttheAuthorsDaveMacLeanisasoftwareengineerandarchitectlivingandworkinginOrlando,Florida.Since1980,hehasprogrammedinmanylanguages,developingsolutionsrangingfromrobotautomationsystemstodatawarehousing,fromwebself-serviceapplicationstoelectronicdatainterchangetransactionprocessors.DavehasworkedforSunMicrosystems,IBM,TrimbleNavigation,GeneralMotors,BlueCrossBlueShieldofFlorida,andseveralsmallcompanies.HehaswrittenseveralbooksonAndroidandafewmagazinearticles.HegraduatedfromtheUniversityofWaterlooinCanadawithaSystemsDesignEngineeringdegree.Visithisblogathttp://[email protected].
SatyaKomatinenihasbeenprogrammingformorethan20yearsintheITandWebspace.HehashadtheopportunitytoworkwithAssembly,C,C++,Rexx,Java,C#,Lisp,HTML,JavaScript,CSS,SVG,relationaldatabases,objectdatabases,andrelatedtechnologies.Hehaspublishedmorethan30articlestouchingmanyoftheseareas,bothinprintandonline.HehasbeenafrequentspeakeratO’ReillyOpenSourceConference,speakingoninnovationsaroundJavaandWeb.SatyahasdoneaconsiderableamountoforiginalworkincreatingAspire,acomprehensiveopen-sourceJava-basedwebframework,andhasexploredpersonalwebproductivityandcollaborationtoolsthroughhisopen-sourceworkforKnowledgeFolders.com.Satyaholdsamaster’sdegreeinelectricalengineeringfromIndianInstituteofTechnologyandabachelor’sdegreeinelectricalengineeringfromAndhraUniversity,India.YoucanfindhiswebsiteatSatyaKomatineni.com.
GrantAllenhasworkedintheITfieldforover20years,asaCTO,enterprisearchitect,anddatabaseadministrator.Grant’sroleshavecoveredprivateenterprise,academia,andthegovernmentsectoraroundtheworld,specializinginglobal-scalesystemsdesign,development,andperformance.Heisafrequentspeakeratindustryandacademicconferences,ontopicsrangingfromdataminingtocompliance,andtechnologiessuchasdatabases(DB2,Oracle,SQLServer,MySQL),contentmanagement,collaboration,disruptiveinnovation,andmobileecosystemslikeAndroid.HisfirstAndroidapplicationwasatasklisttoremindhimtofinishallhisotherunfinishedAndroidprojects.GrantworksforGoogle,andinhissparetimeiscompletingaPhDonbuildinginnovativehigh-technologyenvironments.GrantistheauthorofBeginningAndroidandleadauthorofOracleSQLRecipesandTheDefinitiveGuidetoSQLite.
AbouttheTechincalreviewerShaneKirkearnedaB.S.inComputerSciencefromtheUniversityofKentuckyin2000.He’scurrentlyaSeniorSoftwareEngineerforIDEXXLaboratoriesinWestbrook,Maine,wherehespendshisdaysworkingoncommunicationsolutionsforembeddedsystems.Shane’sforayintomobiledevelopmentbeganin2010,shortlyafterpurchasinghisfirstsmartphone—aDroidXrunningEclair(Android2.1).He’sbeenhookedonAndroideversince.
AcknowledgmentsWritingthisbooktookeffortnotonlyonthepartoftheauthors,butalsofromsomeoftheverytalentedstaffatApress,aswellasthetechnicalreviewer.Therefore,wewouldliketothankSteveAnglin,MatthewMoodie,DouglasPundick,MarkPowers,BrendanFrost,AnaPanchoo,andJillBalzano.
Wewouldalsoliketoextendourdeepestappreciationtothetechnicalreviewer—ShaneKirk—forhisexpertappraisalsandattentiontodetail.Thisbookissomuchbetterbecauseofhisefforts.
Writingatechnicalbookaboutasubjectthatfrequentlychangesisadauntingtask.Whenthedocumentationdidn’tsay,andthesourcecodedidn’treveal,wewouldsearchtheInternetforanswers.Andwe’deventuallyfindwhatwewerelookingfor,buriedhereandthere.ToalltheotherAndroiddevelopersouttherewhoareworkingalongwithustoprovideanswers,wethankyou.
Finally,theauthorsaredeeplygratefultotheirfamiliesforlettingustoilawayonnights,earlymornings,andweekends.Ittakesgreatdedicationtowriteabook,andperhapsevenmoretoputupwithauthorswhiletheywrite.
ForewordWaybackin2008,IwasgivenmyfirstAndroiddevice.ItwastheDream,alsoknownastheG1,andIimmediatelystartedtinkeringwithit.Afterall,herewasasmartphonewiththepromiseofthousandsofapplications,andwhoknewhowmanyhundredsofpossiblehandsets.ThatshowshowmuchIknewatthetime!Ireallyshouldhavebeenthinkingintheorderofmillionsofapplications,andtensofthousandsofdevices,becausethatiswhereAndroidisheadingtoday.
Whetheritistraditionalphones,tablets,cars,in-flightentertainmentsystems,robots,oranyotherofthemyriadAndroiddevicesoutthere,whatmakesthemgreataretheapplicationswrittenbypeoplelikeyou,dearreader!Everyday,Androiddeveloperspushthepossibilitiesofwhatapplications—andAndroid—cando,anditisthatenergythatdrawsmetothecommunity,andtohelpinginmyownsmallwaywithbookslikeProAndroid.
OneofthebestobservationsabouttechnologyandinnovationIhaveheardisthatinnovationhappenswhenyoucreatesomethingandshareitwithanotherperson,whichtheythenadaptanduseinatotallyunexpectedway.Soletmecommendthisbooktoyouinthatspirit.EnjoyeverythingProAndroidhastoofferyou,andtakeittocreatesomethingtotallyunexpected!We’llbefirstinlinetotryitout,whateveritis.
—GrantAllenNewYorkMay2015
IntroductionWelcometothewonderfulworldofAndroid.Aworldwhere,withabitofknowledgeandeffort,youtoocanwriteAndroidapplications.Towritegoodapplications,however,youwillneedtodigdeeper,tounderstandthefundamentalsoftheAndroidarchitecture,tounderstandhowapplicationscanworktogether,tounderstandhowmobileapplicationsaredifferentfromallpreviousformsofprogramming.TheonlinedocumentationonAndroidisfair,butitdoesnotgofarenough.Youcanreadthesourcecode,butthat’snotatalleasy.
Thisbookistheculminationofsevenyearsofresearching,developing,testing,refining,andwritingaboutAndroid.We’vereadalltheonlinedocumentation,scouredthroughsourcecode,exploredthefarreachesoftheInternet,andhavecompiledthisbook.We’vefilledinthegaps,anticipatedthequestionsyouhave,andprovidedanswers.Alongthewaywe’veseenAPIscomeandgoandberevised.We’veseenmajorchangesinhowapplicationsareconstructed.AtfirstweallusedActivities,butwhentabletscamealongwestartedusingFragments.We’vetakeneverythingwe’velearnedandfilledthisbookwithpracticalguidancetousingthelatestAndroidAPIstowriteinterestingapplications.
Youwillstillfindcoverageofthebeginningtopics,tohelpthenewlearnergetstarteddevelopingforAndroid.Youwillalsofindcoverageofthemoreadvancedtopics,suchasGoogleMapsAndroidAPIv2,whichisverydifferentfromv1.We’veupdatedthiseditionwiththelatestinformationontheavailableAPIs.Youwillfindin-depthcoverageofintents,services,broadcastreceivers,communication,fragments,widgets,sensors,animation,security,GoogleCloudMessaging,audioandvideo,andmore.AndforeverytopictherearesampleprogramsthatillustrateeachAPIinmeaningfulways.Allsourcecodeisdownloadable,soyoucancopyandpasteitintoyourapplicationstogetagreatheadstart.
Chapter1
HelloAndroidWelcometothebook,andwelcometotheworldofAndroiddevelopment.Inalittleundertenyears,Androidhashelpedchangethefaceofmodernmobilecomputingandtelephonyandlaunchedarevolutioninhowapplicationsaredeveloped,andbywhom.Withthisbookinyourhands,youarenowpartofthegreatAndroidexplosion!We’regoingtoassumethatyouwanttogetstraightatworkingwithAndroid,sowe'renotgoingtoboreyouwithafiresidechataboutAndroid'shistory,majorcharacters,plaudits,oranyotherprose.We'regoingtogetstraighttoit!
Inthischapter,you’llstartbyseeingwhatyouneedtobeginbuildingapplicationswiththeAndroidsoftwaredevelopmentkit(SDK)andsetupyourchoiceofdevelopmentenvironment.Next,youstepthrougha“HelloWorld!”application.ThenthechapterexplainstheAndroidapplicationlifecycleandendswithadiscussionaboutrunningyourapplicationswithAndroidVirtualDevices(AVDs)andonrealdevices.Solet’sgetstarted.
PrerequisitesforAndroidDevelopmentTobuildapplicationsforAndroid,youneedtheJavaSEDevelopmentKit(JDK),theAndroidSDK,andadevelopmentenvironment.Strictlyspeaking,youcandevelopyourapplicationsusingnothingmorethanaprimitivetexteditorandahandfulofcommand-linetoolslikeAnt.Forthepurposesofthisbook,we’llusethecommonlyavailableEclipseIDE,thoughyouarefreetoadoptAndroidStudioanditsIntelliJunderpinnings—we’llevenwalkthroughAndroidStudioforthosewhohavenotseenit.Withtheexceptionofafewadd-ontools,theexamplesweshareinthebookwillworkequallywellbetweenthesetwoIDEs.
TheAndroidSDKrequiresJDK6or7(thefullJDK,notjusttheJavaRuntimeEnvironment[JRE])andoptionallyasupportedIDE.Currently,GoogledirectlysupportstwoalternativeIDEs,providingsomechoice.Historically,EclipsewasthefirstIDEsupportedbyGoogleforAndroiddevelopment,anddevelopingforAndroid4.4KitKator5.0LollipoprequiresEclipse3.6.2orhigher(thisbookusesEclipse4.2or4.4,alsoknownasJunoandLuna,respectively,andotherversions).ThealternativeenvironmentreleasedandsupportedbyGoogleforAndroidisnowknownasAndroidStudio.ThisisapackagedversionofIDEAIntelliJwithbuilt-inAndroidSDKanddevelopertools.
NoteAtthetimeofthiswriting,Java8wasavailablebutnotyetsupportedbytheAndroidSDK.InpreviousversionsoftheAndroidSDK,Java5wasalsosupported,butthisisnolongerthecase.ThelatestversionofEclipse(4.4,a.k.a.Juno)wasalsoavailable,butAndroidhashistoricallynotbeenreliableonthelatestEclipserightaway.Checkthesystemrequirementsheretofindthelatest:http://developer.android.com/sdk/index.html.
TheAndroidSDKiscompatiblewithWindows(WindowsXP,WindowsVista,andWindows7),MacOSX(Intelonly),andLinux(Intelonly).Intermsofhardware,youneedanIntelmachine,themorepowerfulthebetter.
Tomakeyourlifeeasier,ifyouchooseEclipseasyourIDE,youwillwanttouseAndroiddevelopmenttools(ADT).ADTisanEclipseplug-inthatsupportsbuildingAndroidapplicationswiththeEclipseIDE.
TheAndroidSDKismadeupoftwomainparts:thetoolsandthepackages.WhenyoufirstinstalltheSDK,allyougetarethebasetools.Theseareexecutablesandsupportingfilestohelpyoudevelopapplications.ThepackagesarethefilesspecifictoaparticularversionofAndroid(calledaplatform)oraparticularadd-ontoaplatform.TheplatformsincludeAndroid1.5through4.4.2.Theadd-onsincludetheGoogleMapsAPI,theMarketLicenseValidator,andevenvendor-suppliedonessuchasSamsung’sGalaxyTabadd-on.AfteryouinstalltheSDK,youthenuseoneofthetoolstodownloadandsetuptheplatformsandadd-ons.
Remember,youonlyneedtosetupandconfigureoneofEclipseorAndroidStudio.Youcanusebothifyouaresoinclined,butit’scertainlynotrequired.Let’sgetstarted!
SettingUpYourEclipseEnvironmentInthissection,youwalkthroughdownloadingJDK6,theEclipseIDE,theAndroidSDK(toolsandpackages),andADT.YoualsoconfigureEclipsetobuildAndroidapplications.Googleprovidesapagetodescribetheinstallationprocess(http://developer.android.com/sdk/installing.html)butleavesoutsomecrucialsteps,asyouwillsee.
DownloadingJDKThefirstthingyouneedistheJDK.TheAndroidSDKrequiresJDK6orhigher;we’vedevelopedourexamplesusingJDK6and7,dependingontheversionofEclipseorAndroidStudioinuse.ForWindowsandMacOSX,downloadJDK7fromtheOraclewebsite(www.oracle.com/technetwork/java/javase/downloads/index.html)andinstallit.YouonlyneedtheJDK,notthebundles.ToinstalltheJDKforLinux,openaTerminalwindowandinstructyourpackagemanagertoinstallit.Forexample,inDebianorUbuntutrythefollowing:
sudoapt-getinstallsun-java7-jdk
ThisshouldinstalltheJDKplusanydependenciessuchastheJRE.Ifitdoesn’t,itprobablymeansyouneedtoaddanewsoftwaresourceandthentrythatcommandagain.Thewebpagehttps://help.ubuntu.com/community/Repositories/Ubuntuexplainssoftwaresourcesandhowtoaddtheconnectiontothird-partysoftware.TheprocessisdifferentdependingonwhichversionofLinuxyouhave.Afteryou’vedonethat,retrythecommand.
WiththeintroductionofUbuntu10.04(LucidLynx),UbunturecommendsusingOpenJDKinsteadoftheOracle/SunJDK.ToinstallOpenJDK,trythefollowing:
sudoapt-getinstallopenjdk-7-jdk
Ifthisisnotfound,setupthethird-partysoftwareasoutlinedpreviouslyandrunthecommandagain.AllpackagesonwhichtheJDKdependsareautomaticallyaddedforyou.ItispossibletohavebothOpenJDKandtheOracle/SunJDKinstalledatthesametime.ToswitchactiveJavabetweentheinstalledversionsofJavaonUbuntu,runthiscommandatashellprompt
sudoupdate-alternatives--configjava
andthenchoosewhichJavayouwantasthedefault.
NowthatyouhaveaJavaJDKinstalled,it’stimetosettheJAVA_HOMEenvironmentvariabletopointtotheJDKinstallfolder.TodothisonaWindowsXPmachine,chooseStart MyComputer,right-click,selectProperties,choosetheAdvancedtab,andclickEnvironmentVariables.ClickNewtoaddthevariableorEdittomodifyitifitalreadyexists.ThevalueofJAVA_HOMEissomethinglikeC:\ProgramFiles\Java\jdk1.7.0_79.
ForWindowsVistaandWindows7,thestepstogettotheEnvironmentVariablesscreenarealittledifferent.ChooseStart Computer,right-click,chooseProperties,clickthelinkforAdvancedSystemSettings,andclickEnvironmentVariables.Afterthat,followthesameinstructionsasforWindowsXPtochangetheJAVA_HOMEenvironmentvariable.
ForMacOSX,yousetJAVA_HOMEinthe.bashrcfileinyourhomedirectory.Editorcreatethe.bashrcfile,andaddalinethatlookslikethis
exportJAVA_HOME=path_to_JDK_directory
wherepath_to_JDK_directoryisprobably/Library/Java/Home.ForLinux,edityour.bashrcfileandaddalineliketheoneforMacOSX,exceptthatyourpathtoJavaisprobablysomethinglike/usr/lib/jvm/java-6-sunor/usr/lib/jvm/java-6-openjdk.
DownloadingEclipseAftertheJDKisinstalled,youcandownloadtheEclipseIDEforJavaDevelopers.(Youdon’tneedtheeditionforJavaEE;itworks,butit’smuchlargerandincludesthingsyoudon’tneedforthisbook.)TheexamplesinthisbookuseEclipse4.2or4.4(onbothLinuxandWindowsenvironments).YoucandownloadallversionsofEclipsefromwww.eclipse.org/downloads/.
NoteAsanalternativetotheindividualstepspresentedhere,youcanalsodownloadtheADTBundlefromtheAndroiddevelopersite.ThisincludesEclipsewithbuilt-indevelopertoolsandtheAndroidSDKinonepackage.It’s
agreatwaytogetstartedquickly,butifyouhaveanexistingenvironment,orjustwanttoknowhowallthecomponentsarestitchedtogether,thenfollowingthestep-by-stepinstructionsisthewaytogo.
TheEclipsedistributionisa.zipfilethatcanbeextractedjustaboutanywhere.ThesimplestplacetoextracttoonWindowsisC:\,whichresultsinaC:\eclipsefolderwhereyoufindeclipse.exe.Dependingonyoursecurityconfiguration,WindowsmayinsistonenforcingUACwhenrunningfromC:.ForMacOSX,youcanextracttoApplications.ForLinux,youcanextracttoyourhomedirectoryorhaveyouradministratorputEclipseintoacommonplacewhereyoucangettoit.TheEclipseexecutableisintheeclipsefolderforallplatforms.YoumayalsofindandinstallEclipseusingLinux’sSoftwareCenterforaddingnewapplications,althoughthismaynotprovideyouwiththelatestversion.
WhenyoufirststartupEclipse,itasksyouforalocationfortheworkspace.Tomakethingseasy,youcanchooseasimplelocationsuchasC:\androidoradirectoryunderyourhomedirectory.Ifyousharethecomputerwithothers,youshouldputyourworkspacefoldersomewhereunderneathyourhomedirectory.
DownloadingtheAndroidSDKTobuildapplicationsforAndroid,youneedtheAndroidSDK.Asstatedbefore,theSDKcomeswiththebasetools;thenyoudownloadthepackagepartsthatyouneedand/orwanttouse.ThetoolspartoftheSDKincludesanemulatorsoyoudon’tneedamobiledevicewiththeAndroidOStodevelopAndroidapplications.Italsohasasetuputilitytoallowyoutoinstallthepackagesthatyouwanttodownload.
YoucandownloadtheAndroidSDKfromhttp://developer.android.com/sdk.Itshipsasa.zipfile,similartothewayEclipseisdistributed,soyouneedtounzipittoanappropriatelocation.ForWindows,unzipthefiletoaconvenientlocation(weusedtheC:drive),afterwhichyoushouldhaveafoldercalledsomethinglikeC:\android-sdk-windowsthatcontainsthefilesasshowninFigure1-1.ForMacOSXandLinux,youcanunzipthefiletoyourhomedirectory.NoticethatMacOSXandLinuxdonothaveanSDKManagerexecutable;theequivalentoftheSDKManagerinMacOSXandLinuxistorunthetools/androidprogram.
Figure1-1.BasecontentsoftheAndroidSDK
Analternativeapproach(forWindowsonly)istodownloadaninstallerEXEinsteadofthezipfileandthenruntheinstallerexecutable.ThisexecutablechecksfortheJavaJDK,unpackstheembeddedfilesforyou,andrunstheSDKManagerprogramtohelpyousetuptherestofthedownloads.
WhetherthroughusingtheWindowsinstallerorbyexecutingtheSDKManager,youshouldinstallsomepackagesnext.WhenyoufirstinstalltheAndroidSDK,itdoesnotcomewithanyplatformversions(thatis,versionsofAndroid).Installingplatformsisprettyeasy.Afteryou’velaunchedtheSDKManager,youseewhatisinstalledandwhat’savailabletoinstall,asshowninFigure1-2.YoumustaddAndroidSDKtoolsandplatform-toolsinorderforyourenvironmenttowork.Becauseyouuseitshortly,addatleasttheAndroid1.6SDKplatform,aswellasthelatestplatformshowninyourinstaller.
Figure1-2.AddingpackagestotheAndroidSDK
ClicktheInstallbutton.YouneedtoclickAcceptforeachitemyou’reinstalling(orAcceptAll)andthenclickInstall.Androidthendownloadsyourpackagesandplatformstomakethemavailabletoyou.TheGoogleAPIsareadd-onsfordevelopingapplicationsusingGoogleMaps.Youcanalwayscomebacktoaddmorepackageslater.
UpdatingYourPATHEnvironmentVariableTheAndroidSDKcomeswithatoolsdirectorythatyouwanttohaveinyourPATH.
YoualsoneedinyourPATHtheplatform-toolsdirectoryyoujustinstalled.Let’saddthemnowor,ifyou’reupgrading,makesurethey’recorrect.Whileyou’rethere,youcanalsoaddaJDKbindirectory,whichwillmakelifeeasierlater.
ForWindows,getbacktotheEnvironmentVariableswindow.EditthePATHvariableandaddasemicolon(;)ontheend,followedbythepathtotheAndroidSDKtoolsfolder,followedbyanothersemicolon,followedbythepathtotheAndroidSDKplatform-toolsfolder,followedbyanothersemicolon,andthen%JAVA_HOME%\bin.ClickOKwhenyou’redone.ForMacOSXandLinux,edityour.bashrcfileandaddtheAndroidSDKtoolsdirectorypathtoyourPATHvariable,aswellastheAndroidSDKplatform-toolsdirectoryandthe$JAVA_HOME/bindirectory.SomethinglikethefollowingworksforLinux:
exportPATH=$PATH:$HOME/android-sdk-linux_x86/tools:$HOME/android-sdk-linux_x86/platform-tools:$JAVA_HOME/bin
JustmakesurethatthePATHcomponentthat’spointingtotheAndroidSDKtoolsdirectoriesiscorrectforyourparticularsetup.
TheToolsWindowLaterinthisbook,therearetimeswhenyouneedtoexecuteacommand-lineutilityprogram.TheseprogramsarepartoftheJDKorpartoftheAndroidSDK.ByhavingthesedirectoriesinyourPATH,youdon’tneedtospecifythefullpathnamesinordertoexecutethem,butyouneedtostartupatoolswindowinordertorunthem(laterchaptersrefertothistoolswindow).TheeasiestwaytocreateatoolswindowinWindowsistochooseStart Run,typeincmd,andclickOK.ForMacOSX,chooseTerminalfromyourApplicationsfolderinFinderorfromtheDockifit’sthere.ForLinux,runyourfavoriteterminal.
YoumayneedtoknowtheIPaddressofyourworkstationlater.TofindthisinWindows,launchatoolswindowandenterthecommandipconfig.TheresultscontainanentryforIPv4(orsomethinglikethat)withyourIPaddresslistednexttoit.AnIPaddresslookssomethinglikethis:192.168.1.25.ForMacOSXandLinux,launchatoolswindowandusethecommandifconfig.YoufindyourIPaddressnexttothelabelinetaddr.
Youmayseeanetworkconnectioncalledlocalhostorlo;theIPaddressforthisnetworkconnectionis127.0.0.1.Thisisaspecialnetworkconnectionusedbytheoperatingsystemandisnotthesameasyourworkstation’sIPaddress.Lookforadifferentnumberforyourworkstation’sIPaddress.
InstallingADTNowyouneedtoinstallADT(veryrecentlyrenamedtoGDT,theGoogleDeveloperTools),anEclipseplug-inthathelpsyoubuildAndroidapplications.Specifically,ADTintegrateswithEclipsetoprovidefacilitiesforyoutocreate,test,anddebugAndroid
applications.YouneedtousetheInstallNewSoftwarefacilityinEclipsetoperformtheinstallation.(TheinstructionsforupgradingADTappearlaterinthissection.)Togetstarted,launchtheEclipseIDEandfollowthesesteps:
1. SelectHelp InstallNewSoftware.
2. SelecttheWorkWithfield,typein
https://dl-ssl.google.com/android/eclipse/,
andpressEnter.EclipsecontactsthesiteandpopulatesthelistasshowninFigure1-3.
Figure1-3.InstallingADTusingtheInstallNewSoftwarefeatureinEclipse
3. YoushouldseeanentrynamedDeveloperToolswithfourchildnodes:AndroidDDMS,AndroidDevelopmentTools,AndroidHierarchyViewer,andAndroidTraceview.Justbeforepublishingthisbook,GoogleupdatedtheADTtobepartofthemoregenericGoogleDeveloperToolspluginforEclipse,orGDT.LookforthesameoptionsintheGDT.SelecttheparentnodeDeveloperTools,makesurethechildnodesarealsoselected,andclicktheNextbutton.Theversionsyouseemaybenewerthanthese,andthat’sokay.Youmayalsoseeadditionaltools.Thesetoolsareexplained
furtherinChapter11.
4. Eclipseasksyoutoverifythetoolstoinstall.ClickNext.
5. You’reaskedtoreviewthelicensesforADTaswellasforthetoolsrequiredtoinstallADT.Reviewthelicenses,click“Iaccept,”andthenclicktheFinishbutton.
Eclipsedownloadsthedevelopertoolsandinstallsthem.YouneedtorestartEclipseforthenewplug-intoshowupintheIDE.
IfyoualreadyhaveanolderversionofADTinEclipse,gototheEclipseHelpmenuandchooseCheckforUpdates.YoushouldseethenewversionofADTandbeabletofollowtheinstallationinstructions,pickingupatstep3.
NoteIfyou’redoinganupgradeofADT,youmaynotseesomeofthesetoolsinthelistoftoolstobeupgraded.Ifyoudon’tseethem,thenafteryou’veupgradedtherestoftheADT,gotoInstallNewSoftwareandselecthttps://dl-ssl.google.com/android/eclipse/fromtheWorksWithmenu.Themiddlewindowshouldshowyouothertoolsthatareavailabletobeinstalled.
ThefinalsteptomakeADTfunctionalinEclipseistopointittotheAndroidSDK.InEclipse,selectWindow Preferences.(OnMacOSX,PreferencesisundertheEclipsemenu.)InthePreferencesdialogbox,selecttheAndroidnodeandsettheSDKLocationfieldtothepathoftheAndroidSDK(seeFigure1-4)andthenclicktheApplybutton.NotethatyoumayseeadialogboxaskingifyouwanttosendusagestatisticstoGoogleconcerningtheAndroidSDK;thatdecisionisuptoyou.
Figure1-4.PointingADTtotheAndroidSDK
YoumaywanttomakeonemorePreferenceschangeontheAndroid Buildpage.TheSkipPackagingoptionshouldbecheckedifyou’dliketomakeyourfilesavesfaster.Bydefault,theADTreadiesyourapplicationforlauncheverytimeitbuildsit.Bycheckingthisoption,packagingandindexingoccuronlywhentrulyneeded.
FromEclipse,youcanlaunchtheSDKManager.Todoso,chooseWindow AndroidSDKManager.YoushouldseethesamewindowasinFigure1-2.
Ifyou’vechosenEclipseasyourIDE,youarealmostreadyforyourfirstAndroidapplication—youcanskipthefollowingsectiononAndroidStudioandheadstraighttothe“LearningAndroid’sFundamentalComponents”section.
SettingUpYourAndroidStudioEnvironmentIn2013,Googleintroducedasecondsupporteddevelopmentenvironment,knownasAndroidStudio(orAndroidDeveloperStudioatthetimeoflaunch).ThisisbasedaroundapopularJavaIDE:IDEAIntelliJ.ThemostimportantthingtoknowaboutAndroidStudioisthatitisstillaworkinprogress.Asofthisbook’swriting,thelatestversionis1.2.Anyonefamiliarwiththevagariesofversionnumbersknowsthatstartingwithalow
numberusuallymeans“beware!”
ThesecondmostimportantthingtorememberisthatAndroidStudiocurrentlyassumesa64-bitdevelopmentenvironment.ThatmeansdependencieslikeJavaalsoneedtobe64-bit.
ThenextsectionsbrieflycoverthesetupofAndroidStudioforthoseinterestedorgung-hoenoughtouseit.BemindfulthattherestofthebookpredominantlyshowsexamplesandoptionsusingEclipse.
JavarequirementsforAndroidStudioLikeEclipse,AndroidStudioreliesonaworkingJavainstallation.AndroidStudiowillattempttoautomaticallydiscoveryourJavaenvironmentduringinstallation,soitpaystohaveJavainstalledandconfigured.
ForJavainstallation,rememberthatAndroidStudiois64-bit.Inallotherrespects,youcanfollowtheprecedingsectiontitled“DownloadingJDK”—wewon’trepeatthatword-for-wordheretosavesometrees.Ensureyoufollowalltheinstructionsthere,includingsettingtheJAVA_HOMEenvironmentvariable,asthisisthemainindicatorusedbytheAndroidStudioinstallertofindyourJavainstallation.
DownloadingandInstallingAndroidStudioGooglemakesAndroidStudioavailablefromthemainAndroiddevelopmentsite,currentlyattheURLhttp://developer.android.com/sdk/installing/studio.html.Thatmaychangeatanytime,butaquicksearchonthedeveloper.android.comsiteshouldfindit.AndroidStudioispackagedasamonolithicbundle,withnearlyallthecomponentsyouneed.TheJavaSDKistheexception—we’llcoverthatshortly.ThepackagedownloadedfromtheprecedingURLwillbenamedsomethinglikeandroid-studio-bundle-132.893413-windows.exeforwindows,orasimilarnamewithadifferentextensionforOSXandLinux,andincludesthefollowing:
CurrentlatestbuildoftheAndroidStudiobundleofIntelliJIDEA
Built-inAndroidSDK
AllrelatedAndroidbuildtools
AndroidVirtualDeviceimages
We’lltalkmoreaboutthesecomponentsinlaterchapters.ForaWindowsinstallationruntheexecutableandfollowthepromptstochooseaninstallationpath,anddecidewhetherAndroidStudioismadeavailabletoallusersontheWindowsmachine,orjustthecurrentuser.ForOSX,openthe.dmgfileandcopytheAndroidStudioentrytoyourApplicationsfolder.UnderLinux,extractthecontentsofthe.tgzfiletoyourdesiredlocation.
Onceinstalled,youcanstartAndroidStudiounderWindowsfromthestartmenufolder
youchosewhenprompted;underOSXfromtheApplicationsfolder;andunderLinuxbyrunningthe./android-studio/bin/studio.shfileunderyourinstallationdirectory.Whatevertheoperatingsystem,youshouldseetheAndroidStudiohomescreenasdepictedinFigure1-5.
Figure1-5.AndroidStudiowhenfirstlaunched
LearningAndroid’sFundamentalComponentsEveryapplicationframeworkhassomekeycomponentsthatdevelopersneedtounderstandbeforetheycanbegintowriteapplicationsbasedontheframework.Forexample,youneedtounderstandJavaServerPages(JSP)andservletsinordertowriteJava2Platform,EnterpriseEdition(J2EE)applications.Similarly,youneedtounderstandviews,activities,fragments,intents,contentproviders,services,andtheAndroidManifest.xmlfilewhenyoubuildapplicationsforAndroid.Youbrieflycoverthesefundamentalconceptshereandexploretheminmoredetailthroughoutthebook.
ViewViewsareuserinterface(UI)elementsthatformthebasicbuildingblocksofauserinterface.Aviewcanbeabutton,alabel,atextfield,ormanyotherUIelements.Ifyou’refamiliarwithviewsinJ2EEandSwing,thenyouunderstandviewsinAndroid.Viewsarealsousedascontainersforviews,whichmeansthere’susuallyahierarchyofviewsintheUI.Intheend,everythingyouseeisaview.
ActivityAnactivityisaUIconceptthatusuallyrepresentsasinglescreeninyourapplication.Itgenerallycontainsoneormoreviews,butitdoesn’thaveto.Anactivityisprettymuchlikeitsounds—somethingthathelpstheuserdoonething,whichcouldbeviewingdata,creatingdata,oreditingdata.MostAndroidapplicationshaveseveralactivitieswithinthem.
FragmentWhenascreenislarge,itbecomesdifficulttomanageallofitsfunctionalityinasingleactivity.Fragmentsarelikesub-activities,andanactivitycandisplayoneormorefragmentsonthescreenatthesametime.Whenascreenissmall,anactivityismorelikelytocontainjustonefragment,andthatfragmentcanbethesameoneusedwithinlargerscreens.
IntentAnintentgenericallydefinesan“intention”todosomework.Intentsencapsulateseveralconcepts,sothebestapproachtounderstandingthemistoseeexamplesoftheiruse.Youcanuseintentstoperformthefollowingtasks:
Broadcastamessage
Startaservice
Launchanactivity
Displayawebpageoralistofcontacts
Dialaphonenumberoransweraphonecall
Intentsarenotalwaysinitiatedbyyourapplication—they’realsousedbythesystemtonotifyyourapplicationofspecificevents(suchasthearrivalofatextmessage).
Intentscanbeexplicitorimplicit.IfyousimplysaythatyouwanttodisplayaURL,thesystemdecideswhatcomponentwillfulfilltheintention.Youcanalsoprovidespecificinformationaboutwhatshouldhandletheintention.Intentslooselycoupletheactionandactionhandler.
ContentProviderDatasharingamongmobileapplicationsonadeviceiscommon.Therefore,Androiddefinesastandardmechanismforapplicationstosharedata(suchasalistofcontacts)withoutexposingtheunderlyingstorage,structure,andimplementation.Throughcontentproviders,youcanexposeyourdataandhaveyourapplicationsusedatafromotherapplications.
ServiceServicesinAndroidresembleservicesyouseeinWindowsorotherplatforms—they’rebackgroundprocessesthatcanpotentiallyrunforalongtime.Androiddefinestwotypesofservices:localservicesandremoteservices.Localservicesarecomponentsthatareonlyaccessiblebytheapplicationthatishostingtheservice.Conversely,remoteservicesareservicesthataremeanttobeaccessedremotelybyotherapplicationsrunningonthedevice.
Anexampleofaserviceisacomponentthatisusedbyane-mailapplicationtopollfornewmessages.Thiskindofservicemaybealocalserviceiftheserviceisnotusedbyotherapplicationsrunningonthedevice.Ifseveralapplicationsusetheservice,thenit’simplementedasaremoteservice.
AndroidManifest.xmlAndroidManifest.xml,whichissimilartotheweb.xmlfileintheJ2EEworld,definesthecontentsandbehaviorofyourapplication.Forexample,itlistsyourapplication’sactivitiesandservices,alongwiththepermissionsandfeaturestheapplicationneedstorun.
AVDsAnAVDallowsdeveloperstotesttheirapplicationswithouthookingupanactualAndroiddevice(typicallyaphoneoratablet).AVDscanbecreatedinvariousconfigurationstoemulatedifferenttypesofrealdevices.
HelloWorld!Nowyou’rereadytobuildyourfirstAndroidapplication.Youstartbybuildingasimple“HelloWorld!”program.Createtheskeletonoftheapplicationbyfollowingthesesteps:
1. LaunchEclipse,andselectFile New Project.IntheNewProjectdialogbox,selectAndroidApplicationProjectandthenclickNext.YouseetheNewAndroidProjectdialogbox,asshowninFigure1-6.(EclipsemayhaveaddedAndroidProjecttotheNewmenu,soyoucanuseitifit’sthere.)There’salsoaNewAndroid
Projectbuttononthetoolbar.
Figure1-6.UsingtheNewProjectWizardtocreateanAndroidapplication
2. AsshowninFigure1-6,enterHelloAndroidastheprojectname.YouneedtodistinguishthisprojectfromotherprojectsyoucreateinEclipse,sochooseanamethatwillmakesensetoyouwhenyouarelookingatalltheprojectsinyourEclipseenvironment.YouwillalsoseetheavailableBuildTargets.SelectAndroid2.2.ThisistheversionofAndroidyouuseasyourbasefortheapplication.YoucanrunyourapplicationonlaterversionsofAndroid,suchas4.3and4.4;butAndroid2.2hasallthefunctionalityyouneedforthisexample,sochooseitasyourtarget.Ingeneral,it’sbesttochoosethelowestversionnumberyoucan,becausethatmaximizesthenumberofdevicesthatcanrunyourapplication.
3. LeavetheProjectNametoauto-completeitselfbasedonyourApplicationName.
4. Usecom.androidbook.helloasthepackagename.LikeallJavaapplications,yourapplicationmusthaveabasepackagename,andthisisit.Thispackagenamewillbeusedasanidentifierforyourapplicationandmustbeuniqueacrossallapplications.Forthisreason,it’sbesttostartthepackagenamewithadomainnamethatyouown.Ifyoudon’townone,becreativetoensurethatyourpackagenamewon’tlikelybeusedbyanyoneelse.ClickNext.
5. Thenextwindowprovidesoptionsforcustomerlaunchericons,theactualdirectoryfortheworkspaceinwhichyousourcecodeandotherfilesarestored,andseveralotheroptions.Leavealloftheseatthedefault,andclickNext.
6. ThenextwindowshowsyoutheConfigureLauncherIconoptionsandsettings,asshowninFigure1-7.Feelfreetoplaywiththeoptionshere,thoughanychangesyoumakearecosmeticandaffectthelookofthelaunchericonwhenyourapplicationisdeployed,andnotitsactuallogic.ClickNextwhenready.
Figure1-7.TheAndroidlauncherconfigurationoptionsforanewAndroidproject
7. You’llnextseetheCreateActivityscreen.ChooseBlankActivityastheactivitytype,andclickNexttomovetothelastscreenofthewizard.
8. ThefinalscreenoftheNewAndroidApplicationwizardwillbetheBlankActivitydetailspage.TypeHelloActivityastheActivityName.You’retellingAndroidthatthisactivityistheonetolaunchwhenyourapplicationstartsup.Youmayhaveotheractivitiesinyourapplication,butthisisthefirstonetheusersees.AllowtheLayoutNametoauto-populatewiththevalueactivity_hello.
9. ClicktheFinishbutton,whichtellsADTtogeneratetheprojectskeletonforyou.Fornow,opentheHelloActivity.javafileunderthesrcfolderandmodifytheonCreate()methodasfollows:
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);/**createaTextViewandwriteHelloWorld!*/TextViewtv=newTextView(this);tv.setText("HelloWorld!");/**setthecontentviewtotheTextView*/setContentView(tv);}
Youwillneedtoaddanimportandroid.widget.TextView;statementatthetopofthefilewiththeotherimportstogetridoftheerrorreportedbyEclipse.SavetheHelloActivity.javafile.
Toruntheapplication,youneedtocreateanEclipselaunchconfiguration,andyouneedavirtualdeviceonwhichtorunit.We’llrunquicklythroughthesestepsandcomebacklatertomoredetailsaboutAVDs.CreatetheEclipselaunchconfigurationbyfollowingthesesteps:
1. SelectRun RunConfigurations.
2. IntheRunConfigurationsdialogbox,double-clickAndroidApplicationintheleftpane.ThewizardinsertsanewconfigurationnamedNewConfiguration.
3. RenametheconfigurationRunHelloWorld.
4. ClicktheBrowsebutton,andselecttheHelloAndroidproject.
5. LeaveLaunchActionsettoLaunchDefaultActivity.ThedialogshouldappearasshowninFigure1-8.
Figure1-8.ConfiguringanEclipserunconfigurationtorunthe“HelloWorld!”application
6. ClickApplyandthenRun.You’realmostthere!Eclipseisreadytorunyourapplication,butitneedsadeviceonwhichtorunit.AsshowninFigure1-9,you’rewarnedthatnocompatibletargetswerefoundandaskedifyou’dliketocreateone.ClickYes.
Figure1-9.ErrormessagewarningabouttargetsandaskingforanewAVD
7. You’representedwithawindowthatshowstheexistingAVDs(seeFigure1-10).YouneedtoaddanAVDsuitableforyournewapplication.ClicktheNewbutton.
Figure1-10.TheexistingAVDs
8. FillintheCreateAVDformasshowninFigure1-11.SetNametoKitKat,chooseAndroid4.4-APILevel19(orsomeotherversion)fortheTarget,setSDCardSizeto64(for64MB),andchooseothervaluesasshown.ClickCreateAVD.TheManagermayconfirmthesuccessfulcreationofyourAVD.ClosetheAVDManagerwindowbyclickingXintheupper-rightcorner.
Figure1-11.ConfiguringanAVD
NoteYou’rechoosinganewerversionoftheSDKforyourAVD,butyourapplicationcanalsorunonanolderone.ThisisokaybecauseAVDswithnewerSDKscanrunapplicationsthatrequireolderSDKs.Theopposite,ofcourse,isnottrue:anapplicationthatrequiresfeaturesof
anewerSDKwon’trunonanAVDwithanolderSDK.
9. SelectyournewAVDfromthebottomlist.NotethatyoumayneedtoclicktheRefreshbuttontomakeanynewAVDstoshowupinthelist.ClicktheOKbutton.
10. EclipselaunchestheemulatorwithyourveryfirstAndroidapp(seeFigure1-12)!
Figure1-12.HelloAndroidApprunningintheemulator
NoteItmaytaketheemulatorawhiletoemulatethedevicebootupprocess.Oncethebootupprocesshascompleted,youtypicallyseealockedscreen.ClicktheMenubuttonordragtheunlockimagetounlocktheAVD.Afterunlocking,youshouldseeHelloAndroidApprunningintheemulator,asshowninFigure1-11.Beawarethattheemulatorstartsotherapplicationsinthebackgroundduringthestartupprocess,soyoumayseeawarningorerrormessagefromtimetotime.Ifyoudo,youcangenerallydismissittoallowtheemulatortogotothenextstepinthestartupprocess.Forexample,ifyouruntheemulatorandseeamessagelike“applicationabcisnotresponding,”youcaneitherwaitfortheapplicationtostartorsimplyasktheemulatortoforcefullyclosetheapplication.Generally,youshouldwaitandlettheemulatorstartupcleanly.
NowyouknowhowtocreateanewAndroidapplicationandrunitintheemulator.Next,
we’lllookmorecloselyatAVDs,andalsohowtodeploytoarealdevice.
AVDsAnAVDrepresentsadeviceanditsconfiguration.Forexample,youcouldhaveanAVDrepresentingareallyoldAndroiddevicerunningversion1.5oftheSDKwitha32MBSDcard.TheideaisthatyoucreateAVDsyouaregoingtosupportandthenpointtheemulatortooneofthoseAVDswhendevelopingandtestingyourapplication.Specifying(andchanging)whichAVDtouseisveryeasyandmakestestingwithvariousconfigurationsasnap.Earlier,yousawhowtocreateanAVDusingEclipse.YoucanmakemoreAVDsinEclipsebychoosingWindow AndroidVirtualDeviceManager.YoucanalsocreateAVDsusingthecommandlinewiththeutilitynamedandroidunderthetoolsdirectory(e.g.,c:\android-sdk-windows\tools\).androidallowsyoutocreateanewAVDandmanageexistingAVDs.Forexample,youcanviewexistingAVDs,moveAVDs,andsoonbyinvokingandroidwiththe“avd”option.Youcanseetheoptionsavailableforusingandroidbyrunningandroid-help.Fornow,let’sjustcreateanAVD.
RunningonaRealDeviceThebestwaytotestanAndroidappistorunitonarealdevice.AnycommercialAndroiddeviceshouldworkwhenconnectedtoyourworkstation,butyoumayneedtodoalittleworktosetitup.IfyouhaveaMac,youdon’tneedtodoanythingexceptplugitinusingtheUSBcable.Then,onthedeviceitself,chooseSettings Applications Development(thoughthismayvarybyphoneandversion)andenableUSBdebugging.OnLinux,youprobablyneedtocreateormodifythisfile:/etc/udev/rules.d/51-android.rules.Weputacopyofthisfileonourwebsitewiththeprojectfiles;copyittotheproperdirectory,andmodifytheusernameandgroupvaluesappropriatelyforyourmachine.Then,whenyoupluginanAndroiddevice,itwillberecognized.Next,enableUSBdebuggingonthedevice.
ForWindows,youhavetodealwithUSBdrivers.GooglesuppliessomewiththeAndroidpackages,whichareplacedundertheusb_driversubdirectoryoftheAndroidSDKdirectory.Otherdevicevendorsprovidedriversforyou,solookforthemontheirwebsites.YoucanalsovisittheXDAforums,forum.xda-developers.com,whereadviceonsourcingandconfiguringdriversforavarietyofphonesanddevicesisdiscussed.Whenyouhavethedriverssetup,enableUSBdebuggingonthedevice,andyou’reready.
Nowthatyourdeviceisconnectedtoyourworkstation,whenyoutrytolaunchanapp,eitheritlaunchesdirectlyonthedeviceor(ifyouhaveanemulatorrunningorotherdevicesattached)awindowopensinwhichyouchoosewhichdeviceoremulatortolaunchinto.Ifnot,tryeditingyourRunConfigurationtomanuallyselectthetarget.
ExploringtheStructureofanAndroidApplicationAlthoughthesizeandcomplexityofAndroidapplicationscanvarygreatly,theirstructuresaresimilar.Figure1-13showsthestructureofthe“HelloWorld!”appyoujustbuilt.
Figure1-13.Thestructureofthe“HelloWorld!”application
Androidapplicationshavesomeartifactsthatarerequiredandsomethatareoptional.Table1-1summarizestheelementsofanAndroidapplication.
Table1-1.TheArtifactsofanAndroidApplication
Artifact Description Required?
AndroidManifest.xml
TheAndroidapplicationdescriptorfile.Thisfiledefinestheactivities,contentproviders,services,andintentreceiversoftheapplication.Youcanalsousethisfiletodeclarativelydefinepermissionsrequiredbytheapplication,aswellasinstrumentationandtestingoptions.
Yes
src Afoldercontainingallofthesourcecodeoftheapplication. Yes
assets Anarbitrarycollectionoffoldersandfiles. No
resAfoldercontainingtheresourcesoftheapplication.Thisistheparentfolderofdrawable,animator,layout,menu,values,xml,andraw.
Yes
drawable Afoldercontainingtheimagesorimage-descriptorfilesusedbytheapplication. No
animator AfoldercontainingtheXML-descriptorfilesthatdescribetheanimationsusedbytheapplication. No
layout Afoldercontainingviewsoftheapplication. No
menu AfoldercontainingXML-descriptorfilesformenusintheapplication. No
values Afoldercontainingotherresourcesusedbytheapplication.Examplesofresourcesfoundinthisfolderincludestrings,arrays,styles,andcolors. No
xml AfoldercontainingadditionalXMLfilesusedbytheapplication. No
raw Afoldercontainingadditionaldata—possiblynon-XMLdata—thatisrequiredbytheapplication. No
AsyoucanseefromTable1-1,anAndroidapplicationisprimarilymadeupofthreemandatorypieces:theapplicationdescriptor,acollectionofvariousresources,andtheapplication’ssourcecode.IfyouputasidetheAndroidManifest.xmlfileforamoment,youcanviewanAndroidappinthissimpleway:youhavesomebusinesslogicimplementedincode,andeverythingelseisaresource.
AndroidhasalsoadoptedtheapproachofdefiningviewsviamarkupinXML.Youbenefitfromthisapproachbecauseyoudon’thavetohard-codeyourapplication’sviews;youcanmodifythelookandfeeloftheapplicationbyeditingthemarkup.
Itisalsoworthnotingafewconstraintsregardingresources.First,Androidsupportsonlyasingle-levellistoffileswithinthepredefinedfoldersunderres.Forexample,therearesomesimilaritiesbetweentheassetsfolderandtherawfolderunderres.Bothfolderscancontainrawfiles,butthefilesinrawareconsideredresources,andthefilesinassetsarenot.Sothefilesinrawarelocalized,accessiblethroughresourceIDs,andsoon.Butthecontentsoftheassetsfolderareconsideredgeneral-purposecontenttobeusedwithoutresourceconstraintsandsupport.Notethatbecausethecontentsofthe
assetsfolderarenotconsideredresources,youcanputanarbitraryhierarchyoffoldersandfilesinthisfolder.(Chapter3talksalotmoreaboutresources.)
NoteYoumayhavenoticedthatXMLisusedquiteheavilywithAndroid.YouknowthatXMLcanbeabloateddataformat,sodoesitmakesensetorelyonXMLwhenyouknowyourtargetisadevicewithlimitedresources?ItturnsoutthattheXMLyoucreateduringdevelopmentisactuallycompileddowntobinaryusingtheAndroidAssetPackagingTool(AAPT).Therefore,whenyourapplicationisinstalledonadevice,thefilesonthedevicearestoredasbinary.Whenthefileisneededatruntime,thefileisreadinitsbinaryformandisnottransformedbackintoXML.Thisgivesyouthebenefitsofbothworlds—yougettoworkwithXML,andyoudon’thavetoworryabouttakingupvaluableresourcesonthedevice.
ExaminingtheApplicationLifeCycleThelifecycleofanAndroidapplicationisstrictlymanagedbythesystem,basedontheuser’sneeds,availableresources,andsoon.Ausermaywanttolaunchawebbrowser,forexample,butthesystemultimatelydecideswhethertostarttheapplication.Althoughthesystemistheultimatemanager,itadherestosomedefinedandlogicalguidelinestodeterminewhetheranapplicationcanbeloaded,paused,orstopped.Iftheuseriscurrentlyworkingwithanactivity,thesystemgiveshighprioritytothatapplication.Conversely,ifanactivityisnotvisibleandthesystemdeterminesthatanapplicationmustbeshutdowntofreeupresources,itshutsdownthelower-priorityapplication.
Theconceptofapplicationlifecycleislogical,butafundamentalaspectofAndroidapplicationscomplicatesmatters.Specifically,theAndroidapplicationarchitectureiscomponent-andintegration-oriented.Thisallowsarichuserexperience,seamlessreuse,andeasyapplicationintegrationbutcreatesacomplextaskfortheapplicationlife-cyclemanager.
Let’sconsideratypicalscenario.Auseristalkingtosomeoneonthephoneandneedstoopenane-mailmessagetoansweraquestion.Theusergoestothehomescreen,opensthemailapplication,opensthee-mailmessage,clicksalinkinthee-mail,andanswersthefriend’squestionbyreadingastockquotefromawebpage.Thisscenariorequiresfourapplications:thehomeapplication,atalkapplication,ane-mailapplication,andabrowserapplication.Astheusernavigatesfromoneapplicationtothenext,theexperienceisseamless.Inthebackground,however,thesystemissavingandrestoringapplicationstate.Forinstance,whentheuserclicksthelinkinthee-mailmessage,thesystemsavesmetadataontherunninge-mailmessageactivitybeforestartingthebrowser-applicationactivitytolaunchaURL.Infact,thesystemsavesmetadataonanyactivitybeforestartinganothersothatitcancomebacktotheactivity(whentheuserbacktracks,forexample).Ifmemorybecomesanissue,thesystemhastoshutdownaprocessrunninganactivityandresumeitasnecessary.
Androidissensitivetothelifecycleofanapplicationanditscomponents.Therefore,you
needtounderstandandhandlelife-cycleeventsinordertobuildastableapplication.TheprocessesrunningyourAndroidapplicationanditscomponentsgothroughvariouslife-cycleevents,andAndroidprovidescallbacksthatyoucanimplementtohandlestatechanges.Forstarters,youshouldbecomefamiliarwiththevariouslife-cyclecallbacksforanactivity(seeListing1-1).
Listing1-1.Life-CycleMethodsofanActivity
protectedvoidonCreate(BundlesavedInstanceState);protectedvoidonStart();protectedvoidonRestart();protectedvoidonResume();protectedvoidonPause();protectedvoidonStop();protectedvoidonDestroy();
Listing1-1showsthelistoflife-cyclemethodsthatAndroidcallsduringthelifeofanactivity.It’simportanttounderstandwheneachofthemethodsiscalledbythesysteminordertoensurethatyouimplementastableapplication.Notethatyoudonotneedtoreacttoallofthesemethods.Ifyoudo,however,besuretocallthesuperclassversionsaswell.Figure1-14showsthetransitionsbetweenstates.
Figure1-14.Statetransitionsofanactivity
Thesystemcanstartandstopyouractivitiesbasedonwhatelseishappening.AndroidcallstheonCreate()methodwhentheactivityisfreshlycreated.onCreate()isalwaysfollowedbyacalltoonStart(),butonStart()isnotalwaysprecededbyacalltoonCreate()becauseonStart()canbecalledifyourapplicationwasstopped.WhenonStart()iscalled,youractivityisnotvisibletotheuser,butit’sabouttobe.onResume()iscalledafteronStart(),justwhentheactivityisintheforegroundandaccessibletotheuser.Atthispoint,theusercaninteractwithyouractivity.
Whentheuserdecidestomovetoanotheractivity,thesystemcallsyouractivity’sonPause()method.FromonPause(),youcanexpecteitheronResume()oronStop()tobecalled.onResume()iscalled,forexample,iftheuserbringsyouractivitybacktotheforeground.onStop()iscalledifyouractivitybecomesinvisibleto
theuser.IfyouractivityisbroughtbacktotheforegroundafteracalltoonStop(),thenonRestart()iscalled.Ifyouractivitysitsontheactivitystackbutisnotvisibletotheuser,andthesystemdecidestokillyouractivity,onDestroy()iscalled.
Asadeveloper,youneedn’tdealwitheverypossiblescenario;youmostlyhandleonCreate(),onResume(),andonPause().YouhandleonCreate()tocreatetheuserinterfaceforyouractivity.Inthismethod,youbinddatatoyourwidgetsandwireupanyeventhandlersforyourUIcomponents.InonPause(),youwanttopersistcriticaldatatoyourapplication’sdatastore:it’sthelastsafemethodthatiscalledbeforethesystemkillsyourapplication.onStop()andonDestroy()arenotguaranteedtobecalled,sodon’trelyonthesemethodsforcriticallogic.
Thetakeawayfromthisdiscussion?Thesystemmanagesyourapplication,anditcanstart,stop,orresumeanapplicationcomponentatanytime.Althoughthesystemcontrolsyourcomponents,theydon’trunincompleteisolationwithrespecttoyourapplication.Inotherwords,ifthesystemstartsanactivityinyourapplication,youcancountonanapplicationcontextinyouractivity.
SimpleDebuggingTheAndroidSDKincludesahostoftoolsthatyoucanusefordebuggingpurposes.ThesetoolsareintegratedwiththeEclipseIDE(seeFigure1-15forasmallsample).
Figure1-15.DebuggingtoolsthatyoucanusewhilebuildingAndroidapplications
OneofthetoolsthatyouusethroughoutAndroiddevelopmentisLogCat.Thistooldisplaysthelogmessagesyouemitusingandroid.util.Log,exceptions,System.out.println,andsoon.AlthoughSystem.out.printlnworks,andthemessagesappearintheLogCatwindow,tologmessagesfromyourapplicationyoushouldusetheandroid.util.Logclass.Thisclassdefinesthefamiliarinformational,warning,anderrormethodsthatyoucanfilterintheLogCatwindowtoseejustwhatyouwanttosee.HereisasampleLogcommand:
Log.v("stringTAG","Thisismyverbosemessagetowritetothelog");
Thisexampleshowsthestaticv()methodoftheLogclass,butthereareothersfordifferentlevelsofseverity.It’sbesttousetheappropriatecalllevelforthemessageyouwanttolog,anditgenerallyisn’tagoodideatoleaveaverbosecallinanappthatyou
wanttodeploytoproduction.KeepinmindthatloggingusesmemoryandtakesCPUresources.What’sparticularlyniceaboutLogCatisthatyoucanviewlogmessageswhenyou’rerunningyourapplicationintheemulator,butyoucanalsoviewlogmessageswhenyou’veconnectedarealdevicetoyourworkstationandit’sindebugmode.Infact,logmessagesarestoredsuchthatyoucanevenretrievethemostrecentmessagesfromadevicethatwasdisconnectedwhenthelogmessageswererecorded.WhenyouconnectadevicetoyourworkstationandyouhavetheLogCatviewopen,youseethelastseveralhundredmessages.
LaunchingtheEmulatorEarlieryousawhowtolaunchtheemulatorfromyourprojectinEclipse.Inmostcases,youwanttolaunchtheemulatorfirstandthendeployandtestyourapplicationsinarunningemulator.Tolaunchanemulatoranytime,firstgototheAVDManagerbyrunningtheAndroidprogramwiththeavdoptionfromthetoolsdirectoryoftheAndroidSDKorfromtheWindowmenuinEclipse.OnceintheManager,choosethedesiredAVDfromthelist,andclickStart.
WhenyouclicktheStartbutton,theLaunchOptionsdialogopens(seeFigure1-16).Thisallowsyoutoscalethesizeoftheemulator’swindowtosuityourdisplayandchangethestartupandshutdownoptions.Thescalingresultscansometimesbeunexpectedlylargeorsmall,sopickthevaluethatworksforyoubasedonyourscreensizeandscreendensity.
Figure1-16.TheLaunchOptionsdialog
YoucanalsoworkwithsnapshotsintheLaunchOptionsdialog.Savingtoasnapshotcausesasomewhatlongerdelaywhenyouexittheemulator.Asthenamesuggests,youarewritingoutthecurrentstateoftheemulatortoasnapshotimagefile,whichcanthenbeusedthenexttimeyoulaunchtoavoidgoingthroughanentireAndroidbootupsequence.Launchinggoesmuchfasterifasnapshotispresent,makingthedelayatsavetimewellworthit—youbasicallypickupwhereyouleftoff.
Ifyouwanttostartcompletelyfresh,youcanchooseWipeUserData.YoucanalsodeselectLaunchfromSnapshottokeeptheuserdataandgothroughthebootupsequence.OryoucancreateasnapshotthatyoulikeandenableonlytheLaunchfromSnapshotoption;thisreusesthesnapshotoverandoversoyourstartupisfastandtheshutdownisfasttoo,becauseitdoesn’tcreateanewsnapshotimagefileeverytimeitexits.ThesnapshotimagefileisstoredinthesamedirectoryastherestoftheAVDimagefiles.Ifyoudidn’tenablesnapshotswhenyoucreatedtheAVD,youcanalwaysedittheAVDandenablethemthere.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
http://developer.samsung.com/:Samsung’sdevelopersite,withmanyAndroid-relateddevelopmenttools.
http://developer.htc.com/:HTCsiteforAndroiddevelopers.
http://developer.android.com/guide/developing/tools/index.htmlDeveloperdocumentationfortheAndroiddebuggingtools.
www.droiddraw.org/:DroidDrawsite.ThisisaUIdesignerforAndroidapplicationsthatusesdrag-and-droptobuildlayouts.
SummaryThischaptercoveredthefollowingtopicstogetyousetupforAndroiddevelopment:
DownloadingandinstallingtheJDK,EclipseorAndroidStudio,andtheAndroidSDK
HowtomodifyyourPATHvariableandlaunchatoolswindow
InstallingandupgradingtheADTfundamentalconceptsofviews,activities,fragments,intents,contentproviders,services,andtheAndroidManifest.xmlfile
AndroidVirtualDevices(AVDs),whichcanbeusedtotestappswhenyoudon’thaveadevice(ortheparticulardeviceyouwanttotestwith)
Buildinga“HelloWorld!”appanddeployingittoanemulator
Thebasicrequirementstoinitializeanyapplication(projectname,Androidtarget,applicationname,packagename,mainactivity,minimumSDKversion)
Wheretherunconfigurationsareandhowtochangethem
Connectingarealdevicetoyourworkstationandrunningyournewappsonit
TheinnerstructureofanAndroidapp,andthelifecycleofanactivity
LogCat,andwheretolookfortheinternalmessagesfromapps
Optionsavailablewhenlaunchinganemulator,suchassnapshotsandadjustingthescreendisplaysize
Chapter2
IntroductiontoAndroidApplicationArchitectureThefirstchaptercoveredtheenvironmentandtoolsnecessarytodevelopAndroidapplications.ThischapterwillbeabroadintroductorytourofAndroid’sapplicationarchitecture.Wewilldothatbydoingthreethings.First,wewillpresentthearchitectureofanAndroidappbybuildingone.WewillthenpresenttheessentialcomponentsofAndroidarchitecture,namely,activities,resources,intents,activitylifecycle,andsavingstate.Wewillconcludethechapterwithalearningroadmaponhowtousetherestofthebooktocreatesimpletosophisticatedmobileapps.
Inthefirstsectionofthischapter,aone-pagecalculatorappwillgiveyouabird’seyeviewofwritingapplicationsusingtheAndroidSDK.CreatingthisappwilldemonstratehowtocreatetheUI,writeJavacodetocontroltheUI,andbuildanddeploytheapp.
InadditiontodemonstratingtheUI,thiscalculatorappwillintroduceyoutoactivities,resources,andintents.TheseconceptsgototheheartofAndroidapplicationarchitecture.WewillcoverthesetopicsindetailinthesecondsectionofthechapterinordertogiveyouastrongfootingforunderstandingtherestoftheAndroidSDK.Wewillalsocovertheactivitylifecycleandabriefoverviewofthepersistenceoptionsforyourapplication.
InthethirdsectionwewillgiveyouaroadmapfortherestofthebookthataddressesbasicandadvancedaspectsofbuildingAndroidapplications.Thisfinalsectionbreaksthechaptersintoasetoflearningtracks.ThissectionisabroadintroductiontotheentiresetofAndroidAPIs.
Furthermore,inthischapteryouwillfindanswerstothefollowing:HowcanIcreateUIwitharichsetofcontrols?HowcanIstorestatepersistently?HowcanIreadstaticfilesthatareinputstotheapp?HowcanIreachoutandreadfromorwritetotheweb?WhatotherAPIsdoesAndroidprovidetomakemyappfunctionalandrich?
Withoutfurtherado,let’sdropyouintothesimplecalculatorapplicationtoopenuptheworldofAndroid.
ExploringaSimpleAndroidApplicationThecalculatorapplicationwewanttodemonstrateforthischapterisshowninFigure2-1.
Figure2-1.ACalculatorApp
DisplayinFigure2-1iscalledanactivityinAndroid.Thisactivityhastwoeditcontrolsatthetoprepresentingtwonumbers.Youcanenternumbersintheseeditboxesandusetheoperatorbuttonsatthebottomofthefiguretoperformarithmeticaloperations.Theresultofanoperationwillbeshowninthetopeditcontrol.ThesetwoeditboxesarelabeledOperand1andOperand2.TocreatethistypeofacalculatorapplicationusingtheAndroidSDK,youneedtoperformthefollowingsteps:
1. CreateaUserInterface(UI)definitioninatext/xmlfile(calledalayoutoralayoutfileinAndroid).
2. WriteprogramminglogicinaJavafile(usuallyinaclassextendingthebaseactivityclass).
3. Createaconfigurationfiledescribingyourapplication(thisfileisalwayscalledAndroidManifest.xml).
4. Createaprojectandadirectorystructuretoplacethefilesfromsteps1,2,and3.
5. Buildadeployablepackageusingtheprojectinstep4(itiscalledan.apkfile).
BygoingthroughthedetailsofthesestepsyouwillgetafeelforhowAndroidapplicationsaremade.Wewillgothroughthesestepsnow.
DefiningUIthroughLayoutFilesAnAndroidapplicationresemblesawebapplicationinlotsofways.Inawebapplication
theUIisyourwebpage.UIofawebpageisdefinedthroughHTML.AnHTMLwebpageisaseriesofcontrolslikeparagraphs,divisions,forms,buttons,etc.UIisconstructedsimilarlyinAndroid.AlayoutfileinAndroidislikeanHTMLpage,albeitthecontrolsaredrawnfromtheAndroidSDKinsteadofHTML.InAndroidthisfileiscalledalayoutfile.Listing2-1showsthelayoutfilethatproducedtheUIofFigure2-1.Listing2-1.AnAndroidLayoutFilethatDefinesUIforanActivity
<?xmlversion="1.0"encoding="utf-8"?><!--**********************************************calculator_layout.xml*correspondingactivity:CalculatorMainActivity.java*prefix:cl_(Usedforprefixinguniqueidentifiers)**Use:*Demonstrateasimplecalculator*Demonstratetextviews,edittext,buttons,businesslogic*********************************************--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="5dp"android:padding="5dp"android:background="@android:color/darker_gray"><!--Operand1--><TextViewandroid:layout_width="match_parent"
android:layout_height="wrap_content"android:text="Operand1,(AndResult)"/><EditTextandroid:layout_width="match_parent"
android:layout_height="wrap_content"android:id="@+id/editText1"android:text="0"android:inputType="numberDecimal"/><!--Operand2--><TextViewandroid:layout_width="match_parent"
android:layout_height="wrap_content"android:text="Operand2"android:layout_marginTop="10dp"/><EditTextandroid:layout_width="match_parent"
android:layout_height="wrap_content"android:text="0"android:id="@+id/editText2"android:inputType="numberDecimal">
</EditText><!--ButtonsforVariousOperators--><TextViewandroid:layout_width="match_parent"
android:layout_height="wrap_content"android:text="Operand1=Operand1OperatorOperand2"android:layout_marginTop="10dp"/><LinearLayoutandroid:orientation="horizontal"android:layout_marginTop="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:text="+"android:id="@+id/plusButton"
android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button><Buttonandroid:text="-"android:id="@+id/minusButton"
android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button><Buttonandroid:text="*"android:id="@+id/multiplyButton"
android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button><Buttonandroid:text="/"android:id="@+id/divideButton"
android:layout_weight="1"android:layout_width="wrap_content"android:layout_height="wrap_content"></Button></LinearLayout></LinearLayout>
Let’sgothroughthiscalculatorXMLlayoutfileofListing2-1linebyline.ThisfilelookscomplicatedcomparedtoFigure2-1.Yes,itisverbose,butyouwillseeshortlyitissimpleinitsarchitecture.
SpecifyingCommentsinLayoutFilesAsagoodpracticethecommentsatthetopofthelayoutXMLfileinListing2-1indicatewhatthisfilenameis,whatUIactivitywillbeusedtodisplaythisfile,whatthepurposeofthisfileis,andbrieflywhatcontrolsareinthislayoutfile.
AddingViewsandViewGroupsinLayoutFiles
EachXMLnodeinalayoutfilerepresentsaUIcontrol.Thesecontrolscanbeeitherviewsorcontainersofotherviews.AcontainerofotherviewsiscalledaViewGroup.Forexample,abuttonisaview.ALinearLayoutinListing2-1isaViewGroupthatplacesallitschildviewseitherverticallydownorhorizontallyacross.So,aLinearLayoutislikeanHTMLdivthatlaysoutitschildreneitheracrossordown.
SpecifyingControlPropertiesinLayoutFilesTheUIcontrolsinthecalculatorlayoutfileareLinearLayout,TextView,EditText,andabutton.EachofthesecontrolsrepresentsaJavaobjectwhenpaintedonthescreen.Beinganobject,eachofthesecontrolshasproperties.IfthecontrolsbelongtothecoreAndroidSDK,theirpropertiesareprefixedwith“android:”asin“android:orientation”fortheLinearLayoutcontrol.Themajority,ifnotall,ofthecontrolsthatyounormallyuseinyourappsarefromthecoreAndroidSDK.Whenyouwriteyourowncontrolstheyarecalledcustomcontrols.Thesecustomcontrolsallowyoutodefinecustomproperties.Seethe“Roadmap”sectionofthischapterformoreoncustomcontrols.
IndicatingViewGroupPropertiesSomeofthecontrolpropertiesarelabeledas“android:layout_,”suchasandroid:layout_width.Theseproperties,althoughmentionedinagivenXMLnode,likeabutton,arereadandusedbyparentnode,likeLinearLayout,toplacethechildren.ParentnodesareviewgroupsliketheLinearLayout.YoucanseethisdifferenceinhowpaddingandmarginsaredefinedforthefirstLinearLayoutnodeinthelayoutfileofListing2-1.ThepropertypaddingbelongstothetopmostLinearLayoutobjectinthisexample,whereasthepropertyformarginsofthatsametopmostLinearLayout,thelayout_marginproperty,belongstotheparentoftheLinearLayout,whichisanimplicitviewgroupprovidedbytheAndroidframework.Soforpaddingyousayandroid:padding,andformarginsyousayandroid:layout_margin.Noticethepresenceorlackof“layout_”prefix.Ifyouwanttoknowwhatpropertiesanobject(orcontrol)supports,youcanuseCtrl-Spaceineclipsetoseeasetofsuggestionsforthepropertiesforthatobject.Dependingonyourdevelopmentenvironmentyoucaneasilyfindanequivalentsetofkeycombinationstodothesame.
ControllingWidthandHeightofaControlTwooften-usedpropertiesforacontrolareitslayoutwidthandlayoutheight.Thelayoutparentofacontrolmanagesthesevalues.Valuesforthesepropertiesaretypicallymatch_parentandwrap_content.IfyousayyourTextViewissettomatch_parentforitswidth,thewidthofthecontrolmatchesupwiththeparentwidth.WhenaTextViewissetforitsheightwrap_content,thenitsheightwillbejustsufficienttocontainallitstextintheverticaldirection.Ofcourse,thesetwopropertiesare
availabletoallchildcontrolsofalayout,notjustthetextcontrol.Thesetwolayoutcontrolproperties,match_parentandwrap_content,alsoapplytotheheightofacontrolaswell.
IntroducingResourcesandBackgroundsAlthoughweareinthemiddleofexplainingthecontrolsinthelayoutfile,thisisagoodplacetointroduceresources.Layoutfilesare,andaremadeupof,resources.Inthecalculatorlayoutfile,wehavesetthebackgroundoftheentireviewbysettingthebackgroundontherootLinearLayoutcontrol.Thisinstructionlookedlikethefollowing:
android:background="@android:color/darker_gray"
EveryvieworcontrolinAndroidsupportsthebackgroundproperty.Backgroundsareusuallyidentifiedasresources.Inthisexample,thebackgroundispointingtoaresource,comingfromtheAndroidpackage,whichisoftypecolorwhosereferencedvalueisdarkergray.
AnumberofinputstoyourapplicationarerepresentedasresourcesinAndroid.Someexampleresourcesareimagefiles,entirelayoutfiles,colors,strings,XMLfiles,menus,andmanyotherthingsaslistedintheAndroidSDK.Forinstance,theentirecalculatorlayoutfilewearetalkingaboutisitselfaresource.
Asyoucanseefromthecalculatorlayoutfile,resourcesareofdifferenttypes.InAndroidtheyarefurtherbroadlyclassifiedas“valuebased”or“filebased.”Examplesofresourcesthatarevaluesarestringsandcolors.Examplesofresourcesthatarefilesareimagesorlayoutfiles.Listing2-2showsanexampleofcreatingvalue-basedresourcesthatarestringsandcolors.
Listing2-2.ExampleofValue-BasedResources
<?xmlversion="1.0"encoding="utf-8"?><!--thisfilewillbein/res/valuessubdirectory--><resources><stringname="hello">HelloWorld,CalculatorMainActivity!</string><stringname="app_name">ADemoCalculator</string><colorname="red">#FF0000</color><colorname="blue">#0000FF</color></resources>
Youcanhaveanynumberofvalue-basedfilesaslongastheyareallunderthe/res/valuessubdirectory.Eachfilewillstartwiththeresourcesrootnode.YoucanuseCtrl-Spacetodiscoverwhatotherpossiblevalue-basedresourcesareavailable.
Turningtofile-basedresources,Listing2-3showsanexampleofplacinganumberoffile-basedresourcesundertheirrespectiveresourcesubdirectories.
Listing2-3.ExampleofFile-BasedResources
/res/layout/page1_layout.xml(Alayoutfileforsaypage1)/res/drawable/page1_background.jpg(Anexampleimagefile)/res/drawable-hdpi/page1_background.jpg(Sameimagefileforadifferentdensity)/res/xml/some_preferences.xml(exampleofaninputfileforyourapp)
Anyoftheseresources,beitfilebasedorvaluebased,canbereferencedinthelayoutfilesusingthe“@”resourcereferencesyntax.Forexample,inthecalculatorlayoutfileinListing2-1thebackgroundcanbesetliterallyandexplicitlyasacolorvaluebetweenthequotes,suchas“#FFFFFF,”orpointtoaresourcereference(indicatedbyastarting@color/red)thatisalreadydefinedasacolorresource(asinListing2-2).Inthissyntaxledby“@,”thetypeofreferencedresourceis“color.”Someofthekeywordsforothertypesofresourcesarestring(forstrings),drawable(forimages),etc.
InListing2-1,thewaytoreadthevalueofthebackgroundpropertyoftheLinearLayout,namely,@android:color/darker_gray,isasfollows:Usethevalueoftheresourceidentifiedasdarker_grayintheAndroidcoreframeworkandwhoseresourcetypeiscolor.Withthisknowledgeofresourcereferencesyntax,takealookatthecalculatorlayoutfilelistingonemoretimeandyouwillbeabletoreaditwhereeachcontrolhaspropertiesandeachpropertyhasavaluethatiseitherdirectlyspecifiedorreferencesaresourcethatiselsewheredefinedinaresourcefile.
Theindirectionofacontrolpropertyvaluedefinedasaresourcereferencehasanadvantage.Aresourcecanbecustomizedforlanguages,devicedensityvariation,andavarietyoffactorswithoutalteringthecompiledJavasourcecode.ForexamplewhenyousupplybackgroundimagesyoucanplaceanumberoftheseimagesindifferentdirectoriesandnamethemusingtheconventionspecifiedbyAndroid.ThenAndroidknowshowtolocatetherightimage,givenitsname,dependingonthedeviceyourappisrunningon.
WorkingwithTextControlsintheLayoutFileInthecalculatorlayoutexamplewehaveusedtwotext-basedcontrols.OneisaTextViewcontrol,whichisusedasalabel;theotherisanEditTextcontrol,whichisusedfortakinginputtext.Wehavealreadyshownyouhowtosetthewidthandheightofanyviewbyusingtheattributesthatstartwith“layout_”.Everytext-basedcontrolalsohasanattributecalledtext.Inourexampleswehavedirectlyspecifiedtheliteraltextasavalueforthisproperty.Therecommendationistouseinsteadaresourcereference.Forexample:
android:text="Literaltext"//whatwedidforclarityorandroid:text="@string/LiteralTextId"//doingitproperly
ThelatterresourceID,LiteralTextId,thencanbedefinedinafileinthe
/res/valuessubdirectorymuchlikeinListing2-2.
EditTextcontrolinthecalculatorlayouthasanattributeinputTypetoprovidethenecessaryconstraintsandvalidationsthatneedtotakeplacewhendataistypedintotheeditablefield.Refertothedocumentationtoseealargenumberofconstraintsthatareavailableforeditablefields.Alternatively,youcanuseeclipseADTtodiscovertheavailableinputtypesontheflyduringcoding.
WorkingwithAutogeneratedIDsforControlsTomanipulatethecontrolsthatareinthecalculatorlayoutofListing2-1,weneedawaytoturnthemintoJavaobjects.ThisisdonebylocatingthesecontrolsusingauniqueIDinthecurrentlyloadedlayoutfileofanactivity.Let’slookatoneexampleinthelayoutfilewhereanEditTextcontrolisgivenanIDofeditText2asfollows:
android:id="@+id/editText2"
ThisformattellsAndroidthatIDofthisEditTextcontrolisaresourceoftypeIDanditsintegervalueshouldbeknowninJavaaseditText2.The+isaconveniencetoallocateanewuniqueintegerforeditText2.Ifyoudon’thavethe+sign,thenAndroidlooksforaninteger-valuedresourcedefinedwithanIDthatiscallededitText2.Withtheconvenienceof+wecanavoidseparatelydefiningaresourcefirstandthenuseit.Insomecases,youmayhaveaneedforawell-knownIDthatissharedbymultiplepiecesofcode,inwhichcaseyouwillremovethe+andtakethemultiplestepsofdefiningtheIDfirstandthenusingitsnameinmultipleplaces.Youwillseeintheprogramminglogicsection(soontofollow)howthesecontrolIDsareusedtolocatethecontrolsandmanipulatethem.
ImplementingProgrammingLogicToseethecalculatorlayoutonthescreenofyourdevice,youneedaJavaclassderivedfromtheAndroidSDKsclassactivity.Suchanactivityrepresentsawindowinyourmobileapplication.SoyouneedtocraftacalculatoractivitybyextendingtheAndroidbaseactivityclassasshowninListing2-4.
Listing2-4.ProgrammingLogic:ImplementinganActivityClass
/***Activityname:CalculatorMainActivity*Layoutfile:calculator_layout.xml*Layoutshortcutprefixforids:cl_*Menufile:none*PurposeandLogic********************1.Demonstratebusinesslogicforasimplecalculator*2.Loadthecalculator_layout.xmlaslayout
*3.Setupbuttoncallbacks*4.Respondtobuttonclicks*5.Readvaluesfromedittextcontrols*6.Performoperationandupdateresulteditcontrol*/publicclassCalculatorMainActivityextendsActivity
implementsOnClickListener{privateEditTextnumber1EditText;privateEditTextnumber2EditText;
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.calculator_layout);gatherControls();setupButtons();}privatevoidgatherControls(){
number1EditText=(EditText)this.findViewById(R.id.editText1);number2EditText=(EditText)this.findViewById(R.id.editText2);number2EditText.requestFocus();}privatevoidsetupButtons(){
Buttonb=(Button)this.findViewById(R.id.plusButton);b.setOnClickListener(this);
b=(Button)this.findViewById(R.id.minusButton);b.setOnClickListener(this);
b=(Button)this.findViewById(R.id.multiplyButton);b.setOnClickListener(this);
b=(Button)this.findViewById(R.id.divideButton);b.setOnClickListener(this);}@OverridepublicvoidonClick(Viewv){
StringsNum1=number1EditText.getText().toString();StringsNum2=number2EditText.getText().toString();doublenum1=getDouble(sNum1);doublenum2=getDouble(sNum2);Buttonb=(Button)v;
doublevalue=0;
if(b.getId()==R.id.plusButton){value=plus(num1,num2);}elseif(b.getId()==R.id.minusButton){value=minus(num1,num2);}elseif(b.getId()==R.id.multiplyButton){value=multiply(num1,num2);}elseif(b.getId()==R.id.divideButton){value=divide(num1,num2);}number1EditText.setText(Double.toString(value));}
privatedoubleplus(doublen1,doublen2){
returnn1+n2;}privatedoubleminus(doublen1,doublen2){
returnn1-n2;}privatedoublemultiply(doublen1,doublen2){
returnn1*n2;}privatedoubledivide(doublen1,doublen2){
if(n2==0){return0;}returnn1/n2;}privatedoublegetDouble(Strings){
if(validString(s)){returnDouble.parseDouble(s);}return0;}privatebooleaninvalidString(Strings){return!validString(s);}privatebooleanvalidString(Strings){if(s==null){returnfalse;}if(s.trim().equalsIgnoreCase("")){returnfalse;}returntrue;
}}
Inthislisting,thecalculatoractivityiscalledCalculatorMainActivity.Onceyouhavethisactivity,youcanloadthecalculatorlayoutintoitinordertoseethecalculatorscreenofFigure2-1.
Let’slearnabitaboutanactivityinAndroid.Aprogrammerdoesnotneedtoinstantiateanactivitydirectly.AnactivitycanbeinstantiatedbytheAndroidframeworkbasedonuser’sactions.Inthatsense,anactivityisa“managedcomponent”managedbyAndroid.
AnactivitycangetpartiallyhiddenorcompletelyhiddenwhenanotherUIwithhigherprioritysitsontopofit(forexample,duetoaphonecall).Or,anactivitythatisinthebackgroundcanbetemporarilyremovedduetomemoryconstraint.Inthesecircumstances,theactivitycanbeautomaticallybroughtbackwhenauserrevisitstheapplication.
LoadingtheLayoutFileintoanActivityAstheactivityiseventdriven,anactivityreliesoncallbacks.ThefirstcallbackofimportanceistheonCreate()callback.InthecalculatoractivitygiveninListing2-4,youcaneasilylocatethismethod.Thisiswherewewillloadthecalculatorlayoutintothecalculatoractivity.ThisisdonethroughthemethodsetContentView().Theinputtothismethodisanidentifierforthecalculatorlayoutfile.
AnicefeatureofAndroidiswhatitdoeswiththevariousresourcesincludingthelayoutfiles.ItautogeneratesajavaclasscalledR.javawhereitdefinesintegerIDsforalltheresourcesbetheyvaluebasedorfilebased.IntheactivitygiveninListing2-4,thevariableR.layout.calculator_layoutpointstothecalculatorlayoutfile(whichitselfisinListing2-1).
WhenyouaredippingyourtoesintotheAndroidframework,theothermysteriousthinginonCreate()isthesavedInstanceBundle.AstheAndroidframeworkmaystopandrestart(evenre-create)activities,itneedsawaytopassthelaststateoftheactivitytotheonCreate()method.ThatiswhatthesavedInstanceBundleis.Itisacollectionofkeyvaluepairsholdingthepreviousstateoftheactivity.Youwilllearnaboutthisaspectofstatemanagementinmoredetaillaterinthechapter,andalsoinChapter9,wherewecoverwhathappenswhenadeviceisrotated.Fortheimplementationofthecalculatorexamplewesimplycallthesuperclass’smethodtopassonthatstatebundle.
GatheringControlsThenexttwomethods,gatherControls()andsetupButtons(),setuptheinteractionmodelforthecalculator.InthegatherControls()methodyouobtainjavareferencesfortheeditcontrolsthatyouneedtomanipulate(readorwriteto)andsavethemlocallyinthecalculatoractivityclass.YoudothisbyusingthefindViewById()methodonthebaseactivityclass.ThefindViewById()methodtakesasinputtheID
ofthecontrolthatisinthelayoutfile.HerealsoAndroidautogeneratestheseIDsandplacesthemintotheR.javaclass.Inyoureclipseprojectyoucanseethisfileinthe/gensubdirectory.Listing2-5showsthegeneratedR.javafileforthiscalculatorproject.(Ifyouweretotrythisprojectyourself,theseIDsmaydiffer.Sousethislistingprimarilytounderstandconcepts.)
Listing2-5.AutogeneratedResourceIDs:R.java
publicfinalclassR{publicstaticfinalclassattr{}publicstaticfinalclassdrawable{publicstaticfinalintbackground=0x7f020000;publicstaticfinalinticon=0x7f020001;}publicstaticfinalclassid{publicstaticfinalintdivideButton=0x7f050005;publicstaticfinalinteditText1=0x7f050000;publicstaticfinalinteditText2=0x7f050001;publicstaticfinalintminusButton=0x7f050003;publicstaticfinalintmultiplyButton=0x7f050004;publicstaticfinalintplusButton=0x7f050002;}publicstaticfinalclasslayout{publicstaticfinalintcalculator_layout=0x7f030000;}publicstaticfinalclassstring{publicstaticfinalintapp_name=0x7f040001;publicstaticfinalinthello=0x7f040000;}}
NoticehowR.javausesadifferentclassprefixforeachresourcetype.ThisallowstheprogrammerineclipsetoquicklyseparatetheIDsbywhattheirtypeis.So,forexample,allIDsforlayoutfilesareprefixedwithR.layout,andallimageIDsareprefixedwithR.drawable,andallstringswithR.string,etc.However,thereisacautionwhileworkingtheseIDs.EvenifyouhavetenlayoutfilestheIDsforallthecontrolsaregeneratedintoasinglenamespacesuchasR.id.*(where“id”isanexampleofaresourcetype).Soyoumaywanttogetintothehabitofnamingcontrolsinthelayoutfileswithsomeprefixindicatingwhichlayoutfilestheybelongto.
SettingUpButtonsSomeofthecontrolsinthecalculatorlayoutofListing2-1arethecalculatorbuttons.Theyarethebuttonsrepresentingoperators:+,-,x,and/.Weneedcodetobeinvokedwhenthesebuttonsarepressed.Thewaytodothatisbyregisteringacallbackobjectonthebuttoncontrols.Thesecallbackobjectsmustimplementthe
View.OnClickListenerinterface.ThecalculatoractivityinadditiontoextendingtheactivityclassalsoimplementstheView.OnClickListenerinterface,allowingustoregisterouractivityastheonethatneedstobecalledbackwheneachbuttonispressed.Asyoucanseeinthecodeoftheactivity(Listing2-4),thisisdonebycallingthesetOnClickListeneroneachbutton.
RespondingtoButtonClicks:TyingItAllTogetherWhenanyoftheoperatorbuttonsisclicked,theonClick()methodinthecalculatoractivitygiveninListing2-4getscalled.InthismethodwewillinvestigatetheIDoftheviewthatcalledback.Thiscallingviewshouldbeoneofthebuttons.InthismethodwewillreadthevaluesfromboththeEditTextcontrols(theoperandvalues)andtheninvokeamethodthatisspecifictoeachoperator.TheoperatormethodwillcalculatetheresultandupdatetheEditText,whichislabeledResult.
UpdatingtheAndroidManifest.XMLSofarwehavetheUI(intermsofthelayoutfile)andwehavethebusinesslogicintermsofthecalculatoractivity.EveryAndroidappmusthaveitsconfigurationfile.ThisfileiscalledtheAndroidManifest.xml.Thisisavailableintherootdirectoryoftheproject.Listing2-6showstheAndroidManifest.xmlforthisproject.
Listing2-6.ApplicationConfigurationFile:AndroidManifest.xml
<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.calculator"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="14"/><applicationandroid:icon="@drawable/icon"
android:label="@string/app_name">
<activityandroid:name=".CalculatorMainActivity"android:theme="@android:style/Theme.Light"android:label="@string/app_name"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity>
</application></manifest>
Thepackageattributeofthismanifestfilefollowsanamingstructuresimilartothejavanamespaces.Inthecalculatorapp,thepackageissettocom.androidbook.calculator.Thisislikegivinganameandauniqueidentifiertoyourapp.OnceyousignthisappandinstallitonanapppublisherliketheGooglePlayStore,onlyyouwillbeabletoupdateitorreleasesubsequentversionsofit.Theuses-sdkdirectiveindicatestheAPIforwhichthisappisbackwardcompatible.TheapplicationnodehasanumberofpropertiesincludingitslabelandaniconthatwillshowupintheAndroiddeviceappsmenu.Insideanapplicationnodeweneedtodefinealloftheactivitiesthatmakeupthisapplication.Eachactivityisidentifiedbyitsrespectivejavaclassname.Iftheactivityclassnameisnotfullyqualified,thenthejavapackageisassumedtobethesameastheapplicationpackageidentified.Themefortheactivityindicatesasetofpropertiesthattheviewsbelongingtothatactivitywillinherit.ItislikesettingaCSSstyleontheHTMLUI.Androidcomeswithafewdefaultstyles.Choosingalightthemeisgoodforcontrastwhiletakingscreenshots(asshowninFigure2-1).Chapter7isdedicatedtousingstylesandthemesinyourapps.
IntheAndroidapplicationmanifestfileanactivitycanspecifyaseriesofintentfilters.IntentisaprogrammingconceptthatisuniquetoAndroid.Androidreliesheavilyontheseintents.Androidusesintentobjectstoinvokeapplicationcomponentsincludingactivities.Anintentobjectcancontainanexplicitactivityclassnamesothatwhenyouinvokethatintentyouendupinvokingtheactivity.Orinsteadofhavinganexplicitclassname,anintentcanindicateagenericactionlikeVIEWtoviewawebpage.WhenyouinvokesuchanintentwithagenericactionAndroidwillpresentallpossibleactivitiesthatcansatisfythataction.ActivitiesregisterwithAndroidthroughthemanifestfilethattheycanrespondtosomeactionsthroughanintentfilter.Listing2-7showshowyoucaninvokeanactivitythroughanintentobject.
Listing2-7.UsinganIntentObjecttoInvokeanActivity
//currentActivityreferstotheactivityinwhichthiscoderunsIntenti=newIntent(currentActivity,SomeTargetActivity.class);currentActivity.startActivity(i);//startthetargetactivity
AlthoughweusedcurrentActivityasthevalueofthefirstargumenttocreateanintent,allitneedsisabaseclassreferencecalledContext.Acontextreferencerepresentstheapplicationcontextinwhichacomponentlikeanactivityruns.Comingbacktotheintentobject,ithasanumberofflagsandextradataelementsthatyoucanusetocontrolthebehaviorofthetargetactivitythattheintentisinvoking.Listing2-8showsanexample.
Listing2-8.UsingExtrasonanIntentObject
//currentActivityreferstotheactivityinwhichthiscoderunsIntentintent=new
Intent(currentActivity,SomeTargetActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);intent.putExtra("some-key","some-value");currentActivity.startActivity(intent);
Inthisexample,wewantthetargetactivitytobebroughttothetopofthewindoworactivitystackandcloseanyotheractivitiesthatwereontopofitbefore.Asoneinvokesactivitiesfromotheractivitiestheysitontopofeachother.Thisstackallowsthebackbuttontonavigatebacktothepreviousactivityinthestack.Whenyougoback,thecurrenttopactivityisfinishedandthepreviousactivityisshownintheforeground.ThecodeinListing2-8islikegoingbacktothelastpositionofthetargetactivity,makingthatthetopinstance,andremoving/finishingallrecentactivitiesabovethat.Extrasonintentareasetofkeyvaluepairsthatyoucanpasstothetargetactivityfromthesourceactivity.Activitiesareprettyisolatedfromeachother.Theydon’tsharetheirlocalvariablesbetweeneachother.Instead,theyshouldpasstheirdatathroughobjectsthatcanbeserializedanddeserialized.AndroidusesaninterfacesimilartoSerializablecalledParcelablethatallowsgreaterflexibilityandefficiency.
Ultimately,everyactivityisalmostalwaysstartedbyanintentobject.YoucangettheintentobjectanywhereinyourtargetactivitybycallinggetIntent().Onceyougettheintentobject,youcangetitsextrasandseeifthereisanypertinentdatathatyouneed.
Completestudyofintentsandtheirvariationsisalargetopic.Wewillreturntotalkmoreaboutintentslaterinthischapterafterconcludingourdiscussiononthecalculatorapp.WehavealsoincludedaURLforthefreededicatedchapteronintentsfromourpreviouseditionsattheendofthischapter.
PlacingtheFilesintheAndroidProjectLet’sreturntoourmainlineofthought,thecalculatorapp.Bynowyouhavethethreefilesyouneedtocreatethecalculatorapplication.UsewhatyouhavelearnedinthefirstchaptertocreateanemptyAndroidprojectandadjustthatprojecttoplacethesethreefiles.ThesefilesaregiveninListing2-9alongwiththeirparentdirectories.
Listing2-9.PlacementofFilesfortheCalculatorApp
/res/layout/calculator_layout.xml/src/com/androidbook/calculator/CalculatorActivity.java/AndroidManifest.xml
Figure2-2showsthestructureofyourAndroidprojectineclipse.YoucanseetherelativelocationsofthefilesofListing2-9.
Figure2-2.Acalculatorappdirectorystructure
ThedirectorystructureinFigure2-1alsoshowsyouwhereotherresourceslikeimagesandstringsareplaced.Youcanalsoseethedirectorystructureforthedevice-dependentimagefilesinFigure2-2.ThisishowAndroidsolvesthelocalizationmultilingualsupportaswellbyusingdifferentresourcesubdirectorysuffixes.YouwilllearnabouttheothersubdirectoriesofanAndroidprojectasyougothroughthisbook.
TestingtheCalculatorApponaRealDeviceAllthatisleftnowistobuildtheAPKfile,signit,andbereadytodeploy.ThesimplestwaytotestyourprojectistohaveeclipsedeploytheAPKtotheemulatorandtestit.The
simplestwaytotestthisfile(onceitissigned)onadeviceistoe-mailittoyourselfandopenthee-mailonyourdevice.ThereisasecuritysettingonthedevicetoallowAPKsfromunverifiedsources.Aslongasthisisallowed,youwillbeabletoinstalltheAPKfileandrunitonyourdevice.OryoucanalsoconnectthedevicetotheUSBportandhaveeclipsedeploytheAPKdirectlytothedevice.Youcanevendebugitonthedevicethrougheclipse.YoucanalsocopytheAPKfilefromyourPCorMactothedeviceSDcardandinstallitfromthere.Thisconcludesoursectiononthecalculatorapp,whichillustratedthenatureofAndroidapps.Wewillmovetothesecondsectionofthechapternowwherewewilltalkaboutactivitiesinalotmoredepthandalsorevisitresources,intents,andsavingstate.Let’sstartwithactivities.
AndroidActivityLifeCycleAnAndroidactivityisaself-standingcomponentofanAndroidapplicationthatcanbestarted,stopped,paused,restarted,orreclaimeddependingonvariouseventsincludinguser-initiatedandsystem-initiatedones.Soitisreallyimportanttoreviewthearchitectureofthelifecycleofanactivitybylookingatallofitscallbacks.Figure2-3showsthelifecycleofanactivitybydocumentingtheorderofitscallbacksandthecircumstancesunderwhichthosecallbacksareexecuted.Let’sconsiderthesecallbackmethodsonebyone.
Figure2-3.AnnotatedAndroidactivitylifecycle
voidonCreate(BundlesavedInstanceState)Theactivity’slifecyclestartswiththismethod.Inthismethodyoushouldloadyourviewhierarchiesbyloadingthelayoutsintothecontentviewoftheactivity.Youalsoinitializeanyactivitylevelvariablesthatmaybeusedduringthelifetimeoftheactivity.Likemanyofthecallbacksyoualsocalltheparent’sonCreate()methodfirst.
WhenonCreateiscalled,anactivitymaybeinoneofthreestates.Theactivitymayhavebeenabrand-newactivitystartingoutitslifeforthefirsttime.Oritmaybeanactivitythatisautomaticallyrestartedbecauseofaconfigurationchangesuchadevicerotatingfromoneorientationtoanother.Oritisanactivitythatisrestartedfollowingapreviousprocessshutdownduetolow-memoryconditionsandbeinginthebackground.IntheonCreatecallback,youshouldtakethesescenariosintoaccountifwhatyouneedtodoineachscenarioisdifferent.
NowwecanunderstandtheargumenttothismethodinvolvingthesavedInstanceBundle.Youcanusethisbundletolookintothepreviousstateoftheactivity.Thisbundlemayhavebeenoriginallyusedtosavethestateoftheactivityduringaconfigurationchangeorwhentheactivityanditsprocessshutdownduetolow-memoryconditions.Thestatethatissavedintothisbundleargumentisusuallycalledtheinstancestateoftheactivity.Theinstancestateissomewhattemporaryinnature;specifically,itistiedtothisinstanceoftheapplicationduringthisinvocation.Thistypeofstateisnotexpectedtobewrittentopermanentstoragelikefiles.Theuserwillnotbetoodisconcertedifthisstateistoreverttoaninitialstatewhentheapplicationisrevived.InthecallbackwewillexplainsooncalledonPause()youcansavethestatethatmustbepersistedtolong-termstorage.Ifthathappens,youcanusetheonCreate()methodtoloadthatstateaswellaspartofthestart-up.
Thereisanotherconsiderationthatthismethodcantakeintoaccount.Whenanactivityisrestartedorre-createdbecauseofanorientationchange,theoldactivityisdestroyedandanewactivityiscreatedinitsplace.Thismeansthenewactivityhasanewreferenceinmemory.Theoldactivityreferenceisnolongervalid.Itwouldbewrongtohaveanexternalthreadoraglobalobjectthatisholdingontotheoldactivity.Sothereneedstobeamechanismwhentheactivityisre-createdtotelltheexternalobjectthatthereisanewactivityreference.Todothat,there-createdactivityneedstoknowthereferenceofthatexternalobject.Thisexternalobjectreferenceiscalled“non-configurationinstancereference.”ThereisacallbackmethodcalledonRetainNonConfigurationInstance()thatcanreturnareferencetothisexternalobject;weshallcoverthisshortly.AndroidSDKthenkeepsthisreferenceandmakesitavailabletothere-createdactivitythroughamethodcalledgetLastNonConfigurationInstance().NotethatinChapter8,wewillshowyouhowtodothisbetterthroughwhatarecalledheadlessretainedfragments.WewillreturntothistopicalsoinChapter15onAsyncTask.
ThereisanothernuancetotheonCreatemethod.Youmaywanttoensurethatinthe
layoutsyouhaverightviewsandfragments(whichyouwilllearninChapter8)tomatchwhenthestatewassaved.BecauseasubsequentonRestoreInstanceState()(whichiscalledafteronStart())assumesthatalltheviewandfragmenthierarchiesarepresenttorestoretheirrespectivestates,themerepresenceofthepreviousstatewillnotre-createtheviews.Soitisuptothismethodtoloadtherightlayoutstobeshown.Thisisusuallynotanissueifyoudon’tdeleteoraddviewsduringtheinteractionwiththeactivity.
voidonStart()Afterbeingcreated,thismethodpushestheactivityintoavisiblestate.Inotherwordsthismethodstartsthe“visiblelifecycle”oftheactivity.ThismethodiscalledrightafteronCreate().ThismethodassumesthattheviewhierarchiesareloadedandavailablefromtheonCreate().Younormallydon’tneedtodooverridethismethodandifyoudo,makesureyoucalltheparent’sonStart()first.InFigure2-2notethatthismethodcanalsobecalledfromanothercallbackcalledonRestart.
YoumustbeawarethattheonRestoreInstanceStatemethodiscalledafterthismethod.Soyoushouldn’tmakeassumptionsaboutthestateoftheviewsinthismethod.Sotrynottomanipulatethestateoftheviewsinthismethod.DothatrefinementinthesubsequentonRestoreInstanceStateortheonResumemethod.BecausethisisacounterpartoftheonStop(),dothereverseshouldyouhavestoppedsomethinginonStop()orinonPause().Ifyouseesomethingisbeingdoneinthismethod,lookatitwithcautionandmakesureitiswhatyouwant.Alsoknowthatthestart-and-stopcyclecanhappenmultipletimesduringtheoverallcurrentcycleoftheactivity.
Thismethodcanalsobecalledwhentheactivityisshownafterbeinghiddenfirst,becauseanotheractivityhascometothetopofthevisibilitystack.InthosecasesthismethodiscalledaftertheonRestart(),whichitselfistriggeredaftertheonStop().Sotherearetwopathsintothismethod:eitheronCreate()oronRestart().Inbothcasestheviewhierarchiesareexpectedtobeestablishedandavailablepriortothiscallback.
voidonRestoreInstanceState(BundlesavedInstanceState)Ifanactivityistobelegitimatelyclosedbyauser,thenthestatethatuseriswillingtodiscardistheinstancestate.Forexample,whentheuserchoosesabackbutton,thenhe/sheisinformingAndroidthathe/shenolongerisinterestedinthisactivityandthattheactivitycanbeclosed,discardingallitsstatethatisnotyetsaved.Sothisstate,whichistransitoryandonlyvalidforthelifeoftheactivitywhileitisinmemory,istheinstancestate.
Ifthesystemchosestoclosetheactivity,becausethereisachangeinorientation,thentheuserwillexpectthattransitory(instance)staterightbackwhentheactivityisrestarted.Tofacilitatethis,AndroidcallsthisonRestoreInstanceStatemethodwithabundlethatcontainsthesavedinstancestate.(SeetheonSavedInstanceStatemethod
explanation.)
Incontrasttoinstancestate,thepersistentstateofanactivityissomethingtheuserexpectstoseeevenaftertheactivityfinishesandisnolongerinplay.Thispersistencestatemayhavebeencreatedduringtheactivityormayevenexistbeforetheactivityiscreated.Thistypeofstate,especiallywhenitiscreatedwiththehelpoftheactivity,mustbeexplicitlysavedtoanexternalpersistentstorelikeafile.Iftheactivitydoesn’tuseanexplicit“save”buttonforsuchneeds,thenthe“onPause”methodneedstobeusedtosavesuchimplicitpersistentstates.ThisisbecausenomethodafteronPauseisguaranteedtobecalledincaseoflow-memoryconditions.Youshouldn’trelyontheinstancestateiftheinformationistooimportanttolose.
voidonResume()ThecallbackmethodonResumeistheprecursortohavingtheactivityfullyvisible.Thisisalsothestartoftheforegroundcyclefortheactivity.InthisforegroundcycletheactivitycanmovebetweenonResume()andonPause()multipletimesasotheractivities,notifications,ordialogswithmoreurgencycomeontopandgo.
Bythetimethismethodiscalled,wecanexpecttheviewsandtheirstatefullyrestored.Youcantakethisopportunitytotweakfinalstatechanges.Asthismethoddoesn’thaveabundle,youneedtorelyontheinformationfromonCreateoronRestoreInstanceStatemethodstofine-tunestateiffurtherneeded.
IfyouhadstoppedanycountersoranimationsduringonPauseyoucanrestartthemhere.Youcanalsokeeptrackofthecaseiftheviewsarereallydestroyedornotbyfollowingthepreviouscallbackmethods(whetheronResumeisaresultofonCreate,onRestart,oronPause)anddotheminimumpossibletoadjusttheviewstate.Typicallyyouwillnotdostatemanagementherebutonlythosetasksthatneedtobeturnedonoroffbasedonvisibility.
voidonPause()Thiscallbackindicatesthattheactivityisabouttogointobackground.Youshouldstopanycountersoranimationsthatwererunningwhentheactivitywasfullyvisible.TheactivitymaygotoonResumeorproceedtoonStop.GoingtoonResumewillbringtheactivitytotheforeground.GoingtoonStopwilltaketheactivityintoabackgroundstate.
AspertheSDK,itisalsothelastmethodthatisguaranteedtobecalledbeforetheactivityandtheprocessiscompletelyreclaimed.Soitisthelastopportunitythatadeveloperhastosaveanynon-instanceandpersistentdatatoafile.
AndroidSDKalsowaitsforthismethodtoreturnbeforemakingtheforegroundactivityfullyactive.Soyouwanttobebriefinthismethod.Alsonoticethatthismethodhasnobundlethatispassed.Thisisanindicationthatthismethodisforstoringpersistentdataandalsoinanexternalstoragemediumsuchasafileoranetwork.
Youcanalsousethismethodtostopanycounters,animations,orstatusdisplaysofabackgroundtask.YoucanresumetheminonResume.
voidonStop()ThecallbackmethodonStop()movestheactivityfrompartiallyvisibletothebackgroundstatewhilekeepingalloftheviewhierarchiesintact.ThisisthecounterpartofonStart.TheactivitycanbetakenbacktothevisiblecyclebycallingonStart.ThisstatetransitionofgoingfromonStoptoonStartduringthesameactivitylifecyclegoesthroughtheonRestart()method.
Afterthiscall,theactivityisnolongervisible.ButkeepinmindthatthismaynotbecalledafteronPauseunderlow-memoryconditions.Becauseofthisuncertaintydonotusethismethodtostartorstopservicesthatareoutsideofthisprocess.DothatinonPauseinsteadandresumetheminonResume.However,youcanusethismethodtocontrolservicesorworkthatisinsideyourprocess.Thisisbecause,aslongastheprocessisactive,thismethodisgoingtogetcalled.Ifthewholeprocessistakendownthenthosedependenttasksorglobalvariableswillgoawayanyway.
voidonSaveInstanceState(BundlesaveStateBundle)ThecontrolgoestoonDestroy()comingoutofonStopiftheprocessisstillinmemory.However,ifAndroidrealizesthatactivityisbeingclosedwithouttheuser’sexpectationthenitwouldcalltheonSaveInstanceState()beforecallingonDestroy().Orientationchangeisaveryconcreteexampleofthis.TheSDKwarnsthatthetimingofonSaveInstanceState()isnotpredictablewhetherbeforeorafteronStop().
Thedefaultimplementationofthismethodalreadysavesthestateofviews.HoweverifthereissomeexplicitstatethatisnotknowntotheviewsyouneedtosaveitinthebundleobjectandretrieveitbackintheonRestoreInstanceStatemethod.Youdoneedtocalltheparent’sonSaveInstanceState()methodfirstsothatviewshaveanopportunitytosavetheirstatethemselves.Therearesomerestrictionsandrulesfortheviewstobeabletosavetheirstate.ThechaptersonUIcontrols(Chapters3,4,and5)andconfigurationchange(Chapter9)gointomoredetailonthissubject.
voidonRestart()Thismethodiscalledwhentheactivitytransitionsfrombackgroundstatetopartiallyvisiblestate,i.e.,goingfromonStoptoonStart.YoucanusethisknowledgeinonStartifyouwanttooptimizecodetherebasedonwhetheritisafreshstartorarestart.Whenitisarestarttheviewandtheirstatearefairlyintact.
YoucandothingsinthismethodthatwouldhavebeendoneinonStart,butoptimizedwhentheactivityisnotvisible,buttooexpensivetobedonemultipletimesinonResume.
ObjectonRetainNonConfigurationInstance()Thiscallbackmethodisinplacetodealwithactivityre-creationduetoconfigurationchanges.Thismethodreturnsanobjectreferenceinyourprocessmemorythatneedstoberetiedtotheactivityonceitisre-created.WeexplainedthisinmoredetailpreviouslywhenwedescribedtheonCreatemethod.
Whentheactivityisre-created,theobjectthatisreturnedfromthismethodismadeavailablethroughthemethodgetLastNonConfigurationInstance().NowinonCreate()thenewactivitycanusethepreviouslyestablishedresourcesandobjectreferences.Importantly,ifthosepreviousresourcesareholdingtotheoldactivityreference,thentheresourcescanbetoldtousethenewone.
ThisdilemmaexistsbecauseduringanorientationchangeAndroiddoesn’tkilltheprocess,butjustdiscardstheoldactivity,re-createstheactivityintheneworientation,andexpectstheprogrammertosupplynewlayouts,etc.,tosuitthenewconfiguration.Sotheworkingobjectsarestillthereholdingontoanoldactivity.Thisisthemethodinassociationwithits“get”counterparttoovercomethisobstacle.
WhenyoureadChapter8,youwilllearnthatthismethodisdeprecatedandyouwilluseinitsplacewhatarecalledheadlessretainedfragments.Theseheadlessretainedfragmentshavetheadditionalbenefitofbeingabletotracktheactivitylifecycleandnotjustthereferencetotheactivity.
voidonDestroy()onDestroy()isthecounterpartofonCreate().TheactivityisgoingtofinishafteronDestroy.Anactivitycanfinishfortwoprimaryreasons.
Oneisanexplicitclose.Thiscanhappenwhentheuserhasexplicitlycausedtheactivitytofinisheitherbyclickingabuttonthatisprovidedtoindicatethattheuserisdoneorbyusingabackbuttonleavingtheactivitytogotothepreviousactivity.Undersuchcircumstancestheactivitywillnotbebroughtbackbythesystemunlesstheuserchoosestheactivityagain.InthisscenariotheactivitylifecycleendswiththeonDestroymethod.
Thesecondreasonanactivitycancloseisinvoluntary.Whentheorientationofadevicechanges,theAndroidSDKwillforcefullyclosetheactivityandcalltheonDestroymethodfollowedbyre-creatingtheactivityandcallingtheonCreateagain.
Whenanactivityisinthebackgroundandifthesystemneedsmemory,AndroidmayshutdowntheprocessandmaynothaveanopportunitytocalltheonDestroymethod.Duetothisuncertainty,muchlikeonStop,don’tusethismethodtocontroltasksorservicesthatareoutsidetheprocessinwhichtheactivityhadbeenrunning.However,iftheprocessisstillinmemorytheonDestroywillbecalledaspartofthelifecycleandyoucanplacecleanupcodeinonDestroyaslongasthatcodebelongstothisprocess.
GeneralNotesonActivityCallbacks
UseFigure2-3toguideyoutoseetheorderofthesecallbacksandhowbesttousethem.Ifyouweretooverrideacallbackyouneedtocallbacktheparentmethod.TheSDKdocumentationexplicitlyindicateswhichderivedmethodsarerequiredtocallbacktheirparentequivalents.AlsorefertotheSDKdocumentationtolearnduringwhichcallbacksthesystemwillnotkilltheprocessduetolow-memoryconditions.Alsonoticethatonlyahandfulofcallbackscarryinstancestatebundle.
MoreonResourcesWewanttotellyoulittlemoreabouthowresourcesareusedinAndroidapplications.Inthecalculatorlayoutfile,youhaveseensomeoftheresourcesusedlikestrings,images,IDs,etc.
Otherresourcesthatarenotsoobviousincludedimensions,drawables,stringarrays,languagetermsforplurals,xmlfiles,andalltypesofinputfiles.InAndroid,somethingistreatedasaresourcea)ifitisaninputtoyourprogramandispartoftheapkfileandb)ifthevalueorcontentoftheinputcanhavedifferentvaluesbasedonlanguage,locale,ororientationofthedevice,generallycalledaconfigurationchange.
DirectoryStructureofResourcesAllresourcesinAndroidareplacedunderthe/ressubdirectoryoftherootofyourapplicationpackage.Listing2-10showsanexampleofwhata/resmaylooklike:
Listing2-10.AndroidResourceandAssetsDirectoryStructure
/res/values/strings.xml/colors.xml/dimens.xml/attrs.xml/styles.xml/drawable/*.png/*.jpg/*.gif/*.9.png/*.xml/anim/*.xml`/layout/*.xml/raw/*.*/xml/*.xml/assets/*.*/*.*
Wewillcoverattrs.xmlandstyles.xmlinChapter7.Thexmlfilesintheanimsubdirectorydefineanimationsthatcanbeappliedtovariousviews.Wewillcovertheseanimation-relatedresourcesintheanimationschapter(Chapter18).ThexmlfilesinthexmlsubdirectorygetcompiledawaytobinaryandtheirresourceIDscanbeusedtoread
them.Wewillshowanexampleofthisshortly.The/rawsubdirectoryholdsfilesthatgetplaced,astheyare,withoutgettingconvertedtoanybinaryformat.
The/assetsdirectory,whichisasiblingof/res,isnotpartoftheresourcehierarchy.Thismeansthatthefilesinthissubdirectorydonotchangebasedonlanguageoralocale.AndroiddoesnotgenerateanyIDsforthesefiles.Thisdirectoryismorelikeastaticlocalstorageforanyfilesthatareusedasinputs,suchasconfigurationfilesforyourapplication.
Exceptfortheassetsdirectory,everyotherartifactinthe/ressubdirectorywillendupgeneratinganIDinR.*namespace,asyouhaveseenbefore.EachdistinctresourcetypewillhaveitsownnamespaceunderR.*,asinR.id,R.string,orR.drawable,etc.
ReadingResourcesfromJavaCodeInlayoutfiles,asyouhaveseentheincalculatorlayout,oneresourcecanrefertootherresources.Forexample,thecalculatorlayoutresourcefilereferencedthestringandcolorreferences.Thisapproachiscommon.Alternatively,youcanalsouseJavaAPItoretrievetheresourcevaluesusingthemethodActivity.getResources().ThismethodreturnsareferencetotheAndroidSDKjavaclassresources.YoucanusemethodsonthisclasstogettothevaluesofeachresourceidentifiedinyourlocalR.*namespace.Listing2-11showsanillustrationofthisapproach:
Listing2-11.ReadingResourceValuesinJavaCode
Resourcesres=activity.getResources();//Retrievingacolorresourceintsomecolor=res.getColor(R.color.main_back_ground_color);//UsingadrawableresourceColorDrawableredDrawable=(ColorDrawable)res.getDrawable(R.drawable.red_rectangle);
RuntimeBehaviorofDrawableResourcesThedrawabledirectoryisaninterestingcaseworthcoveringtodemonstratethefluencyofAndroid’sarchitecture.Asshownearlier,thisdirectorycancontainimagesthatcanbesetasbackgrounds.ThisdirectoryalsoallowsXMLfilesthatknowhowtogetconvertedtodrawablejavaobjectswhichcanthenbeusedasbackgroundsthatarerenderedatruntime.Listing2-12showsanexampleofthis:
Listing2-12.ExampleofaShapeDrawableXMLResourceFile
<?xmlversion="1.0"encoding="utf-8"?><shapexmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle">
<solidandroid:color="#f0600000"/><strokeandroid:width="3dp"android:color="#ffff8080"/><cornersandroid:radius="13dp"/><paddingandroid:left="10dp"android:top="10dp"android:right="10dp"android:bottom="10dp"/></shape>
Ifyouplaceafilelikethisinthedrawablesubdirectoryandcallitbackground1.xml,itwillresultinanIDcalledR.drawable.background1.YoucanthenusethatIDasifitwereabackgroundimageforanyviewthatisdrawnwitharectangularborder.Otherpossibleshapesareovals,lines,andrings.
Similartotheshapexmlfile,eachallowedXMLfileinthedrawabledirectorydefinesadrawablethatdefinesaparticularwaytodraw.Examplesofthesedrawablesincludebitmapsthatcanbedecoratedwithcertainbehavior,orimagesthatcantransitionfromoneimagetoanother,layereddrawablesthatarecollectionsofotherdrawables,drawablesthatcanbeselectedbasedoninputparameters,drawablesthatcanrespondtoprogressbyshowingmultipleimages,drawablesthatcanclipotherdrawables,etc…SeethefollowingURLforanumberofsophisticatedthingsyoucandousingtheseruntimedrawableobjects:
http://androidbook.com/item/4236
UsingArbitraryXMLFilesasResourcesAndroidalsoallowsarbitraryXMLfilestobeusedasresourceswhichcanthenbelocalizedortunedforeachdevice.Listing2-13isanexampleofreadingandprocessinganXML-basedresourcefilefromthe/res/xmlsubdirectory.
Listing2-13.ReadinganXMLResourceFile
privateStringreadAnXMLFile(Activityactivity)throwsXmlPullParserException,IOException{StringBuffersb=newStringBuffer();Resourcesres=activity.getResources();XmlResourceParserxpp=res.getXml(R.xml.test);
xpp.next();inteventType=xpp.getEventType();while(eventType!=XmlPullParser.END_DOCUMENT){if(eventType==XmlPullParser.START_DOCUMENT){sb.append("******Startdocument");}elseif(eventType==XmlPullParser.START_TAG){sb.append("\nStarttag"+xpp.getName());}elseif(eventType==XmlPullParser.END_TAG){sb.append("\nEndtag"+xpp.getName());
}elseif(eventType==XmlPullParser.TEXT){sb.append("\nText"+xpp.getText());}eventType=xpp.next();}//eof-whilesb.append("\n******Enddocument");returnsb.toString();}//eof-function
WorkingwithRawResourceFilesAndroidalsoallowsanytypeofnon-compiledfilesasresources.Listing2-14isanexampleofreadingafilethatisplacedinthe/res/rawsubdirectory.Beingaresourceeventherawfilesthatareinthisdirectorycanbecustomizedforlanguageoradeviceconfiguration.AndroidgeneratesIDsautomaticallyforthesefilesaswell,astheyareresourceslikeanyotherresource.
Listing2-14.ReadingaRawResourceFile
StringgetStringFromRawFile(Activityactivity)throwsIOException{Resourcesr=activity.getResources();InputStreamis=r.openRawResource(R.raw.test);//assumingyouhaveafunctiontoconvertastreamtoastringStringmyText=convertStreamToString(is);is.close();//takecareofexceptionsetc.returnmyText;}
ReadingFilesfromtheAssetsDirectoryAlthoughusuallyclubbedwithresources,the/assetsdirectoryisabitdifferent.Thisdirectorydoesnotsitunderthe/respath,sothefilesinthisdirectorydonotbehavelikeresourcefiles.AndroiddoesnotgenerateresourceIDsforthesefilesintheR.*namespace.Thesefilesarenotcustomizablebasedonlocaleordeviceconfiguration.Listing2-15showsanexampleofreadingafilethatisplacedinthe/assetssubdirectory.
Listing2-15.ReadingaFilefromAssetsDirectory
StringgetStringFromAssetFile(Activityactivity){AssetManageram=activity.getAssets();InputStreamis=am.open("test.txt");Strings=convertStreamToString(is);is.close();
returns;}
Thusfar,wehaveusedanactivityreferencetogetholdoftheresourcesoranAssetManagerobjectasinListing2-15.Inreality,allweneedisthebaseclassoftheactivity,thecontextobject.
ReadingResourcesandAssetsWithoutanActivityReferenceSometimesyoumayneedtoreadanXMLresourcefileoranassetfilefromthebowelsofsourcecode,whereitisintrusivetopasstheactivityreference.Forthesecases,youcanusethefollowingapproachtoobtaintheapplicationcontextandthenusethatreferenceinsteadtogettotheassetsandresources.
WhenAndroidloadsyourapplication(inordertoinvokeanyofitscomponents),itinstantiatesandcallsanapplicationobjecttoinformthattheapplicationcouldinitializeitself.ThisapplicationclassnameisspecifiedintheAndroidmanifestfile.IfMyApplication.javaisyourapplicationjavaclass,thenitcanbespecifiedintheAndroidmanifestfileasshowninListing2-16.
Listing2-16.SpecifyinganApplicationClassintheManifestFile
<applicationandroid:name=".MyApplication"android:icon="@drawable/icon".../>
Listing2-17showshowwecancodetheMyApplicationandalsoshowshowwecancapturetheapplicationcontextinaglobalvariable.
Listing2-17.SampleCodeforanApplicationthatCapturesApplicationContext
publicclassMyApplicationextendsApplication{//MakesuretocheckfornullforthisvariablepublicstaticvolatileContexts_appContext=null;
@OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){super.onConfigurationChanged(newConfig);}@OverridepublicvoidonCreate(){super.onCreate();MyApplication.s_appContext=this.getApplicationContext();}@OverridepublicvoidonLowMemory(){
super.onLowMemory();}@OverridepublicvoidonTerminate(){super.onTerminate();}}
Withtheapplicationcontextcapturedinaglobalvariable,wenowcangettotheassetmanagertoreadourassetsasinListing2-18.
Listing2-18.UsingApplicationObjecttoGettoApplicationAssetFiles
AssetManageram=MyApplication.s_appContext.getAssets();InputStreamis=am.open(filename);
UnderstandingResourceDirectories,Language,andLocaleLet’swrapuptheideaofAndroidresourcesbypointingouthowresourcedirectoriesareusedtoloadresourcesbasedonlanguage,locale,oraconfigurationchangeofthedevicelikesayorientation.SeehowinListing2-19alayoutfilewiththesamenameislocatedinmultiplelayoutdirectoriesstartingwithsameprefixoflayoutbutwithdifferentqualifierssuchas“port”forportraitand“land”forlandscape.TherearealargenumberofthesequalifiersavailableintheSDKdocumentation.WealsocoversomeoftheseaspectsinChapter9(ConfigurationChanges).Listing2-19showsanexampleofhowlayoutfilesarearrangedbyportraitorlandscapeconfiguration:
Listing2-19.DemonstratingResourceQualifiers
\res\layout\main_layout.xml\res\layout-port\main_layout.xml\res\layout-land\main_layout.xml
MoreonIntentsWehavetalkedabouthowintentsareusedtoinvokeactivities.Wewantcoverfewmoreessentialaspectsofintentsnow.Listing2-20showshowintentsareusedtoinvokeanumberofprebuiltGoogleapplications.
Listing2-20.SampleCodeUsingIntents
publicclassIntentsUtils{publicstaticvoidinvokeWebBrowser(Activityactivity){Intentintent=newIntent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.google.com"));
activity.startActivity(intent);}publicstaticvoidinvokeWebSearch(Activityactivity){Intentintent=newIntent(Intent.ACTION_WEB_SEARCH);intent.setData(Uri.parse("http://www.google.com"));activity.startActivity(intent);}publicstaticvoiddial(Activityactivity){Intentintent=newIntent(Intent.ACTION_DIAL);activity.startActivity(intent);}publicstaticvoidcall(Activityactivity){Intentintent=newIntent(Intent.ACTION_CALL);intent.setData(Uri.parse("tel:555–555–5555"));activity.startActivity(intent);}publicstaticvoidshowMapAtLatLong(Activityactivity){Intentintent=newIntent(Intent.ACTION_VIEW);//geo:lat,long?z=zoomlevel&q=question-stringintent.setData(Uri.parse("geo:0,0?z=4&q=business+near+city"));activity.startActivity(intent);}}
Noticehowtheseintentsdonotinvokeaspecificactivitybyitsclassnamebutratherusethetargetqualitiesofsuitableactivities.Forexampletoinvokeabrowsertoviewawebpage,theintentsimplysaystheactionisACTION_VIEWandthedataportionoftheintentissettothewebaddress.Androidthenlooksaroundtoseealltheactivitiesthatknowhowtoshowthetypeofdatarequestedinthedataattribute.ItwillthengivetheuseranoptionwhichoftheactivitiesthattheuserwantstochoosetoopentheURL.Thesetypesofintentsthatdon’tspecifytheclassnameofthecomponenttoinvokearecalledimplicitintents.Wewillcoverthisinalittlebitmoredetailshortly.
StartingActivitiesforResultsListing2-21showsanexampleofanactivitywhereoneofitsmethodsisinvokingatargetactivityinordertoobtainaresultwhenthattargetactivityiscompleted.ThisisdonethroughthemethodinvokePick()inasshowninListing2-21.
Listing2-21.UsingIntentstoGetResultsfromActivities
publicclassSomeActivityextendsActivity{.....//Callthismethodtostartatargetactivitythatknowshow
topickanote//UseadataURIthattellsthetargetactivitywhichlistofnotestoshowpublicstaticvoidinvokePick(Activityactivity){
IntentpickIntent=newIntent(Intent.ACTION_PICK);intrequestCode=1;pickIntent.setData(Uri.parse("content://com.google.provider.NotePad/notes"));activity.startActivityForResult(pickIntent,requestCode);}
//thefollowingmethodwillbecalledwhenthetargetactivityfinishes//NoticetheoutputIntentobjectthatispassedbackwhichcould//containadditionalinformation
@OverrideprotectedvoidonActivityResult(intrequestCode,intresultCode,IntentoutputIntent){
super.onActivityResult(requestCode,resultCode,outputIntent);parseResult(this,requestCode,resultCode,outputIntent);}publicstaticvoidparseResult(Activityactivity
,intrequestCode,intresultCode,IntentoutputIntent){if(requestCode!=1){Log.d("Test","Someoneelsecalledthis.notus");return;}if(resultCode!=Activity.RESULT_OK){Log.d("Test","Resultcodeisnotok:"+resultCode);return;}Log.d("Test","Resultcodeisok:"+resultCode);UriselectedUri=outputIntent.getData();Log.d("Test","Theoutputuri:"+selectedUri.toString());
//ProceedtodisplaythenoteoutputIntent.setAction(Intent.ACTION_VIEW);startActivity(outputIntent);}
TheconstantsRESULT_OK,RESULT_CANCELED,andRESULT_FIRST_USERarealldefinedintheactivityclass.TheconstantRESULT_FIRST_USERisusedasa
startingnumberforuser-definedactivityresults.ThenumericalvaluesoftheseconstantsareshowninListing2-22:
Listing2-22.ResultValuesfromReturnedActivities
RESULT_OK=-1;RESULT_CANCELED=0;RESULT_FIRST_USER=1;
TomakethePICKfunctionalitywork,theimplementingorthetargetactivitythatisrespondingshouldhavecodethatexplicitlyaddressestheneedsofanACTION_PICK.Let’slookatanexampleofhowthisisdoneintheGooglesampleNotePadapplication.(Seethereferencessection,whereyoucanfindthisapplication.)Whentheitemisselectedinthelistofitems,theintentthatinvokedthetargetactivityischeckedtoseewhetherit’sanACTION_PICKintent.Ifitis,thedataURIoftheselectednoteitemissetinanewintentandreturnedthroughsetResult()asshowninListing2-23.Thecallingactivitythencaninvestigatethereturnedintenttoseewhatdataithasinit.SeethemethodparseResult()inListing2-21.
Listing2-23.TargetActivityReturningaResultthroughaDataURI
@OverrideprotectedvoidonListItemClick(ListViewl,Viewv,intposition,longid){Uriuri=ContentUris.withAppendedId(getIntent().getData(),id);
Stringaction=getIntent().getAction();if(Intent.ACTION_PICK.equals(action)||Intent.ACTION_GET_CONTENT.equals(action)){//Thecalleriswaitingforustoreturnanoteselectedby//theuser.Theyhaveclickedonone,soreturnitnow.setResult(RESULT_OK,newIntent().setData(uri));finish();}...otherwaysofhowthisactivitymayhavebeeninvoked}
ExercisingtheGET_CONTENTActionACTION_GET_CONTENTissimilartoACTION_PICK.InthecaseofACTION_PICK,youarespecifyingadataURIthatpointstoacollectionofitems,likealistofnotesfromaNotePad-likeapplication.Youwillexpecttheintentactiontopickoneofthenotesandreturnittothecaller.InthecaseofACTION_GET_CONTENT,youindicatetoAndroidthatyouneedanitemofaparticularMIMEtype.Androidsearchesforactivitiesthatcaneithercreateoneofthoseitemsorchoosefromanexistingsetofitemsthatsatisfythat
MIMEtype.
UsingACTION_GET_CONTENT,youcanpickanotefromacollectionofnotessupportedbytheNotePadapplicationusingthecodeshowninListing2-24:
Listing2-24.InvokingActivitiesforCreatingContent
publicstaticvoidinvokeGetContent(Activityactivity){IntentpickIntent=newIntent(Intent.ACTION_GET_CONTENT);intrequestCode=2;pickIntent.setType("vnd.android.cursor.item/vnd.google.note");activity.startActivityForResult(pickIntent,requestCode);}
NoticehowtheintenttypeissettotheMIMEtypeofasinglenote.ContrastthiswiththeACTION_PICKcode,whereitexplicitlyindicatedaURLthatpointstoacollectionofnotes(likeawebURLthatcanretrieveapageworthofdata).
ForanactivitytorespondtoACTION_GET_CONTENT,theactivityhastoregisteranintentfilterindicatingthattheactivitycanprovideanitemofthatMIMEtype.Listing2-25showshowtheSDK’sNotePadapplicationaccomplishesthis:
Listing2-25.ActivityFilterforGetContent
<activityandroid:name="NotesList"android:label="@string/title_notes_list">......<intent-filter><actionandroid:name="android.intent.action.GET_CONTENT"/><categoryandroid:name="android.intent.category.DEFAULT"/><dataandroid:mimeType="vnd.android.cursor.item/vnd.google.note"/></intent-filter>......</activity>
TherestofthecodeforrespondingtoonActivityResult()isidenticaltothepreviousACTION_PICKexample.IftherearemultipleactivitiesthatcanreturnthesameMIMEtype,Androidwillshowyouthechooserdialogtoletyoupickanactivity.
RelatingIntentsandActivitiesAnintentisusedtostartnotonlyactivitiesbutalsoothercomponentslikeaserviceorabroadcastreceiver.Thesecomponentsarecoveredinlaterchapters.Youcanseethese
componentsashavingcertainattributes.Oneattributeofacomponentmaybethecategorytowhichthiscomponentbelongs.Anotherattributemaybewhattypeofdatathiscomponentcanview,edit,update,ordelete.Anotherattributemaybewhattypeofactionsacomponentcanrespondto.Ifyouweretolookuponthesecomponentsasentitiesinadatabase,theirattributescanbeseenascolumns.Thenanintentcanbeseenasawhereclausethatspecifiesallorsomeofthosecharacteristicstochooseacomponentlikeanactivitytostart.Listing2-26isanexampleofdemonstratinghowtoqueryforallactivitiesthatarecategorizedasCATEGORY_LAUNCHER.
Listing2-26.QueryingActivitiesthatMatchanIntent
IntentmainIntent=newIntent(Intent.ACTION_MAIN,null);mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);PackageManagerpm=getPackageManager();List<ResolveInfo>list=pm.queryIntentActivities(mainIntent,0);
PackageManagerisakeyclassthatallowsyoutodiscoveractivitiesthatmatchcertainintentswithoutinvokingthem.Youcancyclethroughthereceivedactivitiesandinvokethemasyouseefit,basedontheResolveInfoAPI.Listing2-27isanextensiontotheprecedingcodethatwalksthroughthelistofactivitiesandinvokesoneoftheactivitiesifitmatchesaname.Inthecode,wehaveusedanarbitrarynametotestit:
Listing2-27.WalkingThroughaMatchedActivityListforanIntent
for(ResolveInfori:list){//ri.activityInfo.Log.d("test",ri.toString());Stringpackagename=ri.activityInfo.packageName;Stringclassname=ri.activityInfo.name;Log.d("test",packagename+":"+classname);if(classname.equals("com.ai.androidbook.resources.TestActivity")){Intentni=newIntent();ni.setClassName(packagename,classname);activity.startActivity(ni);}}
UnderstandingExplicitandImplicitIntentsWhenyouspecifyanexplicitactivityname(oracomponentnamelikeaserviceorabroadcastreceiver)inanintent,suchanintentiscalledanexplicitintent.Whenthisintentisusedtostartanactivity,thatactivityisinvokedirrespectiveofwhatelseisthereinthatintentsuchasitscategoryordata.
Asyouhaveseen,anintentdoesnothavetohaveanactivityspecifiedexplicitlytoinvoke
it.Anintentcanrelyonanactivity’sactionattribute,categoryattribute,ordataattribute.Theseintentsthatomittheexplicitactivityorcomponentclassarecalledimplicitintents.WhenyouuseanimplicitintenttoinvokeanactivityitisparamountthattheactivitymusthaveasoneofitscategoriesCATEGORY_DEFAULT.Ifyouexpectyouractivitytobeexplicitlystartedbyanintent,thenyoudon’tneedtospecifyanycategoryatalltothatactivity.Listing2-28showsanexampleofminimallyregisteringanactivityinanAndroidmanifestfilesothatitcanbeinvokedbyanexplicitintent.
Listing2-28.MinimalActivityDefinition
<activityandroid:name="com.androidbook.asynctask.TestProgressBarDriverActivity"android:label="TestProgressbars"/>
Ifyouwanttoinvokethisactivitythroughanimplicitintentwithoutspecifyingitsclassname,likethroughanactionsay,thenyouneedtoaddthefollowingintentfilters,onefortheactionandonefortheneededmandatorycategoryofdefault,asshowninListing2-29.
Listing2-29.AnActivityDefinitionwithFilters
<activityandroid:name="com.androidbook.asynctask.TestProgressBarDriverActivity"android:label="TestProgressbars"><intent-filter><actionandroid:name="com.androidbook.intent.action.ME"/><categoryandroid:name="android.intent.category.DEFAULT"/></intent-filter></activity>
SavingStateinAndroidAsyoureviewedthecalculatorapp,yournextlikelyneedishowtostorethedataofanAndroidapp.Let’sbrieflycovertheavailableoptions.TherearefivewaystostoredatainAndroid:1)sharedpreferences,2)internalfiles,3)externalfiles,4)SQLlite,and5)networkstorageinthecloud.
SharedpreferencesAPIisasophisticatedAPIintheAndroidSDKtosave,display,andmanipulatepreferencesforyourapplications.Althoughthisfeatureisintendedandtailoredforpreferencesitcanbeusedforsavingarbitrarystateofyourapplication.Sharedpreferencesareinternaltotheapplicationanddevice.Androiddoesnotmakethisdataavailabletootherapplications.AuserisnotexpectedtodirectlymanipulatethisdatabymountingontoaUSBport.Thisdataisremovedautomaticallywhentheapplicationisremoved.ThesesharedpreferencesarecoveredindetailinChapter11.
Whilesharedpreferencesdataisstructuredkey/valuepairdataandfollowsafewothersemanticsimposed,internalfilesarestand-alonefilesthatyoucanwritetowithouta
predefinedstructure.Wehaven’tfoundacompellingadvantageofusinginternalfilesoversharedpreferences,ortheotherway,especiallyforsmall-tomedium-sizedstate.Soformostappsyoucanchooseoneortheother.
Unlikeinternalfiles,whicharestoredontheinternalstorageofthedevice,externalfilesarestoredontheSDcard.Thesebecomepublicfilesthatotherappsincludingtheusercouldseeoutsidethecontextofyourapplication.Theexternalfilescanbeusedtostoredatathatmakessenseevenoutsideofyourappsuchasimagefilesorvideofiles.Forstrictlytheinternalstateoftheapp,internalfilesareabetteroption.
Theexternalfilesmayalsobeanoptionifthestateisverylargerunningintotensofmegabytes.Usuallywhenthathappensyoudon’twanttosavethestateasamonolithicfileanywayandoptformoregranularstorageasarelationaldatabaseliketheSQLlite.
WewillgiveaquickoverviewandbriefcodesamplesinChapter25onhowtousepreferences,internalfiles,andexternalfilestostoreyourappstate.OneofthetricksistopersistjavaobjecttreedirectlyusingJSONandGSOnwhilegivingconsiderationtoseeifthislevelofgranularityisappropriate.IfyouarenotfamiliarwithJSON,itisanobjecttransportandstorageformatforJavaScript-basedobjects.Itisalsogenerallyapplicableanyobjectstructureaswellincludingjavaobjectsandoftenusedthatwaylately.TheGSONisaGooglelibrarythatconvertsJavaobjectstoandfromJSONstrings.
SQLliteisareallygoodoptionthatisrecommendedtostorethestateofanapp.Theshortdrawbackisyourlogictosaveandreaddatabecomeverboseandcumbersome.YoucanprobablyuseO/Rmappinglibrariestoovercomethismismatchbetweenjavaobjectsanditsrelationalrepresentation.SQLliteisalsooftenusedtostoredatathatneedstobesharedbymultipleapplicationsthroughaconceptcalledcontentproviders.ThisisthecentraltopicofChapter25.
Finally,cloud-basednetworkstorageiscomingintoitsown.ForexampleanumberofMBAAS(MobileBackendasaService)platformssuchasparse.comsupportstoringthemobiledatadirectlyinthecloudforbothonlineandofflineusage.Thismodelisgoingtobeincreasinglyrelevantasyoustartmakingyourappavailableonmultipledevicesforthesameuserorbeingabletocollaboratewithotherusers.ThistopiciscoveredingreatdetailinourcompanionbookExpertAndroidfromApress.
ManytimeforyourappstheGSONoptiontostoreappstateinaninternalfileisreallythequickestandmostpracticalwaytogo.Ofcourseyoudowanttoanalyzethegranularityofthesolutionandseeifthissimplerapproachwon’tbecomeaburdenoncomputingpowerorbatterylife.IfyourappgainslotofpopularityyoumaywanttouseasecondreleasewithSQLlitebyoptimizingstoragespeedorusecloudstorageifthatismoreappropriateforthatrelease.
RoadmapforLearningAndroidandtheRestoftheBookLet’squicklyreviewwhatwehavecoveredsofar.Intheone-pagerapplicationyouhaveseenhowtheUIisputtogether,howthebusinesslogiciscodedinJava,andthenhowthe
applicationisdefinedtotheAndroidsdkusingtheAndroidmanifestfile.Weexplainedwhatresourcesare,howtheyreferenceeachother,howtheyarereferencedinlayoutfiles,andevenhowtoreadyourinputfilesasresources.Wehaveshownyouwhatintentsare,theirintricacies,andhowtousethemtoinvokeordiscoveractivities.Wehavecoveredtheactivity’slifecycle,whichisreallyimportanttounderstandAndroidarchitecture.Wehavealsogivenaquickrundownofhowyoucansavethestateofyourapplication.Thisisaprettygoodfoundationtoplanandwritesimpleapplications.
Wenowwanttofollowupthisbird’seyeviewofAndroidapplicationswitharoadmapofbecominganexpertappdeveloperontheAndroidplatform.Thisroadmapdividesthechaptersofthisbookintothefollowingsixkeylearningtracks:
Track1:UIessentialsforyourAndroidapplications
Track2:Savingstate
Track3:Preparing/takingyourapplicationtoGooglePlay
Track4:Makingyourapplicationrobust
Track5:Bringingfinessetoyourapps
Track6:Integratingwithotherdevicesandthecloud
Amongthesesixtracks,thefirstthreearethebasictracksthatyoumustknowwelltowriteAndroidappsthatareusefultoyouandthelargercommunity.Tracks4,5,and6aretheretomakeyourappsbetterandfeaturerichinsubsequentreleases.Wewilltalkaboutwhatchaptersmakeupeachtrackandwhatyouareexpectedtogainfromthattrack.
Track1:UIEssentialsforYourAndroidApplicationsAndroidhasanumberofUIcontrolsandlayoutsoutoftheboxtowriteveryfeaturerich-applications.Someexamplesarebuttons,variousTextViews,EditTextcontrols,checkboxes,RadioButtons,dateandtimecontrols,listcontrols,controlstoshowanaloganddigitalclocks,controlstoshowimagesandvideos,controlstopicknumbers,etc.WewillcoveranumberoftheseinChapter3.Inthatchapter,wewillalsocovertheessentiallayoutsthatareneededtocomposetheUIfromthosecontrols.
OnceyouareabletousethebasiccontrolstoconstructyourUI,theonecontrolthatyouabsolutelyneedinyourappsisthelistcontrol.Wedidnotcoverlistcontrolasabasiccontrolbecauseitisabitinvolved.AlsoAndroidhasanumberoffeaturesandapproachestodolist-basedapplications.Sowehavededicatedaseparatechapterforlistcontrolsandthedataadaptersthatarenecessarytopopulatethoselistcontrols.TheseaspectsarecoveredinChapter4.
Onceyoumasterthebasiccontrols,basiclayouts,andlistcontrols,youwillstartlookingaroundformoresophisticatedlayoutslikethegridandtablelayouts.ThesearecoveredinChapter5under“UsingAdvancedLayouts.”
MenusarecoveredinChapter6.Android’smenuinfrastructureincludescontextmenus,
pop-upmenus,optioniconsinanactionbar,etc.
Yourmobileappisnotreallycompletewithoutrefiningitthroughstyling,muchlikeCSS.Chapter7covershowstylesandthemesworkinAndroid.
DialogsareessentialinanyUI.DialogsareabitinvolvedinAndroid.TounderstanddialogsinAndroidyouhavetofirstunderstandtheconceptoffragments.Architectureofdialogsisonlyoneaspectoffragments.FragmentsarenowcoretotheAndroidUI.Chapter8explainswhatfragmentsareandinChapter10wecoverdialogs,buildinguponChapter8.
Inmobileapps,youcannotwriteanappwithoutunderstandingwhathappenstoyourapplicationwhenthedeviceorientationchanges.ProgrammingcorrectlyfororientationchangeisnottrivialinAndroid.HowtoprogramfororientationandotherdeviceconfigurationchangesiscoveredinChapter9.
ForanyreasonablyusefulapplicationyouwilllikelyneedtoknowalltheseUIessentials.SoTrack1isanessentialtrack.
Track2:SavingStateOnceyouknowhowtoconstructtheUIofyourapplication,thenextneedyouwillrunintoistosavethestateofyourapplication.Refertotheearliersectiononsavingstatetoseewhatoptionsareavailableandinwhichchaptersthoseoptionsarecovered.Track2isalsoanessentialtrackasyoushouldknowhowtosavestate.
Track3:Preparing/TakingYourApplicationtotheMarketBycompletingTracks1and2youcanbuildaprettyreasonableapplicationthatyoucandeploytothemarketplace.Chapter30showsyouhowyoucantakeyourapptotheGooglePlaystore.
Track4:MakingYourApplicationRobustTrack4isanadvancedtrackgettingintotheinternalsofAndroid.YouwillneedtogothroughthechaptersinthistracktosolidifyyourunderstandingofhowAndroidworks.WestartthistrackwithChapter12oncompatibilitylibrary.Thischapterteacheshowtomakeyourapprunwellonolderreleaseswhileusingfeaturesthatareavailableonlyonthenewerplatforms.
Androidallowsyoutoruncodeinyourapplicationeventhoughyouarenotactivelyusingtheapplicationintheforeground.Itcouldbethemusicyouareplayinginthebackground,oritcouldbebackingupyourimagestoacloud,etc.ThistypeofcodeiscalledaServiceinAndroid.WorkingwithservicesiscoveredinChapter13.Theseservicescanbetriggeredbyadirectuseractionorthroughalarmsorbroadcastevents.AlarmmanageriscoveredinChapter17.
Whenyouuseintentstoinvokecomponentssuchasactivitiesorservicesyouaretargetingasinglecomponent.Androidalsosupportsapublish-and-subscribeprotocolwhereanintentcanbeusedtoinvokemultiplecomponentsthatregisterforitatthesametime.Thesecomponentsarecalledreceiversorbroadcastreceivers.Abroadcastreceiverisapieceofcodeinyourapplicationthatisexecutedinresponsetoabroadcastedeventevenifyourapplicationhadnotbeenstartedorwasjustdormantatthetimeoftheevent.HowtoworkwithbroadcastreceiversiscoveredinChapter16.
AsyoustartusingmoreandmorefeaturesofAndroidsuchasservices,broadcastreceivers,andcontentprovidersyouwillneedtounderstandhowAndroidusesasinglemainthreadtorunthecodeinthesecomponents.ThisthreadingmodeliscoveredindetailinChapter14.Knowingthiswillhelpyouwritecodethatisrobust.Inthistrack,youwillalsolearnabouttheveryusefulAsyncTask,whichisusedtosimplifyoffloadingworkfromthemainthread.ThisAPIisoftenusedfromUItoreadmessagesfromtheweborcheckfore-mails,etc.AsyncTaskiscoveredinChapter15.
Track5:BringingFinessetoYourAppsTomakeyourappslookappealing,oneofthefirstthingsyoucandoistoaddalittleoralotofanimation.ThisiscoveredinChapter18.Touch-basedinterfacesarenowthenorm.Manipulatingyourenvironmentwithdraganddropismorenatural.Youwanttoemploysensorstowriteappsthatintegratewiththeexternalworldbetter.Thesetouchscreens,draganddrop,andsensorsarerespectivelycoveredinChapters22,23,and24.
Homescreenwidgetsareawonderfulwaytoextractpiecesofyourappandmakeitavailableonanyhomescreenofyourchoosing.Thispersonalizationfeature,whenusedinnovativelywithvalueinmind,makestheinteractionwiththedevicesimpleandjoyful.WidgetsarecoveredinChapter21.
Map-andlocation-basedappsaremadeformobiledevices.ThistopiciscoveredinChapter19.
YoucanveryeasilyintegrateaudioandvideointoyourappsonAndroid.ThisAPIiscoveredinChapter20.
Track6:IntegratingwithOtherDevicesandtheCloudYoucanuseGooglecloudmessagingtoreachouttotheusersofyourmobileapplications.GooglecloudmessagingiscoveredinChapter29.WithNFCandBluetoothcapabilitiesinAndroidyoucanstartinteractingwithyourphysicalenvironmentinyourapps.Wehopetopostsomematerialonthesetopicstotheonlinecompanionforthebook.
FinalTrack:GettingaHelpingHandfromExpertAndroid
Now,wearegoingtotalkaboutafewtopicsthatarenotcoveredinthisbook.Youmaywanttoconsiderthesetopics,shouldyoufindthemrelevanttoyourneeds.MostofthesearebasedonourresearchfortheExpertAndroidbookthatwepublishedinearly2014throughApress.
AndroidhasapublicAPItowritecustomcomponentsthatcanworkandbehavedifferentlythanwhatcomeoutofthebox.Youcanwritecustomviewswhereyoucancontrolwhattodrawandhowtodraw,whichcanthencoexistwithothercontrolsthatareoutoftheboxlikeabuttonoratextcontrol.Youcanalsocombinemultipleexistingcontrolsintoacompoundcontrolthatcanthenbehavelikeanindependentcontrol.Youcanalsodesignnewlayoutsthatsuityourdisplayneeds.Therearealotoftrickythingstocreatethesecustomcomponentswell.YouhavetounderstandthecoreAndroidviewarchitecture.Thismaterialiscoveredoverthreechaptersand100pagesintheExpertAndroidbookfromApress.
Ifyourappsareformbased,youwillneedtowritealotofcodetovalidatetheforminput.Youreallyneedaframeworktohandlethis.ExpertAndroidhasachapteroncreatingasmallformprocessingframeworkthatisreallyusefulandwillreduceerrorsandtheamountofcodeyouneedtowrite.
MBAAS,MobileBackendasaService,isaneededtechnologyformobileappsandisnowprettywidelyavailable.ThefacilitiesanMBAASoffersareuserlogins,sociallogins,usermanagement,savinddataonbehalfofusersinthecloud,communicationwiththeusers,collaborationbetweentheusersthemselves,etc.InExpertAndroidwehavemultiplechaptersdedicatedtoanMBAASplatformcalledParse.
OpenGLhascomealongwayonAndroidwithnow-substantialsupportforthenewgenerationofprogrammableGPUs.AndroidhasbeensupportingES2.0forsometimenow.InExpertAndroidwehaveover100pagesofcoverageonOpenGL.Westartatthebeginningandexplainalltheconceptswithoutneedingtorefertoexternalbooks,althoughwedogiveanextensivebibliographyonOpenGL.WecoverES2.0reallywellandprovideguidancetocombineOpenGLandregularviewstopavethewayfor3Dcomponents.
FederatedsearchprotocolofAndroidispowerfulasyoucanuseitinquiteafewimaginativeways.ExpertAndroidfullyexploresitsfundamentalsandalsosomealternatewaysofusingitoptimally.
Androidprovidesanincreasinglylargesetoffeaturesfordebugging.ThesetopicsarecoveredinExpertAndroid.Acellphoneisultimatelyatalkingdevice,althoughitisusedlessandlessoftenforthat.WehaveachapteronutilizingthetelephonyAPIinExpertAndroidaswell.
AsWeLeaveYouNowwiththeRestoftheBookFinally,youmaybewonderingwhyyoushouldevenbecomeamobiledeveloper.Wecancitetwostrongarguments,oneofwhichneverexistedbefore.Thefamiliaroneistobe
partofanITorganizationfortheirmobileprogrammingefforts.TheITopportunitiesareontherisebutnotfullyrealizedyetunlikewhathappenedwiththeWebprogrammingparadigmwhenitcameintobeing.Weexpectthisneed,however,tobeagraduallyincreasingdemand.
Ontheotherhand,theimmediateandexcitingopportunityisforyoutobecomeanindependentapppublisher.Theavailabilityofasaleschannelfortheappsthatyouwriteisauniqueoneinthesoftwareindustry.NoteveryoneofusisgoingtobearisingstarinanITorganization.Theindependentdeveloperpathgivesanavenueforyoutogrowatyourownpaceandinadirectionthatsatisfiesyou.Luckandpatiencemightevenmakeyourich.Atleastyoucanaddvaluetothesocietywhilemeetingyourneeds.
ShouldyoudecidetoventureintotheAndroidmobileprogrammingspace,youwanttobepreparedwiththerighthardwarethatmakesthisexperiencebearable.IfyouarebuyingaWindowslaptopseeifyoucangetonewithatleast8Gofmemory,solid-stateharddrive,andareasonablyfastprocessor.Expecttospendabout$1,000to$1,500.IfyouarebuyingaMaclaptop,asimilarconfigurationmaycostyouabout$2,500.AgoodfastconfigurationisimportantforAndroiddevelopment.IfyouareaseasonedJavaprogrammer,giventhisinvestment,andthisbookinhand,ifyoufollowthetrackslaidouthereyoucanbecomeacompetentmobileAndroidappdeveloperinaboutsixmonths.
ReferencesHereareadditionalresourcesforthetopicsdiscussedinthischapter.
http://androidbook.com/free-android-chapters:YoucanusethisURLtodownloaddetailedchaptersonresourcesandintents(madeavailablefreefrompreviouseditionsduetospacelimitations).
http://androidbook.com/working-with-avds:YouwillfindatthisURLnotesoninstallingAndroid,workingwithAVDs,signingAPKfiles,andmoretogetyoustartedwithAndroid.
http://androidbook.com/item/3574:ThisURLshowshowtorunanAndroidapplicationonadevicefromtheeclipseADT.ThislinkalsoshowsyouhowtohookupyourdevicethroughaUSBporttoyourdevelopmentcomputer.
http://androidbook.com/item/4629:ThisURLtalksaboutkeycallbackfunctionsonanactivity.Monitoringtheactivitycallbacksisagoodwaytogetahandleontheactivitylifecycle.Youcancopythecodefromheretocreateabaseactivitythatcanmonitorandlogthesecallbacksforyou.
http://androidbook.com/item/4440:ThisURLtalksabouthowyoucanuseGSONandJSONforpersistenceneedsofyourapplication.Thisarticlesuggestsaneasywaytopersistdataon
thedeviceforyourapps.
ExpertAndroidfromApresstalksaboutpassingobjectsthroughAndroidbundlesasparcelablesindepth.
http://developer.android.com/guide/topics/resources/index.htmlAndroidSDKroadmaptothedocumentationonresources.
http://developer.android.com/guide/topics/resources/available-resources.html:Androiddocumentationofvarioustypesofresourcesavailable.
http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources:AlistofvariousconfigurationqualifiersprovidedbythelatestAndroidSDK.
http://developer.android.com/guide/practices/screens_support.htmlGuidelinesonhowtodesignAndroidapplicationsformultiplescreensizes.
http://developer.android.com/reference/android/content/res/Resources.htmlVariousJavamethodsavailabletoreadresources.
http://developer.android.com/reference/android/R.htmlResourcesasdefinedtothecoreAndroidplatform.
http://androidbook.com/item/3542:Ourresearchonplurals,stringarrays,resourcequalifiers,andalternateresources,aswellaslinkstootherreferences.
http://androidbook.com/item/4236:Usingdrawableresourcestocontrolbackgrounds.
http://developer.android.com/training/notepad/index.htmlAbeginner’sguide,yetacomprehensiveintroductiontoAndroidapplicationsthroughaNotePadexample.
http://developer.android.com/reference/android/content/Intent.htmlOverviewofintents,includingwell-knownactions,extras,andsoon.
http://developer.android.com/guide/appendix/g-app-intents.html:ListstheintentsforasetofGoogleapplications.Here,youwillseeherehowtoinvokeBrowser,Map,Dialer,andGoogleStreetView.
http://developer.android.com/reference/android/content/IntentFilter.htmlTalksaboutintentfiltersandisusefulwhenyouareregisteringintentfiltersforactivitiesandothercomponentsinthemanifestfile.
http://developer.android.com/guide/topics/intents/intents-filters.html:Goesintotheresolutionrulesofintentfilters.
http://developer.android.com/training/notepad/index.html
URLwhereyoucandownloadthesamplecodeforaNotePadapplication.ThisisagoodsampleapplicationthatfeaturesanumberofAndroidAPIs.Agoodplacetogoafterthecalculatorapplication.
http://developer.android.com/samples/index.html:ThisistheprimarylinktobrowsethroughthevarioussamplespresentedfortheAndroidSDKbyGoogle.
http://developer.android.com/training/index.html:ThisistheprimarylearningsitefromGooglethatpresentsaseriesoflessonstolearnAndroid.
https://code.google.com/p/openintents/:AwebefforttomakevariousAndroidapplicationsworktogether.
http://androidbook.com/item/4623:AroadmapforlearningAndroid.Althoughsomeofthesepointsarecoveredhere,seethisURLforthelatestguidanceonlearningandmaximizingAndroid.
http://androidbook.com/item/4764:ThisisaknowledgefoldercontainingaseriesofarticlesandtidbitsonprogrammingwithAndroidbasicUI.
http://www.androidbook.com/proandroid5/projects.Lookhereforalistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch02_Calculator.zip.
SummaryThischapterlaidouteverythingyouneedtounderstandtocreatemobileapplicationswiththeAndroidSDK.YouhaveseenhowUIisconstructed.Youknowwhatactivitiesare.Youknowtheintricaciesoftheactivitylifecycle.Youunderstoodresourcesandintents.Youknowhowtosavestate.Finally,yougottoseethebreadthoftheAndroidSDKbyreadingthelearningtracksthatsummarizedtherestofthebook.WehopethesefirsttwochaptersgaveyouaheadstartforyourdevelopmenteffortswiththeAndroidSDK.
Chapter3
BuildingBasicUserInterfacesandUsingControlsThepreviouschaptergaveyouacrashcourseinsomeoftheUIelementsavailableinAndroid,andhowtoputthemtogetherquicklytocreatethecalculatorapplication.Whilethatwasfun,wehopeyoustartedthinkingaboutwhatotherUIwidgetsareavailableinAndroid,overandabovetheTextView,EditText,andButtoncontrolsintroducedinChapter2.
Inthischapter,wearegoingtodiscussuserinterfacesandcontrolsindetail.WewillbeginbydiscussingthegeneralphilosophyofUIdevelopmentinAndroid,andthenwe’lldescribemanyoftheUIcontrolsthatshipwiththeAndroidSDK.Thesearethebuildingblocksoftheinterfacesyou’llcreate.Inthesubsequentchapters,wewillalsodiscussviewadaptersandlayoutmanagers,andyou’llseehowtheybuildonthebasiccontrolsweintroduceinthischapter.
Bytheendofthischapter,you’llhaveasolidunderstandingofthemanyUIcontrolsavailableinthestockAndroidtoolset,andhowtolayoutUIcontrolsintoscreensandpopulatethemwithdata.
UIDevelopmentinAndroidUIdevelopmentinAndroidisfun.It’sfunbecauseit’srelativelyeasy.WithAndroid,wehaveasimple-to-understandframeworkwithalimitedsetofout-of-the-boxcontrols.Theavailablescreenareaisgenerallylimited—onphonesifnotontablets—andthisguidestheunderlyingphilosophyof“simplepower”forAndroidcontrols.AndroidalsotakescareofalotoftheheavyliftingnormallyassociatedtodesigningandbuildingqualityUIs.This,combinedwiththefactthattheuserusuallywantstodoonespecificaction,allowsustoeasilybuildagoodUItodeliveragooduserexperience.
TheAndroidSDKshipswithahostofcontrolsthatyoucanusetobuildUIsforyourapplication.SimilartootherSDKs,theAndroidSDKprovidestextfields,buttons,lists,grids,andsoon.Inaddition,Androidprovidesacollectionofcontrolsthatareappropriateformobiledevices.
Attheheartofthecommoncontrolsaretwoclasses:android.view.Viewandandroid.view.ViewGroup.Asthenameofthefirstclasssuggests,theViewclassrepresentsageneral-purposeViewobject.ThecommoncontrolsinAndroidultimatelyextendtheViewclass.ViewGroupisalsoaview,butitcontainsotherviewstoo.ViewGroupisthebaseclassforalistoflayoutclasses.Android,likeSwing,usestheconceptoflayoutstomanagehowcontrolsarelaidoutwithinacontainerview.Usinglayouts,aswe’llsee,makesiteasyforustocontrolthepositionandorientationofthe
controlsinourUIs.
YoucanchoosefromseveralapproachestobuildUIsinAndroid.YoucanconstructUIsentirelyincode.YoucanalsodefineUIsinXML.Youcanevencombinethetwo—definetheUIinXMLandthenrefertoit,andmodifyit,incode.Todemonstratethis,inthischapterwearegoingtobuildasimpleUIusingeachofthesethreeapproaches.
Beforewegetstarted,let’sdefinesomenomenclature.InthisbookandotherAndroidliterature,youwillfindthetermsview,control,widget,container,andlayoutindiscussionsregardingUIdevelopment.IfyouarenewtoAndroidprogrammingorUIdevelopmentingeneral,youmightnotbefamiliarwiththeseterms.We’llbrieflydescribethembeforewegetstarted(seeTable3-1).
Table3-1.UINomenclature
Term Description
View,widget,control
EachoftheserepresentsaUIelement.Examplesincludeabutton,agrid,alist,awindow,adialogbox,andsoon.Thetermsview,widget,andcontrolareusedinterchangeablyinthischapter.
Container Thisisaviewusedtocontainotherviews.Forexample,agridcanbeconsideredacontainerbecauseitcontainscells,eachofwhichisaview.
Layout Thisisavisualarrangementofcontainersandviewsandcanincludeotherlayouts.WewillworkwithlayoutsinthischapterandreturnforafullexplorationofAndroid’sLayoutfeaturesinChapter5.
Figure3-1showsascreenshotoftheapplicationthatwearegoingtobuild.Nexttothescreenshotisthelayouthierarchyofthecontrolsandcontainersintheapplication.
Figure3-1.TheUIandlayoutofanactivity
Wewillrefertothislayouthierarchyaswediscussthesampleprograms.Fornow,knowthattheapplicationhasoneactivity.TheUIfortheactivityiscomposedofthreecontainers:acontainerthatcontainsaperson’sname,acontainerthatcontainstheaddress,andanouterparentcontainerforthechildcontainers.
BuildingaUICompletelyinCodeThefirstexample,Listing3-1,demonstrateshowtobuildtheUIentirelyincode.Totrythis,createanewAndroidApplicationprojectusingaprojectnameofcontrols,apackagenameofcom.androidbook.controls,andwithanactivitynamed
MainActivityandthencopythecodefromListing3-1intoyourMainActivityclass.
NoteWewillgiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoEclipsedirectlyinsteadofcopyingandpastingcode.
Listing3-1.CreatingaSimpleUserInterfaceEntirelyinCode
packagecom.androidbook.controls;importandroid.app.Activity;importandroid.os.Bundle;importandroid.view.ViewGroup.LayoutParams;importandroid.widget.LinearLayout;importandroid.widget.TextView;publicclassMainActivityextendsActivity{privateLinearLayoutnameContainer;
privateLinearLayoutaddressContainer;
privateLinearLayoutparentContainer;
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);
createNameContainer();
createAddressContainer();
createParentContainer();
setContentView(parentContainer);}
privatevoidcreateNameContainer(){nameContainer=newLinearLayout(this);
nameContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));nameContainer.setOrientation(LinearLayout.HORIZONTAL);
TextViewnameLbl=newTextView(this);
nameLbl.setText("Name:");
TextViewnameValue=newTextView(this);nameValue.setText("JohnDoe");
nameContainer.addView(nameLbl);nameContainer.addView(nameValue);}
privatevoidcreateAddressContainer(){addressContainer=newLinearLayout(this);
addressContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));addressContainer.setOrientation(LinearLayout.VERTICAL);
TextViewaddrLbl=newTextView(this);addrLbl.setText("Address:");
TextViewaddrValue=newTextView(this);addrValue.setText("911HollywoodBlvd");
addressContainer.addView(addrLbl);addressContainer.addView(addrValue);}
privatevoidcreateParentContainer(){parentContainer=newLinearLayout(this);
parentContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));parentContainer.setOrientation(LinearLayout.VERTICAL);
parentContainer.addView(nameContainer);parentContainer.addView(addressContainer);}}
AsshowninListing3-1,theactivitycontainsthreeLinearLayoutobjects.WewillbediscussinglayoutsinmuchmoredepthinChapter5,butthere’salittlechicken-and-eggissueofneedingtoknowjustalittleaboutlayoutssoonecanlearnaboutthemanybasiccontrols.Fornow,it’senoughtoknowthatlayoutobjectscontainlogictopositionobjectswithinaportionofthescreen.ALinearLayout,forexample,knowshowtolayoutcontrolseitherverticallyorhorizontally.Layoutobjectscancontainanytypeofview—
evenotherlayouts.
ThenameContainerobjectcontainstwoTextViewcontrols:oneforthelabelName:andtheothertoholdtheactualtextforthename(suchasJohnDoe).TheaddressContaineralsocontainstwoTextViewcontrols.ThedifferencebetweenthetwocontainersisthatthenameContainerislaidouthorizontallyandtheaddressContainerislaidoutvertically.BothofthesecontainerslivewithintheparentContainer,whichistherootviewoftheactivity.Afterthecontainershavebeenbuilt,theactivitysetsthecontentoftheviewtotherootviewbycallingsetContentView(parentContainer).WhenitcomestimetorendertheUIoftheactivity,therootviewiscalledtorenderitself.Therootviewthencallsitschildrentorenderthemselves,andthechildcontrolscalltheirchildren,andsoon,untiltheentireUIisrendered.
AsshowninListing3-1,wehaveseveralLinearLayoutcontrols.Twoofthemarelaidoutvertically,andoneislaidouthorizontally.ThenameContainerislaidouthorizontally.ThismeansthetwoTextViewcontrolsappearsidebysidehorizontally.TheaddressContainerislaidoutvertically,whichmeansthetwoTextViewcontrolsarestackedoneontopoftheother.TheparentContainerisalsolaidoutvertically,whichiswhythenameContainerappearsabovetheaddressContainer.Noteasubtledifferencebetweenthetwoverticallylaid-outcontainers,addressContainerandparentContainer.parentContainerissettotakeuptheentirewidthandheightofthescreen:
parentContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
andaddressContainerwrapsitscontentvertically:
addressContainer.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
Saidanotherway,WRAP_CONTENTmeanstheviewshouldtakejustthespaceitneedsinthatdimensionandnomore,uptowhatthecontainingviewwillallow.FortheaddressContainer,thismeansthecontainerwilltaketwolinesvertically,becausethat’sallitneedsforthedummyaddresswehaveprovided.
BuildingaUICompletelyinXMLNowlet’sbuildthesameUIinXML(seeListing3-2).XMLlayoutfilesarestoredundertheresources(/res/)directoryinafoldercalledlayout.Totrythisexample,createanewAndroidprojectinEclipse.Bydefault,youwillgetanXMLlayoutfilenamedactivity_main.xml,locatedundertheres/layoutfolder.Double-clickactivity_main.xmltoseethecontents.Eclipsewilldisplayavisualeditorforyour
layoutfile.Youprobablyhaveastringatthetopoftheviewthatsays“HelloWorld,MainActivity!”orsomethinglikethat.Clicktheactivity_main.xmltabatthebottomoftheviewtoseetheXMLoftheactivity_main.xmlfile.ThisrevealsaLinearLayoutandaTextViewcontrol.UsingeithertheLayoutoractivity_main.xmltab,orboth,re-createListing3-2intheactivity_main.xmlfile.Saveit.
Listing3-2.CreatingaUserInterfaceEntirelyinXML
<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><!--NAMECONTAINER--><LinearLayoutandroid:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="wrap_content">
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Name:"/>
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="JohnDoe"/>
</LinearLayout>
<!--ADDRESSCONTAINER--><LinearLayoutandroid:orientation="vertical"android:layout_width="fill_parent"android:layout_height="wrap_content">
<TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Address:"/>
<TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="911HollywoodBlvd"/></LinearLayout>
</LinearLayout>
Underyournewproject’ssrcdirectory,thereisa.javafilecontaininganActivityclassdefinition.Double-clickthatfiletoseeitscontents.Noticethestatement
setContentView(R.layout.activity_main).TheXMLsnippetshowninListing3-2,combinedwithacalltosetContentView(R.layout.activity_main),willrenderthesameUIasbeforewhenwegenerateditcompletelyincode.TheXMLfileisself-explanatory,butnotethatwehavethreecontainerviewsdefined.ThefirstLinearLayoutistheequivalentofourparentcontainer.Thiscontainersetsitsorientationtoverticalbysettingthecorrespondingpropertylikethis:android:orientation=“vertical”.TheparentcontainercontainstwoLinearLayoutcontainers,whichrepresentnameContainerandaddressContainer.
RunningthisapplicationwillproducethesameUIasourpreviousexampleapplication.ThelabelsandvalueswillbedisplayedasshowninFigure3-1.
BuildingaUIinXMLwithCodeListing3-2isacontrivedexample.Itdoesn’tmakeanysensetohard-codethevaluesoftheTextViewcontrolsintheXMLlayout.Ideally,weshoulddesignourUIsinXMLandthenreferencethecontrolsfromcode.Thisapproachenablesustobinddynamicdatatothecontrolsdefinedatdesigntime.Infact,thisistherecommendedapproach.ItisfairlyeasytobuildlayoutsinXMLandthenusecodetopopulatethedynamicdata.
Listing3-3showsthesameUIwithslightlydifferentXML.ThisXMLassignsIDstotheTextViewcontrolssothatwecanrefertothemincode.
Listing3-3.CreatingaUserInterfaceinXMLwithIDs
<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><!--NAMECONTAINER--><LinearLayoutandroid:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="wrap_content">
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/name_text"/>
<TextViewandroid:id="@+id/nameValue"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</LinearLayout>
<!--ADDRESSCONTAINER-->
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="wrap_content">
<TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="@string/addr_text"/>
<TextViewandroid:id="@+id/addrValue"android:layout_width="fill_parent"android:layout_height="wrap_content"/></LinearLayout>
</LinearLayout>
InadditiontoaddingtheIDstotheTextViewcontrolsthatwewanttopopulatefromcode,wealsohavelabelTextViewcontrolsthatwe’repopulatingwithtextfromourstringsresourcefile.ThesearetheTextViewswithoutIDsthathaveanandroid:textattribute.TheactualstringsfortheseTextViewswillcomefromourstrings.xmlfileinthe/res/valuesfolder.Listing3-4showswhatourstrings.xmlfilemightlooklike.
Listing3-4.strings.xmlFileforListing3-3
<?xmlversion="1.0"encoding="utf-8"?><resources><stringname="app_name">CommonControls</string><stringname="name_text">Name:</string><stringname="addr_text">Address:</string></resources>
ThecodeinListing3-5demonstrateshowyoucanobtainreferencestothecontrolsdefinedintheXMLtosettheirproperties.YoumightputthisintoyouronCreate()methodforyouractivity.
Listing3-5.ReferringtoControlsinResourcesatRuntime
setContentView(R.layout.activity_main);TextViewnameValue=(TextView)findViewById(R.id.nameValue);nameValue.setText("JohnDoe");TextViewaddrValue=(TextView)findViewById(R.id.addrValue);addrValue.setText("911HollywoodBlvd.");
ThecodeinListing3-5isstraightforward,butnotethatweloadtheresourcebycallingsetContentView(R.layout.activity_main)beforecallingfindViewById()—wecannotgetreferencestoviewsiftheyhavenotbeenloadedyet.
ThedevelopersofAndroidhavedoneanicejobofmakingjustabouteveryaspectofacontrolsettableviaXMLorcode.It’susuallyagoodideatosetthecontrol’sattributesintheXMLlayoutfileratherthanusingcode.However,therewillbeavarietyoftimeswhenyouneedtousecode,suchassettingavaluetobedisplayedtotheuser.
FILL_PARENTvs.MATCH_PARENTTheconstantFILL_PARENTwasdeprecatedinAndroid2.2andreplacedwithMATCH_PARENT.Thiswasstrictlyanamechange,though.Thevalueofthisconstantisstill–1.Similarly,forXMLlayouts,fill_parentwasreplacedwithmatch_parent.Sowhatvaluedoyouuse?InsteadofFILL_PARENTorMATCH_PARENT,youcouldsimplyusethevalue–1,andyou’dbefine.However,thisisn’tveryeasytoread,andyoudon’thaveanequivalentunnamedvaluetousewithyourXMLlayouts.There’sabetterway.
DependingonwhichAndroidAPIsyouneedtouseinyourapplication,youcaneitherbuildyourapplicationagainstaversionofAndroidbefore2.2andrelyonforwardcompatibilityorbuildyourapplicationagainstversion2.2orlaterofAndroidandsetminSdkVersiontothelowestversionofAndroidyourapplicationwillrunon.Forexample,ifyouonlyneedAPIsthatexistedinAndroid1.6,buildagainstAndroid1.6anduseFILL_PARENTandfill_parent.YourapplicationshouldrunwithnoproblemsinalllaterversionsofAndroidincluding2.2andbeyond.IfyouneedAPIsfromAndroid2.2orlater,goaheadandbuildagainstthatversionofAndroid,useMATCH_PARENTandmatch_parent,andsetminSdkVersiontosomethingolder:forexample,4(forAndroid1.6).YoucanstilldeployanAndroidapplicationbuiltinAndroid2.2toanolderversionofAndroid,butyou’llhavetobecarefulabouttheclassesand/ormethodsthataren’tintheearlierreleasesoftheAndroidSDK.Therearewaysaroundthis,suchasusingreflectionorcreatingwrapperclassestohandledifferencesinAndroidversions.Wewillcoverthoseadvancedtopicsinlaterchapters.
UnderstandingAndroid’sCommonControlsWewillnowstartourdiscussionofthecommoncontrolsintheAndroidSDK.We’llstartwithtextcontrolsandthencoverbuttons,checkboxes,radiobuttons,lists,grids,dateandtimecontrols,andamap-viewcontrol.Thesewillgohandinhandwiththelayoutcontrolswe’llintroduceinChapter4.
TextControlsTextcontrolsarelikelytobethefirsttypeofcontrolthatyou’llworkwithinAndroid.Androidhasacompletebutnotoverwhelmingsetoftextcontrols.Inthissection,wearegoingtodiscusstheTextView,EditText,AutoCompleteTextView,andMultiAutoCompleteTextViewcontrols.Figure3-2showsthecontrolsinaction.
Figure3-2.TextcontrolsinAndroid
TextViewYou’vealreadyseenasimpleXMLspecificationforaTextViewcontrol,inListing3-3,andhowtohandleTextViewsincodeinListing3-4.NoticehowwespecifiedtheID,width,height,andvalueofthetextinXMLandhowwesetthevalueusingthesetText()method.TheTextViewcontrolknowshowtodisplaytextbutdoesnotallowediting.Thismightleadyoutoconcludethatthecontrolisessentiallyadummylabel.Nottrue.TheTextViewcontrolhasafewinterestingpropertiesthatmakeitveryhandy.IfyouknowthatthecontentoftheTextViewisgoingtocontainawebURLorane-mailaddress,forexample,youcansettheautoLinkpropertytoemail|web,andthecontrolwillfindandhighlightanye-mailaddressesandURLs.Moreover,whentheuserclicksoneofthesehighlighteditems,thesystemwilltakecareoflaunchingthee-mailapplicationwiththee-mailaddressorabrowserwiththeURL.InXML,thisattributewouldbeinsidetheTextViewtagandwouldlooksomethinglikethis:
<TextView...android:autoLink="email|web".../>
Youspecifyapipe-delimitedsetofvaluesincludingweb,email,phone,ormap,oruse
none(thedefault)orall.IfyouwanttosetautoLinkbehaviorincodeinsteadofusingXML,thecorrespondingmethodcallissetAutoLinkMask().Youwouldpassitanintrepresentingthecombinationofvaluessortoflikebefore,suchasLinkify.EMAIL_ADDRESSES|Linkify.WEB_URLS.Toachievethisfunctionality,TextViewisutilizingtheandroid.text.util.Linkifyclass.Listing3-6showsanexampleofauto-linkingwithcode.
Listing3-6.UsingLinkifyonTextinaTextView
TextViewtv=(TextView)this.findViewById(R.id.tv);tv.setAutoLinkMask(Linkify.ALL);tv.setText("Pleasevisitmywebsite,http://[email protected].");
Noticethatwesettheauto-linkoptionsonourTextViewbeforewesetthetext.Thisisimportantbecausesettingtheauto-linkoptionsaftersettingthetextwon’taffecttheexistingtext.Becausewe’reusingcodetoaddhyperlinkstoourtext,ourXMLfortheTextViewinListing3-6doesnotrequireanyspecialattributesandcanlookassimpleasthis:
<TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
Ifyouwantto,youcaninvokethestaticaddLinks()methodoftheLinkifyclasstofindandaddlinkstothecontentofanyTextVieworanySpannableondemand.InsteadofusingsetAutoLinkMask(),wecouldhavedonethefollowingaftersettingthetext:
Linkify.addLinks(tv,Linkify.ALL);
Clickingalinkwillcausethedefaultintenttobecalledforthataction.Forexample,clickingawebURLwilllaunchthebrowserwiththeURL.Clickingaphonenumberwilllaunchthephonedialer,andsoon.TheLinkifyclasscanperformthisworkrightoutofthebox.
Linkifycanalsodetectcustompatternsyouwanttolookfor,decidewhethertheyareamatchforsomethingyoudecideneedstobeclickable,andsetuphowtofireanintenttomakeaclickturnintosomesortofaction.Wewon’tgointothosedetailshere,butknowthatthesethingscanbedone.
TherearemanymorefeaturesofTextViewtoexplore,fromfontattributestominLinesandmaxLinesandmanymore.Thesearefairlyself-explanatory,andyouareencouragedtoexperimenttoseehowyoumightbeabletousethem.AlthoughyoushouldkeepinmindthatsomefunctionalityintheTextViewclassisnotapplicabletoaread-onlyfield,thefunctionalityisthereforthesubclassesofTextView,oneofwhichwewillcovernext.
EditTextTheEditTextcontrolisasubclassofTextView.Assuggestedbythename,theEditTextcontrolallowsfortextediting.EditTextisnotaspowerfulasthetext-editingcontrolsthatyoufindontheInternet,butusersofAndroid-baseddevicesprobablywon’ttypedocumentsdirectlyintoanEditTextcontrol—they’lltypeacoupleofparagraphsatmostoruseamorefullyfunctionalHTML-basedpageinstead.Therefore,theclasshaslimitedbutappropriatefunctionalityandmayevensurpriseyou.Forexample,oneofthemostsignificantpropertiesofanEditTextistheinputType.YoucansettheinputTypepropertytotextAutoCorrecttohavethecontrolcorrectcommonmisspellings.YoucansetittotextCapWordstohavethecontrolcapitalizewords.Otheroptionsexpectonlyphonenumbersorpasswords.
Thereareolder,nowdeprecated,waysofspecifyingcapitalization,multilinetext,andotherfeatures.IfthesearespecifiedwithoutaninputTypeproperty,theycanberead;butifinputTypeisspecified,theseolderpropertiesareignored.
TheolddefaultbehavioroftheEditTextcontrolistodisplaytextononelineandexpandasneeded.Inotherwords,iftheusertypespastthefirstline,anotherlinewillappear,andsoon.Youcould,however,forcetheusertoasinglelinebysettingthesingleLinepropertytotrue.Inthiscase,theuserwillhavetocontinuetypingonthesameline.WithinputType,ifyoudon’tspecifytextMultiLine,theEditTextwilldefaulttosingle-lineonly.Soifyouwanttheolddefaultbehaviorofmultilinetyping,youneedtospecifyinputTypewithtextMultiLine.
OneofthenicefeaturesofEditTextisthatyoucanspecifyhinttext.Thistextwillbedisplayedslightlyfadedanddisappearsassoonastheuserstartstotypetext.Thepurposeofthehintistolettheuserknowwhatisexpectedinthisfield,withouttheuserhavingtoselectanderasedefaulttext.InXML,thisattributeisandroid:hint=“yourhinttexthere”orandroid:hint=”@string/your_hint_name”,whereyour_hint_nameisaresourcenameofastringtobefoundin/res/values/strings.xml.Incode,youwouldcallthesetHint()methodwitheitheraCharSequenceoraresourceID.
AutoCompleteTextViewTheAutoCompleteTextViewcontrolisaTextViewwithauto-completefunctionality.Inotherwords,astheusertypesintheTextView,thecontrolcandisplaysuggestionsforselection.Listing3-7demonstratestheAutoCompleteTextViewcontrolwithXMLandwiththecorrespondingcode.
Listing3-7.UsinganAutoCompleteTextViewControl
<AutoCompleteTextViewandroid:id="@+id/actv"android:layout_width="fill_parent"android:layout_height="wrap_content"/>AutoCompleteTextViewactv=(AutoCompleteTextView)
this.findViewById(R.id.actv);
ArrayAdapter<String>aa=newArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line,newString[]{"English","Hebrew","Hindi","Spanish","German","Greek"});
actv.setAdapter(aa);
TheAutoCompleteTextViewcontrolshowninListing3-7suggestsalanguagetotheuser.Forexample,iftheusertypesen,thecontrolsuggestsEnglish.Iftheusertypesgr,thecontrolrecommendsGreek,andsoon.
Ifyouhaveusedasuggestioncontrolorasimilarauto-completecontrol,youknowthatcontrolslikethishavetwoparts:atext-viewcontrolandacontrolthatdisplaysthesuggestion(s).That’sthegeneralconcept.Touseacontrollikethis,youhavetocreatethecontrol,createthelistofsuggestions,tellthecontrolthelistofsuggestions,andpossiblytellthecontrolhowtodisplaythesuggestions.Alternatively,youcouldcreateasecondcontrolforthesuggestionsandthenassociatethetwocontrols.
Androidhasmadethissimple,asisevidentfromListing3-7.TouseanAutoCompleteTextView,youcandefinethecontrolinyourlayoutfileandreferenceitinyouractivity.YouthencreateanadapterclassthatholdsthesuggestionsanddefinetheIDofthecontrolthatwillshowthesuggestion(inthiscase,asimplelistitem).InListing3-7,thesecondparametertotheArrayAdaptertellstheadaptertouseasimplelistitemtoshowthesuggestion.ThefinalstepistoassociatetheadapterwiththeAutoCompleteTextView,whichyoudousingthesetAdapter()method.Don’tworryabouttheadapterforthemoment;we’llcoverthoselaterinthischapter.
MultiAutoCompleteTextViewIfyouhaveplayedwiththeAutoCompleteTextViewcontrol,youknowthatthecontrolofferssuggestionsonlyfortheentiretextinthetextview.Inotherwords,ifyoutypeasentence,youdon’tgetsuggestionsforeachword.That’swhereMultiAutoCompleteTextViewcomesin.YoucanusetheMultiAutoCompleteTextViewtoprovidesuggestionsastheusertypes.Forexample,Figure3-2showsthattheusertypedthewordEnglishfollowedbyacomma,andthenGe,atwhichpointthecontrolsuggestedGerman.Iftheuserweretocontinue,thecontrolwouldofferadditionalsuggestions.
UsingtheMultiAutoCompleteTextViewislikeusingtheAutoCompleteTextView.Thedifferenceisthatyouhavetotellthecontrolwheretostartsuggestingagain.Forexample,inFigure3-2,youcanseethatthecontrolcanoffersuggestionsatthebeginningofthesentenceandafteritseesacomma.TheMultiAutoCompleteTextViewcontrolrequiresthatyougiveitatokenizerthatcanparsethesentenceandtellitwhethertostartsuggestingagain.Listing3-8demonstratesusingtheMultiAutoCompleteTextViewcontrolwiththeXMLandthentheJava
code.
Listing3-8.UsingtheMultiAutoCompleteTextViewControl
<MultiAutoCompleteTextViewandroid:id="@+id/mactv"android:layout_width="fill_parent"android:layout_height="wrap_content"/>
MultiAutoCompleteTextViewmactv=(MultiAutoCompleteTextView)this.findViewById(R.id.mactv);ArrayAdapter<String>aa2=newArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line,newString[]{"English","Hebrew","Hindi","Spanish","German","Greek"});
mactv.setAdapter(aa2);
mactv.setTokenizer(newMultiAutoCompleteTextView.CommaTokenizer());
TheonlysignificantdifferencesbetweenListings3-7and3-8aretheuseofMultiAutoCompleteTextViewandthecalltothesetTokenizer()method.BecauseoftheCommaTokenizerinthiscase,afteracommaistypedintotheEditTextfield,thefieldwillagainmakesuggestionsusingthearrayofstrings.Anyothercharacterstypedinwillnottriggerthefieldtomakesuggestions.SoevenifyouweretotypeFrenchSpani,thepartialwordSpaniwouldnottriggerthesuggestionbecauseitdidnotfollowacomma.Androidprovidesanothertokenizerfore-mailaddressescalledRfc822Tokenizer.Youcanalwayscreateyourowntokenizerifyouwantto.
ButtonControlsButtonsarecommoninanywidgettoolkit,andAndroidisnoexception.Androidoffersthetypicalsetofbuttonsaswellasafewextras.Inthissection,wewilldiscussthreetypesofbuttoncontrols:thebasicbutton,theimagebutton,andthetogglebutton.Figure3-3showsaUIwiththesecontrols.Thebuttonatthetopisthebasicbutton,themiddlebuttonisanimagebutton,andthelastoneisatogglebutton.
Figure3-3.Androidbuttoncontrols
Let’sgetstartedwiththebasicbutton.
TheButtonControlThebasicbuttonclassinAndroidisandroid.widget.Button.There’snotmuchtothistypeofbutton,beyondhowyouuseittohandleclickevents.Listing3-9showsafragmentofanXMLlayoutfortheButtoncontrol,plussomeJavathatwemightsetupintheonCreate()methodofouractivity.OurbasicbuttonwouldlooklikethetopbuttoninFigure3-3.
Listing3-9.HandlingClickEventsonaButton
<Buttonandroid:id="@+id/button1"android:text="@string/basicBtnLabel"android:layout_width="fill_parent"android:layout_height="wrap_content"/>
Buttonbutton1=(Button)this.findViewById(R.id.button1);button1.setOnClickListener(newOnClickListener(){publicvoidonClick(Viewv){Intentintent=newIntent(Intent.ACTION_VIEW,Uri.parse("http://www.androidbook.comstartActivity(intent);}});
Listing3-9showshowtoregisterforabutton-clickevent.Youregisterfortheon-clickeventbycallingthesetOnClickListener()methodwithanOnClickListener.InListing3-9,ananonymouslisteneriscreatedontheflytohandleclickeventsfor
button1.Whenthebuttonisclicked,theonClick()methodofthelisteneriscalledand,inthiscase,launchesthebrowsertoourwebsite.
SinceAndroidSDK1.6,thereisaneasierwaytosetupaclickhandlerforyourbuttonorbuttons.Listing3-10showstheXMLforaButtonwhereyouspecifyanattributeforthehandler,plustheJavacodethatistheclickhandler.
Listing3-10.SettingUpaClickHandlerforaButton
<Button...android:onClick="myClickHandler".../>publicvoidmyClickHandler(Viewtarget){switch(target.getId()){caseR.id.button1:...
ThehandlermethodwillbecalledwithtargetsettotheViewobjectrepresentingthebuttonthatwasclicked.NoticehowtheswitchstatementintheclickhandlermethodusestheresourceIDsofthebuttonstoselectthelogictorun.Usingthismethodmeansyouwon’thavetoexplicitlycreateeachButtonobjectinyourcode,andyoucanreusethesamemethodacrossmultiplebuttons.Thismakesthingseasiertounderstandandmaintain.Thisworkswiththeotherbuttontypesaswell.
TheImageButtonControlAndroidprovidesanimagebuttonviaandroid.widget.ImageButton.Usinganimagebuttonissimilartousingthebasicbutton(seeListing3-11).OurimagebuttonwouldlooklikethemiddlebuttoninFigure3-3.
Listing3-11.UsinganImageButton
<ImageButtonandroid:id="@+id/imageButton2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="myClickHandler"android:src="@drawable/icon"/>
ImageButtonimageButton2=(ImageButton)this.findViewById(R.id.imageButton2);imageButton2.setImageResource(R.drawable.icon);
Herewe’vecreatedtheimagebuttoninXMLandsetthebutton’simagefromadrawableresource.Theimagefileforthebuttonmustexistunder/res/drawable.Inourcase,we’resimplyreusingtheAndroidiconforthebutton.WealsoshowinListing3-11howyoucansetthebutton’simagedynamicallybycallingsetImageResource()methodonthebuttonandpassingitaresourceID.Notethatyouonlyneedtodooneortheother.Youdon’tneedtospecifythebuttonimagebothintheXMLfileandincode.
Oneofthenicefeaturesofanimagebuttonisthatyoucanspecifyatransparentbackgroundforthebutton.Theresultwillbeaclickableimagethatactslikeabuttonbut
canlooklikewhateveryouwantittolooklike.Justsetandroid:background=”@null”fortheimagebutton.
Becauseyourimagemaybesomethingverydifferentthanastandardbutton,youcancustomizehowthebuttonlooksinthetwootherstatesitcanbeinwhenusedinyourUI.Besidesappearingasnormal,buttonscanhavefocus,andtheycanbepressed.Havingfocussimplymeansthebuttoniscurrentlywhereeventswillgo.YoucandirectfocustoabuttonusingthearrowkeysonthekeypadorD-pad,forexample.Pressedmeansthatthebutton’sappearancechangeswhenithasbeenpressedbutbeforetheuserhasletgo.TotellAndroidwhatthethreeimagesareforourbutton,andwhichoneiswhich,wesetupaselector.ThisisasimpleXMLfile,imagebuttonselector,thatresidesinthe/res/drawablefolderofourproject.Thisissomewhatcounterintuitive,becausethisisanXMLfileandnotanimagefile,yetthatiswheretheselectorfilemustgo.ThecontentofaselectorfilewilllooklikeListing3-12.
Listing3-12.UsingaSelectorwithanImageButton
<?xmlversion="1.0"encoding="utf-8"?><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:state_pressed="true"android:drawable="@drawable/button_pressed"/><!--pressed--><itemandroid:state_focused="true"android:drawable="@drawable/button_focused"/><!--focused--><itemandroid:drawable="@drawable/icon"/><!--default--></selector>
Thereareseveralthingstonoteabouttheselectorfile.First,youdonotspecifya<resources>tagasinvaluesXMLfiles.Second,theorderofthebuttonimagesisimportant.Androidwilltesteachitemintheselector,inorder,toseeifitmatches.Therefore,youwantthenormalimagetobelastsoitisusedonlyifthebuttonisnotpressedandifthebuttondoesnothavefocus.Ifthenormalimagewaslistedfirst,itwouldalwaysmatchandbeselectedevenifthebuttonispressedorhasfocus.Ofcourse,thedrawablesyourefertomustexistinthe/res/drawablesfolder.InthedefinitionofyourbuttoninthelayoutXMLfile,youwanttosettheandroid:srcpropertytotheselectorXMLfileasifitwerearegulardrawable,likeso:
<Button...android:src="@drawable/imagebuttonselector".../>
TheToggleButtonControlTheToggleButtoncontrol,likeacheckboxoraradiobutton,isatwo-statebutton.ThisbuttoncanbeineithertheOnorOffstate.AsshowninFigure3-3,the
ToggleButton’sdefaultbehavioristoshowacoloredbarwhenintheOnstateandagrayed-outbarwhenintheOffstate.Moreover,thedefaultbehavioralsosetsthebutton’stexttoOnwhenit’sintheOnstateandOffwhenit’sintheOffstate.YoucanmodifythetextfortheToggleButtonifOn/Offisnotappropriateforyourapplication.Forexample,ifyouhaveabackgroundprocessthatyouwanttostartandstopviaaToggleButton,youcouldsetthebutton’stexttoStopandRunbyusingandroid:textOnandandroid:textOffproperties.
Listing3-13showsanexample.OurtogglebuttonisthebottombuttoninFigure3-3,anditisintheOnposition,sothelabelonthebuttonsaysStop.
Listing3-13.TheAndroidToggleButton
<ToggleButtonandroid:id="@+id/cctglBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="ToggleButton"android:textOn="Stop"android:textOff="Run"/>
BecauseToggleButtonshaveonandofftextasseparateattributes,theandroid:textattributeofaToggleButtonisnotreallyused.It’savailablebecauseithasbeeninherited(fromTextView),butinthiscase,youdon’tneedtouseit.
TheCheckBoxControlTheCheckBoxcontrolisanothertwo-statebuttonthatallowstheusertotoggleitsstate.Thedifferenceisthat,formanysituations,theusersdon’tviewitasabuttonthatinvokesimmediateaction.FromAndroid’spointofview,however,itisabutton,andyoucandoanythingwithacheckboxthatyoucandowithabutton.
InAndroid,youcancreateacheckboxbycreatinganinstanceofandroid.widget.CheckBox.SeeListing3-14andFigure3-4.
Listing3-14.CreatingCheckBoxes
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<CheckBoxandroid:id="@+id/chickenCB"android:text="Chicken"android:checked="true"android:layout_width=""wrap_content"android:layout_height="wrap_content"/>
<CheckBoxandroid:id="@+id/fishCB"
android:text="Fish"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<CheckBoxandroid:id="@+id/steakCB"android:text="Steak"android:checked="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</LinearLayout>
Figure3-4.UsingtheCheckBoxcontrol
YoumanagethestateofacheckboxbycallingsetChecked()ortoggle().YoucanobtainthestatebycallingisChecked().
Ifyouneedtoimplementspecificlogicwhenacheckboxischeckedorunchecked,youcanregisterfortheon-checkedeventbycallingsetOnCheckedChangeListener()withanimplementationoftheCompoundButton.OnCheckedChangeListenerinterface.You’llthenhavetoimplementtheonCheckedChanged()method,whichwillbecalledwhenthecheckboxischeckedorunchecked.Listing3-15showsomecodethatdealswithaCheckBox.
Listing3-15.UsingCheckBoxesinCode
publicclassCheckBoxActivityextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.checkbox);
CheckBoxfishCB=(CheckBox)findViewById(R.id.fishCB);
if(fishCB.isChecked())
fishCB.toggle();//flipsthecheckboxtouncheckedifitwaschecked
fishCB.setOnCheckedChangeListener(newCompoundButton.OnCheckedChangeListener(){
@OverridepublicvoidonCheckedChanged(CompoundButtonarg0,booleanisChecked){Log.v("CheckBoxActivity","Thefishcheckboxisnow"+(isChecked?"checked":"notchecked"));}});}}
ThenicepartofsettinguptheOnCheckedChangeListeneristhatyouarepassedthenewstateoftheCheckBoxbutton.YoucouldinsteadusetheOnClickListenertechniqueasweusedwithbasicbuttons.WhentheonClick()methodiscalled,youwouldneedtodeterminethenewstateofthebuttonbycastingitappropriatelyandthencallingisChecked()onit.Listing3-16showswhatthiscodemightlooklikeifweaddedandroid:onClick=“myClickHandler”totheXMLdefinitionofourCheckBoxbuttons.
Listing3-16.UsingCheckBoxesinCodewithandroid:onClick
publicvoidmyClickHandler(Viewview){switch(view.getId()){caseR.id.steakCB:Log.v("CheckBoxActivity","Thesteakcheckboxisnow"+(((CheckBox)view).isChecked()?"checked":"notchecked"));}}
TheSwitchControlTheSwitchwidgetwasintroducedinAndroid4.0andprovidesverysimilarbehaviortotheCheckBox.Infact,thetwowidgetsaresosimilar,you’llalmostcertainlygetasenseofdejavuwhenreviewingthecodeforaSwitchobject.Manypeople(includingsomeofthisbook’sauthors)believethattheSwitchwasintroducedforaestheticreasonsmorethananything.ThetrendinUIdesigninthelastfewyearshasbeentowardtheskewmorphicidealofwidgetslookinglikereal-worldthings,andaSwitchisaconcreteselectorintherealworld—notmanykitchenapplianceshaveaCheckBoxafterall.
TheSwitchsimilaritiestotheCheckBoxcontrolextendtocommonmethodsforexaminingandchangingstate.ThismimickingofmethodsincludessetChecked()toturntheSwitchon,isChecked()totestcurrentstate,andsoon.OneaestheticdifferenceofferedbytheSwitchwidgetistheabilitytochangetheassociatedtextbetweenstates.Additionalmethodsareavailabletocontrolthistext:
getTextOn():returnsthetextdisplayediftheSwitchison.
getTextOff():returnsthetextdisplayediftheSwitchisoff.
setTextOn():setsthetexttobedisplayediftheSwitchison.Whilegooddesignwouldusuallymeanonewouldn’tchangethetext,thereareafewcaseswherealiveupdateofsomemetricintheswitchtextcanbehelpful.
setTextOff():setsthetexttobedisplayediftheSwitchisoff.
AnexamplelayoutincludingaSwitchisshowninListing3-17.
Listing3-17.CreatingaLayoutUsingaSwitch
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<Switchandroid:id="@+id/switchdemo"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Thisswitchis:off"/>
</LinearLayout>
CautionRememberthat“switch”isareservedwordinJava.SoweuseanIDthatdoesn’tclash.
ThecodeforourexampleSwitchinListing3-18shouldprovokethosefeelingsofdejavuwementionedwithrespecttotheCheckBoxcontrol.
Listing3-18.ControllingSwitchBehaviorinCode
publicclassSwitchDemoextendsActivityimplementsCompoundButton.OnCheckedChangeListener{Switchsw;
@OverridepublicvoidonCreate(Bundleicicle){
super.onCreate(icicle);setContentView(R.layout.main);
sw=(Switch)findViewById(R.id.switchdemo);sw.setOnCheckedChangeListener(this);}
publicvoidonCheckedChanged(CompoundButtonbuttonView,booleanisChecked){if(isChecked){sw.setTextOn("Thisswitchis:on");}else{sw.setTextOff("Thisswitchis:off");}}}
TheresultsofourSwitchworkareshowinginFigure3-5.
Figure3-5.UsingtheSwitchcontrol
TheRadioButtonControlRadioButtoncontrolsareanintegralpartofanyUItoolkit.Aradiobuttongivestheusersseveralchoicesandforcesthemtoselectasingleitem.Toenforcethissingle-selectionmodel,radiobuttonsgenerallybelongtoagroup,andeachgroupisforcedtohaveonlyoneitemselectedatatime.
TocreateagroupofradiobuttonsinAndroid,firstcreateaRadioGroup,andthenpopulatethegroupwithradiobuttons.Listing3-19andFigure3-6showanexample.
Listing3-19.UsingAndroidRadioButtonWidgets
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<RadioGroupandroid:id="@+id/rBtnGrp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical">
<RadioButtonandroid:id="@+id/chRBtn"android:text="Chicken"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<RadioButtonandroid:id="@+id/fishRBtn"android:text="Fish"android:checked="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<RadioButtonandroid:id="@+id/stkRBtn"android:text="Steak"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</RadioGroup>
</LinearLayout>
Figure3-6.Usingradiobuttons
InAndroid,youimplementaradiogroupusingandroid.widget.RadioGroupand
aradiobuttonusingandroid.widget.RadioButton.
Notethattheradiobuttonswithintheradiogroupare,bydefault,uncheckedtobeginwith,althoughyoucansetonetocheckedintheXMLdefinition,aswedidwithFishinListing3-19.Tosetoneoftheradiobuttonstothecheckedstateprogrammatically,youcanobtainareferencetotheradiobuttonandcallsetChecked():
RadioButtonsteakBtn=(RadioButton)this.findViewById(R.id.stkRBtn);steakBtn.setChecked(true);
Youcanalsousethetoggle()methodtotogglethestateoftheradiobutton.AswiththeCheckBoxcontrol,youwillbenotifiedofon-checkedoron-uncheckedeventsifyoucallthesetOnCheckedChangeListener()withanimplementationoftheOnCheckedChangeListenerinterface.Thereisaslightdifferencehere,though.Thisisadifferentclassthanbefore.Thistime,it’stechnicallytheRadioGroup.OnCheckedChangeListenerclassactingfortheRadioGroup,whereasbeforeitwastheCompoundButton.OnCheckedChangeListenerclass.
TheRadioGroupcanalsocontainviewsotherthantheradiobutton.Forexample,Listing3-20addsaTextViewafterthelastradiobutton.Alsonotethatthefirstradiobutton(anotherRadBtn)liesoutsidetheradiogroup.
Listing3-20.ARadioGroupwithMoreThanJustRadioButtons
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<RadioButtonandroid:id="@+id/anotherRadBtn"android:text="Outside"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<RadioGroupandroid:id="@+id/radGrp"android:layout_width="wrap_content"android:layout_height="wrap_content">
<RadioButtonandroid:id="@+id/chRBtn"android:text="Chicken"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<RadioButtonandroid:id="@+id/fishRBtn"android:text="Fish"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<RadioButtonandroid:id="@+id/stkRBtn"android:text="Steak"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<TextViewandroid:text="MyFavorite"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</RadioGroup></LinearLayout>
Listing3-20showsthatyoucanhavenon-RadioButtoncontrolsinsidearadiogroup.Youshouldalsoknowthattheradiogroupcanonlyenforcesingle-selectionontheradiobuttonsinitsowncontainer.Thatis,theradiobuttonwithIDanotherRadBtnwillnotbeaffectedbytheradiogroupshowninListing3-20becauseitisnotoneofthegroup’schildren.
YoucanmanipulatetheRadioGroupprogrammatically.Forexample,youcanobtainareferencetoaradiogroupandaddaradiobutton(orothertypeofcontrol).Listing3-21demonstratesthisconcept.
Listing3-21.AddingaRadioButtontoaRadioGroupinCode
RadioGroupradGrp=(RadioGroup)findViewById(R.id.radGrp);RadioButtonnewRadioBtn=newRadioButton(this);newRadioBtn.setText("Pork");radGrp.addView(newRadioBtn);
Onceauserhascheckedaradiobuttonwithinaradiogroup,theusercannotuncheckitbyclickingitagain.TheonlywaytoclearallradiobuttonsinaradiogroupistocalltheclearCheck()methodontheRadioGroupprogrammatically.
Ofcourse,youwanttodosomethinginterestingwiththeRadioGroup.Youprobablydon’twanttopolleachRadioButtontodeterminewhetherit’schecked.Fortunately,theRadioGrouphasseveralmethodstohelpyouout.WedemonstratethosewithListing3-22.TheXMLforthiscodeisinListing3-20.
Listing3-22.UsingaRadioGroupProgrammatically
publicclassRadioGroupActivityextendsActivity{protectedstaticfinalStringTAG="RadioGroupActivity";
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.radiogroup);
RadioGroupradGrp=(RadioGroup)findViewById(R.id.radGrp);
intcheckedRadioButtonId=radGrp.getCheckedRadioButtonId();
radGrp.setOnCheckedChangeListener(newRadioGroup.OnCheckedChangeListener(){@OverridepublicvoidonCheckedChanged(RadioGrouparg0,intid){switch(id){case-1:Log.v(TAG,"Choicescleared!");break;caseR.id.chRBtn:Log.v(TAG,"ChoseChicken");break;caseR.id.fishRBtn:Log.v(TAG,"ChoseFish");break;caseR.id.stkRBtn:Log.v(TAG,"ChoseSteak");break;default:Log.v(TAG,"Huh?");break;}}});}}
WecanalwaysgetthecurrentlycheckedRadioButtonusinggetCheckedRadioButtonId(),whichreturnstheresourceIDofthecheckeditemor–1ifnothingischecked(possibleifthere’snodefaultandtheuserhasn’tchosenanoptionyet).WeshowedthisinouronCreate()methodpreviously,butinreality,you’dwanttouseitattheappropriatetimetoreadtheuser’scurrentchoice.WecanalsosetupalistenertobenotifiedimmediatelywhentheuserchoosesoneoftheRadioButtons.NoticethattheonCheckedChanged()methodtakesaRadioGroupparameter,allowingyoutousethesameOnCheckedChangeListenerformultipleRadioGroups.Youmayhavenoticedtheswitchoptionof–1.ThiscanalsooccuriftheRadioGroupisclearedthroughcodeusingclearCheck().
TheImageViewControlOneofthebasiccontrolswehaven’tcoveredyetistheImageViewcontrol.Thisisused
todisplayanimage,wheretheimagecancomefromafile,acontentprovider,oraresourcesuchasadrawable.Youcanevenspecifyjustacolor,andtheImageViewwilldisplaythatcolor.Listing3-23showssomeXMLexamplesofImageViews,followedbysomecodethatshowshowtocreateanImageView.
Listing3-23.ImageViewsinXMLandinCode
<ImageViewandroid:id="@+id/image1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/icon"/>
<ImageViewandroid:id="@+id/image2"android:layout_width="125dip"android:layout_height="25dip"android:src="#555555"/>
<ImageViewandroid:id="@+id/image3"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<ImageViewandroid:id="@+id/image4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/manatee02"android:scaleType="centerInside"android:maxWidth="35dip"android:maxHeight="50dip"/>
ImageViewimgView=(ImageView)findViewById(R.id.image3);
imgView.setImageResource(R.drawable.icon);
imgView.setImageBitmap(BitmapFactory.decodeResource(this.getResources(),R.drawable.manatee14));
imgView.setImageDrawable(Drawable.createFromPath("/mnt/sdcard/dave2.jpg"));
imgView.setImageURI(Uri.parse("file://mnt/sdcard/dave2.jpg"));
Inthisexample,wehavefourimagesdefinedinXML.Thefirstissimplytheiconforourapplication.Thesecondisagraybarthatiswiderthanitistall.ThethirddefinitiondoesnotspecifyanimagesourceintheXML,butweassociateanIDwiththisone(image3)thatwecanusefromourcodetosettheimage.Thefourthimageisanotherofourdrawableimagefileswherewenotonlyspecifythesourceoftheimagefilebutalsosetthemaximumdimensionsoftheimageonthescreenanddefinewhattodoiftheimageislargerthanourmaximumsize.Inthiscase,wetelltheImageViewtocenterandscaletheimagesoitfitsinsidethesizewespecified.
IntheJavacodeofListing3-23weshowseveralwaystosettheimageofimage3.We
firstofcoursemustgetareferencetotheImageViewbyfindingitusingitsresourceID.Thefirstsettermethod,setImageResource(),simplyusestheimage’sresourceIDtolocatetheimagefiletosupplytheimageforourImageView.ThesecondsetterusestheBitmapFactorytoreadinanimageresourceintoaBitmapobjectandthensetstheImageViewtothatBitmap.NotethatwecouldhavedonesomemodificationstotheBitmapbeforeapplyingittoourImageView,butinourcase,weuseditasis.Inaddition,theBitmapFactoryhasseveralmethodsofcreatingaBitmap,includingfromabytearrayandanInputStream.YoucouldusetheInputStreammethodtoreadanimagefromawebserver,createtheBitmapimage,andthensettheImageViewfromthere.
ThethirdsettingusesaDrawableforourimagesource.Inthiscase,we’reshowingthesourceoftheimagecomingfromtheSDcard.You’llneedtoputsomesortofimagefileoutontheSDcardwiththepropernameforthistoworkforyou.SimilartoBitmapFactory,theDrawableclasshasafewdifferentwaystoconstructDrawables,includingfromanXMLstream.
ThefinalsettermethodtakestheURIofanimagefileandusesthatastheimagesource.Forthislastcall,don’tthinkthatyoucanuseanyimageURIasthesource.Thismethodisreallyonlyintendedtobeusedforlocalimagesonthedevice,notforimagesthatyoumightfindthroughHTTP.TouseInternet-basedimagesasthesourceforyourImageView,you’dmostlikelyuseBitmapFactoryandanInputStream.
DateandTimeControlsDateandtimecontrolsarecommoninmanywidgettoolkits.Androidoffersseveraldate-andtime-basedcontrols,someofwhichwe’lldiscussinthissection.Specifically,wearegoingtointroducetheDatePicker,TimePicker,DigitalClock,andAnalogClockcontrols.
TheDatePickerandTimePickerControlsAsthenamessuggest,youusetheDatePickercontroltoselectadateandtheTimePickercontroltopickatime.Listing3-24andFigure3-7showexamplesofthesecontrols.
Listing3-24.TheDatePickerandTimePickerControlsinXML
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<TextViewandroid:id="@+id/dateDefault"android:layout_width="fill_parent"android:layout_height="wrap_content"/>
<DatePickerandroid:id="@+id/datePicker"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<TextViewandroid:id="@+id/timeDefault"android:layout_width="fill_parent"android:layout_height="wrap_content"/>
<TimePickerandroid:id="@+id/timePicker"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</LinearLayout>
Figure3-7.TheDatePickerandTimePickerUIs
IfyoulookattheXMLlayout,youcanseethatdefiningthesecontrolsiseasy.AswithanyothercontrolintheAndroidtoolkit,youcanaccessthecontrolsprogrammaticallytoinitializethemortoretrievedatafromthem.Forexample,youcaninitializethesecontrolsasshowninListing3-23.
Listing3-25.InitializingtheDatePickerandTimePickerwithDateandTime,
Respectively
publicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.datetimepicker);
TextViewdateDefault=(TextView)findViewById(R.id.dateDefault);TextViewtimeDefault=(TextView)findViewById(R.id.timeDefault);
DatePickerdp=(DatePicker)this.findViewById(R.id.datePicker);//Themonth,andjustthemonth,iszero-based.Add1fordisplay.dateDefault.setText("Datedefaultedto"+(dp.getMonth()+1)+"/"+dp.getDayOfMonth()+"/"+dp.getYear());//Andhere,subtract1fromDecember(12)tosetittoDecemberdp.init(2008,11,10,null);
TimePickertp=(TimePicker)this.findViewById(R.id.timePicker);
java.util.FormattertimeF=newjava.util.Formatter();timeF.format("Timedefaultedto%d:%02d",tp.getCurrentHour(),tp.getCurrentMinute());timeDefault.setText(timeF.toString());
tp.setIs24HourView(true);tp.setCurrentHour(newInteger(10));tp.setCurrentMinute(newInteger(10));}
Listing3-25setsthedateontheDatePickertoDecember10,2008.Notethatforthemonth,theinternalvalueiszero-based,whichmeansthatJanuaryis0andDecemberis11.FortheTimePicker,thenumberofhoursandminutesissetto10.Notealsothatthiscontrolsupports24-hourview.Ifyoudonotsetvaluesforthesecontrols,thedefaultvalueswillbethecurrentdateandtimeasknowntothedevice.
Finally,notethatAndroidoffersversionsofthesecontrolsasmodalwindows,suchasDatePickerDialogandTimePickerDialog.Thesecontrolsareusefulifyouwanttodisplaythecontroltotheuserandforcetheusertomakeaselection.We’llcoverdialogsinmoredetailinChapter8.
TheTextClockandAnalogClockControls
AndroidalsooffersTextClockandAnalogClockcontrols(seeFigure3-8).
Figure3-8.UsingtheAnalogClockandDigitalClock
Asshown,thetextclocksupportssecondsinadditiontohoursandminutes.TheanalogclockinAndroidisatwo-handedclock,withonehandforthehourindicatorandtheotherhandfortheminuteindicator.Toaddthesetoyourlayout,usetheXMLasshowninListing3-26.
Listing3-26.AddingaDigitalClockoranAnalogClockinXML
<TextClockandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:format12Hour="hh:mm:ssaa"android:format24Hour="kk:mm:ss"/>
<AnalogClockandroid:layout_width="wrap_content"android:layout_height="wrap_content"/>
Thesetwocontrolsarereallyjustfordisplayingthecurrenttime,astheydon’tletyoumodifythedateortime.Inotherwords,theyarecontrolswhoseonlycapabilityistodisplaythecurrenttime.Thus,ifyouwanttochangethedateortime,you’llneedtosticktotheDatePicker/TimePickerorDatePickerDialog/TimePickerDialog.Thenicepartaboutthesetwoclocks,though,isthattheywillupdatethemselveswithoutyouhavingtodoanything.Thatis,thesecondstickawayintheTextClock,andthehandsmoveontheAnalogClockwithoutanythingextrafromus.
TheMapViewControlWiththeintroductionofGooglePlayServices,Android’sapproachtodisplayingmap-baseddataunderwentsomechanges.However,thevastmajorityofdevelopersstillfavor
theoriginalMapViewControlforarangeofreasons—backwardcompatibility,simplicity,andsoon.Asthenamesuggests,thecom.google.android.maps.MapViewcontrolcandisplayamap.YoucaninstantiatethiscontroleitherviaXMLlayoutorcode,buttheactivitythatusesitmustextendMapActivity.MapActivitytakescareofmultithreadingrequeststoloadamap,performcaching,andsoon.
NoteStrictly,theMapViewispartoftheGoogleAPI,notthestockAndroidAPI.Inordertotestcodeetc.forMapView,ensureyouremulatoriscreatedagainstaversionoftheSDKwiththeGoogleAPIsincluded.
Listing3-27showsanexampleinstantiationofaMapView.
Listing3-27.CreatingaMapViewControlviaXMLLayout
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<com.google.android.maps.MapViewandroid:layout_width="fill_parent"android:layout_height="fill_parent"android:enabled="true"android:clickable="true"android:apiKey="myAPIKey"/>
</LinearLayout>
We’lldiscussthelocation-basedservicesindetailinChapter19.Thisisalsowhereyou’lllearnhowtoobtainyourownmappingAPIkey.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
http://www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch03_Controls.zip.ThisZIPfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoEclipsefromoneoftheseZIPfiles.
http://developer.android.com/resources/articles/index.htmlSeveral“LayoutTricks”–typetechnicalarticlesthatarewellworth
reading.TheygetintoperformanceaspectsofdesigningandbuildingUIsinAndroid.LookforotherarticlesinthislistrelatedtobuildingUIs.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutbuildinguserinterfaces:
HowXMLresourcesdefineUIappearances,andhowcodefillsinthedata
ThefullrangeofbasicUserInterfacecontrolsavailableinAndroid
Ahintofwhat’stocomewithListviewsinChapter4andLayoutsinChapter5.
Chapter4
AdaptersandListControlsInChapter3,weintroducedarangeofbasicUserInterfacecontrolswithwhichyoucanconstructAndroidapplications.IfyourecalltheexamplesonTextView,oneofthetypesofcontrolsweexploredwastheAutoCompleteTextView,whichwhencoupledwithasourceofdata—anadapter—wasabletoprompttheuserwitharangeofpredeterminedvalues.Inthischapter,we’llexploreadaptersfurther,andthewidertopicoflistcontrolsthatenableconstructionofmoreelaborateandsophisticatedscreendesigns.
UnderstandingAdaptersBeforewegetintothedetailsoflistcontrolsofAndroid,weneedtotalkaboutadapters.Listcontrolsareusedtodisplaycollectionsofdata.Butinsteadofusingasingletypeofcontroltomanageboththedisplayandthedata,Androidseparatesthesetworesponsibilitiesintolistcontrolsandadapters.Listcontrolsareclassesthatextendandroid.widget.AdapterViewandincludeListView,GridView,Spinner,andGallery(seeFigure4-1).
Figure4-1.AdapterViewclasshierarchy
AdapterViewitselfextendsandroid.widget.ViewGroup,whichmeansthatListView,GridView,andsoonarecontainercontrols.Inotherwords,listcontrolscontaincollectionsofchildviews.ThepurposeofanadapteristomanagethedataforanAdapterViewandtoprovidethechildviewsforit.Let’sseehowthisworksbyexaminingtheSimpleCursorAdapter.
GettingtoKnowSimpleCursorAdapterTheSimpleCursorAdapterisdepictedinFigure4-2.
Figure4-2.TheSimpleCursorAdapter
Thisisaveryimportantpicturetounderstand.OntheleftsideistheAdapterView;inthisexample,itisaListViewmadeupofTextViewchildren.Ontherightsideisthedata;inthisexample,it’srepresentedasaresultsetofdatarowsthatcamefromaqueryagainstacontentprovider.
TomapthedatarowstotheListView,theSimpleCursorAdapterneedstohaveachildlayoutresourceID.Thechildlayoutmustdescribethelayoutforeachofthedataelementsfromtherightsidethatshouldbedisplayedontheleftside.Alayoutinthiscaseisjustlikethelayoutswe’vebeenworkingwithforouractivities,butitonlyneedstospecifythelayoutofasinglerowofourListView.Forexample,ifyouhavearesultsetofinformationfromtheContactscontentprovider,andyouonlywanttodisplayeachcontactnameinyourListView,youwouldneedtoprovidealayouttodescribewhatthenamefieldshouldlooklike.IfyouwantedtodisplaythenameandanimagefromtheresultsetineachrowoftheListView,yourlayoutmustsayhowtodisplaythenameandtheimage.
Thisdoesnotmeanyoumustprovidealayoutspecificationforeveryfieldinyourresultset,nordoesitmeanyoumusthaveapieceofdatainyourresultsetforeverythingyouwanttoincludeineachrowoftheListView.Forexample,we’llshowyouinabithowyoucanhavecheckboxesinyourListViewforselectingrows,andthosecheckboxesdon’tneedtobesetfromdatainaresultset.We’llalsoshowyouhowtogettodataintheresultsetthatisnotpartoftheListView.Andalthoughwe’vejusttalkedaboutListViews,TextViews,cursors,andresultsets,pleasekeepinmindthattheadapterconceptismoregeneralthanthis.Theleftsidecanbeagallery,andtherightsidecanbeasimplearrayofimages.Butlet’skeepthingsfairlysimplefornowandlookatSimpleCursorAdapterinmoredetail.
ThesimplestconstructorofSimpleCursorAdapterlookslikethis:
SimpleCursorAdapter(Contextcontext,intchildLayout,Cursorc,String[]from,int[]to)
Thisadapterconvertsarowfromthecursortoachildviewforthecontainercontrol.ThedefinitionofthechildviewisdefinedinanXMLresource(childLayoutparameter).Notethatbecausearowinthecursormighthavemanycolumns,youtelltheSimpleCursorAdapterwhichcolumnsyouwanttoselectfromtherowbyspecifyinganarrayofcolumnnames(usingthefromparameter).
Similarly,becauseeachcolumnyouselectmustbemappedtoaViewinthelayout,youmustspecifytheIDsinthetoparameter.There’saone-to-onemappingbetweenthecolumnyouselectandaViewthatdisplaysthedatainthecolumn,sothefromandtoparameterarraysmusthavethesamenumberofelements.Aswementionedbefore,thechildviewcouldcontainothertypesofviews;theydon’thavetobeTextViews.YoucoulduseanImageView,forexample.
ThereisacarefulcollaborationgoingonbetweentheListViewandouradapter.WhentheListViewwantstodisplayarowofdata,itcallsthegetView()methodoftheadapter,passinginthepositiontospecifytherowofdatatobedisplayed.Theadapterrespondsbybuildingtheappropriatechildviewusingthelayoutthatwassetintheadapter’sconstructorandbypullingthedatafromtheappropriaterecordintheresultset.TheListView,therefore,doesn’thavetodealwithhowthedataexistsontheadapterside;itonlyneedstocallforchildviewsasneeded.Thisisacriticalpoint,becauseitmeansourListViewdoesn’tnecessarilyneedtocreateeverychildviewforeverydatarow.Itreallyonlyneedstohaveasmanychildviewsasarenecessaryforwhat’svisibleinthedisplaywindow.Ifonlytenrowsarebeingdisplayed,technicallytheListViewneedstohaveonlytenchildlayoutsinstantiated,eveniftherearehundredsofrecordsinourresultset.Inreality,morethantenchildlayoutsgetinstantiated,becauseAndroidusuallykeepsextrasonhandtomakeitfastertobringanewrowtovisibility.TheconclusionyoushouldreachisthatthechildviewsmanagedbytheListViewcanberecycled.We’lltalkmoreaboutthatalittlelater.
Figure4-2revealssomeflexibilityinusingadapters.Becausethelistcontrolusesanadapter,youcansubstitutevarioustypesofadaptersbasedonyourdataandchildview.Forexample,ifyouarenotgoingtopopulateanAdapterViewfromacontentproviderordatabase,youdon’thavetousetheSimpleCursorAdapter.Youcanoptforaneven“simpler”adapter—theArrayAdapter.
GettingtoKnowArrayAdapterTheArrayAdapteristhesimplestoftheadaptersinAndroid.ItspecificallytargetslistcontrolsandassumesthatTextViewcontrolsrepresentthelistitems(thechildviews).CreatinganewArrayAdaptercanlookassimpleasthis:
ArrayAdapter<String>adapter=newArrayAdapter<String>
(this,android.R.layout.simple_list_item_1,newString[]{"Dave","Satya","Dylan"});
Westillpassthecontext(this)andachildLayoutresourceID.Butinsteadofpassingafromarrayofdatafieldspecifications,wepassinanarrayofstringsastheactualdata.Wedon’tpassacursororatoarrayofViewresourceIDs.TheassumptionhereisthatourchildlayoutconsistsofasingleTextView,andthat’swhattheArrayAdapterwilluseasthedestinationforthestringsthatareinourdataarray.
Nowwe’regoingtointroduceaniceshortcutforthechildLayoutresourceID.Insteadofcreatingourownlayoutfileforthelistitems,wecantakeadvantageofpredefinedlayoutsinAndroid.NoticethattheprefixontheresourceforthechildlayoutresourceIDisandroid.Insteadoflookinginourlocal/resdirectory,Androidlooksinitsown.YoucanbrowsetothisfolderbynavigatingtotheAndroidSDKfolderandlookingunderplatforms/<android-version>/data/res/layout.Thereyou’llfindsimple_list_item_1.xmlandcanseeinsidethatitdefinesasimpleTextView.ThatTextViewiswhatourArrayAdapterwillusetocreateaview(initsgetView()method)togivetotheListView.Feelfreetobrowsethroughthesefolderstofindpredefinedlayoutsforallsortsofuses.We’llbeusingmoreoftheselater.
ArrayAdapterhasotherconstructors.IfthechildLayoutisnotasimpleTextView,youcanpassintherowlayoutresourceIDplustheresourceIDoftheTextViewtoreceivethedata.Whenyoudon’thaveaready-madearrayofstringstopassin,youcanusethecreateFromResource()method.Listings4-1,4-2,and4-3showanexampleinwhichwecreateanArrayAdapterforaspinner.
Listing4-1.ManifestFragmentforCreatinganArrayAdapterfromaString-ResourceFile
<Spinnerandroid:id="@+id/spinner"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
Listing4-2.CodeFragmentforCreatinganArrayAdapterfromaString-ResourceFile
Spinnerspinner=(Spinner)findViewById(R.id.spinner);
ArrayAdapter<CharSequence>adapter=ArrayAdapter.createFromResource(this,R.array.planets,android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
Listing4-3.TheActualString-ResourceFile
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/values/planets.xml--><resources><string-arrayname="planets"><item>Mercury</item><item>Venus</item><item>Earth</item><item>Mars</item><item>Jupiter</item><item>Saturn</item><item>Uranus</item><item>Neptune</item></string-array></resources>
ThefirstlistingistheXMLlayoutforaspinner.ThesecondJavalistinginshowshowyoucancreateanArrayAdapterwhosedatasourceisdefinedinastringresourcefile.UsingthismethodallowsyoutonotonlyexternalizethecontentsofthelisttoanXMLfilebutalsouselocalizedversions.We’lltalkaboutspinnersalittlelater,butfornow,knowthataspinnerhasaviewtoshowthecurrentlyselectedvalue,plusalistviewtoshowthevaluesthatcanbeselectedfrom.It’sbasicallyadrop-downmenu.Listing4-3istheXMLresourcefilecalled/res/values/planets.xml,whichisreadintoinitializetheArrayAdapter.
WorthmentioningisthattheArrayAdapterallowsfordynamicmodificationstotheunderlyingdata.Forexample,theadd()methodwillappendanewvalueontheendofthearray.Theinsert()methodwilladdanewvalueataspecifiedpositionwithinthearray.Andremove()takesanobjectoutofthearray.Youcanalsocallsort()toreorderthearray.Ofcourse,onceyou’vedonethis,thedataarrayisoutofsyncwiththeListView,sothat’swhenyoucallthenotifyDataSetChanged()methodoftheadapter.ThismethodwillresynctheListViewwiththeadapter.
ThefollowinglistsummarizestheadaptersthatAndroidprovides:
ArrayAdapter<T>:Thisisanadapterontopofagenericarrayofarbitraryobjects.It’smeanttobeusedwithaListView.
CursorAdapter:Thisadapter,alsomeanttobeusedinaListView,providesdatatothelistviaacursor.
SimpleAdapter:Asthenamesuggests,thisadapterisasimpleadapter.Itisgenerallyusedtopopulatealistwithstaticdata(possiblyfromresources).
ResourceCursorAdapter:ThisadapterextendsCursorAdapterandknowshowtocreateviewsfromresources.
SimpleCursorAdapter:Thisadapterextends
ResourceCursorAdapterandcreatesTextView/ImageViewviewsfromthecolumnsinthecursor.Theviewsaredefinedinresources.
We’vecoveredenoughofadapterstostartshowingyousomerealexamplesofworkingwithadaptersandlistcontrols(alsoknownasAdapterViews).Let’sgettoit.
UsingAdapterswithAdapterViewsNowthatyou’vebeenintroducedtoadapters,itistimetoputthemtoworkforus,providingdataforlistcontrols.Inthissection,we’regoingtofirstcoverthebasiclistcontrol,theListView.Then,we’lldescribehowtocreateyourowncustomadapter,andfinally,we’lldescribetheothertypesoflistcontrols:GridViews,spinners,andthegallery.
TheBasicListControl:ListViewTheListViewcontroldisplaysalistofitemsvertically.Thatis,ifwe’vegotalistofitemstoviewandthenumberofitemsextendsbeyondwhatwecancurrentlyseeinthedisplay,wecanscrolltoseetherestoftheitems.YougenerallyuseaListViewbywritinganewactivitythatextendsandroid.app.ListActivity.ListActivitycontainsaListView,andyousetthedatafortheListViewbycallingthesetListAdapter()method.
Aswedescribedpreviously,adapterslinklistcontrolstothedataandhelppreparethechildviewsforthelistcontrol.ItemsinaListViewcanbeclickedtotakeimmediateactionorselectedtoactonthesetofselecteditemslater.We’regoingtostartreallysimpleandthenaddfunctionalityaswego.
DisplayingValuesinaListViewFigure4-3showsaListViewcontrolinitssimplestform.
Figure4-3.UsingtheListViewcontrol
Forthisexercise,wewillplaceaListViewintoadefaultAndroidlayout,withnospecialtweaksorchanges,soyoucanseehowtheyfitwithinatypicalmainlayoutXMLfile.Listing4-4showstheJavacodeforouractivity.
Listing4-4.AddingItemstoaListView
publicclassMainActivityextendsActivity{privateListViewlistView1;privateArrayAdapter<String>listAdapter1;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
listView1=(ListView)findViewById(R.id.listView1);
String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));
listAdapter1=newArrayAdapter<String>(this,android.R.id.text1,colorArrayList);
listView1.setAdapter(listAdapter1);
}...}
Listing4-2createsaListViewcontrolpopulatedwiththelistofcolorswespecifyinanarray,someColors.Inourexample,wetakethecontentsofthearrayandmaptheStringcolornamestoaTextViewcontrol(android.R.id.text1).Afterthat,wecreateanarrayadapterandsetthelist’sadapter.TheadapterclasshasthesmartstotaketherowsinwhateverdatasourceyouprovidetopopulatetheUI.
WecouldhavetakenadvantageoftheverybasicListActivitysupplyingthemainlayout,astherearenootherUIelementsorcomplexitytotakecareof.Howeverwe’vechosentodeploytheListViewwithinatypicalnewprojectandutilizethebasicactivity.We’realsousinganAndroid-providedlayoutforourchildview(resourceIDandroid.R.layout.simple_list_item_1),whichcontainsanAndroid-providedTextView(resourceIDandroid.R.id.text1).Allinall,prettysimpletosetup.
Wecanextendthisexample,andyourunderstanding,byshowinghowtoreplacetheAndroid-providedlayoutforthechildviewwithoneofourowndesign.Createanewemptyfileintheres/layoutfolderofyourprojectandnameitsimple_list_row.xml.Listing4-5showstheXMLforourownlayoutforasimpleTextViewtorepresenteachlinetoberenderedinourListView(oranyotherlayoutthatreferstothissimple_list_rowlayout).
Listing4-5.CreatingaCustomTextViewChildViewforListRendering
<TextViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/rowTextView"android:layout_width="fill_parent"android:layout_height="wrap_content"android:padding="12dp"android:textSize="24sp"></TextView>
WeneedonlychangethereferencethatbindsthechosenlayoutforuseintheListViewinourcode,tousethenewsimple_list_rowlayout,likeso:
listAdapter1=newArrayAdapter<String>(this,R.layout.simple_list_row,colorArrayList);
Notethatwhenwerefertoourowncustomlayoutinthisway,wedroptheleading“android”reference.Wecannowrunthisexampletoseethecompleteeffect,asshowninFigure4-4.
Figure4-4.TheListViewexampleinaction
ClickableItemsinaListViewOfcourse,whenyourunthisexample,you’llseethatyou’reabletoscrollupanddownthelisttoseeallyourcolornames,butthat’saboutit.Whatifwewanttodosomethingalittlemoreinterestingwiththisexample,likehavetheapplicationrespondwhenauserclicksoneoftheitemsinourListView?Listing4-6showsamodificationtoourexampletoacceptuserinput.
Listing4-6.AcceptingUserInputonaListView
publicclassMainActivityextendsActivity{
privateListViewlistView1;privateArrayAdapter<String>listAdapter1;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
listView1=(ListView)findViewById(R.id.listView1);
String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));
listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,colorArrayList);
listView1.setAdapter(listAdapter1);
listView1.setOnItemClickListener(newOnItemClickListener(){
@OverridepublicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){StringitemValue=(String)listView1.getItemAtPosition(position);Toast.makeText(getApplicationContext(),itemValue,Toast.LENGTH_LONG).show();}});}...}
OuractivityisnowimplementingtheOnItemClickListenerinterface,whichmeanswe’llreceiveacallbackwhentheuserclickssomethinginourListView.AsyoucanseebyouronItemClick()method,wegetalotofinformationaboutwhatwasclicked,includingtheviewreceivingtheclick,thepositionoftheclickeditemintheListView,andtheIDoftheitemaccordingtoouradapter.WecastaccordinglybeforecallingthemakeText()methodtoworkwiththecolor’sname.ThepositionvaluerepresentswherethisitemisinrelationtotheoveralllistofitemsintheListView,andit’szero-based.Therefore,thefirstiteminthelistisatposition0.
TheIDvaluedependsentirelyontheadapterandthesourceofthedata.Inourexample,wehappentobequeryingstringswiththenamesofcolorsinanarray,sotheIDaccordingtothisadapteristhepositionoftheentryinthearrayfromthecontentprovider.Butyourdatasourceinothersituationsmaynotbeasstraightforwardasthis,soyoushouldnot
thinkthatyoucanalwaysknowthingslikeorderinginadvanceaswe’vedoneinthisexample.IfwewereusinganSimpleCursorAdapterthathadreaditsvaluesfromthesystem’sContactsdatabase,theIDgiventouswillbetheunderlying_IDoftherecord,andthatcouldbeanyvaluedependingontheageofthecontactinthesystem.
WhenwediscussedArrayAdaptersbefore,wementionedthenotifyDataSetChanged()methodtohavetheadapterupdatetheListViewifthedatahaschanged.Someadapters,suchastheSimpleCursorAdapter,areawareofupdatesthathappentounderlyingdatasourcessuchastheContactscontentproviderandwilldynamicallyupdateListViewcontentsforyoubasedonchanges.WithArrayAdapters,however,youwillneedtoinvokethenotifyDataSetChanged()methodyourself.
Thatwasprettyeasytodo.WegeneratedourownListViewofcolornames,andbyclickingacolorweshowedamessagetotheuser.Butwhatifwewanttoselectabunchofnamesfirstandthendosomethingwiththesubsetofpeople?Forthenextexampleapplication,we’regoingtomodifythelayoutofalistitemtoincludeacheckbox,andwe’regoingtoaddabuttontotheUItothenactonthesubsetofselecteditems.
AddingOtherControlswithaListViewIfyouwantadditionalcontrolsinyourmainlayout,youcanprovideyourownlayoutXMLfile,putinaListView,andaddotherdesiredcontrols.Forexample,youcouldaddabuttonbelowtheListViewintheUItosubmitanactionontheselecteditems,asshowninFigure4-5.
Figure4-5.Anadditionalbuttonthatletstheusersubmittheselecteditem(s)
ThemainlayoutforthisexampleisinListing4-7,anditcontainstheUIdefinitionoftheactivity—theListViewandtheButton.
Listing4-7.OverridingtheListViewReferencedbyOurActivity
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.artifexdigital.android.listviewdemo3.MainActivity">
<ListViewandroid:id="@+id/listView1"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"/>
<Buttonandroid:id="@+id/button1"android:layout_width="match_parent"android:layout_height="wrap_content"android:onClick="doClick"android:text="Submitselection"/>
</LinearLayout>
NoticethewaywehavetospecifytheheightandweightoftheListViewinLinearLayout.WewantourbuttontoappearonthescreenatalltimesnomatterhowmanyitemsareinourListView,andwedon’twanttobescrollingallthewaytothebottomofthepagejusttofindthebutton.Toaccomplishthis,wesetthelayout_heighttowrap_contentandthenuselayout_weighttosaythatthiscontrolshouldtakeupallavailableroomfromtheparentcontainer.ThistrickallowsroomforthebuttonandretainsourabilitytoscrolltheListView.We’lltalkmoreaboutlayoutsandweightslaterinthischapter.
TheactivityimplementationwouldthenlooklikeListing4-8.
Listing4-8.ReadingUserInputfromtheListActivity
publicclassMainActivityextendsActivity{
privateListViewlistView1;privateArrayAdapter<String>listAdapter1;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
listView1=(ListView)findViewById(R.id.listView1);
String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));
listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_checked,colorArrayList);
listView1.setAdapter(listAdapter1);
listView1.setChoiceMode(listView1.CHOICE_MODE_MULTIPLE);
listView1.setOnItemClickListener(newOnItemClickListener(){
@OverridepublicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){StringitemValue=(String)listView1.getItemAtPosition(position);Toast.makeText(getApplicationContext(),itemValue,Toast.LENGTH_LONG).show();}});}
publicvoiddoClick(Viewview){intcount=listView1.getCount();SparseBooleanArrayviewItems=listView1.getCheckedItemPositions();for(inti=0;i<count;i++){if(viewItems.get(i)){StringselectedColor=(String)listView1.getItemAtPosition(i);Log.v("ListViewDemo",selectedColor+"ischeckedatposition"+i);}}}}
Withinthesetupoftheadapter,we’repassinganotheroftheAndroid-providedviewsforaListViewlineitem(android.R.layout.simple_list_item_checked),whichresultsineachrowhavingaTextViewandaCheckBox.Ifyoulookinsidethislayoutfile,youwillseeanothersubclassofTextView,thisonecalledCheckedTextView.ThisspecialtypeofTextViewisintendedforusewithListViews.See,wetoldyouthereweresomeinterestingthingsinthatAndroidlayoutfolder!YouwillseethattheIDoftheCheckedTextViewistext1,whichiswhatweneededtopassinourviewsarraytotheconstructoroftheSimpleCursorAdapter.
Becausewewanttheusertobeabletoselectourrows,wesetthechoicemodetoCHOICE_MODE_MULTIPLE.Bydefault,thechoicemodeisCHOICE_MODE_NONE.TheotherpossiblevalueisCHOICE_MODE_SINGLE.Ifyouwanttousethatchoicemodeforthisexample,youwouldwanttouseadifferentlayout,mostlikelyandroid.R.layout.simple_list_item_single_choice.
Inthisexample,we’veimplementedabasicbuttonthatcallsthedoClick()methodof
ouractivity.Tokeepthingssimple,wejustwanttowriteouttoLogCatthenamesoftheitemsthatwerecheckedbytheuser.Thegoodnewsisthatthesolutionisprettyeasy;thebadnewsisthatAndroidhasevolvedsothebestsolutiondependsonwhichversionofAndroidyou’retargeting.TheListViewsolutionwe’veshownherehasworkedsinceAndroid1(althoughwetooktheAndroid1.6shortcutonthebuttoncallback).Thatis,thegetCheckedItemPositions()methodisold,butitstillworks.Thereturnvalueisanarraythatcantellyouwhetheranitemhasbeenchecked.So,weiteratethroughthearray.viewItems.get(i)willreturntrueifthecorrespondingrowinourListViewhasbeenchecked.OurdataisaccessibledirectlyfromtheListView,usingthegetItemAtPosition()methodoftheListView.Inourcase,theobjectreturnedfromgetItemAtPosition()wouldturnouttobeaStringobject.Aswesaidbefore,inothersituations,wemightgetsomeothertypeofobject,suchasaCursorWrapper,whenworkingwithsomespecificcontentprovidersliketheContactsproviderdiscussedlaterinthisbook.Youhavetounderstandyourdatasourceandyouradaptertoknowwhattoexpect.
IfwegoaheadandhittheSubmitSelectionbuttonshowninFigure4-5,wecanwatchthelogcatwindowinEclipseorAndroidStudioasitemitsthedatafromourselectionasimplementedinthedoClick()method.ThisisshowninFigure4-6.
Figure4-6.UsinguserinputfromaListViewforfurtherprocessing
AnotherWaytoReadSelectionsfromaListViewAndroid1.6introducedanothermethodforretrievingalistofthecheckedrowsfromaListView:getCheckItemIds().Then,inAndroid2.2,thismethodwasdeprecatedandreplacedwithgetCheckedItemIds().Itwasasubtlenamechange,butthewayyouusethemethodisbasicallythesame.Listing4-9showstheJavacodechangeswe’dmaketoreflectthisevolutionofdealingwithcheckeditemsinalist.FortheXMLlayoutoflist.xml,wecancontinuetousethefileinListing4-7.
Listing4-9.AnotherWayofReadingUserInputfromtheListActivity
publicclassMainActivityextendsActivity{privateListViewlistView1;privateArrayAdapter<String>listAdapter1;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
listView1=(ListView)findViewById(R.id.listView1);
String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));
listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_checked,colorArrayList);
listView1.setAdapter(listAdapter1);
listView1.setChoiceMode(listView1.CHOICE_MODE_MULTIPLE);
listView1.setOnItemClickListener(newOnItemClickListener(){
@OverridepublicvoidonItemClick(AdapterView<?>parent,Viewview,intposition,longid){StringitemValue=(String)listView1.getItemAtPosition(position);Toast.makeText(getApplicationContext(),itemValue,Toast.LENGTH_LONG).show();}});}
...
publicvoiddoClick(Viewview){if(!listAdapter1.hasStableIds()){Log.v(TAG,"Dataisnotstable");return;}long[]viewItems=listView1.getCheckedItemIds();for(inti=0;i<viewItems.length;i++){StringselectedColor=(String)listView1.getItemAtPosition(i);Log.v("ListViewDemo",selectedColor+"ischecked
atposition"+i);}}}}
Inthisexampleapplication,whenweclickthebutton,ourcallbackcallsthemethodgetCheckedItemIds().Whereasinourlastexample,wegotanarrayofpositionsofthecheckeditemsintheListView,thistimewegetanarrayofIDsoftherecordsfromtheadapterthathavebeencheckedintheListView.WecanbypasstheListViewandthecursornow,becausetheIDscanbeusedtodrivewhateveractionwedesire.
We’veshownyouhowtoworkwithListViewsfromavarietyofscenarios.We’veshownthatadaptersdoalotoftheworktosupportaListView.Next,we’llcovertheothertypesoflistcontrols,startingwiththeGridView.
TheGridViewControlMostwidgettoolkitsofferoneormoregrid-basedcontrols.AndroidhasaGridViewcontrolthatcandisplaydataintheformofagrid.Notethatalthoughweusethetermdatahere,thecontentsofthegridcanbetext,images,andsoon.
TheGridViewcontroldisplaysinformationinagrid.TheusagepatternfortheGridViewistodefinethegridintheXMLlayout(seeListings4-10and4-11)andthenbindthedatatothegridusinganandroid.widget.ListAdapter.
Listing4-10.DefinitionofaGridViewinanXMLLayout
<RelativeLayoutxmlns: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="com.artifexdigital.android.gridviewdemo.MainActivity">
<GridViewandroid:id="@+id/gridView1"android:layout_width="fill_parent"android:layout_height="fill_parent"android:padding="10dp"android:verticalSpacing="10dp"android:horizontalSpacing="10dp"android:numColumns="auto_fit"android:columnWidth="100dp"android:stretchMode="columnWidth"android:gravity="center"/>
</RelativeLayout>
Listing4-11.JavaImplementationfortheGridView
publicclassMainActivityextendsActivity{privateGridViewgridView1;privateArrayAdapter<String>listAdapter1;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
gridView1=(GridView)findViewById(R.id.gridView1);
String[]someColors=newString[]{"Red","Orange","Yellow","Green","Blue","Indigo","Violet","Black","White"};
ArrayList<String>colorArrayList=newArrayList<String>();colorArrayList.addAll(Arrays.asList(someColors));
listAdapter1=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,colorArrayList);
gridView1.setAdapter(listAdapter1);
}
}
Listing4-10definesasimpleGridViewinanXMLlayout.Thegridisthenloadedintotheactivity’scontentview.ThegeneratedUIisshowninFigure4-7.
Figure4-7.AGridViewpopulatedwithcolors
ThegridshowninFigure4-7displaysthenamesofthecolorsfromourarray.WehavedecidedtoshowaTextViewwiththecolornames,butyoucouldeasilygenerateagridfilledwithimagesorothercontrols.We’veagaintakenadvantageofpredefinedlayoutsinAndroid.Infact,thisexamplelooksverymuchlikeListing4-7exceptforafewimportantdifferences.WemustcallsetContentView()tosetthelayoutforourGridView;therearenodefaultviewstofallbackon.Andtosettheadapter,wecallsetAdapter()ontheGridViewobjectinsteadofcallingsetListAdapter()onActivity.
You’venodoubtnoticedthattheadapterusedbythegridisaListAdapter.Listsaregenerallyone-dimensional,whereasgridsaretwo-dimensional.Wecanconclude,then,thatthegridactuallydisplayslist-orienteddata.Anditturnsoutthatthelistisdisplayedbyrows.Thatis,thelistgoesacrossthefirstrow,thenacrossthesecondrow,andsoon.
Asbefore,wehavealistcontrolthatworkswithanadaptertohandlethedatamanagementandthegenerationofthechildviews.ThesametechniquesweusedbeforeshouldworkjustfinewithGridViews.Oneexceptionrelatestomakingselections:thereisnowaytospecifymultiplechoicesinaGridView,aswedidinListing4-7.
TheSpinnerControlTheSpinnercontrolislikeadrop-downmenu.Itistypicallyusedtoselectfromarelativelyshortlistofchoices.Ifthechoicelististoolongforthedisplay,ascrollbarisautomaticallyaddedforyou.YoucaninstantiateaSpinnerviaXMLlayoutassimplyasthis:
<Spinnerandroid:id="@+id/spinner"android:prompt="@string/spinnerprompt"android:layout_width="wrap_content"android:layout_height="wrap_content"
/>
Althoughaspinneristechnicallyalistcontrol,itwillappeartoyoumorelikeasimpleTextViewcontrol.Inotherwords,onlyonevaluewillbedisplayedwhenthespinnerisatrest.Thepurposeofthespinneristoallowtheusertochoosefromasetofpredeterminedvalues:whentheuserclicksthesmallarrow,alistisdisplayed,andtheuserisexpectedtopickanewvalue.Populatingthislistisdoneinthesamewayastheotherlistcontrols:withanadapter.
Becauseaspinnerisoftenusedlikeadrop-downmenu,itiscommontoseetheadaptergetthelistchoicesfromaresourcefile.AnexamplethatsetsupaspinnerusingaresourcefileisshowninListing4-12.Noticethenewattributecalledandroid:promptforsettingapromptatthetopofthelisttochoosefrom.Theactualtextforourspinnerpromptisinour/res/values/strings.xmlfile.Asyoushouldexpect,theSpinnerclasshasamethodforsettingthepromptincodeaswell.
Listing4-12.CodetoCreateaSpinnerfromaResourceFile
publicclassSpinnerActivityextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.spinner);
Spinnerspinner=(Spinner)findViewById(R.id.spinner);
ArrayAdapter<CharSequence>adapter=ArrayAdapter.createFromResource(this,R.array.planets,android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);}}
Youmayrecallseeingtheplanets.xmlfileinListing4-1.WeshowinthisexamplehowaSpinnercontroliscreated;theadapterissetupandthenassociatedtothespinner.SeeFigure4-8forwhatthislookslikeinaction.
Figure4-8.Aspinnerforchoosingaplanet
Oneofthedifferencesfromourearlierlistcontrolsisthatwe’vegotanextralayouttocontendwithwhenworkingwithaspinner.TheleftsideofFigure4-8showsthenormalmodeofaspinner,wherethecurrentselectionisshown.Inthiscase,thecurrentselectionisSaturn.Nexttothewordisadownward-pointingarrowindicatingthatthiscontrolisaspinnerandcanbeusedtopopupalisttoselectadifferentvalue.Thefirstlayout,suppliedasaparametertotheArrayAdapter.createFromResource()method,defineshowthespinnerlooksinnormalmode.OntherightsideofFigure4-8,weshowthespinnerinthepop-uplistmode,waitingfortheusertochooseanewvalue.ThelayoutforthislistissetusingthesetDropDownViewResource()method.Againinthisexample,we’reusingAndroid-providedlayoutsforthesetwoneeds,soifyouwanttoinspectthedefinitionofeitheroftheselayouts,youcanvisittheAndroidres/layoutfolder.Andofcourse,youcanspecifyyourownlayoutdefinitionforeitherofthesetogettheeffectyouwant.
TheGalleryControlTheGallerycontrolisahorizontallyscrollablelistcontrolthatalwaysfocusesatthecenterofthelist.Thiscontrolgenerallyfunctionsasaphotogalleryintouchmode.YoucaninstantiateaGalleryviaeitherXMLlayoutorcode:
<Galleryandroid:id="@+id/gallery"
android:layout_width="fill_parent"android:layout_height="wrap_content"/>
TheGallerycontrolistypicallyusedtodisplayimages,soyouradapterislikelygoingtobespecializedforimages.We’llshowyouacustomimageadapterinnextsectiononcustomadapters.Visually,agallerylookslikeFigure4-9.
Figure4-9.Agallerywithimagesofmanatees
SummaryInthischapter,we’veexpandedyourunderstandingandproficiencywithUIcomponentsinthefollowingways:
ThemainlistcontrolsavailableinAndroid
Howtouseadapterstopopulatethedatainalistcontrol
Chapter5
BuildingMoreAdvancedUILayoutsInthepreviouschapterswereviewedmanyofthestandardlayoutsprovidedwithAndroid,whichcoverabroadarrayofpossibleUIapproaches.WhenthestocklayoutsofferedbyAndroiddon'tquitedowhatyouwant,wheredoyouturn?Inthischapter,wewillquicklyexplorehowAndroidprovidesyouwiththeabilitytobuildyourowncustomlayoutsandmanagetherelatedadaptersforpopulatingthemwithusefuldata.
CreatingCustomAdaptersStandardadaptersinAndroidareeasytouse,buttheyhavesomelimitations.Toaddressthis,AndroidprovidesanabstractclasscalledBaseAdapterthatyoucanextendifyouneedacustomadapter.Youwoulduseacustomadapterifyouhadspecialdata-managementneedsorifyouwantedmorecontroloverhowtodisplaychildviews.Youmightalsouseacustomadaptertoimproveperformancebyusingcachingtechniques.We’regoingtoshowyouhowtobuildacustomadapternext.
Listing5-1showswhattheXMLlayoutandtheJavacodecouldlooklikeforacustomadapter.Forthisnextexample,ouradapterisgoingtodealwithimagesofmanatees,sowe’llcallitManateeAdapter.We’regoingtocreateitinsideofanactivityaswell.
Listing5-1.OurCustomAdapter:ManateeAdapter
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisat/res/layout/gridviewcustom.xml--><GridViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/gridview"android:layout_width="fill_parent"android:layout_height="fill_parent"android:padding="10dip"android:verticalSpacing="10dip"android:horizontalSpacing="10dip"android:numColumns="auto_fit"android:gravity="center"/>
JavaImplementation
publicclassGridViewCustomAdapterextendsActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState)
{super.onCreate(savedInstanceState);setContentView(R.layout.gridviewcustom);GridViewgv=(GridView)findViewById(R.id.gridview);ManateeAdapteradapter=newManateeAdapter(this);gv.setAdapter(adapter);}
publicstaticclassManateeAdapterextendsBaseAdapter{privatestaticfinalStringTAG="ManateeAdapter";privatestaticintconvertViewCounter=0;privateContextmContext;privateLayoutInflatermInflater;staticclassViewHolder{ImageViewimage;}
privateint[]manatees={R.drawable.manatee00,R.drawable.manatee01,R.drawable.manatee02,//...manymoremanateeshere-seethesamplecodefolderR.drawable.manatee32,R.drawable.manatee33};
privateBitmap[]manateeImages=newBitmap[manatees.length];privateBitmap[]manateeThumbs=newBitmap[manatees.length];
publicManateeAdapter(Contextcontext){Log.v(TAG,"ConstructingManateeAdapter");this.mContext=context;mInflater=LayoutInflater.from(context);
for(inti=0;i<manatees.length;i++){manateeImages[i]=BitmapFactory.decodeResource(context.getResources(),manatees[i]);manateeThumbs[i]=Bitmap.createScaledBitmap(manateeImages[i],100,100,false);}}
@OverridepublicintgetCount(){Log.v(TAG,"ingetCount()");returnmanatees.length;
}
publicintgetViewTypeCount(){Log.v(TAG,"ingetViewTypeCount()");return1;}
publicintgetItemViewType(intposition){Log.v(TAG,"ingetItemViewType()forposition"+position);return0;}
@OverridepublicViewgetView(intposition,ViewconvertView,ViewGroupparent){ViewHolderholder;
Log.v(TAG,"ingetViewforposition"+position+",convertViewis"+((convertView==null)?"null":"beingrecycled"));
if(convertView==null){convertView=mInflater.inflate(R.layout.gridimage,null);convertViewCounter++;Log.v(TAG,convertViewCounter+"convertViewshavebeencreated");holder=newViewHolder();holder.image=(ImageView)convertView.findViewById(R.id.gridImageView);convertView.setTag(holder);}else{holder=(ViewHolder)convertView.getTag();}
holder.image.setImageBitmap(manateeThumbs[position]);
returnconvertView;}
@OverridepublicObjectgetItem(intposition){Log.v(TAG,"ingetItem()forposition"+position);
returnmanateeImages[position];}
@OverridepubliclonggetItemId(intposition){Log.v(TAG,"ingetItemId()forposition"+position);returnposition;}}}
Whenyourunthisapplication,youshouldseeadisplaythatlookslikeFigure5-1.
Figure5-1.AGridViewwithimagesofmanatees
Thereisalottoexplaininthisexample,eventhoughitlooksrelativelysimple.We’llstartwithourActivityclass,whichlooksalotliketheoneswe’vebeenworkingwiththroughoutthissectionofthechapter.There’samainlayoutfromgridviewcustom.xml,whichcontainsjustaGridViewdefinition.WeneedtogetareferencetotheGridViewfrominsidethelayout,sowedefineandsetgv.We
instantiateourManateeAdapter,passingitourcontext,andwesettheadapteronourGridView.Thisisprettystandardstuffsofar,althoughyou’venodoubtnoticedthatourcustomadapterdoesn’tusenearlyasmanyparametersaspredefinedadapterswhenbeingcreated.Thisismainlybecausewe’reincompletecontroloverthisparticularadapter,andwe’reusingitwithonlythisapplication.Ifweweremakingthisadaptermoregeneral,wewouldmostlikelybesettingmoreparameters.Butlet’skeepgoing.OurjobinsideanadapteristomanagethepassingofdataintoAndroidViewobjects.TheViewobjectswillbeusedbythelistcontrol(aGridViewinthiscase).Thedatacomesfromsomedatasource.Intheearlierexamples,thedatacameviaacursorobjectthatwaspassedintotheadapter.Inourcustomcasehere,ouradapterknowsallaboutthedataandwhereitcomesfrom.ThelistcontrolwillaskforthingssoitknowshowtobuildtheUI.Itisalsokindenoughtopassinviewsforrecyclingwhenithasaviewitnolongerneeds.Itmayseemabitstrangetothinkthatouradaptermustknowhowtoconstructviews,butintheend,itallmakessense.
WhenweinstantiateourcustomadapterManateeAdapter,itiscustomarytopassinthecontextandfortheadaptertoholdontoit.Itisoftenveryusefultohaveitavailablewhenneeded.Thesecondthingwewanttodoinouradapteristohangontotheinflater.Thiswillhelpperformancewhenweneedtocreateanewviewtoreturntothelistcontrol.ThethirdthingthatistypicalinanadapteristocreateaViewHolderobject,tocontaintheViewobjectsforthedatawearemanaging.Takingthisapproachalsoactsasaperformanceoptimization,savingusfromrepeatedlylookinguptheViews.Forthisexample,wearesimplystoringanImageView,butifwehadadditionalfieldstodealwith,wewouldaddthemintothedefinitionofViewHolder.Forexample,ifwehadaListViewwhereeachrowcontainedanImageViewandtwoTextViews,ourViewHolderwouldhaveanImageViewandtwoTextViews.
Becausewe’redealingwithimagesofmanateesinthisadapter,wesetupanarrayoftheirresourceIDstobeusedduringconstructiontocreatebitmaps.Wealsodefineanarrayofbitmapstouseasourdatalist.
AsyoucanseefromourManateeAdapterconstructor,wesavethecontext,createandhangontoaninflater,andthenweiteratethroughtheimageresourceIDsandbuildanarrayofbitmaps.Thisbitmaparraywillbeourdata.
Asyoulearnedpreviously,settingtheadapterwillcauseourGridViewtocallmethodsontheadaptertosetitselfupwithdatatodisplay.Forexample,ourGridViewgvwillcalltheadapter’sgetCount()methodtodeterminehowmanyobjectstherearefordisplaying.ItwillalsocallthegetViewTypeCount()methodtodeterminehowmanydifferenttypesofviewscouldbedisplayedwithintheGridView.Forourpurposesinthisexample,wesetthisto1.However,ifwehadaListViewandwantedtoputseparatorsinbetweenregularrowsofdata,wewouldhavetwotypesofviewsandwouldneedtoreturn2fromgetViewTypeCount().Youcouldhaveasmanydifferentviewtypesasyoulike,aslongasyouappropriatelyreturnthecorrectcountfromthismethod.RelatedtothismethodisgetItemViewType().Wejustsaidthatwecouldhavemorethanonetypeofviewtoreturnfromtheadapter,buttokeepthingssimpler,
getItemViewType()needstoreturnonlyanintegervaluetoindicatewhichofourviewtypesisataparticularpositioninthedata.Therefore,ifwehadtwotypesofviewstoreturn,getItemViewType()wouldneedtoreturneither0or1toindicatewhichtype.Ifwehavethreetypesofviews,thismethodneedstoreturn0,1,or2.
IfouradapterisdealingwithseparatorsinaListView,itmusttreattheseparatorsasdata.Thatmeansthereisapositioninthedatathatistakenupbyaseparator.WhengetView()iscalledbyalistcontroltoretrievetheappropriateviewforthatposition,getView()willneedtoreturnaseparatorasaviewinsteadofregulardataasaview.AndwhenaskedingetItemViewType()fortheviewtypeforthatposition,weneedtoreturntheappropriateintegervaluethatwe’vedecidedmatchesthatviewtype.TheotherthingyoushoulddoifusingseparatorsistoimplementtheisEnabled()method.Thisshouldreturntrueforlistitemsandfalseforseparatorsbecauseseparatorsshouldnotbeselectableorclickable.
ThemostinterestingmethodinManateeAdapteristhegetView()methodcall.OncetheGridViewhasdeterminedhowmanyitemsareavailable,itstartstoaskforthedata.Now,wecantalkaboutrecyclingviews.Alistcontrolcanonlyshowasmanychildviewsonthedisplayaswillfit.Thatmeansthere’snopointincallinggetView()foreverypieceofdataintheadapter;itonlymakessensetocallgetView()forasmanyitemsascanbedisplayed.Asgvgetschildviewsbackfromtheadapter,itisdetermininghowmanywillfitonthedisplay.Whenthedisplayisfullofchildviews,gvcanstopcallinggetView().
IfyoulookatLogCatafterstartingthisexampleapplication,youwillseethevariouscalls,butyouwillalsoseethatgetView()stopsbeingcalledbeforeallimageshavebeenrequested.IfyoustartscrollingupanddowntheGridView,youwillseemorecallstogetView()inLogCat,andyouwillnoticethat,oncewe’vecreatedacertainnumberofchildviews,getView()isbeingcalledwithconvertViewsettosomething,notnull.Thismeanswe’renowrecyclingchildviews—andthat’sverygoodforperformance.
IfwegetanonnullconvertViewvaluefromgvingetView(),itmeansgvisrecyclingthatview.Byreusingtheviewpassedin,weavoidhavingtoinflateanXMLlayout,andweavoidhavingtofindtheImageView.BylinkingaViewHolderobjecttotheViewthatwereturn,wecanbemuchfasteratrecyclingtheviewthenexttimeitcomesbacktous.AllwehavetodoingetView()isreacquiretheViewHolderandassigntherightdataintotheview.
Forthisexample,wewantedtoshowthatthedataplacedintotheviewisnotnecessarilyexactlywhatexistsinthedata.ThecreateScaledBitmap()methodiscreatingasmallerversionofthedatafordisplaypurposes.ThepointisthatourlistcontroldoesnotcallthegetItem()method.Thismethodwouldbecalledbyourothercodethatwantstodosomethingwiththedataiftheuseractsonthelistcontrol.Onceagain,foranyadapter,itisveryimportantthatyouunderstandwhatitisdoing.Youdon’tnecessarilywanttorelyondataintheviewfromthelistcontrol,ascreatedbygetView()intheadapter.Sometimes,youwillneedtocalltheadapter’sgetItem()methodtogettheactualdatatobeoperatedon.Andsometimes,aswedidintheearlierListView
examples,you’llwanttogotoacursorforthedata.Italldependsontheadapterandwherethedataisultimatelycomingfrom.AlthoughweusedthecreateScaledBitmap()methodinourexample,Android2.2introducedanotherclassthatmighthavebeenhelpfulhere:ThumbnailUtils.Thisclasshassomestaticmethodsforgeneratingthumbnailimagesfrombitmapsandvideos.
ThelastthingtopointoutfromthisexampleisthegetItemId()methodcall.InourearlierexampleswithListViewsandcontacts,theitemIDwasthe_IDvaluefromthecontentprovider.Forthisexample,wedon’treallyneedtouseanythingotherthanpositionfortheitemID.ThepointofitemIDsistoprovideamechanismtorefertothedataseparatelyfromitsposition.Thisisespeciallytruewhenthedatahasalifeawayfromthisadapter,asisthecasewithourcontacts.Whenwehavethiskindofdirectcontroloverthedata,aswedowithourimagesofmanatees,andweunderstandhowtogettotheactualdatainourapplication,itisacommonshortcuttosimplyusepositionastheitemID.Thisisparticularlytrueinourcase,becausewedon’tevenallowaddingorremovalofdata.
OtherControlsinAndroidTherearemany,manycontrolsinAndroidthatyoucanuse.We’vecoveredquiteafewsofar,andmorewillbecoveredinlaterchapters(suchasMapViewinChapter19andVideoViewandMediaControllerinChapter20).Youwillfindthattheothercontrols,becausethey’realldescendedfromView,havealotincommonwhattheoneswe’vecoveredhere.Fornow,we’lljustmentionafewofthecontrolsyoumightwanttoexplorefurtheronyourown.
ScrollViewisacontrolforsettingupaViewcontainerwithaverticalscrollbar.Thisisusefulwhenyouhavetoomuchtofitontoasinglescreen.
TheProgressBarandRatingBarcontrolsarelikesliders.Thefirstshowstheprogressofsomeoperationvisually(perhapsafiledownloadormusicplaying),andthesecondshowsaratingscaleofstars.
TheChronometercontrolisatimerthatcountsup.There’saCountDownTimerclassifyouwantsomethingtohelpyoudisplayacountdowntimer,butit’snotaViewclass.
TheSwitchcontrol,whichfunctionslikeaToggleButtonbutvisuallyhasaside-to-sidepresentation,wasintroducedinAndroid4.0,alongwiththeSpaceview,alightweightviewthatcanbeusedinlayoutstomoreeasilycreatespacesbetweenotherviews.
WebViewisaveryspecialviewfordisplayingHTML.Itcandoalotmorethanthat,includinghandlingcookiesandJavaScriptandlinkingtoJavacodeinyourapplication.Butbeforeyougoimplementingawebbrowserinsideyourapplication,youshouldcarefullyconsiderinvokingtheon-devicewebbrowsertoletitdoallthatheavylifting.
Thatcompletesourintroductionofcontrolsinthischapter.We’llnowmoveontostylesandthemesformodifyingthelookandfeelofourcontrolsandthentolayoutsforarrangingourcontrolsonscreens.
StylesandThemesAndroidprovidesseveralwaystoalterthestyleofviewsinyourapplication.We’llfirstcoverusingmarkuptagsinstringsandthenhowtouseSpannablestochangespecificvisualattributesoftext.Butwhatifyouwanttocontrolhowthingslookusingacommonspecificationforseveralviewsoracrossanentireactivityorapplication?We’lldiscussAndroidstylesandthemestoshowyouhow.
UsingStylesSometimes,youwanttohighlightorstyleaportionoftheView’scontent.Youcandothisstaticallyordynamically.Statically,youcanapplymarkupdirectlytothestringsinyourstringresources,asshownhere:
<stringname="styledText"><i>Static</i>styleina<b>TextView</b>.</string>
YoucanthenreferenceitinyourXMLorfromcode.NotethatyoucanusethefollowingHTMLtagswithstringresources:<i>,<b>,and<u>foritalics,bold,andunderlined,respectively,aswellas<sup>(superscript),<sub>(subscript),<strike>(strikethrough),<big>,<small>,and<monospace>.Youcanevennestthesetoget,forexample,smallsuperscripts.ThisworksnotjustinTextViewsbutalsoinotherviews,likebuttons.Figure5-2showswhatstyledandthemedtextlookslike,usingmanyoftheexamplesinthissection.
Figure5-2.Examplesofstylesandthemes
StylingaTextViewcontrol’scontentprogrammaticallyrequiresalittleadditionalworkbutallowsformuchmoreflexibility(seeListing5-2),becauseyoucanstyleitatruntime.ThisflexibilitycanonlybeappliedtoaSpannable,though,whichishowEditTextnormallymanagestheinternaltext,whereasTextViewdoesnotnormallyuseSpannable.SpannableisbasicallyaStringtowhichyoucanapplystyles.TogetaTextViewtostoretextasaSpannable,youcancallsetText()thisway:
tv.setText("ThistextisstoredinaSpannable",TextView.BufferType.SPANNABLE);
Then,whenyoucalltv.getText(),you’llgetaSpannable.
AsshowninListing5-2,youcangetthecontentoftheEditText(asaSpannableobject)andthensetstylesforportionsofthetext.Thecodeinthelistingsetsthetextstylingtoboldanditalicsandsetsthebackgroundtored.YoucanuseallthestylingoptionsaswehavewiththeHTMLtagsasdescribedpreviously,andthensome.
Listing5-2.ApplyingStylesDynamicallytotheContentofanEditText
EditTextet=(EditText)this.findViewById(R.id.et);et.setText("StylingthecontentofanEditTextdynamically");Spannablespn=(Spannable)et.getText();spn.setSpan(newBackgroundColorSpan(Color.RED),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);spn.setSpan(newStyleSpan(android.graphics.Typeface.BOLD_ITALIC),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Thesetwotechniquesforstylingworkonlyontheoneviewthey’reappliedto.Androidprovidesastylemechanismtodefineacommonstyletobereusedacrossviews,aswellasathememechanism,whichbasicallyappliesastyletoanentireactivityortheentireapplication.Tobeginwith,weneedtotalkaboutstyles.
AstyleisacollectionofViewattributesthatisgivenanamesoyoucanrefertothatcollectionbyitsnameandassignthatstylebynametoviews.Forexample,Listing5-3showsaresourceXMLfile,savedin/res/values,thatwecoulduseforallerrormessages.
Listing5-3.DefiningaStyletoBeUsedAcrossManyViews
<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText"><itemname="android:layout_width">fill_parent</item><itemname="android:layout_height">wrap_content</item><itemname="android:textColor">#FF0000</item><itemname="android:typeface">monospace</item></style></resources>
Thesizeoftheviewisdefinedaswellasthefontcolor(red)andtypeface.NoticehowthenameattributeoftheitemtagistheXMLattributenameweusedinourlayoutXMLfiles,andthevalueoftheitemtagnolongerrequiresdoublequotes.WecannowusethisstyleforanerrorTextView,asshowninListing5-4.
Listing5-4.UsingaStyleinaView
<TextViewandroid:id="@+id/errorText"style="@style/ErrorText"android:text="Noerrorsatthistime"/>
ItisimportanttonotethattheattributenameforastyleinthisViewdefinitiondoesnotstartwithandroid:.Watchoutforthis,becauseeverythingseemstouseandroid:exceptthestyle.Whenyou’vegotmanyviewsinyourapplicationthatshareastyle,changingthatstyleinoneplaceismuchsimpler;youneedtomodifythestyle’sattributesonlyintheoneresourcefile.Youcan,ofcourse,createmanydifferentstylesforvarious
controls.Buttonscouldshareacommonstyle,forexample,that’sdifferentfromthecommonstylefortextinmenus.
Onereallyniceaspectofstylesisthatyoucansetupahierarchyofthem.WecoulddefineanewstyleforreallybaderrormessagesandbaseitonthestyleofErrorText.Listing5-5showshowthismightlook.
Listing5-5.DefiningaStylefromaParentStyle
<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText.Danger"><itemname="android:textStyle">bold</item></style></resources>
Thisexampleshowsthatwecansimplynameourchildstyleusingtheparentstyleasaprefixtothenewstylename.Therefore,ErrorText.DangerisachildofErrorTextandinheritsthestyleattributesoftheparent.ItthenaddsanewattributefortextStyle.Thiscanberepeatedagainandagaintocreateawholetreeofstyles.
Aswasthecaseforadapterlayouts,Androidprovidesalargesetofstylesthatwecanuse.TospecifyanAndroid-providedstyle,usesyntaxlikethis:
style="@android:style/TextAppearance"
ThisstylesetsthedefaultstylefortextinAndroid.TolocatethemasterAndroidstyles.xmlfile,visittheAndroidSDK/platforms/<android-version>/data/res/values/folder.Insidethisfile,youwillfindquiteafewstylesthatareready-madeforyoutouseorextend.Here’sawordofcautionaboutextendingtheAndroid-providedstyles:thepreviousmethodofusingaprefixwon’tworkwithAndroid-providedstyles.Instead,youmustusetheparentattributeofthestyletag,likethis:
<stylename="CustomTextAppearance"parent="@android:style/TextAppearance"><item...yourextensionsgohere…/></style>
Youdon’talwayshavetopullinanentirestyleonyourview.Youcouldchoosetoborrowjustapartofthestyleinstead.Forexample,ifyouwanttosetthecolorofthetextinyourTextViewtoasystemstylecolor,youcoulddothefollowing:
<EditTextandroid:id="@+id/et2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textColor="?android:textColorSecondary"android:text="@string/hello_world"/>
Noticethatinthisexample,thenameofthetextColorattributevaluestartswiththe?
[email protected]?characterisusedsoAndroidknowstolookforastylevalueinthecurrenttheme.Becausewesee?android,welookintheAndroidsystemthemeforthisstylevalue.
UsingThemesOneproblemwithstylesisthatyouneedtoaddanattributespecificationofstyle=”@style/…”toeveryviewdefinitionthatyouwantittoapplyto.Ifyouhavesomestyleelementsyouwantappliedacrossanentireactivity,oracrossthewholeapplication,youshoulduseathemeinstead.Athemeisreallyjustastyleappliedbroadly;butintermsofdefiningatheme,it’sexactlylikeastyle.Infact,themesandstylesarefairlyinterchangeable:youcanextendathemeintoastyleorrefertoastyleasatheme.Typically,onlythenamesgiveahintastowhetherastyleisintendedtobeusedasastyleoratheme.
Tospecifyathemeforanactivityoranapplication,addanattributetothe<activity>or<application>tagintheAndroidManifest.xmlfileforyourproject.Thecodemightlooklikethis:
<activityandroid:theme="@style/MyActivityTheme"><applicationandroid:theme="@style/MyApplicationTheme"><applicationandroid:theme="@android:style/Theme.NoTitleBar">
YoucanfindtheAndroid-providedthemesinthesamefolderastheAndroid-providedstyles,withthethemesinafilecalledthemes.xml.Whenyoulookinsidethethemesfile,youwillseealargesetofstylesdefined,withnamesthatstartwithTheme.YouwillalsonoticethatwithintheAndroid-providedthemesandstyles,thereisalotofextendinggoingon,whichiswhyyouendupwithstylescalledTheme.Dialog.AppError,forexample.
ThisconcludesourdiscussionoftheAndroidcontrolset.Aswementionedinthebeginningofthechapter,buildingUIsinAndroidrequiresyoutomastertwothings:thecontrolsetandthelayoutmanagers.Inthenextsection,wearegoingtodiscusstheAndroidlayoutmanagers.
UnderstandingLayoutManagersAndroidoffersacollectionofviewclassesthatactascontainersforviews.Thesecontainerclassesarecalledlayouts(orlayoutmanagers),andeachimplementsaspecificstrategytomanagethesizeandpositionofitschildren.Forexample,theLinearLayoutclasslaysoutitschildreneitherhorizontallyorvertically,oneaftertheother.AlllayoutmanagersderivefromtheViewclass,thereforeyoucannestlayoutmanagersinsideofoneanother.
ThelayoutmanagersthatshipwiththeAndroidSDKincludethecommonlyusedonesdefinedinTable5-1.
Table5-1.AndroidLayoutManagers
LayoutManager Description
LinearLayout Organizesitschildreneitherhorizontallyorvertically
TableLayout Organizesitschildrenintabularform
RelativeLayout Organizesitschildrenrelativetooneanotherortotheparent
FrameLayout Allowsyoutodynamicallychangethecontrol(s)inthelayout
GridLayout Organizesitschildreninagridarrangement
Wewilldiscusstheselayoutmanagersinthesectionsthatfollow.ThelayoutmanagercalledAbsoluteLayouthasbeendeprecatedandwillnotbecoveredinthisbook.
TheLinearLayoutLayoutManagerTheLinearLayoutlayoutmanageristhemostbasic.Thislayoutmanagerorganizesitschildreneitherhorizontallyorverticallybasedonthevalueoftheorientationproperty.We’veusedLinearLayoutinseveralofourexamplessofar.Listing5-6showsLinearLayoutwithahorizontalconfiguration.
Listing5-6.LinearLayoutwithaHorizontalConfiguration
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="wrap_content">
<!--addchildrenhere-->
</LinearLayout>
YoucancreateaverticallyorientedLinearLayoutbysettingthevalueoforientationtovertical.Becauselayoutmanagerscanbenested,youcould,forexample,constructaverticallayoutmanagerthatcontainedhorizontallayoutmanagerstocreateafill-inform,whereeachrowhadalabelnexttoanEditTextcontrol.Eachrowwouldbeitsownhorizontallayout,buttherowsasacollectionwouldbeorganizedvertically.
UnderstandingWeightandGravityTheorientationattributeisthefirstimportantattributerecognizedbytheLinearLayoutlayoutmanager.Otherimportantpropertiesthatcanaffectsizeandpositionofchildcontrolsareweightandgravity.
Youuseweighttoassignsizeimportancetoacontrolrelativetotheothercontrolsinthecontainer.Supposeacontainerhasthreecontrols:onehasaweightof1,whereastheothershaveaweightof0.Inthiscase,thecontrolwhoseweightequals1willconsumetheemptyspaceinthecontainer.Gravityisessentiallyalignment.Forexample,ifyouwanttoalignalabel’stexttotheright,youwouldsetitsgravitytoright.Therearequiteafewpossiblevaluesforgravity,includingleft,center,right,top,bottom,center_vertical,clip_horizontal,andothers.Seedeveloper.android.comfordetailsontheseandtheothervaluesofgravity.
NoteLayoutmanagersextendandroid.widget.ViewGroup,asdomanycontrol-basedcontainerclassessuchasListView.Althoughthelayoutmanagersandcontrol-basedcontainersextendthesameclass,thelayoutmanagerclasses,byconventionifnotstrictrequirement,dealwiththesizingandpositionofcontrolsandnotuserinteractionwithchildcontrols.
Nowlet’slookatanexampleinvolvingtheweightandgravityproperties(seeFigure5-3).
Figure5-3.UsingtheLinearLayoutlayoutmanager
Figure5-3showsthreeUIsthatutilizeLinearLayout,withdifferentweightandgravitysettings.TheUIontheleftusesthedefaultsettingsforweightandgravity.TheXMLlayoutforthisfirstUIisshowninListing5-7.
Listing5-7.ThreeTextFieldsArrangedVerticallyinaLinearLayout,UsingDefaultValuesforWeightandGravity
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<EditTextandroid:layout_width="fill_parent"
android:layout_height="wrap_content"android:text="one"/><EditTextandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="two"/><EditTextandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="three"/></LinearLayout>
TheUIinthecenterofFigure5-3usesthedefaultvalueforweightbutsetsandroid:gravityforthecontrolsinthecontainertoleft,center,andright,respectively.Thelastexamplesetstheandroid:layout_weightattributeofthecentercomponentto1.0andleavestheotherstothedefaultvalueof0.0(seeListing5-8).Bysettingtheweightattributeto1.0forthemiddlecomponentandleavingtheweightattributesfortheothertwocomponentsat0.0,wearespecifyingthatthecentercomponentshouldtakeupalltheremainingwhitespaceinthecontainerandthattheothertwocomponentsshouldremainattheiridealsize.
Similarly,ifyouwanttwoofthethreecontrolsinthecontainertosharetheremainingwhitespaceamongthem,youwouldsettheweightto1.0forthosetwoandleavethethirdoneat0.0.Finally,ifyouwantthethreecomponentstosharethespaceequally,you’dsetalloftheirweightvaluesto1.0.Doingthiswouldexpandeachtextfieldequally.
Listing5-8.LinearLayoutwithWeightConfigurations
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<EditTextandroid:layout_width="fill_parent"android:layout_weight="0.0"android:layout_height="wrap_content"android:text="one"android:gravity="left"/>
<EditTextandroid:layout_width="fill_parent"android:layout_weight="1.0"android:layout_height="wrap_content"android:text="two"android:gravity="center"/>
<EditTextandroid:layout_width="fill_parent"android:layout_weight="0.0"android:layout_height="wrap_content"android:text="three"android:gravity="right"/></LinearLayout>
android:gravityvs.android:layout_gravityNotethatAndroiddefinestwosimilargravityattributes:android:gravityandandroid:layout_gravity.Here’sthedifference:android:gravityisasettingusedbytheview,whereasandroid:layout_gravityisusedbythecontainer(android.view.ViewGroup).Forexample,youcansetandroid:gravitytocentertohavethetextintheEditTextcenteredwithinthecontrol.Similarly,youcanalignanEditTexttothefarrightofaLinearLayout(thecontainer)bysettingandroid:layout_gravity=“right”.SeeFigure5-4andListing5-9.
Figure5-4.Applyinggravitysettings
Listing5-9.UnderstandingtheDifferenceBetweenandroid:gravityandandroid:layout_gravity
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<EditTextandroid:layout_width="wrap_content"android:gravity="center"android:layout_height="wrap_content"android:text="one"android:layout_gravity="right"/></LinearLayout>
AsshowninFigure5-4,thetextiscenteredintheEditText,whichisalignedtotherightoftheLinearLayout.
TheTableLayoutLayoutManagerTheTableLayoutlayoutmanagerisanextensionofLinearLayout.Thislayoutmanagerstructuresitschildcontrolsintorowsandcolumns.Listing5-10showsanexample.
Listing5-10.ASimpleTableLayout
<?xmlversion="1.0"encoding="utf-8"?><TableLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent">
<TableRow><TextViewandroid:text="FirstName:"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<EditTextandroid:text="Edgar"android:layout_width="wrap_content"android:layout_height="wrap_content"/></TableRow>
<TableRow><TextViewandroid:text="LastName:"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<EditTextandroid:text="Poe"android:layout_width="wrap_content"android:layout_height="wrap_content"/></TableRow>
</TableLayout>
Tousethislayoutmanager,youcreateaninstanceofTableLayoutandplaceTableRowelementswithinit.TheseTableRowelementscontainthecontrolsofthetable.TheUIforListing5-10isshowninFigure5-5.
Figure5-5.TheTableLayoutlayoutmanager
ThereareanumberofmorecomplexlayoutspossiblewithTableLayout,includingnesting,asymmetricalrowsandcolumns,andmore.WehaveabonussectiononmoreoptionsforTableLayoutonthebookwebsite,www.androidbook.com.
TheRelativeLayoutLayoutManagerAnotherinterestinglayoutmanagerisRelativeLayout.Asthenamesuggests,thislayoutmanagerimplementsapolicywherethecontrolsinthecontainerarelaidoutrelativetoeitherthecontaineroranothercontrolinthecontainer.Listing5-11andFigure5-6showanexample.
Listing5-11.UsingaRelativeLayoutLayoutManager
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content">
<TextViewandroid:id="@+id/userNameLbl"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Username:"android:layout_alignParentTop="true"/>
<EditTextandroid:id="@+id/userNameText"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_toRightOf="@id/userNameLbl"/>
<TextViewandroid:id="@+id/pwdLbl"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/userNameText"android:text="Password:"/>
<EditTextandroid:id="@+id/pwdText"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_toRightOf="@id/pwdLbl"android:layout_below="@id/userNameText"/>
<TextViewandroid:id="@+id/pwdCriteria"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_below="@id/pwdText"android:text="PasswordCriteria…"/>
<TextViewandroid:id="@+id/disclaimerLbl"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:text="Useatyourownrisk…"/>
</RelativeLayout>
Figure5-6.AUIlaidoutusingtheRelativeLayoutlayoutmanager
Asshown,theUIlookslikeasimpleloginform.Theusernamelabelispinnedtothetopofthecontainer,becausewesetandroid:layout_alignParentToptotrue.Similarly,theUsernameinputfieldispositionedbelowtheUsernamelabelbecausewesetandroid:layout_below.ThePasswordlabelappearsbelowtheUsernamelabel,andthePasswordinputfieldappearsbelowthePasswordlabel.Thedisclaimerlabelispinnedtothebottomofthecontainerbecausewesetandroid:layout_alignParentBottomtotrue.
Besidesthesethreelayoutattributes,youcanalsospecifylayout_above,layout_toRightOf,layout_toLeftOf,layout_centerInParent,andseveralmore.WorkingwithRelativeLayoutisfunduetoitssimplicity.Infact,onceyoustartusingit,it’llbecomeyourfavoritelayoutmanager—you’llfindyourselfgoingbacktoitoverandoveragain.
TheFrameLayoutLayoutManagerThelayoutmanagersthatwe’vediscussedsofarimplementvariouslayoutstrategies.Inotherwords,eachonehasaspecificwaythatitpositionsandorientsitschildrenonthescreen.Withtheselayoutmanagers,youcanhavemanycontrolsonthescreenatonetime,eachtakingupaportionofthescreen.Androidalsooffersalayoutmanagerthatismainlyusedtodisplayasingleitem:FrameLayout.Youmainlyusethisutilitylayoutclasstodynamicallydisplayasingleview,butyoucanpopulateitwithmanyitems,settingonetovisiblewhiletheothersareinvisible.Listing5-12demonstratesusingFrameLayout.
Listing5-12.PopulatingFrameLayout
<?xmlversion="1.0"encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/frmLayout"android:layout_width="fill_parent"android:layout_height="fill_parent">
<ImageView
android:id="@+id/oneImgView"android:src="@drawable/one"android:scaleType="fitCenter"android:layout_width="fill_parent"android:layout_height="fill_parent"/><ImageViewandroid:id="@+id/twoImgView"android:src="@drawable/two"android:scaleType="fitCenter"android:layout_width="fill_parent"android:layout_height="fill_parent"android:visibility="gone"/>
</FrameLayout>
publicclassFrameLayoutActivityextendsActivity{privateImageViewone=null;privateImageViewtwo=null;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.listing6_48);
one=(ImageView)findViewById(R.id.oneImgView);two=(ImageView)findViewById(R.id.twoImgView);
one.setOnClickListener(newOnClickListener(){
publicvoidonClick(Viewview){two.setVisibility(View.VISIBLE);
view.setVisibility(View.GONE);}});
two.setOnClickListener(newOnClickListener(){
publicvoidonClick(Viewview){one.setVisibility(View.VISIBLE);
view.setVisibility(View.GONE);}});}}
Listing5-12showsthelayoutfileaswellastheonCreate()methodoftheactivity.TheideaofthedemonstrationistoloadtwoImageViewobjectsintheFrameLayout,withonlyoneoftheImageViewobjectsvisibleatatime.IntheUI,whentheuserclicksthevisibleimage,wehideoneimageandshowtheotherone.
LookatListing5-12morecloselynow,startingwiththelayout.Youcanseethatwe
defineaFrameLayoutwithtwoImageViewobjects(anImageViewisacontrolthatknowshowtodisplayimages).NoticethatthesecondImageView’svisibilityissettogone,makingthecontrolinvisible.Now,lookattheonCreate()method.IntheonCreate()method,weregisterlistenerstoclickeventsontheImageViewobjects.Intheclickhandler,wehideoneImageViewandshowtheother.
Aswesaidearlier,yougenerallyuseFrameLayoutwhenyouneedtodynamicallysetthecontentofaviewtoasinglecontrol.Althoughthisisthegeneralpractice,thecontrolwillacceptmanychildren,aswedemonstrated.Listing5-12addstwocontrolstothelayoutbuthasoneofthecontrolsvisibleatatime.FrameLayout,however,doesnotforceyoutohaveonlyonecontrolvisibleatatime.Ifyouaddmanycontrolstothelayout,FrameLayoutwillsimplystackthecontrols,oneontopoftheother,withthelastoneontop.ThiscancreateaninterestingUI.Forexample,Figure5-7showsaFrameLayoutcontrolwithtwoImageViewobjectsthatarevisible.Youcanseethatthecontrolsarestacked,andthatthetoponeispartiallycoveringtheimagebehindit.
Figure5-7.FrameLayoutwithtwoImageViewobjects
AnotherinterestingaspectoftheFrameLayoutisthatifyouaddmorethanonecontroltothelayout,thesizeofthelayoutiscomputedasthesizeofthelargestiteminthecontainer.InFigure5-7,thetopimageisactuallymuchsmallerthantheimagebehindit,
butbecausethesizeofthelayoutiscomputedbasedonthelargestcontrol,theimageontopisstretched.AlsonotethatifyouputmanycontrolsinsideaFrameLayoutwithoneormoreoftheminvisibletostart,youmightwanttoconsiderusingsetMeasureAllChildren(true)onyourFrameLayout.Becausethelargestchilddictatesthelayoutsize,you’llhaveaproblemifthelargestchildisinvisibletobeginwith:whenitbecomesvisible,itisonlypartiallyvisible.Toensurethatallitemsarerenderedproperly,callsetMeasureAllChildren()andpassitavalueoftrue.TheequivalentXMLattributeforFrameLayoutisandroid:measureAllChildren=“true”.
TheGridLayoutLayoutManagerAndroid4.0broughtwithitanewlayoutmanagercalledGridLayout.Asyoumightexpect,itlaysoutviewsinagridpatternofrowsandcolumns,somewhatlikeTableLayout.However,it’seasiertousethanTableLayout.WithaGridLayout,youcanspecifyarowandcolumnvalueforaview,andthat’swhereitgoesinthegrid.Thismeansyoudon’tneedtospecifyaviewforeverycell,justthosethatyouwanttoholdaview.Viewscanspanmultiplegridcells.Youcanevenputmorethanoneviewintothesamegridcell.
Whenlayingoutviews,youmustnotusetheweightattribute,becauseitdoesnotworkinchildviewsofaGridLayout.Youcanusethelayout_gravityattributeinstead.OtherinterestingattributesyoucanusewithGridLayoutchildviewsincludelayout_columnandlayout_columnSpantospecifytheleft-mostcolumnandthenumberofcolumnstheviewtakesup,respectively.Similarly,therearelayout_rowandlayout_rowSpanattributes.Interestingly,youdonotneedtospecifylayout_heightandlayout_widthforGridLayoutchildviews;theydefaulttoWRAP_CONTENT.
CustomizingtheLayoutforVariousDeviceConfigurationsBynow,youknowverywellthatAndroidoffersahostoflayoutmanagersthathelpyoubuildUIs.Ifyou’veplayedaroundwiththelayoutmanagerswe’vediscussed,youknowthatyoucancombinethelayoutmanagersinvariouswaystoobtainthelookandfeelyouwant.Butevenwithallthelayoutmanagers,buildingUIs—andgettingthemright—canbeachallenge.Thisisespeciallytrueformobiledevices.Usersandmanufacturersofmobiledevicesaregettingmoreandmoresophisticated,andthatmakesthedeveloper’sjobevenmorechallenging.
OneofthechallengesisbuildingaUIforanapplicationthatdisplaysinvariousscreenconfigurations.Forexample,whatwouldyourUIlooklikeifyourapplicationweredisplayedinportraitversuslandscapemode?Ifyouhaven’trunintothisyet,yourmindisprobablyracingrightnow,wonderinghowtodealwiththiscommonscenario.
Interestingly,andfortunately,Androidprovidessomesupportforthisusecase.
Here’showitworks:whenbuildingalayout,Androidwillfindandloadlayoutsfromspecificfoldersbasedontheconfigurationofthedevice.Adevicecanbeinoneofthreeconfigurations:portrait,landscape,orsquare(squareisrare).Toprovidedifferentlayoutsforthevariousconfigurations,youhavetocreatespecificfoldersforeachconfigurationfromwhichAndroidwillloadtheappropriatelayout.Asyouknow,thedefaultlayoutfolderislocatedatres/layout.Tosupportportraitdisplay,createafoldercalledres/layout-port.Forlandscape,createafoldercalledres/layout-land.Andforasquare,createonecalledres/layout-square.
Agoodquestionatthispointis,“Withthesethreefolders,doIneedthedefaultlayoutfolder(res/layout)?”Generally,yes.Android’sresource-resolutionlogiclooksintheconfiguration-specificdirectoryfirst.IfAndroiddoesn’tfindaresourcethere,itgoestothedefaultlayoutdirectory.Therefore,youshouldplacedefaultlayoutdefinitionsinres/layoutandthecustomizedversionsintheconfiguration-specificfolders.
Anothertrickistousethe<include/>taginalayoutfile.Thisallowsyoutocreatecommonchunksoflayoutcode(forexample,inthedefaultlayoutdirectory)andincludetheminlayoutsdefinedinlayout-portandlayout-land.Anincludetagmightlooklikethis:
<includelayout="@layout/common_chunk1"/>
Iftheconceptofincludeinterestsyou,youshouldalsocheckoutthe<merge/>tagandtheViewStubclassintheAndroidAPI.Thesegiveyouevenmoreflexibilitywhenorganizinglayouts,withoutduplicatingviews.
NotethattheAndroidSDKdoesnotofferanyAPIsforyoutoprogrammaticallyspecifywhichconfigurationtoload—thesystemsimplyselectsthefolderbasedontheconfigurationofthedevice.Youcan,however,settheorientationofthedeviceincode,forexample,usingthefollowing:
importandroid.content.pm.ActivityInfo;...setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Thisforcesyourapplicationtoappearonthedeviceinlandscapemode.Goaheadandtryitinoneofyourearlierprojects.AddthecodetoyouronCreate()methodofanactivity,runitintheemulator,andseeyourapplicationsideways.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutbuildinguserinterfaces:
Themaintypesoflayoutsandwhentouseeach
ViewssupportedinAndroidandhowtodefinethembothinXML
andviacode
Stylesandthemesyoucanusetomanagethelookandfeelofyourapplicationfromacommonsetofresources
Chapter6
WorkingwithMenusandActionBarsAndroidSDKsupportsregularmenus,submenus,contextmenus,iconmenus,andsecondarymenus.Android3.0introducedtheactionbar,whichintegrateswellwithmenus.Wewillcoverbothmenusandactionbarsinthischapter.
Likemanyotherchaptersinthebook,wewillpresenttheessentialcodesnippetsthatyoucanusetoworkwithmenusandactionbars.Thecompletercodecontextforthesesnippetsisavailableinthedownloadableapplicationthatisspecificallydevelopedforthischapter.Thelinkforthesedownloadableprojectsisgiveninthe“Resources”sectionattheendofthischapter.
WorkingwithMenusThroughXMLFilesInAndroidtheeasiestwaytoworkwithmenusisthroughXMLmenuresourcefiles.ThisXMLapproachtomenucreationoffersseveraladvantages,suchastheabilitytonamemenus,orderthemautomatically,andallocateIDs.AsXMLmenusareresources,youalsogetthelocalizationsupportforthemenutextandicons.
CreatingXMLMenuResourceFilesAsamplemenuXMLfileisgivenListing6-1.YouseeinthislistingaseriesofmenuitemsgroupedtogetherunderagroupXMLnode.YoucanspecifyanIDforthegroupusingthe@+idresourcereferenceapproach.YoucanusethisIDinjavacodetogetaccesstothemenugroupandmanageitwhenneeded.GroupingisoptionalandyoucanomitthegroupXMLnode.
EachmenuXMLfilehasaseriesofmenuitemswiththeirmenuitemIDstiedtosymbolicnames.Thetitleindicatesthemenutitle,andtheorderInCategoryindicatestheorderinwhichthemenuitemappearsinthemenu.YoucanrefertotheAndroidSDKdocumentationforallthepossibleattributesfortheseXMLtags.ThereferenceURLisprovidedinthe“Resources”sectionofthischapter.
Listing6-1.MenuXMLResourceFilewithMenuDefinitions
<menuxmlns:android="http://schemas.android.com/apk/res/android"><groupandroid:id="@+id/menuGroup_Main">
<itemandroid:id="@+id/menu_item1"
android:orderInCategory="1"android:title="item1text"/><itemandroid:id="@+id/menu_item2"
android:orderInCategory="2"android:enabled="true"
android:icon="@drawable/some-file"android:title="item2text"/><itemandroid:id="@+id/menu_item3"
android:orderInCategory="3"android:title="item3text"/></group></menu>
AllthechildmenuitemsinListing6-1areallocatedmenuitemIDsbasedontheirnames(example:menu_item1)inthisXMLfile.Let’sseenowhowwetakethismenuXMLfileandassociateitwithanactivity.
PopulatingActivityMenufromMenuXMLFilesAssumethatthenameofthemenuXMLfileismy_menu.xml.Youneedtoplacethisfileinthe/res/menusubdirectory.Placingthefilein/res/menuautomaticallygeneratesaresourceIDcalledR.menu.my_menu.
ThekeyclassinAndroidmenusupportisandroid.view.Menu.EveryactivityinAndroidisassociatedwithonemenuobjectofthistype.InthelifecycleofanactivityAndroidcallsamethodcalledonCreateOptionsMenu()topopulatethisMenuobject.InthismethodweloadtheXMLmenufileintotheMenuobject.ThisisshowninListing6-2.
Listing6-2.UsingMenuInflater
//Thiscallbackmethodisavailableoneveryactivityclass@OverridepublicbooleanonCreateOptionsMenu(Menumenu){super.onCreateOptionsMenu(menu);MenuInflaterinflater=getMenuInflater();//fromactivityinflater.inflate(R.menu.my_menu,menu);
//Itisimportanttoreturntruetoseethemenureturntrue;
}
Oncethemenuitemsarepopulated,thecodeshouldreturntruetomakethemenuvisible.Ifthismethodreturnsfalse,themenuisinvisible.
RespondingtoXML-BasedMenuItemsYourespondtomenuitemsintheonOptionsItemSelected()callbackmethod.AndroidnotonlygeneratesaresourceIDfortheXMLmenufile(asusedinListing6-2)butalsogeneratesthenecessarymenuitemIDstohelpyoudistinguishbetweenthemenuitems.ThecodeinListing6-3illustrateshowtorespondtomenuitems.
Listing6-3.RespondingtoMenuItemsfromanXMLMenuResourceFile
@OverridepublicvoidonOptionsItemSelected(MenuItemitem){if(item.getItemId()==R.id.menu_item1){
//dosomething//foritemshandledreturntrue;}elseif(item.getItemId()==R.id.menu_item2){
//dosomethingreturntrue;}//fortherest...returnsuper.onOptionsItemSelected(item);}
NoticehowthemenuitemnamesfromtheXMLmenuresourcefilehaveautomaticallygeneratedmenuitemIDsintheR.idspace.
StartinginSDK3.0,youcanalsousetheandroid:onClickattributeofamenuitemtodirectlyindicatethenameofamethodinanactivitythatisattachedtothismenu.Thisactivitymethodisthencalledwiththemenuitemobjectasthesoleinput.Thisfeatureisonlyavailablein3.0andabove.Listing6-4showsanexample.
Listing6-4.SpecifyingaMenuCallbackMethodinanXMLMenuResourceFile
<itemandroid:id="..."android:onClick="a-method-name-in-your-activity"...</item>
ItisthissimpletoworkwithmenuitemsinAndroid.Let’sexploretheJavaAPIforthemenusabitnow.
WorkingwithMenusinJavaCodeAsindicatedthekeyclassinAndroidmenusupportisandroid.view.Menu.EveryactivityinAndroidisassociatedwithonemenuobjectofthistype.Themenuobjectthencontainsanumberofmenuitemsandsubmenus.Menuitemsarerepresentedbyandroid.view.MenuItem.Submenusarerepresentedbyandroid.view.SubMenu.
PriortoSDK3.0,onCreateOptionsMenu()iscalledthefirsttimeanactivity’soptionsmenuisaccessed.Startingwith3.0,thismethodiscalledaspartofactivitycreation.Alsonotethatthismethodiscalledonlyonceforthelifecycleoftheactivity.IfyouwanttoaddmenusdynamicallyyouwillneedtousethemethodonPrepareOptionsMenu(),whichiscoveredalittlelater.ThecodeinListing6-5
showshowtoaddthreemenuitemsusingasinglegroupIDalongwithincrementalmenuitemIDsandorderIDs.
Listing6-5.AddingMenuItems
@OverridepublicbooleanonCreateOptionsMenu(Menumenu){super.onCreateOptionsMenu(menu);menu.add(0//Group,1//itemid,0//order,"item1");//title
menu.add(0,2,1,"item2");menu.add(0,3,2,"item3");//Itisimportanttoreturntruetoseethemenureturntrue;}
Youshouldalsocallthebase-classimplementationofthismethodtogivethesystemanopportunitytopopulatethemenuwithsystemmenuitems(nosystemmenuitemsaredefinedsofar).
TheargumentstocreatethemenuitemareexplainedinListing6-5.Thelastargumentisthenameortitleofthemenuitem.Insteadoffreetext,youcanuseastringresourcethroughtheR.javaconstantsfile.Thegroup,menuitem,andorderIDsarealloptional;youcanuseMenu.NONEifyoudon’twanttospecifyanyofthem.IfMenu.NONEisspecifiedforagroup,thentheitemsareoutsideofanygroup.IfMenu.NONEisspecifiedforanitem,thenthismightbeasubmenuoraseparator.IfMenu.NONEisspecifiedfortheorder,Androidwillchoosesomemechanismtoorderthem.
WorkingwithMenuGroupsNow,let’slookathowtoworkwithmenugroups.Listing6-6showshowyouaddtwogroupsofmenus:Group1andGroup2.
Listing6-6.UsingGroupIDstoCreateMenuGroups
@OverridepublicbooleanonCreateOptionsMenu(Menumenu){//Group1intgroup1=1;menu.add(group1,1,1,"g1.item1");menu.add(group1,2,2,"g1.item2");
//Group2intgroup2=2;menu.add(group2,3,3,"g2.item1");menu.add(group2,4,4,"g2.item2");
returntrue;//itisimportanttoreturntrue}
Androidprovidesasetofmethodsontheandroid.view.MenuclassthatarebasedongroupIDs.Youcanmanipulateagroup’smenuitemsusingthemethodsshowninListing6-7:
Listing6-7.MenuGroup–RelatedMethods
removeGroup(id)setGroupCheckable(id,checkable,exclusive)setGroupEnabled(id,enabled)setGroupVisible(id,visible)
removeGroup()removesallmenuitemsfromthatgroup,giventhegroupID.YoucanenableordisablemenuitemsinagivengroupusingthesetGroupEnabledmethod().Similarly,youcancontrolthevisibilityofagroupofmenuitemsusingsetGroupVisible().
setGroupCheckable()isinteresting.Youcanusethismethodtoshowacheckmarkonamenuitemwhenthatmenuitemisselected.Whenappliedtoagroup,itenablesthisfunctionalityforallmenuitemswithinthatgroup.Ifthismethod’sexclusiveflagisset,onlyonemenuitemwithinthatgroupisallowedtogointoacheckedstate.Theothermenuitemsremainunchecked.
Younowknowhowtopopulateanactivity’smainmenuwithasetofmenuitemsandgroupthemaccordingtotheirnature.ThewayyourespondtothesemenuitemsisidenticaltohowyouwouldhaverespondedtofortheirXMLcounterpartsexceptthatthemenuitemsIDsareexplicitlycontrolledbytheprogrammer.
RespondingtoMenuItemsThroughListenersYouusuallyrespondtomenusbyoverridingonOptionsItemSelected();amenuitemalsoallowsyoutoregisteralistenerthatcouldbeusedasacallback.Thisapproachisatwo-stepprocess.Inthefirststep,youimplementtheMenuItem.OnMenuItemClickListenerinterface.Then,youtakeaninstanceofthisimplementationandpassittothemenuitem.Whenthemenuitemisclicked,themenuitemcallstheonMenuItemClick()methodoftheMenuItem.OnMenuItemClickListenerinterface(seeListing6-8).
Listing6-8.UsingaListenerasaCallbackforaMenuItemClick
//Step1publicclassMyResponseimplementsMenuItem.OnMenuItemClickListener{publicMyResponse(...someargs…){}//aconstructor@overridepublicbooleanOnMenuItemClick(MenuItemitem){
//doyourthingreturntrue;}}
//Step2MyResponsemyResponse=newMyResponse(..yourargs..);//supplyyourargsmenuItem.setOnMenuItemClickListener(myResponse);...
TheonMenuItemClick()methodiscalledwhenthemenuitemhasbeeninvoked.Thiscodeexecutesassoonasthemenuitemisclicked,evenbeforetheonOptionsItemSelected()methodiscalled.IfonMenuItemClick()returnstrue,noothercallbacksareexecuted—includingtheonOptionsItemSelected()callbackmethod.ThismeansthatthelistenercodetakesprecedenceovertheonOptionsItemSelected()method.
UsinganIntenttoRespondtoMenuItemsYoucanalsoassociateamenuitemwithanintentbyusingtheMenuItem’smethodsetIntent(intent).Whenanintentisassociatedwithamenuitem,andnothingelsehandlesthemenuitem,thenthedefaultbehavioristoinvoketheintentusingstartActivity(intent).Forthistowork,allthehandlers—especiallytheonOptionsItemSelected()method—shouldcalltheparentclass’sonOptionsItemSelected()methodforthosemenuitemsthatarenothandled.
UnderstandingExpandedMenusIfanapplicationhasmoremenuitemsthanitcandisplayonthemainscreen,AndroidshowsaMoremenuitemtoallowtheusertoseetherest.Thismenu,calledanexpandedmenu,appearsautomaticallywhentherearetoomanymenuitemstodisplayinthelimitedamountofspace.
WorkingwithIconMenusAndroidsupportsnotonlytextbutalsoimagesoriconsaspartofitsmenurepertoire.Creatinganiconmenuitemisstraightforward.Youcreatearegulartext-basedmenuitemasbefore,andthenyouusethesetIcon()methodontheMenuItemclasstosettheimage.Youneedtousetheimage’sresourceID,soyoumustgenerateitfirstbyplacingtheimageoriconinthe/res/drawabledirectory.Forexample,iftheicon’sfilenameisballoons,thentheresourceIDisR.drawable.balloons.Listing6-9demonstrateshowtoaddanicontoamenuitem.
Listing6-9.AttachinganIcontoaMenuItem
//addamenuitemandrememberitsothatyoucanuseit//subsequentlytosettheicononit.MenuItemitem=menu.add(...);//supplythemenuitemdetailsitem.setIcon(R.drawable.balloons);
Theiconshowsaslongasthemenuitemisdisplayedonthemainapplicationscreen.Ifit’sdisplayedaspartoftheexpandedmenu,theicondoesn’tshow,justthetext.ThereisanicontagavailableaswelltoindicatetheiconinanXMLmenuresourcefile.ThereareconditionsunderwhichAndroidmaychoosenottoshowtheiconsandrecommendsthattextisalwaysprovided.
WorkingwithSubmenusAMenuobjectcanhavemultipleSubMenuobjects.EachSubMenuobjectisaddedtotheMenuobjectthroughacalltotheMenu.addSubMenu()method(seeListing6-10).Youaddmenuitemstoasubmenuthesamewaythatyouaddmenuitemstoamenu.ThisisbecauseSubMenuisalsoderivedfromaMenuobject.However,youcannotaddadditionalsubmenustoasubmenu.
Listing6-10.AddingSubmenus
privatevoidaddSubMenu(Menumenu){//Secondaryitemsareshownjustlikeeverythingelseintbase=Menu.FIRST+100;SubMenusm=menu.addSubMenu(base,base+1,Menu.NONE,"submenu");sm.add(base,base+2,base+2,"subitem1");sm.add(base,base+3,base+3,"subitem2");sm.add(base,base+4,base+4,"subitem3");
//thefollowingisoksm.setIcon(R.drawable.icon48x48_1);
//Thiswillresultinruntimeexception//sm.addSubMenu("trythis");}
NoteSubMenu,asasubclassoftheMenuobject,continuestocarrytheaddSubMenu()method.Thecompilerwon’tcomplainifyouaddasubmenutoanothersubmenu,butyou’llgetaruntimeexceptionifyoutrytodoit.
TheAndroidSDKdocumentationalsosuggeststhatsubmenusdonotsupporticonmenuitems.Whenyouaddanicontoamenuitemandthenaddthatmenuitemtoasubmenu,themenuitemignoresthaticon,evenifyoudon’tseeacompile-timeorruntimeerror.However,thesubmenuitselfcanhaveanicon.
WorkingwithContextMenusAndroidsupportstheideaofcontextmenusthroughanactioncalledalongclick.AlongclickisalongpresshelddownslightlylongerthanusualonanyAndroidview.Anactivityownsaregularoptionsmenu,whereasaviewownsacontextmenu.Thisistobeexpected,becausethelongclicksthatactivatecontextmenusapplytotheviewbeingclicked.Soanactivitycanhaveonlyoneoptionsmenubutmanycontextmenus.
RegisteringaViewforaContextMenuThefirststepinimplementingacontextmenuistoregisteraviewforthecontextmenuinanactivity’sonCreate()method.YoucanregisteraTextViewforacontextmenubyusingthecodeinListing6-11.YoufirstfindtheTextViewandthencallregisterForContextMenu()ontheactivityusingtheTextViewasanargument.ThissetsuptheTextViewforcontextmenus.
Listing6-11.RegisteringaTextViewforaContextMenu
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
TextViewtv=(TextView)this.findViewById(R.id.textViewId);registerForContextMenu(tv);}
PopulatingaContextMenuOnceaviewliketheTextViewinthisexampleisregisteredforcontextmenus,AndroidcallstheonCreateContextMenu()methodwiththisviewastheargument.Thisiswhereyoucanpopulatethecontextmenuitemsforthatcontextmenu.TheonCreateContextMenu()callbackmethodprovidesthreeargumentstoworkwith.Listing6-12demonstratestheonCreateContextMenu()method.
Listing6-12.TheonCreateContextMenu()Method
@OverridepublicvoidonCreateContextMenu(ContextMenumenu,Viewv,ContextMenuInfomenuInfo){menu.setHeaderTitle("SampleContextMenu");menu.add(200,200,200,"item1");}
ThefirstargumentisapreconstructedContextMenuobject,thesecondistheview
(suchastheTextView)thatgeneratedthecallback,andthethirdistheContextMenuInfoclass.Foralotofsimplecases,youcanjustignoretheContextMenuInfoobject.However,someviewsmaypassextrainformationthroughthisobject.Inthosecases,youneedtocasttheContextMenuInfoclasstoasubclassandthenusetheadditionalmethodstoretrievetheadditionalinformation.
SomeexamplesofclassesderivedfromContextMenuInfoincludeAdapterContextMenuInfoandExpandableListContextMenuInfo.ViewsthatareAdapterViewssuchastheListViewinAndroidusetheAdapterContextMenuInfoclasstopasstherowIDwithinthatviewforwhichthecontextmenuisbeingdisplayed.Inasense,youcanusethisclasstofurtherclarifytheobjectunderneaththetouchortheclick,evenwithinagivencompositeview.
RespondingtoContextMenuItemsAndroidprovidesacallbackmethodsimilartoonOptionsItemSelected()calledonContextItemSelected().Listing6-13demonstratesonContextItemSelected().
Listing6-13.RespondingtoContextMenus
//Thismethodisavailableforallactivities@OverridepublicbooleanonContextItemSelected(MenuItemitem){if(item.getItemId()==some-menu-item-id){//handlethismenuitemreturntrue;}...otherexceptionprocessing}
IncorporatingDynamicMenusSofar,we’vetalkedaboutstaticmenus—yousetthemuponce,andtheydon’tchangedynamicallyaccordingtowhat’sonscreen.Ifyouwanttocreatedynamicmenus,usetheonPrepareOptionsMenu()methodthatAndroidprovidesonanactivityclass.ThismethodresemblesonCreateOptionsMenu()exceptthatitiscalledeverytimeamenuisdisplayedpriortodisplaying.YoushoulduseonPrepareOptionsMenu()alongwiththeonCreateOptionsMenu()toeffectivelymanageyourmenuifithasdynamicmenuoptions.onPrepareOptionMenu()iswhereyouwanttoenableordisablesomemenuitemsormenugroupsbasedonwhatyouaredisplaying.For3.0andabovewhenyouwanttochangeamenu,becauseamenu-relatedcomponentliketheactionbarisalwaysdisplayed,youhavetoexplicitlycallanewprovisionedmethodcalledActivity.invalidateOptionsMenu(),whichinturninvokestheonCreateOptionsMenu()andredrawsthemenuandtherebyalsoresultsincallingonPrepareOptionsMenu()priortothedisplay.Youcancallthismethodanytime
somethingchangesinyourapplicationstatethatwouldrequireachangetothemenu.
WorkingwithPop-upMenusAndroid3.0introducedanothertypeofmenucalledapop-upmenu.SDK4.0enhancedthisslightlybyaddingacoupleofutilitymethods(forexample,PopupMenu.inflate)tothePopupMenuclass.(SeethePopupMenuAPIdocumentationtolearnaboutthesemethods.Listing6-14alsodrawsattentiontothisdifference.)
Apop-upmenucanbeinvokedagainstanyviewinresponsetoanyUIevent.AnexampleofaUIeventisabuttonclickoraclickonanimageview.Figure6-1showsapop-upmenuinvokedagainstaview.
Figure6-1.Pop-upmenuattachedtoatextview
Tocreateapop-upmenuliketheoneinFigure6-1,startwitharegularXMLmenufileandusetheJavacodeinListing6-14toloadthismenuXMLasapop-upmenu.Seethedownloadableprojectforthischapterifyouwanttoseethefullimplementation.
Listing6-14.WorkingwithaPop-upMenu
//Otheractivitycodegoeshere…//InvokethefollowingmethodtoshowapopupmenuprivatevoidshowPopupMenu(){//GetholdofaviewtoanchorthepopupTextViewtv=findViewById(R.id.SOME_TEXT_VIEW_ID);
//instantiateapopupmenu.var"this"standsforactivityPopupMenupopup=newPopupMenu(this,tv);
//thefollowingcodefor3.0sdk//popup.getMenuInflater().inflate(R.menu.popup_menu,popup.getMenu());//Orinsdk4.0andabovepopup.inflate(R.menu.popup_menu);popup.setOnMenuItemClickListener(newPopupMenu.OnMenuItemClickListener(){publicbooleanonMenuItemClick(MenuItemitem){//dosomethingherereturntrue;}});popup.show();}
Asyoucansee,apop-upmenubehavesmuchlikeanoptionsmenu.Thekeydifferencesareasfollows:
Apop-upmenuisusedondemand,whereasanoptionsmenuisalwaysavailable.
Apop-upmenuisanchoredtoaview,whereasanoptionsmenubelongstotheentireactivity.
Apop-upmenuusesitsownmenuitemcallback,whereastheoptionsmenuusestheonOptionsItemSelected()callbackontheactivity.
ExploringActionBarsIntroducedinAndroid3.0andexpandedinAndroid4.0,anActionBarextendsthereachofmenusintothetitlebarofanactivity.Thisallowsfrequentlyusedactionseasilyavailabletotheuserwithoutsearchingthroughoptionmenusorcontextmenus.Inadditiontoiconsandmenuitems,anactionbarcanaccommodateotherviewssuchastabs,oralist,orasearchboxtohelpwithnavigation.Figure6-2showsanactionbarintabbednavigationmode.
Figure6-2.Anactivitywithatabbedactionbar
Youcanseethevariouspartsofanactionbarhere.TheiconatupperleftontheactionbariscalledaHomeicon.ClickingthisHomeiconsendsacallbacktotheoptionmenuwithmenuIDandroid.R.id.home.Followedbythehomeiconisthetitleareaforthisactivity.Thenyouseeasetoftabs(oradrop-downlistifthisweretobealist-basedactionbar).Inthemiddle,youseesearchview.Towardstheend,youseeasetofactionicons.Thelastpartofthisactionbarisadottedverticallinerepresentingthemenuforthisactivity.Whenyouclickonthaticon,astandarddrop-downmenuwillappear(SeeFigure6-3).
TheactionbaryouseeinFigure6-1isatabbedactionbar.Thetwoothermodesofanactionbarareastandardandalist.Inalistactionbar,thetabsarereplacedbyadrop-downlist.Inastandardactionbar,thereisnoareasetasideforalistortabs.Now,let’sshowyouhowtoimplementasimplestandardactionbar.
ImplementingaStandardActionBarListing6-15presentssamplesourcecodeforimplementingastandardnavigationactionbarforanactivity.
Listing6-15.StandardNavigationActionBarActivity
publicclassStandardNavigationActionBarActivityextendsActivity{//.....othercode@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);
ActionBarbar=this.getActionBar();bar.setTitle("Sometitleofyourchoosing");bar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);}publicbooleanonCreateOptionsMenu(MenumainMenu){//loadthemenuxmlfileintothemainMenuobjectasusualherereturntrue;}}
AsyoucanseefromListing6-15,itiseasytoworkwithanactionbar.NoticeinthatlistinghowwehaveusedthegetActionBar()togetaccesstotheactionbarobjectandthensetitstitleandnavigationmode.AnymenuyousetintheonCreateOptionsMenu()canbeinvokeddirectlyfromtheactionbarasshowninFigure6-3.(However,whenamenuispresentedinthisfashionfromanactionbar,duetospacelimitations,thesystemmaynotshowtheiconsalongwithmenutext.)
Figure6-3.Anactivitywithanactionbarandexpandedmenu
Withtheintroductionofactionbar,themenuXMLfileisenhancedwithnewattributestoindicatesomemenuitemstobeshownintheactionbardirectlyasicons.(YoucanseetheseiconsinactionbarabovetheexpandedmenuinFigure6-3).TheXMLmenufileexampleinListing6-16demonstrateshowamenuitemcanbespecifiedtobecomeanicondirectlyontheactionbar.
Listing6-16.MenuXMLFileforThisProject
<!--/res/menu/menu.xml--><menuxmlns:android="http://schemas.android.com/apk/res/android"><!--Thisgroupusesthedefaultcategory.--><groupandroid:id="@+id/menuGroup_Main"><!--aregularmenuitem--><itemandroid:id="@+id/menu_da_clear"android:title="clear"/><!--itemtobeshowndirectlyontheactionbar--><itemandroid:id="@+id/menu_action_icon1"android:title="ActionIcon1"android:icon="@drawable/creep001"android:showAsAction="ifRoom"/>
<!--..othermenuitems--></group></menu>
ThemenuitemsthataretobeshownontheactionbarareindicatedwiththetagshowAsAction.Intheprecedingcode,thisattributeissetto“ifRoom“.TheotherpossiblevaluesforthisXMLtagareasfollows:always,never,withText,collapseActionView.YoucanalsoaccomplishthesameeffectwithaJavaAPIavailableontheMenuItemclass.Theoptionalwaysmeans“showthisitemasabuttonintheactionbar.”Theoptionnevermeans“nevershowthisitem.”TheoptionwithTextmeans“showthisitemwithitstextlabelandtheicon.”TheoptioncollapseActionViewmeans“collapsethespacetakenbytheactionviewofthisactionmenuitemwhennotselected.”Becausetheseactionsaremerelymenuitems,theybehaveassuchandcalltheonOptionsItemSelected()callbackmethodoftheactivityclass.
ImplementingaTabbedActionBarListing6-17showshowtosetupatabbedactionbar.
Listing6-17.Tab-Navigation–EnabledActionBarActivity
//ActivitySourcecodepublicclassTabNavigationActionBarActivityextendsActivity{@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);workwithTabbedActionBar();}publicvoidworkwithTabbedActionBar(){ActionBarbar=this.getActionBar();bar.setTitle(tag);bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);TestTabListenertl=newTestTabListener();Tabtab1=bar.newTab();tab1.setText("Tab1");tab1.setTabListener(tl);bar.addTab(tab1);Tabtab2=bar.newTab();tab2.setText("Tab2");tab2.setTabListener(tl);bar.addTab(tab2);}}//eof-class
Atabbedactionbar,asthenamesuggests,hasmultipletabs.InListing6-17youseethatthereareafewadditionalmethodsandclassesthatareusedtoworkwithtabbedactionbars.Unlikeastandardactionbar,atabbedactionbarrequiresatablistenerforeachtab.ThislistenerneedstoimplementtheTabListenerinterface.InListing6-18theclassTestTabListenerimplementstheTabListenerinterface.IfyouforgettocallthesetTabListener()methodonatabthatisaddedtotheactionbar,yougetaruntimeerrorindicatingthatalistenerisneeded.Listing6-18showsthecodefortheTestTabListenerclass.
Listing6-18.TabListenertoRespondtoTabActions
publicclassTestTabListenerimplementsActionBar.TabListener{//constructorcodepublicTestTabListener(){}//callbackspublicvoidonTabReselected(Tabtab,FragmentTransactionft){//applynecessarylogichere
}publicvoidonTabSelected(Tabtab,FragmentTransactionft){//applynecessarylogichere}publicvoidonTabUnselected(Tabtab,FragmentTransactionft){//applynecessarylogichere}}
Astabsareselectedandunselected,thecallbackmethodsinListing6-18willbecalled.Actionbarisapropertyoftheactivityanddoesnotcrossactivityboundaries.Inotherwords,onecannotuseanactionbartocontrolorinfluencemultipleactivities.Eachactivitymustprovisionitsownactionbars.Anycommonalityofactionsbetweenactionbarsislefttotheprogrammertoorchestrate.
InListing6-17,onceweobtainedtheactionbarforanactivity,wesetitsnavigationmodetoActionBar.NAVIGATION_MODE_TABS.TheothertwopossibleactionbarnavigationmodesareNAVIGATION_MODE_LISTandNAVIGATION_MODE_STANDARD.Let’sseenowhowtoimplementalist-basedactionbar.
ImplementingaList-BasedActionBarTobeabletoinitializeanactionbarwithlistnavigationmode,youneedthefollowingtwothings:
Aspinneradapterthatcanbeusedpopulatethedrop-downlistofnavigationchoices.
Alistnavigationlistenersothatwhenoneofthelistitemsispickedyoucangetacallback.
Listing6-19presentstheSimpleSpinnerArrayAdapterthatimplementstheSpinnerAdapterinterface.Asstatedearlier,thegoalofthisclassistogivealistofitemstoshow.
Listing6-19.CreatingaSpinnerAdapterforListNavigation
publicclassSimpleSpinnerArrayAdapterextendsArrayAdapter<String>implementsSpinnerAdapter{publicSimpleSpinnerArrayAdapter(Contextctx){super(ctx,android.R.layout.simple_spinner_item,newString[]{"one","two"});
this.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);}publicViewgetDropDownView(intposition,ViewconvertView,ViewGroupparent){returnsuper.getDropDownView(position,convertView,parent);}}
ThereisnoSDKclassthatdirectlyimplementstheSpinnerAdapterinterfacerequiredbylistnavigation.So,youderivethisclassfromanArrayAdapterandprovideasimpleimplementationfortheSpinnerAdapter.AttheendofthechapterisareferenceURLonspinneradaptersforfurtherreading.Let’smoveonnowtothelistnavigationlistener.ThisisasimpleclassimplementingtheActionBar.OnNavigationListener.Listing6-20showsthecodeforthisclass.
Listing6-20.CreatingaListListenerforListNavigation
publicclassListListenerimplementsActionBar.OnNavigationListener{//simpleconstructor…publicListListener(){}//neededcallbacktorespondtoactionspublicbooleanonNavigationItemSelected(intitemPosition,longitemId){//respondandreturntruereturntrue;}}
Younowhavewhatyourequiretosetupalistnavigationactionbar.Thesourcecodenecessaryforworkingwithalist-basedactionbarisshowninListing6-21.
Listing6-21.ListNavigationActionBarActivity
//ActivitySourcecodepublicclassTabNavigationActionBarActivityextendsActivity{@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);workwithTabbedActionBar();}publicvoidworkwithListActionBar(){ActionBarbar=this.getActionBar();bar.setTitle("title");bar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
bar.setListNavigationCallbacks(newSimpleSpinnerArrayAdapter(this),newListListener());}}//eof-class
Figure6-4showshowalistbaractionbarlookswhenexpanded.
Figure6-4.Anactivitywithanopenednavigationlist
Thatconcludeshowwecanuseanactionbarforregularmenus,tabbednavigation,andlist-basednavigation.Let’sseenowhowwecanembedasearchviewliketheoneshowninFigure6-2.
ExploringActionBarandSearchViewThissectionshowshowtouseasearchwidgetintheactionbar.Youneedthefollowingtousesearchinyouractionbar:
1. DefineamenuiteminamenuXMLfilepointingtoasearchviewclassprovidedbytheSDK.Youalsoneedanactivityintowhichyoucanloadthismenu.Thisisoftencalledthesearchinvokeractivity.
2. Createanotheractivitythatcantakethequeryfromthesearchviewinstep1andprovideresults.Thisisoftencalledthesearchresultsactivity.
3. CreateanXMLfilethatallowsyoutocustomizethesearchviewintheactionbar.Thisfileisoftencalledsearchable.xmlandresidesintheres/xmlsubdirectory.
4. Declarethesearchresultsactivityinthemanifestfile.ThisdefinitionneedstopointtotheXMLfiledefinedinstep3.
5. Inyourmenusetupforthesearchinvokeractivity,indicatethatthesearchviewneedstotargetthesearchresultsactivityfromstep2.
Let’sstartwiththeSearchviewwidget.
DefiningaSearchViewWidgetasaMenuItemTodefineasearchviewtoappearintheactionbarofyouractivity,youneedtodefinea
menuiteminoneofyourmenuXMLfiles,asshowninListing6-22.
Listing6-22.SearchViewMenuItemDefinition
<itemandroid:id="@+id/menu_search"android:title="Search"android:showAsAction="ifRoom"
android:actionViewClass="android.widget.SearchView"
/>
ThekeyelementinListing6-22istheactionViewClassattributepointingtoandroid.widget.SearchView.Yousawtheotherattributesearlierinthechapterwhenyoudeclaredyournormalmenuitemstoappearasactioniconsintheactionbar.
CreatingaSearchResultsActivityToenablesearchinyourapplication,youneedanactivitythatcanrespondtoasearchquery.Thiscanbelikeanyotheractivity.AnexampleisshowninListing6-23.
Listing6-23.SearchResultsActivity
publicclassSearchResultsActivityextendsActivity{publicstaticStringtag="SearchResultsActivity";@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);finalIntentqueryIntent=getIntent();doSearchQuery(queryIntent);}@OverridepublicvoidonNewIntent(finalIntentnewIntent){super.onNewIntent(newIntent);finalIntentqueryIntent=getIntent();doSearchQuery(queryIntent);}privatevoiddoSearchQuery(finalIntentqueryIntent){finalStringqueryAction=queryIntent.getAction();if(!(Intent.ACTION_SEARCH.equals(queryAction))){Log.d(tag,"intentNOTforsearch");return;}finalStringqueryString=queryIntent.getStringExtra(SearchManager.QUERY);Log.d(tag,queryString);}}//eof-class
InListing6-23,theactivitycheckstoseewhethertheactionthatinvokeditisinitiatedby
search.Or,thisactivitycouldhavebeennewlycreatedorjustbroughttothetop,inwhichcaseitneedstodosomethingidenticaltotheonCreate()methodinitsonNewIntent()methodaswell.Ontheotherhand,ifthisactivityisinvokedbysearch,itretrievesthequerystringusinganextraparametercalledSearchManager.QUERY.Thentheactivitylogswhatthatstringis.Inarealscenario,youwouldusethatstringtopaintmatchingresults.
SpecifyingaSearchableXMLFileAsindicatedintheearliersteps,let’slookattheXMLfilethatisrequiredandcustomizesthesearchwidget;seeListing6-24.
Listing6-24.SearchableXMLFile
<!--/res/xml/searchable.xml--><searchablexmlns:android="http://schemas.android.com/apk/res/android"android:label="@string/search_label"android:hint="@string/search_hint"/>
Thehintattributewillappearonthesearchviewwidgetasahintthatdisappearswhenyoustarttyping.Thelabeldoesn’tplayasignificantroleintheactionbar.However,whenyouusethesamesearchresultsactivityinasearchdialog,thedialoghasthelabeldefinedhere.YoucanlearnmoreaboutsearchableXMLattributesatthefollowingURL:
http://developer.android.com/guide/topics/search/searchable-config.html
DefiningtheSearchResultsActivityintheManifestFileNowlet’sseehowtotiethisXMLfiletothesearchresultsactivity.Thisisdoneinthemanifestfileaspartofdefiningthesearchresultsactivity:seeListing6-25.NoticethemetadatadefinitionpointingtothesearchableXMLfileresource.
Listing6-25.TyinganActivitytoItsSearchable.xml
<activityandroid:name=".SearchResultsActivity"android:label="SearchResults"><intent-filter><actionandroid:name="android.intent.action.SEARCH"/></intent-filter><meta-dataandroid:name="android.app.searchable"android:resource="@xml/searchable"/></activity>
IdentifyingtheSearchTargetfortheSearchViewWidgetSofar,youhavethesearchviewinyouractionbar,andyouhavetheactivitythatcanrespondtosearch.YouneedtotietogetherthesetwopiecesusingJavacode.YoudothisintheonCreateOptions()callbackofthesearch-invokingactivityaspartofsettingupyourmenu.ThefunctioninListing6-26canbecalledfromonCreateOptions()tolinkthesearchviewwidgetandthesearchresultsactivity.
Listing6-26.TyingtheSearchViewWidgettotheSearchResultsActivity
privatevoidsetupSearchView(Menumenu){//Step1:LocatethesearchviewwidgetSearchViewsearchView=(SearchView)menu.findItem(R.id.menu_search).getActionView();//reporterrorandreturnifsearchViewisnull
//Step2:getSearchManagerandsearchableInfoSearchManagersearchManager=(SearchManager)getSystemService(Context.SEARCH_SERVICE);ComponentNamecn=newComponentName(this,SearchResultsActivity.class);SearchableInfoinfo=searchManager.getSearchableInfo(cn);//reporterrorandreturnifsearchableinfoisnull
//Step3:setsearchableInfoonthesearchviewwidgetsearchView.setSearchableInfo(info);//Donoticonifythewidget;expanditbydefaultsearchView.setIconifiedByDefault(false);}
Let’swalkthroughwhatishappeninginListing6-26.Thegoalofthiscodeistotellthesearchviewwhereitcanfindthesearchable.xmlthatdefinesthesearchbehavior.Todothis,thefirststepistogetareferencetotheSearchView.ThisisdonethroughtheMenuobject.Thesecondstepistoaskthesystem-widesearchmanagerwhatsearchableXMLfileistiedtotheactivitySearchResultsActivity.ThisisdonebycallingthemethodgetSearchableInfoontheSearchManagersystemservice.OncewehavetheSearchableInfoobjectrepresentingtheXMLfile,wepassthatinformationtotheSearchViewobject.Withallthisinplace,nowifyoutypesomethinginthesearchbox,thatinformationwillbepassedtothesearchresultsactivity,whichwillshowtheresults.
AndroidSearchAPIisalargeAPIwithalotofnuancesthat,duetospace,wehavenotincludedinthisbook.Therearethreesuggestions.WehaveprovidedaURLinthe“Resources”sectionthatpointstoaseriesofarticlesandnotesontheGooglesearchAPI.
WealsohavealargechapteronSearchfromthepreviouseditionmadeavailableonline.Thelinktothisisalsointhe“Resources”section.WehavealsoupdatedthatSearchmaterialfromthepreviouseditionandaddedthatcontenttotheExpertAndroideditionfromApress.
ResourcesAsyoulearnaboutandworkwithAndroidmenusandactionbars,youmaywanttokeepthefollowingURLshandy:
http://developer.android.com/guide/topics/ui/menus.htmlPrimarydocumentfromGoogledescribinghowtoworkwithmenus.
http://developer.android.com/guide/topics/resources/menu-resource.html:InformationaboutvariousXMLtagsyoucanuseinamenuresource.
http://developer.android.com/reference/android/app/ActionBar.htmlAPIURLfortheActionBarclass.
http://www.androidbook.com/item/3624:Ourresearchonactionbar,includingalistoffurtherreferences,samplecode,linkstoexamples,andUIfiguresrepresentingvariousactionbarmodes.
http://www.androidbook.com/item/3627:Tosetuplistnavigationmode,youneedtounderstandhowdrop-downlistsandspinnerswork.ThisbriefarticleshowsafewsamplesandreferencelinksonhowtousespinnersinAndroid.
http://www.androidbook.com/item/3885:Explainshowsearchworks,tohelpyouutilizetheactionbartoitsfullextent.
http://www.androidicons.com:Websitefromwhichacoupleoftheiconsusedinthischapterareborrowed.TheseiconsareunderCreativeCommonsLicense3.0.
http://www.androidbook.com/item/3302:“PleasingAndroidLayouts.”Afewnotesandsamplecodeforsimplelayouts.
http://www.androidbook.com/item/4060:YouwillfindhereafreecopyoftheSearchchapterfromthepreviousedition.ThisprovidesextensivecoverageonAndroidsearch.
http://androidbook.com/proandroid5/projects:ProjectdownloadURLforthisbook.ThedownloadableprojectZIPfilesforthischapterareProAndroid5_ch06_TestMenus.zipandProAndroid5_ch06_TestActionBar.zip.
SummaryMenusandactionbarsareanintegralpartofwritingmobileapps.Thischaptercoversregularmenus,contextmenus,pop-upmenus,standardactionbars,tabbedactionbars,andlist-basedactionbars.Thischapteralsocoversthebasicsofhowtoembedasearchviewwidgetinanactionbar.
Chapter7
StylesandThemesThusfar,wehavecoveredsomefundamentalsoftheAndroiduserinterface(UI).Inthischapter,wearegoingtodiscussstylesandthemes,whichhelptoencapsulatecontrol-appearanceattributesforeasiersetupandmaintenance.Androidprovidesseveralwaystoalterthestyleofviewsinyourapplication,inXMLandincode.We’llfirstcoverusingmarkuptagsinstringsandthenhowtousespannablestochangespecificvisualattributesoftext.Butwhatifyouwanttocontrolhowthingslookusingacommonspecificationforseveralviewsoracrossanentireactivityorapplication?We’lldiscussAndroidstylesandthemestoshowyouhow.
UsingStylesSometimes,youwanttohighlightorstyleaportionoftheView’scontent.Youcandothisstaticallyordynamically.Statically,youcanapplymarkupdirectlytothestringsinyourstringresources,asshownhere:
<stringname="styledText"><i>Static</i>styleina<b>TextView</b>.</string>
YoucanthenreferenceitinyourXMLorfromcode.NotethatyoucanusethefollowingHTMLtagswithstringresources:<i>,<b>,and<u>,foritalics,bold,andunderlined,respectively,aswellas<sup>(superscript),<sub>(subscript),<strike>(strikethrough),<big>,<small>,and<monospace>.Youcanevennestthesetoget,forexample,smallsuperscripts.ThisworksnotjustinTextViewsbutalsoinotherviews,likebuttons.Figure7-1showswhatstyledandthemedtextlookslike,usingmanyoftheexamplesinthissection.
Figure7-1.Examplesofstylesandthemes
StylingaTextViewcontrol’scontentprogrammaticallyrequiresalittleadditionalworkbutallowsformuchmoreflexibility(seeListing7-1),becauseyoucanstyleitatruntime.Thisflexibilitycanonlybeappliedtoaspannable,though,whichishowEditTextnormallymanagestheinternaltext,whereasTextViewdoesnotnormallyuseSpannable.SpannableisbasicallyaStringthatyoucanapplystylesto.TogetaTextViewtostoretextasaspannable,youcancallsetTextthisway:
tv.setText("ThistextisstoredinaSpannable",TextView.BufferType.SPANNABLE);
Then,whenyoucalltv.getText,you’llgetaspannable.
AsshowninListing7-1,youcangetthecontentoftheEditText(asaSpannableobject)andthensetstylesforportionsofthetext.Thecodeinthelistingsetsthetextstylingtoboldanditalicsandsetsthebackgroundtored.YoucanuseallthestylingoptionsaswehavewiththeHTMLtagsasdescribedpreviously,andthensome.
Listing7-1.ApplyingStylesDynamicallytotheContentofanEditText
EditTextet=(EditText)this.findViewById(R.id.et);
et.setText("StylingthecontentofanEditTextdynamically");Spannablespn=(Spannable)et.getText();spn.setSpan(newBackgroundColorSpan(Color.RED),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);spn.setSpan(newStyleSpan(android.graphics.Typeface.BOLD_ITALIC),0,7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Thesetwotechniquesforstylingonlyworkontheoneviewthey’reappliedto.Androidprovidesastylemechanismtodefineacommonstyletobereusedacrossviews,aswellasathememechanism,whichbasicallyappliesastyletoanentireactivityortheentireapplication.Tobeginwith,weneedtotalkaboutstyles.
AstyleisacollectionofViewattributesthatisgivenanamesoyoucanrefertothatcollectionbyitsnameandassignthatstylebynametoviews.Forexample,Listing7-2showsaresourceXMLfile,savedin/res/values,thatwecoulduseforallerrormessages.
Listing7-2.DefiningaStyletoBeUsedAcrossManyViews
<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText"><itemname="android:layout_width">fill_parent</item><itemname="android:layout_height">wrap_content</item><itemname="android:textColor">#FF0000</item><itemname="android:typeface">monospace</item></style></resources>
Thesizeoftheviewisdefinedaswellasthefontcolor(red)andtypeface.Noticehowthenameattributeoftheitemtag(e.g.,android:layout_width)istheXMLattributenameweusedinourlayoutXMLfilesinearlierchapters,andthevalueoftheitemtagnolongerrequiresdoublequotes.WecannowusethisstyleforanerrorTextView,asshowninListing7-3.
Listing7-3.UsingaStyleinaView
<TextViewandroid:id="@+id/errorText"style="@style/ErrorText"android:text="Noerrorsatthistime"/>
ItisimportanttonotethattheattributenameforastyleinthisViewdefinitiondoesnotstartwithandroid:.Watchoutforthis,becauseeverythingseemstouseandroid:exceptthestyle.Whenyou’vegotmanyviewsinyourapplicationthatshareastyle,changingthatstyleinoneplaceismuchsimpler;youonlyneedtomodifythestyle’sattributesintheoneresourcefile.
Youcan,ofcourse,createmanydifferentstylesforvariouscontrols.Forexample,buttonscouldshareacommonstylethatisdifferentfromthecommonstylefortextinmenus.Itiscommontoseetextattributesmanagedwithstyles,includingandroid:textColor,android:textStyle,andandroid:textSize.Othercommonattributesusedwithstylesincludethepaddingvalues,android:background,andcolors.
Onereallyniceaspectofstylesisthatyoucansetupahierarchyofthem.WecoulddefineanewstyleforreallybaderrormessagesandbaseitonthestyleofErrorText.Listing7-4showshowthismightlook.
Listing7-4.DefiningaStylefromaParentStyle
<?xmlversion="1.0"encoding="utf-8"?><resources><stylename="ErrorText.Danger"><itemname="android:textStyle">bold</item></style></resources>
Thisexampleshowsthatwecansimplynameourchildstyleusingtheparentstyleasaprefixtothenewstylename.Therefore,ErrorText.DangerisachildofErrorTextandinheritsthestyleattributesoftheparent.ItthenaddsanewattributefortextStyle.Thiscanberepeatedagainandagaintocreateawholetreeofstyles.
Aswasthecaseforadapterlayouts,Androidprovidesalargesetofstylesthatwecanuse.TospecifyanAndroid-providedstyle,usesyntaxlikethis:
style="@android:style/TextAppearance"
ThisstylesetsthedefaultstylefortextinAndroid.TolocatethemasterAndroidstyles.xmlfile,visittheAndroidSDK/platforms/<android-version>/data/res/values/folder,whereyouinstalledtheAndroidSDK;<android-version>istheparticularversionofAndroidyouwanttoseestylesfor.Insidethisfile,youwillfindquiteafewstylesthatareready-madeforyoutouseorextend.Aquicknoteabout@android:style/TextAppearance:thisstyledoesnotsetandroid:layout_heightorandroid:layout_width,soaViewspecificationwouldneedmorethanthisstyletocompileproperly.
Here’sawordofcautionaboutextendingtheAndroid-providedstyles:thepreviousmethodofusingaprefixwon’tworkwithAndroid-providedstyles.Instead,youmustusetheparentattributeofthestyletag,likethis:
<stylename="CustomTextAppearance"parent="@android:style/TextAppearance"><item...yourextensionsgohere…/></style>
Youdon’talwayshavetopullinanentirestyleonyourview.Youcouldchoosetoborrowjustapartofthestyleinstead.Forexample,ifyouwanttosetthecolorofthetextinyour
TextViewtoasystemstylecolor,youcoulddothefollowing:
<TextViewandroid:id="@+id/tv2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:textColor="?android:textColorSecondary"android:text="@string/hello_world"/>
Noticethatinthisexample,thenameofthetextColorattributevaluestartswiththe?characterinsteadofthe@character.The?characterisusedsoAndroidknowstolookforastylevalueinthecurrenttheme.Becausewesee?android,welookintheAndroidsystemthemeforthisstylevalue.
UsingThemesOneproblemwithstylesisthatyouneedtoaddanattributespecificationofstyle=”@style/…”toeveryviewdefinitionthatyouwantittoapplyto.Ifyouhavesomestyleelementsyouwantappliedacrossanentireactivity,oracrossthewholeapplication,youshoulduseathemeinstead.Athemeisreallyjustastyleappliedbroadly;butintermsofdefiningatheme,it’sexactlylikeastyle.Infact,themesandstylesarefairlyinterchangeable:youcanextendathemeintoastyleorrefertoastyleasatheme.Typically,onlythenamesgiveahintastowhetherastyleisintendedtobeusedasastyleoratheme.
Tospecifyathemeforanactivityoranapplication,youwouldaddanattributetothe<activity>or<application>tagintheAndroidManifest.xmlfileforyourproject.Thecodemightlooklikeoneofthese:
<activityandroid:theme="@style/MyActivityTheme"><applicationandroid:theme="@style/MyApplicationTheme"><applicationandroid:theme="@android:style/Theme.NoTitleBar">
YoucanfindtheAndroid-providedthemesinthesamefolderastheAndroid-providedstyles,withthethemesinafilecalledthemes.xml.Whenyoulookinsidethethemesfile,youwillseealargesetofstylesdefined,withnamesthatstartwithTheme.Itmightbegoodtoreadthatlastlineafewtimes.Putanotherway,allstylesandthemesareoftypestyle,evenifthestylenamehas“Theme”init.YouwillalsonoticethatwithintheAndroid-providedthemesandstyles,thereisalotofextendinggoingon,whichiswhyyouendupwithstylescalledTheme.Dialog.AppError,forexample.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistof
downloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch07_Styles.zip.ThisZIPfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.
http://developer.android.com/guide/topics/ui/themes.htmlTheAndroidguidetostylesandthemes.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutstylesandthemes:
Stylesarejustcollectionsofviewattributesforeasyreuseacrossviews,activities,andapplications.
Youcanmakeyourownstyles,useapredefinedstyle,orextendanexistingstyle.
Themesarewhatyoucallastylewhenitisappliedtoanactivityorapplication.
Chapter8
FragmentsSofar,we’veexploredseveralbitsandpiecesofanAndroidapplication,andyou’verunsomesimpleapplicationstailoredtoasmartphone-sizedscreen.AllyouhadtothinkaboutwashowtolayouttheUIcontrolsonthescreenforanactivity,andhowoneactivityflowedtothenext,andsoon.ForthefirsttwomajorreleasesofAndroid,smallscreenswereit.ThencametheAndroidtablets:deviceswithscreensizesof10”.Andthatcomplicatedthings.Why?Becausenowtherewassomuchscreenrealestatethatasimpleactivityhadahardtimefillingascreenwhileatthesametimekeepingtoasinglefunction.Itnolongermadesensetohaveane-mailapplicationthatshowedonlyheadersinoneactivity(fillingalargescreen),andaseparateactivitytoshowanindividuale-mail(alsofillingalargescreen).Withthatmuchroomtoworkwith,anapplicationcouldshowalistofe-mailheadersdowntheleftsideofthescreenandtheselectede-mailcontentsontherightsideofthescreen.Coulditbedoneinasingleactivitywithasinglelayout?Well,yes,butyoucouldn’treusethatactivityorlayoutforanyofthesmaller-screendevices.
OneofthecoreclassesintroducedinAndroid3.0wastheFragmentclass,especiallydesignedtohelpdevelopersmanageapplicationfunctionalitysoitwouldprovidegreatusabilityaswellaslotsofreuse.Thischapterwillintroduceyoutothefragment,whatitis,howitfitsintoanapplication’sarchitecture,andhowtouseit.Fragmentsmakealotofinterestingthingspossiblethatweredifficultbefore.Ataboutthesametime,GooglereleasedafragmentSDKthatworksonoldAndroids.Soevenifyouweren’tinterestedinwritingapplicationsfortablets,youmayhavefoundthatfragmentsmadeyourlifeeasieronnon-tabletdevices.Nowit’seasierthanevertowritegreatapplicationsforsmartphonesandtabletsandevenTVsandotherdevices.
Let’sgetstartedwithAndroidfragments.
WhatIsaFragment?Thisfirstsectionwillexplainwhatafragmentisandwhatitdoes.Butfirst,let’ssetthestagetoseewhyweneedfragments.Asyoulearnedearlier,anAndroidapplicationonsmall-screendevicesusesactivitiestoshowdataandfunctionalitytoauser,andeachactivityhasafairlysimple,well-definedpurpose.Forexample,anactivitymightshowtheuseralistofcontactsfromtheiraddressbook.Anotheractivitymightallowtheusertotypeane-mail.TheAndroidapplicationistheseriesoftheseactivitiesgroupedtogethertoachievealargerpurpose,suchasmanagingane-mailaccountviathereadingandsendingofmessages.Thisisfineforasmall-screendevice,butwhentheuser’sscreenisverylarge(10”orlarger),there’sroomonthescreentodomorethanjustonesimplething.Anapplicationmightwanttolettheuserviewthelistofe-mailsintheirinboxandatthesametimeshowthecurrentlyselectede-mailtextnexttothelist.Oranapplicationmightwanttoshowalistofcontactsandatthesametimeshowthecurrentlyselectedcontactin
adetailview.
AsanAndroiddeveloper,youknowthatthisfunctionalitycouldbeaccomplishedbydefiningyetanotherlayoutforthexlargescreenwithListViewsandlayoutsandallsortsofotherviews.Andby“yetanotherlayout”wemeanlayoutsinadditiontothoseyou’veprobablyalreadydefinedforthesmallerscreens.Ofcourse,you’llwanttohaveseparatelayoutsfortheportraitcaseaswellasthelandscapecase.Andwiththesizeofanxlargescreen,thiscouldmeanquiteafewviewsforallthelabelsandfieldsandimagesandsoonthatyou’llneedtolayoutandthenprovidecodefor.Ifonlytherewereawaytogrouptheseviewobjectstogetherandconsolidatethelogicforthem,sothatchunksofanapplicationcouldbereusedacrossscreensizesanddevices,minimizinghowmuchworkadeveloperhastodotomaintaintheirapplication.Andthatiswhywehavefragments.
Onewaytothinkofafragmentisasasub-activity.Andinfact,thesemanticsofafragmentarealotlikeanactivity.Afragmentcanhaveaviewhierarchyassociatedwithit,andithasalifecyclemuchlikeanactivity’slifecycle.FragmentscanevenrespondtotheBackbuttonlikeactivitiesdo.Ifyouwerethinking,“IfonlyIcouldputmultipleactivitiestogetheronatablet’sscreenatthesametime,”thenyou’reontherighttrack.Butbecauseitwouldbetoomessytohavemorethanoneactivityofanapplicationactiveatthesametimeonatabletscreen,fragmentswerecreatedtoimplementbasicallythatthought.Thismeansfragmentsarecontainedwithinanactivity.Fragmentscanonlyexistwithinthecontextofanactivity;youcan’tuseafragmentwithoutanactivity.Fragmentscancoexistwithotherelementsofanactivity,whichmeansyoudonotneedtoconverttheentireuserinterfaceofyouractivitytousefragments.Youcancreateanactivity’slayoutasbeforeandonlyuseafragmentforonepieceoftheuserinterface.
Fragmentsarenotlikeactivities,however,whenitcomestosavingstateandrestoringitlater.Thefragmentsframeworkprovidesseveralfeaturestomakesavingandrestoringfragmentsmuchsimplerthantheworkyouneedtodoonactivities.
Howyoudecidewhentouseafragmentdependsonafewconsiderations,whicharediscussednext.
WhentoUseFragmentsOneoftheprimaryreasonstouseafragmentissoyoucanreuseachunkofuserinterfaceandfunctionalityacrossdevicesandscreensizes.Thisisespeciallytruewithtablets.Thinkofhowmuchcanhappenwhenthescreenisaslargeasatablet’s.It’smorelikeadesktopthanaphone,andmanyofyourdesktopapplicationshaveamultipaneuserinterface.Asdescribedearlier,youcanhavealistandadetailviewoftheselecteditemonscreenatthesametime.Thisiseasytopictureinalandscapeorientationwiththelistontheleftandthedetailsontheright.Butwhatiftheuserrotatesthedevicetoportraitmodesothatnowthescreenistallerthanitiswide?Perhapsyounowwantthelisttobeinthetopportionofthescreenandthedetailsinthebottomportion.Butwhatifthisapplicationisrunningonasmallscreenandthere’sjustnoroomforthetwoportionstobeonthescreenatthesametime?Wouldn’tyouwanttheseparateactivitiesforthelistandforthedetailstobeabletosharethelogicyou’vebuiltintotheseportionsforalargescreen?Wehopeyouansweredyes.Fragmentscanhelpwiththat.Figure8-1makesthisalittle
clearer.
Figure8-1.FragmentsusedforatabletUIandforasmartphoneUI
Inlandscapemode,twofragmentsmaysitnicelysidebyside.Inportraitmode,wemightbeabletoputonefragmentabovetheother.Butifwe’retryingtorunthesameapplicationonadevicewithasmallerscreen,wemightneedtoshoweitherfragment1orfragment2butnotbothatthesametime.Ifwetriedtomanageallthesescenarioswithlayouts,we’dbecreatingquiteafew,whichmeansdifficultytryingtokeepeverythingcorrectacrossmanyseparatelayouts.Whenusingfragments,ourlayoutsstaysimple;eachactivitylayoutdealswiththefragmentsascontainers,andtheactivitylayoutsdon’tneedtospecifytheinternalstructureofeachfragment.Eachfragmentwillhaveitsownlayoutforitsinternalstructureandcanbereusedacrossmanyconfigurations.
Let’sgobacktotherotatingorientationexample.Ifyou’vehadtocodefororientationchangesofanactivity,youknowthatitcanbearealpaintosavethecurrentstateoftheactivityandtorestorethestateoncetheactivityhasbeenre-created.Wouldn’titbeniceifyouractivityhadchunksthatcouldbeeasilyretainedacrossorientationchanges,soyoucouldavoidallthetearingdownandre-creatingeverytimetheorientationchanged?Ofcourseitwould.Fragmentscanhelpwiththat.
Nowimaginethatauserisinyouractivity,andthey’vebeendoingsomework.Andimaginethattheuserinterfacehaschangedwithinthesameactivity,andtheuserwantstogobackastep,ortwo,orthree.Inanold-styleactivity,pressingtheBackbuttonwilltaketheuseroutoftheactivityentirely.Withfragments,theBackbuttoncanstepbackwardthroughastackoffragmentswhilestayinginsidethecurrentactivity.
Next,thinkaboutanactivity’suserinterfacewhenabigchunkofcontentchanges;you’dliketomakethetransitionlooksmooth,likeapolishedapplication.Fragmentscandothat,too.
Nowthatyouhavesomeideaofwhatafragmentisandwhyyou’dwanttouseone,let’sdigalittledeeperintothestructureofafragment.
TheStructureofaFragmentAsmentioned,afragmentislikeasub-activity:ithasafairlyspecificpurposeandalmost
alwaysdisplaysauserinterface.ButwhereanactivityissubclassedfromContext,afragmentisextendedfromObjectinpackageandroid.app.AfragmentisnotanextensionofActivity.Likeactivities,however,youwillalwaysextendFragment(oroneofitssubclasses)soyoucanoverrideitsbehavior.
Afragmentcanhaveaviewhierarchytoengagewithauser.Thisviewhierarchyislikeanyotherviewhierarchyinthatitcanbecreated(inflated)fromanXMLlayoutspecificationorcreatedincode.Theviewhierarchyneedstobeattachedtotheviewhierarchyofthesurroundingactivityifitistobeseenbytheuser,whichyou’llgettoshortly.Theviewobjectsthatmakeupafragment’sviewhierarchyarethesamesortsofviewsthatareusedelsewhereinAndroid.Soeverythingyouknowaboutviewsappliestofragmentsaswell.
Besidestheviewhierarchy,afragmenthasabundlethatservesasitsinitializationarguments.Similartoanactivity,afragmentcanbesavedandlaterrestoredautomaticallybythesystem.Whenthesystemrestoresafragment,itcallsthedefaultconstructor(withnoarguments)andthenrestoresthisbundleofargumentstothenewlycreatedfragment.Subsequentcallbacksonthefragmenthaveaccesstotheseargumentsandcanusethemtogetthefragmentbacktoitspreviousstate.Forthisreason,itisimperativethatyou
Ensurethatthere’sadefaultconstructorforyourfragmentclass.
Addabundleofargumentsassoonasyoucreateanewfragmentsothesesubsequentmethodscanproperlysetupyourfragment,andsothesystemcanrestoreyourfragmentproperlywhennecessary.
Anactivitycanhavemultiplefragmentsinplayatonetime;andifafragmenthasbeenswitchedoutwithanotherfragment,thefragment-switchingtransactioncanbesavedonabackstack.Thebackstackismanagedbythefragmentmanagertiedtotheactivity.ThebackstackishowtheBackbuttonbehaviorismanaged.Thefragmentmanagerisdiscussedlaterinthischapter.Whatyouneedtoknowhereisthatafragmentknowswhichactivityitistiedto,andfromthereitcangettoitsfragmentmanager.Afragmentcanalsogettotheactivity’sresourcesthroughitsactivity.
Alsosimilartoanactivity,afragmentcansavestateintoabundleobjectwhenthefragmentisbeingre-created,andthisbundleobjectgetsgivenbacktothefragment’sonCreate()callback.ThissavedbundleisalsopassedtoonInflate(),onCreateView(),andonActivityCreated().Notethatthisisnotthesamebundleastheoneattachedasinitializationarguments.Thisbundleisoneinwhichyouarelikelytostorethecurrentstateofthefragment,notthevaluesthatshouldbeusedtoinitializeit.
AFragment’sLifeCycleBeforeyoustartusingfragmentsinsampleapplications,youneedunderstandthelifecycleofafragment.Why?Afragment’slifecycleismorecomplicatedthananactivity’slifecycle,andit’sveryimportanttounderstandwhenyoucandothingswithfragments.Figure8-2showsthelifecycleofafragment.
Figure8-2.Lifecycleofafragment
IfyoucomparethistoFigure2-3(thelifecycleforanactivity),you’llnoticeseveraldifferences,duemostlytotheinteractionrequiredbetweenanactivityandafragment.Afragmentisverydependentontheactivityinwhichitlivesandcangothroughmultiplestepswhileitsactivitygoesthroughone.
Attheverybeginning,afragmentisinstantiated.Itnowexistsasanobjectinmemory.Thefirstthingthatislikelytohappenisthatinitializationargumentswillbeaddedtoyourfragmentobject.Thisisdefinitelytrueinthesituationwherethesystemisre-creatingyourfragmentfromasavedstate.Whenthesystemisrestoringafragmentfromasavedstate,thedefaultconstructorisinvoked,followedbytheattachmentoftheinitializationargumentsbundle.Ifyouaredoingthecreationofthefragmentincode,anicepatterntouseisthatinListing8-1,whichshowsafactorytypeofinstantiatorwithintheMyFragmentclassdefinition.
Listing8-1.InstantiatingaFragmentUsingaStaticFactoryMethod
publicstaticMyFragmentnewInstance(intindex){MyFragmentf=newMyFragment();Bundleargs=newBundle();args.putInt("index",index);f.setArguments(args);
returnf;}
Fromtheclient’spointofview,theygetanewinstancebycallingthestaticnewInstance()methodwithasingleargument.Theygettheinstantiatedobjectback,andtheinitializationargumenthasbeensetonthisfragmentintheargumentsbundle.Ifthisfragmentissavedandreconstructedlater,thesystemwillgothroughaverysimilarprocessofcallingthedefaultconstructorandthenreattachingtheinitializationarguments.Foryourparticularcase,youwoulddefinethesignatureofyournewInstance()method(ormethods)totaketheappropriatenumberandtypeofarguments,andthenbuildtheargumentsbundleappropriately.ThisisallyouwantyournewInstance()methodtodo.Thecallbacksthatfollowwilltakecareoftherestofthesetupofyourfragment.
TheonInflate()CallbackThenextthingthathappensislayoutviewinflation.Ifyourfragmentisdefinedbya<fragment>taginalayout,yourfragment’sonInflate()callbackwillbecalled.Thispassesinareferencetothesurroundingactivity,anAttributeSetwiththeattributesfromthe<fragment>tag,andasavedbundle.Thesavedbundleistheonewiththesavedstatevaluesinit,puttherebyonSaveInstanceState()ifthisfragmentexistedbeforeandisbeingre-created.TheexpectationofonInflate()isthatyou’llreadattributevaluesandsavethemforlateruse.Atthisstageinthefragment’slife,it’stooearlytoactuallydoanythingwiththeuserinterface.Thefragmentisnotevenassociatedtoitsactivityyet.Butthat’sthenexteventtooccurtoyourfragment.
TheonAttach()CallbackTheonAttach()callbackisinvokedafteryourfragmentisassociatedwithitsactivity.Theactivityreferenceispassedtoyouifyouwanttouseit.Youcanatleastusetheactivitytodetermineinformationaboutyourenclosingactivity.Youcanalsousetheactivityasacontexttodootheroperations.OnethingtonoteisthattheFragmentclasshasagetActivity()methodthatwillalwaysreturntheattachedactivityforyourfragmentshouldyouneedit.Keepinmindthatallduringthislifecycle,theinitializationargumentsbundleisavailabletoyoufromthefragment’sgetArguments()method.However,oncethefragmentisattachedtoitsactivity,youcan’tcallsetArguments()again.Therefore,youcan’taddtotheinitializationargumentsexceptintheverybeginning.
TheonCreate()CallbackNextupistheonCreate()callback.Althoughthisissimilartotheactivity’sonCreate(),thedifferenceisthatyoushouldnotputcodeinherethatreliesontheexistenceoftheactivity’sviewhierarchy.Yourfragmentmaybeassociatedtoitsactivitybynow,butyouhaven’tyetbeennotifiedthattheactivity’sonCreate()hasfinished.That’scomingup.Thiscallbackgetsthesavedstatebundlepassedin,ifthereisone.Thiscallbackisaboutasearlyaspossibletocreateabackgroundthreadtogetdatathatthis
fragmentwillneed.YourfragmentcodeisrunningontheUIthread,andyoudon’twanttododiskinput/output(I/O)ornetworkaccessesontheUIthread.Infact,itmakesalotofsensetofireoffabackgroundthreadtogetthingsready.Yourbackgroundthreadiswhereblockingcallsshouldbe.You’llneedtohookupwiththedatalater,perhapsusingahandlerorsomeothertechnique.
NoteOneofthewaystoloaddatainabackgroundthreadistousetheLoaderclass.ThiswillbecoveredinChapter28.
TheonCreateView()CallbackThenextcallbackisonCreateView().Theexpectationhereisthatyouwillreturnaviewhierarchyforthisfragment.TheargumentspassedintothiscallbackincludeaLayoutInflater(whichyoucanusetoinflatealayoutforthisfragment),aViewGroupparent(calledcontainerinListing8-2),andthesavedbundleifoneexists.ItisveryimportanttonotethatyoushouldnotattachtheviewhierarchytotheViewGroupparentpassedin.Thatassociationwillhappenautomaticallylater.Youwillverylikelygetexceptionsifyouattachthefragment’sviewhierarchytotheparentinthiscallback—oratleastoddandunexpectedapplicationbehavior.
Listing8-2.CreatingaFragmentViewHierarchyinonCreateView()
@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){if(container==null)returnnull;
Viewv=inflater.inflate(R.layout.details,container,false);TextViewtext1=(TextView)v.findViewById(R.id.text1);text1.setText(myDataSet[getPosition()]);returnv;}
Theparentisprovidedsoyoucanuseitwiththeinflate()methodoftheLayoutInflater.Iftheparentcontainervalueisnull,thatmeansthisparticularfragmentwon’tbeviewedbecausethere’snoviewhierarchyforittoattachto.Inthiscase,youcansimplyreturnnullfromhere.Rememberthattheremaybefragmentsfloatingaroundinyourapplicationthataren’tbeingdisplayed.Listing8-2showsasampleofwhatyoumightwanttodointhismethod.
HereyouseehowyoucanaccessalayoutXMLfilethatisjustforthisfragmentandinflateittoaviewthatyoureturntothecaller.Thereareseveraladvantagestothisapproach.Youcouldalwaysconstructtheviewhierarchyincode,butbyinflatingalayout
XMLfile,you’retakingadvantageofthesystem’sresource-findinglogic.Dependingonwhichconfigurationthedeviceisin,orforthatmatterwhichdeviceyou’reon,theappropriatelayoutXMLfilewillbechosen.Youcanthenaccessaparticularviewwithinthelayout—inthiscase,thetext1TextViewfield—todowhatyouwantwith.Torepeataveryimportantpoint:donotattachthefragment’sviewtothecontainerparentinthiscallback.YoucanseeinListing8-2thatyouuseacontainerinthecalltoinflate(),butyoualsopassfalsefortheattachToRootparameter.
TheonViewCreated()CallbackThisoneiscalledrightafteronCreateView()butbeforeanysavedstatehasbeenputintotheUI.TheviewobjectpassedinisthesameviewobjectthatgotreturnedfromonCreateView().
TheonActivityCreated()CallbackYou’renowgettingclosetothepointwheretheusercaninteractwithyourfragment.ThenextcallbackisonActivityCreated().ThisiscalledaftertheactivityhascompleteditsonCreate()callback.Youcannowtrustthattheactivity’sviewhierarchy,includingyourownviewhierarchyifyoureturnedoneearlier,isreadyandavailable.Thisiswhereyoucandofinaltweakstotheuserinterfacebeforetheuserseesit.It’salsowhereyoucanbesurethatanyotherfragmentforthisactivityhasbeenattachedtoyouractivity.
TheonViewStateRestored()CallbackThisoneisrelativelynew,introducedwithJellyBean4.2.Yourfragmentwillhavethiscallbackcalledwhentheviewhierarchyofthisfragmenthasallstaterestored(ifapplicable).PreviouslyyouhadtomakedecisionsinonActivityCreated()abouttweakingtheUIforarestoredfragment.Nowyoucanputthatlogicinthiscallbackknowingdefinitelythatthisfragmentisbeingrestoredfromasavedstate.
TheonStart()CallbackThenextcallbackinyourfragmentlifecycleisonStart().Nowyourfragmentisvisibletotheuser.Butyouhaven’tstartedinteractingwiththeuserjustyet.Thiscallbackistiedtotheactivity’sonStart().Assuch,whereaspreviouslyyoumayhaveputyourlogicintotheactivity’sonStart(),nowyou’remorelikelytoputyourlogicintothefragment’sonStart(),becausethatisalsowheretheuserinterfacecomponentsare.
TheonResume()CallbackThelastcallbackbeforetheusercaninteractwithyourfragmentisonResume().Thiscallbackistiedtotheactivity’sonResume().Whenthiscallbackreturns,theuserisfreetointeractwiththisfragment.Forexample,ifyouhaveacamerapreviewinyourfragment,youwouldprobablyenableitinthefragment’sonResume().
Sonowyou’vereachedthepointwheretheappisbusilymakingtheuserhappy.Andthentheuserdecidestogetoutofyourapp,eitherbyBack’ingout,orbypressingtheHomebutton,orbylaunchingsomeotherapplication.Thenextsequence,similartowhathappenswithanactivity,goesintheoppositedirectionofsettingupthefragmentforinteraction.
TheonPause()CallbackThefirstundocallbackonafragmentisonPause().Thiscallbackistiedtotheactivity’sonPause();justaswithanactivity,ifyouhaveamediaplayerinyourfragmentorsomeothersharedobject,youcouldpauseit,stopit,orgiveitbackviayouronPause()method.Thesamegood-citizenrulesapplyhere:youdon’twanttobeplayingaudioiftheuseristakingaphonecall.
TheonSaveInstanceState()CallbackSimilartoactivities,fragmentshaveanopportunitytosavestateforlaterreconstruction.ThiscallbackpassesinaBundleobjectforthisfragmenttobeusedasthecontainerforwhateverstateinformationyouwanttohangonto.Thisisthesaved-statebundlepassedtothecallbackscoveredearlier.Topreventmemoryproblems,becarefulaboutwhatyousaveintothisbundle.Onlysavewhatyouneed.Ifyouneedtokeepareferencetoanotherfragment,don’ttrytosaveorputtheotherfragment,ratherjustsavetheidentifierfortheotherfragmentsuchasitstagorID.WhenthisfragmentrunsonViewStateRestored(),thenyoucouldre-establishconnectionstotheotherfragmentsthatthisfragmentdependson.
AlthoughyoumayseethismethodusuallycalledrightafteronPause(),theactivitytowhichthisfragmentbelongscallsitwhenitfeelsthatthefragment’sstateshouldbesaved.ThiscanoccuranytimebeforeonDestroy().
TheonStop()CallbackThenextundocallbackisonStop().Thisoneistiedtotheactivity’sonStop()andservesapurposesimilartoanactivity’sonStop().AfragmentthathasbeenstoppedcouldgostraightbacktotheonStart()callback,whichthenleadstoonResume().
TheonDestroyView()CallbackIfyourfragmentisonitswaytobeingkilledofforsaved,thenextcallbackintheundodirectionisonDestroyView().ThiswillbecalledaftertheviewhierarchyyoucreatedonyouronCreateView()callbackearlierhasbeendetachedfromyourfragment.
TheonDestroy()CallbackNextupisonDestroy().Thisiscalledwhenthefragmentisnolongerinuse.Notethatitisstillattachedtotheactivityandisstillfindable,butitcan’tdomuch.
TheonDetach()CallbackThefinalcallbackinafragment’slifecycleisonDetach().Oncethisisinvoked,thefragmentisnottiedtoitsactivity,itdoesnothaveaviewhierarchyanymore,andallitsresourcesshouldhavebeenreleased.
UsingsetRetainInstance()YoumayhavenoticedthedottedlinesinthediagraminFigure8-2.Oneofthecoolfeaturesofafragmentisthatyoucanspecifythatyoudon’twantthefragmentcompletelydestroyediftheactivityisbeingre-createdandthereforeyourfragmentswillbecomingbackalso.Therefore,FragmentcomeswithamethodcalledsetRetainInstance(),whichtakesabooleanparametertotellit“Yes;Iwantyoutohangaroundwhenmyactivityrestarts”or“No;goaway,andI’llcreateanewfragmentfromscratch.”AgoodplacetocallsetRetainInstance()isintheonCreate()callbackofafragment,butinonCreateView()works,asdoesonActivityCreated().
Iftheparameteristrue,thatmeansyouwanttokeepyourfragmentobjectinmemoryandnotstartoverfromscratch.However,ifyouractivityisgoingawayandbeingre-created,you’llhavetodetachyourfragmentfromthisactivityandattachittothenewone.Thebottomlineisthatiftheretaininstancevalueistrue,youwon’tactuallydestroyyourfragmentinstance,andthereforeyouwon’tneedtocreateanewoneontheotherside.ThedottedlinesonthediagrammeanyouwouldskiptheonDestroy()callbackonthewayout,you’dskiptheonCreate()callbackwhenyourfragmentisbeingre-attachedtoyournewactivity,andallothercallbackswouldfire.Becauseanactivityisre-createdmostlikelyforconfigurationchanges,yourfragmentcallbacksshouldprobablyassumethattheconfigurationhaschanged,andthereforeshouldtakeappropriateaction.ThiswouldincludeinflatingthelayouttocreateanewviewhierarchyinonCreateView(),forexample.ThecodeprovidedinListing8-2wouldtakecareofthatasitiswritten.Ifyouchoosetousetheretain-instancefeature,youmaydecidenottoputsomeofyourinitializationlogicinonCreate()becauseitwon’talwaysgetcalledthewaytheothercallbackswill.
SampleFragmentAppShowingtheLifeCycleThere’snothinglikeseeingarealexampletogetanappreciationforaconcept.You’lluseasampleapplicationthathasbeeninstrumentedsoyoucanseeallthesecallbacksinaction.You’regoingtoworkwithasampleapplicationthatusesalistofShakespeareantitlesinonefragment;whentheuserclicksoneofthetitles,sometextfromthatplaywillappearinaseparatefragment.Thissampleapplicationwillworkinbothlandscapeandportraitmodesonatablet.Thenyou’llconfigureittorunasifonasmallerscreensoyoucanseehowtoseparatethetextfragmentintoanactivity.You’llstartwiththeXMLlayoutofyouractivityinlandscapemodeinListing8-3,whichwilllooklikeFigure8-3whenitruns.
Listing8-3.YourActivity’sLayoutXMLforLandscapeMode
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout-land/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="match_parent">
<fragmentclass="com.androidbook.fragments.bard.TitlesFragment"android:id="@+id/titles"android:layout_weight="1"android:layout_width="0px"android:layout_height="match_parent"/><FrameLayoutandroid:id="@+id/details"android:layout_weight="2"android:layout_width="0px"android:layout_height="match_parent"/>
</LinearLayout>
Figure8-3.Theuserinterfaceofyoursamplefragmentapplication
NoteAttheendofthechapteristheURLyoucanusetodownloadtheprojects
inthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDE(suchasEclipseorAndroidStudio)directly.
Thislayoutlookslikealotofotherlayoutsyou’veseenthroughoutthebook,horizontallylefttorightwithtwomainobjects.There’saspecialnewtag,though,called<fragment>,andthistaghasanewattributecalledclass.Keepinmindthatafragmentisnotaview,sothelayoutXMLisalittledifferentforafragmentthanitisforeverythingelse.Theotherthingtokeepinmindisthatthe<fragment>tagisjustaplaceholderinthislayout.Youshouldnotputchildtagsunder<fragment>inalayoutXMLfile.
Theotherattributesforafragmentlookfamiliarandserveapurposesimilartothatforaview.Thefragmenttag’sclassattributespecifiesyourextendedclassforthetitlesofyourapplication.Thatis,youmustextendoneoftheAndroidFragmentclassestoimplementyourlogic,andthe<fragment>tagmustknowthenameofyourextendedclass.Afragmenthasitsownviewhierarchythatwillbecreatedlaterbythefragmentitself.ThenexttagisaFrameLayout—notanother<fragment>tag.Whyisthat?We’llexplaininmoredetaillater,butfornow,youshouldbeawarethatyou’regoingtobedoingsometransitionsonthetext,swappingoutonefragmentwithanother.YouusetheFrameLayoutastheviewcontainertoholdthecurrenttextfragment.Withyourtitlesfragment,youhaveone—andonlyone—fragmenttoworryabout:noswappingandnotransitions.FortheareathatdisplaystheShakespeareantext,you’llhaveseveralfragments.
TheMainActivityJavacodeisinListing8-4.Actually,thelistingonlyshowstheinterestingcode.Thecodeisinstrumentedwithloggingmessagessoyoucanseewhat’sgoingonthroughLogCat.PleasereviewthesourcecodefilesforShakespeareInstrumentedfromthewebsitetoseeallofit.
Listing8-4.InterestingSourceCodefromMainActivity
publicbooleanisMultiPane(){returngetResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE;}
/***Helperfunctiontoshowthedetailsofaselecteditem,eitherby*displayingafragmentin-placeinthecurrentUI,orstartinga*wholenewactivityinwhichitisdisplayed.*/publicvoidshowDetails(intindex){Log.v(TAG,"inMainActivityshowDetails("+index+")");
if(isMultiPane()){//Checkwhatfragmentisshown,replaceifneeded.
DetailsFragmentdetails=(DetailsFragment)getFragmentManager().findFragmentById(R.id.details);if((details==null)||(details.getShownIndex()!=index)){//Makenewfragmenttoshowthisselection.details=DetailsFragment.newInstance(index);
//Executeatransaction,replacinganyexisting//fragmentwiththisoneinsidetheframe.Log.v(TAG,"abouttorunFragmentTransaction…");FragmentTransactionft=getFragmentManager().beginTransaction();ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//ft.addToBackStack("details");ft.replace(R.id.details,details);ft.commit();}
}else{//Otherwiseyouneedtolaunchanewactivitytodisplay//thedialogfragmentwithselectedtext.Intentintent=newIntent();intent.setClass(this,DetailsActivity.class);intent.putExtra("index",index);startActivity(intent);}}
Thisisaverysimpleactivitytowrite.Todeterminemultipanemode(thatis,whetheryouneedtousefragmentssidebyside),youjustusetheorientationofthedevice.Ifyou’reinlandscapemode,you’remultipane;ifyou’reinportraitmode,you’renot.ThehelpermethodshowDetails()istheretofigureouthowtoshowthetextwhenatitleisselected.Theindexisthepositionofthetitleinthetitlelist.Ifyou’reinmultipanemode,you’regoingtouseafragmenttoshowthetext.You’recallingthisfragmentaDetailsFragment,andyouuseafactory-typemethodtocreateonewiththeindex.TheinterestingcodefortheDetailsFragmentclassisshowninListing8-5(minusalloftheloggingcode).AswedidbeforeinTitlesFragment,thevariouscallbacksofDetailsFragmenthaveloggingaddedsowecanwatchwhathappensviaLogCat.You’llcomebacktoyourshowDetails()methodlater.
Listing8-5.SourceCodeforDetailsFragment
publicclassDetailsFragmentextendsFragment{
privateintmIndex=0;
publicstaticDetailsFragmentnewInstance(intindex){Log.v(MainActivity.TAG,"inDetailsFragmentnewInstance("+index+")");
DetailsFragmentdf=newDetailsFragment();
//Supplyindexinputasanargument.Bundleargs=newBundle();args.putInt("index",index);df.setArguments(args);returndf;}
publicstaticDetailsFragmentnewInstance(Bundlebundle){intindex=bundle.getInt("index",0);returnnewInstance(index);}
@OverridepublicvoidonCreate(BundlemyBundle){Log.v(MainActivity.TAG,"inDetailsFragmentonCreate.Bundlecontains:");if(myBundle!=null){for(Stringkey:myBundle.keySet()){Log.v(MainActivity.TAG,""+key);}}else{Log.v(MainActivity.TAG,"myBundleisnull");}super.onCreate(myBundle);
mIndex=getArguments().getInt("index",0);}
publicintgetShownIndex(){returnmIndex;}
@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){Log.v(MainActivity.TAG,
"inDetailsFragmentonCreateView.container="+container);
//Don'ttiethisfragmenttoanythingthroughtheinflater.//Androidtakescareofattachingfragmentsforus.The//containerisonlypassedinsoyoucanknowaboutthe//containerwherethisViewhierarchyisgoingtogo.Viewv=inflater.inflate(R.layout.details,container,false);TextViewtext1=(TextView)v.findViewById(R.id.text1);text1.setText(Shakespeare.DIALOGUE[mIndex]);returnv;}}
TheDetailsFragmentclassisactuallyfairlysimpleaswell.Nowyoucanseehowtoinstantiatethisfragment.It’simportanttopointoutthatyou’reinstantiatingthisfragmentincodebecauseyourlayoutdefinestheViewGroupcontainer(aFrameLayout)thatyourdetailsfragmentisgoingtogointo.BecausethefragmentisnotitselfdefinedinthelayoutXMLfortheactivity,asyourtitlesfragmentwas,youneedtoinstantiateyourdetailsfragmentsincode.
Tocreateanewdetailsfragment,youuseyournewInstance()method.Asdiscussedearlier,thisfactorymethodinvokesthedefaultconstructorandthensetstheargumentsbundlewiththevalueofindex.OncenewInstance()hasrun,yourdetailsfragmentcanretrievethevalueofindexinanyofitscallbacksbyreferringtotheargumentsbundleviagetArguments().Foryourconvenience,inonCreate()youcansavetheindexvaluefromtheargumentsbundletoamemberfieldinyourDetailsFragmentclass.
Youmightwonderwhyyoudidn’tsimplysetthemIndexvalueinnewInstance().ThereasonisthatAndroidwill,behindthescenes,re-createyourfragmentusingthedefaultconstructor.Thenitsetstheargumentsbundletowhatitwasbefore.Androidwon’tuseyournewInstance()method,sotheonlyreliablewaytoensurethatmIndexissetistoreadthevaluefromtheargumentsbundleandsetitinonCreate().TheconveniencemethodgetShownIndex()retrievesthevalueofthatindex.NowtheonlymethodlefttodescribeinthedetailsfragmentisonCreateView().Andthisisverysimple,too.
ThepurposeofonCreateView()istoreturntheviewhierarchyforyourfragment.Rememberthatbasedonyourconfiguration,youcouldwantallkindsofdifferentlayoutsforthisfragment.Therefore,themostcommonthingtodoisutilizealayoutXMLfilefor
yourfragment.Inyoursampleapplication,youspecifythelayoutforthefragmenttobedetails.xmlusingtheresourceR.layout.details.TheXMLfordetails.xmlisinListing8-6.
Listing8-6.Thedetails.xmlLayoutFilefortheDetailsFragment
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/details.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ScrollViewandroid:id="@+id/scroller"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/text1"android:layout_width="match_parent"android:layout_height="match_parent"/></ScrollView></LinearLayout>
Foryoursampleapplication,youcanusetheexactsamelayoutfilefordetailswhetheryou’reinlandscapemodeorinportraitmode.Thislayoutisnotfortheactivity,it’sjustforyourfragmenttodisplaythetext.Becauseitcouldbeconsideredthedefaultlayout,youcanstoreitinthe/res/layoutdirectoryanditwillbefoundandusedevenifyou’reinlandscapemode.WhenAndroidgoeslookingforthedetailsXMLfile,ittriesthespecificdirectoriesthatcloselymatchthedevice’sconfiguration,butitwillendupinthe/res/layoutdirectoryifitcan’tfindthedetails.xmlfileinanyoftheotherplaces.Ofcourse,ifyouwanttohaveadifferentlayoutforyourfragmentinlandscapemode,youcoulddefineaseparatedetails.xmllayoutfileandstoreitunder/res/layout-land.Feelfreetoexperimentwithdifferentdetails.xmlfiles.
Whenyourdetailsfragment’sonCreateView()iscalled,youwillsimplygrabtheappropriatedetails.xmllayoutfile,inflateit,andsetthetexttothetextfromtheShakespeareclass.TheentireJavacodeforShakespeareisnotshownhere,butaportionisinListing8-7soyouunderstandhowitwasdone.Forthecompletesource,accesstheprojectdownloadfiles,asdescribedinthe“References”sectionattheendofthischapter.
Listing8-7.SourceCodeforShakespeare.java
publicclassShakespeare{publicstaticStringTITLES[]={"HenryIV(1)","HenryV","HenryVIII","RomeoandJuliet","Hamlet",
"TheMerchantofVenice","Othello"};publicstaticStringDIALOGUE[]={"Soshakenasweare,sowanwithcare,\n…...andsoon…
Nowyourdetailsfragmentviewhierarchycontainsthetextfromtheselectedtitle.Yourdetailsfragmentisreadytogo.AndyoucanreturntoMainActivity’sshowDetails()methodtotalkaboutFragmentTransactions.
FragmentTransactionsandtheFragmentBackStackThecodeinshowDetails()thatpullsinyournewdetailsfragment(partiallyshownagaininListing8-8)looksrathersimple,butthere’salotgoingonhere.It’sworthspendingsometimetoexplainwhatishappeningandwhy.Ifyouractivityisinmultipanemode,youwanttoshowthedetailsinafragmentnexttothetitlelist.Youmayalreadybeshowingdetails,whichmeansyoumayhaveadetailsfragmentvisibletotheuser.Eitherway,theresourceIDR.id.detailsisfortheFrameLayoutforyouractivity,asshowninListing8-3.Ifyouhaveadetailsfragmentsittinginthelayoutbecauseyoudidn’tassignanyotherIDtoit,itwillhavethisID.Therefore,tofindoutifthere’sadetailsfragmentinthelayout,youcanaskthefragmentmanagerusingfindFragmentById().Thiswillreturnnulliftheframelayoutisemptyorwillgiveyouthecurrentdetailsfragment.Youcanthendecideifyouneedtoplaceanewdetailsfragmentinthelayout,eitherbecausethelayoutisemptyorbecausethere’sadetailsfragmentforsomeothertitle.Onceyoumakethedeterminationtocreateanduseanewdetailsfragment,youinvokethefactorymethodtocreateanewinstanceofadetailsfragment.Nowyoucanputthisnewfragmentintoplacefortheusertosee.
Listing8-8.FragmentTransactionExample
publicvoidshowDetails(intindex){Log.v(TAG,"inMainActivityshowDetails("+index+")");
if(isMultiPane()){//Checkwhatfragmentisshown,replaceifneeded.DetailsFragmentdetails=(DetailsFragment)getFragmentManager().findFragmentById(R.id.details);if(details==null||details.getShownIndex()!=index){//Makenewfragmenttoshowthisselection.details=DetailsFragment.newInstance(index);
//Executeatransaction,replacinganyexisting
//fragmentwiththisoneinsidetheframe.Log.v(TAG,"abouttorunFragmentTransaction…");FragmentTransactionft=getFragmentManager().beginTransaction();ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//ft.addToBackStack("details");ft.replace(R.id.details,details);ft.commit();}//Therestwasleftouttosavespace.}
Akeyconcepttounderstandisthatafragmentmustliveinsideaviewcontainer,alsoknownasaviewgroup.TheViewGroupclassincludessuchthingsaslayoutsandtheirderivedclasses.FrameLayoutisagoodchoiceasthecontainerforthedetailsfragmentinthemain.xmllayoutfileofyouractivity.AFrameLayoutissimple,andallyouneedisasimplecontainerforyourfragment,withouttheextrabaggagethatcomeswithothertypesoflayouts.TheFrameLayoutiswhereyourdetailsfragmentisgoingtogo.Ifyouhadinsteadspecifiedanother<fragment>tagintheactivity’slayoutfileinsteadofaFrameLayout,youwouldnotbeabletoreplacethecurrentfragmentwithanewfragment(i.e.,swapfragments).
TheFragmentTransactioniswhatyouusetodoyourswapping.Youtellthefragmenttransactionthatyouwanttoreplacewhateverisinyourframelayoutwithyournewdetailsfragment.YoucouldhaveavoidedallthisbylocatingtheresourceIDofthedetailsTextViewandjustsettingthetextofittothenewtextforthenewShakespearetitle.Butthere’sanothersidetofragmentsthatexplainswhyyouuseFragmentTransactions.
Asyouknow,activitiesarearrangedinastack,andasyougetdeeperanddeeperintoanapplication,it’snotuncommontohaveastackofseveralactivitiesgoingatonce.WhenyoupresstheBackbutton,thetopmostactivitygoesaway,andyouarereturnedtotheactivitybelow,whichresumesforyou.Thiscancontinueuntilyou’reatthehomescreenagain.
Thiswasfinewhenanactivitywasjustsingle-purpose,butnowthatanactivitycanhaveseveralfragmentsgoingatonce,andbecauseyoucangodeeperintoyourapplicationwithoutleavingthetopmostactivity,AndroidreallyneededtoextendtheBackbuttonstackconcepttoincludefragmentsaswell.Infact,fragmentsdemandthisevenmore.Whenthereareseveralfragmentsinteractingwitheachotheratthesametimeinanactivity,andthere’satransitiontonewcontentacrossseveralfragmentsatonce,pressingtheBackbuttonshouldcauseeachofthefragmentstorollbackonesteptogether.Toensurethateachfragmentproperlyparticipatesintherollback,aFragmentTransactioniscreatedandmanagedtoperformthatcoordination.
Beawarethatabackstackforfragmentsisnotrequiredwithinanactivity.YoucancodeyourapplicationtolettheBackbuttonworkattheactivitylevelandnotatthefragment
levelatall.Ifthere’snobackstackforyourfragments,pressingtheBackbuttonwillpopthecurrentactivityoffthestackandreturntheusertowhateverwasunderneath.Ifyouchoosetotakeadvantageofthebackstackforfragments,youwillwanttouncommentinListing8-8thelinethatsaysft.addToBackStack(“details”).Forthisparticularcase,you’vehardcodedthetagparametertobethestring“details”.Thistagshouldbeanappropriatestringnamethatrepresentsthestateofthefragmentsatthetimeofthetransaction.Thetagisnotnecessarilyanameforaspecificfragmentbutratherforthefragmenttransactionandallthefragmentsinthetransaction.Youwillbeabletointerrogatethebackstackincodeusingthetagvaluetodeleteentries,aswellaspopentriesoff.Youwillwantmeaningfultagsonthesetransactionstobeabletofindtheappropriateoneslater.
FragmentTransactionTransitionsandAnimationsOneoftheverynicethingsaboutfragmenttransactionsisthatyoucanperformtransitionsfromanoldfragmenttoanewfragmentusingtransitionsandanimations.Thesearenotliketheanimationscominglater,inChapter18.Thesearemuchsimpleranddonotrequirein-depthgraphicsknowledge.Let’suseafragmenttransactiontransitiontoaddspecialeffectswhenyouswapouttheolddetailsfragmentwithanewdetailsfragment.Thiscanaddpolishtoyourapplication,makingtheswitchfromtheoldtothenewfragmentlooksmooth.
OnemethodtoaccomplishthisissetTransition(),asshowninListing8-8.However,thereareafewdifferenttransitionsavailable.Youusedafadeinyourexample,butyoucanalsousethesetCustomAnimations()methodtodescribeotherspecialeffects,suchasslidingonefragmentouttotherightasanotherslidesinfromtheleft.Thecustomanimationsusethenewobjectanimationdefinitions,nottheoldones.TheoldanimXMLfilesusetagssuchas<translate>,whereasthenewXMLfilesuse<objectAnimator>.TheoldstandardXMLfilesarelocatedinthe/data/res/animdirectoryundertheappropriateAndroidSDKplatformsdirectory(suchasplatforms/android-11forHoneycomb).TherearesomenewXMLfileslocatedinthe/data/res/animatordirectoryhere,too.Yourcodecouldbesomethinglike
ft.setCustomAnimations(android.R.animator.fade_in,android.R.animator.fade_out);
whichwillcausethenewfragmenttofadeinastheoldfragmentfadesout.Thefirstparameterappliestothefragmententering,andthesecondparameterappliestothefragmentexiting.FeelfreetoexploretheAndroidanimatordirectoryformorestockanimations.Ifyou’dliketocreateyourown,there’ssectionontheobjectanimatorinChapter18tohelpyou.Theotherveryimportantbitofknowledgeyouneedisthatthetransitioncallsneedtocomebeforethereplace()call;otherwise,theywillhavenoeffect.
Usingtheobjectanimatorforspecialeffectsonfragmentscanbeafunwaytodotransitions.TherearetwoothermethodsonFragmentTransactionyoushouldknow
about:hide()andshow().Bothofthesemethodstakeafragmentasaparameter,andtheydoexactlywhatyou’dexpect.Forafragmentinthefragmentmanagerassociatedtoaviewcontainer,themethodssimplyhideorshowthefragmentintheuserinterface.Thefragmentdoesnotgetremovedfromthefragmentmanagerintheprocess,butitcertainlymustbetiedintoaviewcontainerinordertoaffectitsvisibility.Ifafragmentdoesnothaveaviewhierarchy,orifitsviewhierarchyisnottiedintothedisplayedviewhierarchy,thenthesemethodswon’tdoanything.
Onceyou’vespecifiedthespecialeffectsforyourfragmenttransaction,youhavetotellitthemainworkthatyouwantdone.Inyourcase,you’rereplacingwhateverisintheframelayoutwithyournewdetailsfragment.That’swherethereplace()methodcomesin.Thisisequivalenttocallingremove()foranyfragmentsthatarealreadyintheframelayoutandthenadd()foryournewdetailsfragment,whichmeansyoucouldjustcallremove()oradd()asneededinstead.
Thefinalactionyoumusttakewhenworkingwithafragmenttransactionistocommitit.Thecommit()methoddoesnotcausethingstohappenimmediatelybutratherschedulestheworkforwhentheUIthreadisreadytodoit.
Nowyoushouldunderstandwhyyouneedtogotosomuchtroubletochangethecontentinasimplefragment.It’snotjustthatyouwanttochangethetext;youmightwantaspecialgraphicseffectduringthetransition.Youmayalsowanttosavethetransitiondetailsinafragmenttransactionthatyoucanreverselater.Thatlastpointmaybeconfusing,sowe’llclarify.
Thisisnotatransactioninthetruestsenseoftheword.Whenyoupopfragmenttransactionsoffthebackstack,youarenotundoingallthedatachangesthatmayhavetakenplace.Ifdatachangedwithinyouractivity,forexample,asyoucreatedfragmenttransactionsonthebackstack,pressingtheBackbuttondoesnotcausetheactivitydatachangestorevertbacktotheirpreviousvalues.Youaremerelysteppingbackthroughtheuserinterfaceviewsthewayyoucamein,justasyoudowithactivities,butinthiscaseit’sforfragments.Becauseofthewayfragmentsaresavedandrestored,theinnerstateofafragmentthathasbeenrestoredfromasavedstatewilldependonwhatvaluesyousavedwiththefragmentandhowyoumanagetorestorethem.Soyourfragmentsmaylookthesameastheydidpreviouslybutyouractivitywillnot,unlessyoutakestepstorestoreactivitystatewhenyourestorefragments.
Inyourexample,you’reonlyworkingwithoneviewcontainerandbringinginonedetailsfragment.Ifyouruserinterfaceweremorecomplicated,youcouldmanipulateotherfragmentswithinthefragmenttransaction.Whatyouareactuallydoingisbeginningthetransaction,replacinganyexistingfragmentinyourdetailsframelayoutwithyournewdetailsfragment,specifyingafade-inanimation,andcommittingthetransaction.Youcommentedoutthepartwherethistransactionisaddedtothebackstack,butyoucouldcertainlyuncommentittotakepartinthebackstack.
TheFragmentManager
TheFragmentManagerisacomponentthattakescareofthefragmentsbelongingtoanactivity.Thisincludesfragmentsonthebackstackandfragmentsthatmayjustbehangingaround.We’llexplain.
Fragmentsshouldonlybecreatedwithinthecontextofanactivity.Thisoccurseitherthroughtheinflationofanactivity’slayoutXMLorthroughdirectinstantiationusingcodelikethatinListing8-1.Wheninstantiatedthroughcode,afragmentusuallygetsattachedtotheactivityusingafragmenttransaction.Ineithercase,theFragmentManagerclassisusedtoaccessandmanagethesefragmentsforanactivity.
YouusethegetFragmentManager()methodoneitheranactivityoranattachedfragmenttoretrieveafragmentmanager.YousawinListing8-8thatafragmentmanageriswhereyougetafragmenttransaction.Besidesgettingafragmenttransaction,youcanalsogetafragmentusingthefragment’sID,itstag,oracombinationofbundleandkey.Thefragment’sIDwilleitherbethefragment’sresourceIDifthefragmentwasinflatedfromXML,oritwillbethecontainer’sresourceIDifthefragmentwasplacedintoaviewusingafragmenttransaction.Afragment’stagisaStringthatyoucanassigninthefragment’sXMLdefinition,orwhenthefragmentisplacedinaviewviaafragmenttransaction.ThebundleandkeymethodofretrievingafragmentonlyworksforfragmentsthatwerepersistedusingtheputFragment()method.
Forgettingafragment,thegettermethodsincludefindFragmentById(),findFragmentByTag(),andgetFragment().ThegetFragment()methodwouldbeusedinconjunctionwithputFragment(),whichalsotakesabundle,akey,andthefragmenttobeput.ThebundleismostlikelygoingtobethesavedStatebundle,andputFragment()willbeusedintheonSaveInstanceState()callbacktosavethestateofthecurrentactivity(oranotherfragment).ThegetFragment()methodwouldprobablybecalledinonCreate()tocorrespondtoputFragment(),althoughforafragment,thebundleisavailabletotheothercallbackmethods,asdescribedearlier.
Obviously,youcan’tusethegetFragmentManager()methodonafragmentthathasnotbeenattachedtoanactivityyet.Butit’salsotruethatyoucanattachafragmenttoanactivitywithoutmakingitvisibletotheuseryet.Ifyoudothis,youshouldassociateaStringtagtothefragmentsoyoucangettoitinthefuture.You’dmostlikelyusethismethodofFragmentTransactiontodothis:
publicFragmentTransactionadd(Fragmentfragment,Stringtag)
Infact,youcanhaveafragmentthatdoesnotexhibitaviewhierarchy.Thismightbedonetoencapsulatecertainlogictogethersuchthatitcouldbeattachedtoanactivity,yetstillretainsomeautonomyfromtheactivity’slifecycleandfromotherfragments.Whenanactivitygoesthroughare-createcycleduetoadevice-configurationchange,thisnon-UIfragmentcouldremainlargelyintactwhiletheactivitygoesawayandcomesbackagain.ThiswouldbeagoodcandidateforthesetRetainInstance()option.
Thefragmentbackstackisalsothedomainofthefragmentmanager.Whereasafragment
transactionisusedtoputfragmentsontothebackstack,thefragmentmanagercantakefragmentsoffthebackstack.Thisisusuallydoneusingthefragment’sIDortag,butitcanbedonebasedonpositioninthebackstackorjusttopopthetopmostfragment.
Finally,thefragmentmanagerhasmethodsforsomedebuggingfeatures,suchasturningondebuggingmessagestoLogCatusingenableDebugLogging()ordumpingthecurrentstateofthefragmentmanagertoastreamusingdump().NotethatyouturnedonfragmentmanagerdebuggingintheonCreate()methodofyouractivityinListing8-4.
CautionWhenReferencingFragmentsIt’stimetorevisittheearlierdiscussionofthefragment’slifecycleandtheargumentsandsaved-statebundles.Androidcouldsaveoneofyourfragmentsatmanydifferenttimes.Thismeansthatatthemomentyourapplicationwantstoretrievethatfragment,it’spossiblethatitisnotinmemory.Forthisreason,wecautionyounottothinkthatavariablereferencetoafragmentisgoingtoremainvalidforalongtime.Iffragmentsarebeingreplacedinacontainerviewusingfragmenttransactions,anyreferencetotheoldfragmentisnowpointingtoafragmentthatispossiblyonthebackstack.Orafragmentmaygetdetachedfromtheactivity’sviewhierarchyduringanapplicationconfigurationchangesuchasascreenrotation.Becareful.
Ifyou’regoingtoholdontoareferencetoafragment,beawareofwhenitcouldgetsavedaway;whenyouneedtofinditagain,useoneofthegettermethodsofthefragmentmanager.Ifyouwanttohangontoafragmentreference,suchaswhenanactivityisgoingthroughaconfigurationchange,youcanusetheputFragment()methodwiththeappropriatebundle.Inthecaseofbothactivitiesandfragments,theappropriatebundleisthesavedStatebundlethatisusedinonSaveInstanceState()andthatreappearsinonCreate()(or,inthecaseoffragments,theotherearlycallbacksofthefragment’slifecycle).Youwillprobablyneverstoreadirectfragmentreferenceintotheargumentsbundleofafragment;ifyou’retemptedtodoso,pleasethinkverycarefullyaboutitfirst.
TheotherwayyoucangettoaspecificfragmentisbyqueryingforitusingaknowntagorknownID.Thegettermethodsdescribedpreviouslywillallowretrievaloffragmentsfromthefragmentmanagerthisway,whichmeansyouhavetheoptionofjustrememberingthetagorIDofafragmentsothatyoucanretrieveitfromthefragmentmanagerusingoneofthosevalues,asopposedtousingputFragment()andgetFragment().
SavingFragmentStateAnotherinterestingclasswasintroducedinAndroid3.2:Fragment.SavedState.UsingthesaveFragmentInstanceState()methodofFragmentManager,youcanpassthismethodafragment,anditreturnsanobjectrepresentingthestateofthatfragment.Youcanthenusethatobjectwheninitializingafragment,usingFragment’ssetInitialSavedState()method.Chapter9discussesthisinmoredetail.
ListFragmentsand<fragment>Therearestillafewmorethingstocovertomakeyoursampleapplicationcomplete.ThefirstistheTitlesFragmentclass.Thisistheonethatiscreatedviathemain.xmlfileofyourmainactivity.The<fragment>tagservesasyourplaceholderforwherethisfragmentwillgoanddoesnotdefinewhattheviewhierarchywilllooklikeforthisfragment.TheinterestingcodeforyourTitlesFragmentisinListing8-9.Forallofthecodepleaserefertothesourcecodefiles.TitlesFragmentdisplaysthelistoftitlesforyourapplication.
Listing8-9.TitlesFragmentJavaCode
publicclassTitlesFragmentextendsListFragment{privateMainActivitymyActivity=null;intmCurCheckPosition=0;
@OverridepublicvoidonAttach(ActivitymyActivity){Log.v(MainActivity.TAG,"inTitlesFragmentonAttach;activityis:"+myActivity);super.onAttach(myActivity);this.myActivity=(MainActivity)myActivity;}
@OverridepublicvoidonActivityCreated(BundlesavedState){Log.v(MainActivity.TAG,"inTitlesFragmentonActivityCreated.savedStatecontains:");if(savedState!=null){for(Stringkey:savedState.keySet()){Log.v(MainActivity.TAG,""+key);}}else{Log.v(MainActivity.TAG,"savedStateisnull");}super.onActivityCreated(savedState);
//Populatelistwithyourstaticarrayoftitles.setListAdapter(newArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1,Shakespeare.TITLES));
if(savedState!=null){//Restorelaststateforcheckedposition.
mCurCheckPosition=savedState.getInt("curChoice",0);}
//GetyourListFragment'sListViewandupdateitListViewlv=getListView();lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);lv.setSelection(mCurCheckPosition);
//Activityiscreated,fragmentsareavailable//GoaheadandpopulatethedetailsfragmentmyActivity.showDetails(mCurCheckPosition);}@OverridepublicvoidonSaveInstanceState(BundleoutState){Log.v(MainActivity.TAG,"inTitlesFragmentonSaveInstanceState");super.onSaveInstanceState(outState);outState.putInt("curChoice",mCurCheckPosition);}
@OverridepublicvoidonListItemClick(ListViewl,Viewv,intpos,longid){Log.v(MainActivity.TAG,"inTitlesFragmentonListItemClick.pos="+pos);myActivity.showDetails(pos);mCurCheckPosition=pos;}
@OverridepublicvoidonDetach(){Log.v(MainActivity.TAG,"inTitlesFragmentonDetach");super.onDetach();myActivity=null;}}
UnlikeDetailsFragment,forthisfragmentyoudon’tdoanythingintheonCreateView()callback.Thisisbecauseyou’reextendingtheListFragmentclass,whichcontainsaListViewalready.ThedefaultonCreateView()foraListFragmentcreatesthisListViewforyouandreturnsit.It’snotuntilonActivityCreated()thatyoudoanyrealapplicationlogic.Bythistimeinyourapplication,youcanbesurethattheactivity’sviewhierarchy,plusthisfragment’s,hasbeencreated.TheresourceIDforthatListViewisandroid.R.id.list1,butyou
canalwayscallgetListView()ifyouneedtogetareferencetoit,whichyoudoinonActivityCreated().BecauseListFragmentmanagestheListView,donotattachtheadaptertotheListViewdirectly.YoumustusetheListFragment’ssetListAdapter()methodinstead.Theactivity’sviewhierarchyisnowsetup,soyou’resafegoingbackintotheactivitytodotheshowDetails()call.
Atthispointinyoursampleactivity’slife,you’veaddedalistadaptertoyourlistview,you’verestoredthecurrentposition(ifyoucamebackfromarestore,dueperhapstoaconfigurationchange),andyou’veaskedtheactivity(inshowDetails())tosetthetexttocorrespondtotheselectedShakespeareantitle.
YourTitlesFragmentclassalsohasalisteneronthelistsowhentheuserclicksanothertitle,theonListItemClick()callbackiscalled,andyouswitchthetexttocorrespondtothattitle,againusingtheshowDetails()method.
Anotherdifferencebetweenthisfragmentandtheearlierdetailsfragmentisthatwhenthisfragmentisbeingdestroyedandre-created,yousavestateinabundle(thevalueofthecurrentpositioninthelist),andyoureaditbackinonCreate().UnlikethedetailsfragmentsthatgetswappedinandoutoftheFrameLayoutonyouractivity’slayout,thereisjustonetitlesfragmenttothinkabout.Sowhenthereisaconfigurationchangeandyourtitlesfragmentisgoingthroughasave-and-restoreoperation,youwanttorememberwhereyouwere.Withthedetailsfragments,youcanre-createthemwithouthavingtorememberthepreviousstate.
InvokingaSeparateActivityWhenNeededThere’sapieceofcodewehaven’ttalkedaboutyet,andthatisinshowDetails()whenyou’reinportraitmodeandthedetailsfragmentwon’tfitproperlyonthesamepageasthetitlesfragment.Ifthescreenrealestatewon’tpermitfeasibleviewingofafragmentthatwouldotherwisebeshownalongsidetheotherfragments,youwillneedtolaunchaseparateactivitytoshowtheuserinterfaceofthatfragment.Foryoursampleapplication,youimplementadetailsactivity;thecodeisinListing8-10.
Listing8-10.ShowingaNewActivityWhenaFragmentDoesn’tFit
publicclassDetailsActivityextendsActivity{
@OverridepublicvoidonCreate(BundlesavedInstanceState){Log.v(MainActivity.TAG,"inDetailsActivityonCreate");super.onCreate(savedInstanceState);
if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){//Ifthescreenisnowinlandscapemode,itmeans//thatyourMainActivityisbeingshownwithboth
//thetitlesandthetext,sothisactivityis//nolongerneeded.BailoutandlettheMainActivity//doallthework.finish();return;}
if(getIntent()!=null){//Thisisanotherwaytoinstantiateadetails//fragment.DetailsFragmentdetails=DetailsFragment.newInstance(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content,details).commit();}}}
Thereareseveralinterestingaspectstothiscode.Foronething,itisreallyeasytoimplement.Youmakeasimpledeterminationofthedevice’sorientation,andaslongasyou’reinportraitmode,yousetupanewdetailsfragmentwithinthisdetailsactivity.Ifyou’reinlandscapemode,yourMainActivityisabletodisplayboththetitlesfragmentandthedetailsfragment,sothereisnoreasontobedisplayingthisactivityatall.Youmaywonderwhyyouwouldeverlaunchthisactivityifyou’reinlandscapemode,andtheansweris,youwouldn’t.However,oncethisactivityhasbeenstartedinportraitmode,iftheuserrotatesthedevicetolandscapemode,thisdetailsactivitywillgetrestartedduetotheconfigurationchange.Sonowtheactivityisstartingup,andit’sinlandscapemode.Atthatmoment,itmakessensetofinishthisactivityandlettheMainActivitytakeoveranddoallthework.
AnotherinterestingaspectaboutthisdetailsactivityisthatyouneversettherootcontentviewusingsetContentView().Sohowdoestheuserinterfacegetcreated?Ifyoulookcarefullyattheadd()methodcallonthefragmenttransaction,youwillseethattheviewcontainertowhichyouaddthefragmentisspecifiedastheresourceandroid.R.id.content.Thisisthetop-levelviewcontainerforanactivity,andthereforewhenyouattachyourfragmentviewhierarchytothiscontainer,yourfragmentviewhierarchybecomestheonlyviewhierarchyfortheactivity.YouusedtheverysameDetailsFragmentclassasbeforewiththeothernewInstance()methodtocreatethefragment(theonethattakesabundleasaparameter),thenyousimplyattachedittothetopoftheactivity’sviewhierarchy.Thiscausesthefragmenttobedisplayedwithinthisnewactivity.
Fromtheuser’spointofview,theyarenowlookingatjustthedetailsfragmentview,whichisthetextfromtheShakespeareanplay.Iftheuserwantstoselectadifferenttitle,
theypresstheBackbutton,whichpopsthisactivitytorevealyourmainactivity(withthetitlesfragmentonly).Theotherchoicefortheuseristorotatethedevicetogetbacktolandscapemode.Thenyourdetailsactivitywillcallfinish()andgoaway,revealingthealso-rotatedmainactivityunderneath.
Whenthedeviceisinportraitmode,ifyou’renotshowingthedetailsfragmentinyourmainactivity,youshouldhaveaseparatemain.xmllayoutfileforportraitmodeliketheoneinListing8-11.
Listing8-11.TheLayoutforaPortraitMainActivity
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">
<fragmentclass="com.androidbook.fragments.bard.TitlesFragment"android:id="@+id/titles"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>
Ofcourse,youcouldmakethislayoutwhateveryouwantittobe.Foryourpurposeshere,yousimplymakeitshowthetitlesfragmentbyitself.It’sverynicethatyourtitlesfragmentclassdoesn’tneedtoincludemuchcodetodealwiththedevicereconfiguration.
Takeamomenttoviewthisapplication’smanifestfile.InityoufindthemainactivitywithacategoryofLAUNCHERsothatitwillappearinthedevice’slistofapps.ThenyouhavetheseparateDetailsActivitywithacategoryofDEFAULT.ThisallowsyoutostartthedetailsactivityfromcodebutwillnotshowthedetailsactivityasanappintheApplist.
PersistenceofFragmentsWhenyouplaywiththissampleapplication,makesureyourotatethedevice(pressingCtrl+F11rotatesthedeviceintheemulator).Youwillseethatthedevicerotates,andthefragmentsrotaterightalongwithit.IfyouwatchtheLogCatmessages,youwillseealotofthemforthisapplication.Inparticular,duringadevicerotation,paycarefulattentiontothemessagesaboutfragments;notonlydoestheactivitygetdestroyedandre-created,butthefragmentsdoalso.
Sofar,youonlywroteatinybitofcodeonthetitlesfragmenttorememberthecurrentpositioninthetitleslistacrossrestarts.Youdidn’tdoanythinginthedetailsfragmentcodetohandlereconfigurations,andthat’sbecauseyoudidn’tneedto.Androidwilltakecareofhangingontothefragmentsthatareinthefragmentmanager,savingthemaway,
thenrestoringthemwhentheactivityisbeingre-created.Youshouldrealizethatthefragmentsyougetbackafterthereconfigurationiscompleteareverylikelynotthesamefragmentsinmemorythatyouhadbefore.Thesefragmentshavebeenreconstructedforyou.Androidsavedtheargumentsbundleandtheknowledgeofwhichtypeoffragmentitwas,anditstoredthesaved-statebundlesforeachfragmentthatcontainsaved-stateinformationaboutthefragmenttousetorestoreitontheotherside.
TheLogCatmessagesshowyouthefragmentsgoingthroughtheirlifecyclesinsyncwiththeactivity.Youwillseethatyourdetailsfragmentgetsre-created,butyournewInstance()methoddoesnotgetcalledagain.Instead,Androidusesthedefaultconstructor,attachestheargumentsbundletoit,andthenstartscallingthecallbacksonthefragment.ThisiswhyitissoimportantnottodoanythingfancyinthenewInstance()method:whenthefragmentgetsre-created,itwon’tdoitthroughnewInstance().
Youshouldalsoappreciatebynowthatyou’vebeenabletoreuseyourfragmentsinafewdifferentplaces.Thetitlesfragmentwasusedintwodifferentlayouts,butifyoulookatthetitlesfragmentcode,itdoesn’tworryabouttheattributesofeachlayout.Youcouldmakethelayoutsratherdifferentfromeachother,andthetitlesfragmentcodewouldlookthesame.Thesamecanbesaidofthedetailsfragment.Itwasusedinyourmainlandscapelayoutandwithinthedetailsactivityallbyitself.Again,thelayoutforthedetailsfragmentcouldhavebeenverydifferentbetweenthetwo,andthecodeofthedetailsfragmentwouldbethesame.Thecodeofthedetailsactivitywasverysimple,also.
Sofar,you’veexploredtwoofthefragmenttypes:thebaseFragmentclassandtheListFragmentsubclass.Fragmenthasothersubclasses:theDialogFragment,PreferenceFragment,andWebViewFragment.We’llcoverDialogFragmentandPreferenceFragmentinChapters10and11,respectively.
CommunicationswithFragmentsBecausethefragmentmanagerknowsaboutallfragmentsattachedtothecurrentactivity,theactivityoranyfragmentinthatactivitycanaskforanyotherfragmentusingthegettermethodsdescribedearlier.Oncethefragmentreferencehasbeenobtained,theactivityorfragmentcouldcastthereferenceappropriatelyandthencallmethodsdirectlyonthatactivityorfragment.Thiswouldcauseyourfragmentstohavemoreknowledgeabouttheotherfragmentsthanmightnormallybedesired,butdon’tforgetthatyou’rerunningthisapplicationonamobiledevice,socuttingcornerscansometimesbejustified.AcodesnippetisprovidedinListing8-12toshowhowonefragmentmightcommunicatedirectlywithanotherfragment.ThesnippetwouldbepartofoneofyourextendedFragmentclasses,andFragmentOtherisadifferentextendedFragmentclass.
Listing8-12.DirectFragment-to-FragmentCommunication
FragmentOtherfragOther=(FragmentOther)getFragmentManager().findFragmentByTag("other");fragOther.callCustomMethod(arg1,arg2);
InListing8-12,thecurrentfragmenthasdirectknowledgeoftheclassoftheotherfragmentandalsowhichmethodsexistonthatclass.Thismaybeokaybecausethesefragmentsarepartofoneapplication,anditcanbeeasiertosimplyacceptthefactthatsomefragmentswillknowaboutotherfragments.We’llshowyouacleanerwaytocommunicatebetweenfragmentsintheDialogFragmentsampleapplicationinChapter10.
UsingstartActivity()andsetTargetFragment()Afeatureoffragmentsthatisverymuchlikeactivitiesistheabilityofafragmenttostartanactivity.FragmenthasastartActivity()methodandstartActivityForResult()method.Theseworkjustliketheonesforactivities;whenaresultispassedback,itwillcausetheonActivityResult()callbacktofireonthefragmentthatstartedtheactivity.
There’sanothercommunicationmechanismyoushouldknowabout.Whenonefragmentwantstostartanotherfragment,thereisafeaturethatletsthecallingfragmentsetitsidentitywiththecalledfragment.Listing8-13showsanexampleofwhatitmightlooklike.
Listing8-13.Fragment-to-Target-FragmentSetup
mCalledFragment=newCalledFragment();mCalledFragment.setTargetFragment(this,0);fm.beginTransaction().add(mCalledFragment,"work").commit();
Withthesefewlines,you’vecreatedanewCalledFragmentobject,setthetargetfragmentonthecalledfragmenttothecurrentfragment,andaddedthecalledfragmenttothefragmentmanagerandactivityusingafragmenttransaction.Whenthecalledfragmentstartstorun,itwillbeabletocallgetTargetFragment(),whichwillreturnareferencetothecallingfragment.Withthisreference,thecalledfragmentcouldinvokemethodsonthecallingfragmentorevenaccessviewcomponentsdirectly.Forexample,inListing8-14,thecalledfragmentcouldsettextintheUIofthecallingfragmentdirectly.
Listing8-14.TargetFragment-to-FragmentCommunication
TextViewtv=(TextView)getTargetFragment().getView().findViewById(R.id.text1);tv.setText("Setfromthecalledfragment");
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.ThefilecalledProAndroid5_Ch08_Fragments.zipcontainsallprojects
fromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoanIDEfromoneofthesezipfiles.ItincludessomeprojectsthatusetheFragmentCompatibilitySDKforolderAndroidsaswell.
http://developer.android.com/guide/components/fragments.htmlTheAndroidDeveloper’sGuidepagetofragments.
http://developer.android.com/design/patterns/multi-pane-layouts.html:Androiddesignguidelinesformultipanelayouts.
http://developer.android.com/training/basics/fragments/index.htmlAndroidtrainingpageforfragments.
SummaryThischapterintroducedtheFragmentclassanditsrelatedclassesforthemanager,transactions,andsubclasses.Thisisasummaryofwhat’sbeencoveredinthischapter:
TheFragmentclass,whatitdoes,andhowtouseit.
Whyfragmentscannotbeusedwithoutbeingattachedtooneandonlyoneactivity.
ThatalthoughfragmentscanbeinstantiatedwithastaticfactorymethodsuchasnewInstance(),youmustalwayshaveadefaultconstructorandawaytosaveinitializationvaluesintoaninitializationargumentsbundle.
Thelifecycleofafragmentandhowitisintertwinedwiththelifecycleoftheactivitythatownsthefragment.
FragmentManageranditsfeatures.
Managingdeviceconfigurationsusingfragments.
Combiningfragmentsintoasingleactivity,orsplittingthembetweenmultipleactivities.
Usingfragmenttransactionstochangewhat’sdisplayedtoauser,andanimatingthosetransitionsusingcooleffects.
NewbehaviorsthatarepossiblewiththeBackbuttonwhenusingfragments.
Usingthe<fragment>taginalayout.
UsingaFrameLayoutasaplaceholderforafragmentwhenyouwanttousetransitions.
ListFragmentandhowtouseanadaptertopopulatethedata
(verymuchlikeaListView).
Launchinganewactivitywhenafragmentcan’tfitontothecurrentscreen,andhowtoadjustwhenaconfigurationchangemakesitpossibletoseemultiplefragmentsagain.
Communicatingbetweenfragments,andbetweenafragmentanditsactivity.
Chapter9
RespondingtoConfigurationChangesWe’vecoveredafairbitofmaterialsofar,andnowseemslikeagoodtimetocoverconfigurationchanges.Whenanapplicationisrunningonadevice,andthedevice’sconfigurationchanges(forexample,isrotated90degrees),yourapplicationneedstorespondaccordingly.Thenewconfigurationwillmostlikelylookdifferentfromthepreviousconfiguration.Forexample,switchingfromportraittolandscapemodemeansthescreenwentfrombeingtallandnarrowtobeingshortandwide.TheUIelements(buttons,text,lists,andsoon)willneedtoberearranged,resized,orevenremovedtoaccommodatethenewconfiguration.
InAndroid,aconfigurationchangebydefaultcausesthecurrentactivitytogoawayandbere-created.Theapplicationitselfkeepsonrunning,butithastheopportunitytochangehowtheactivityisdisplayedinresponsetotheconfigurationchange.Intherarecasethatyouneedtohandleaconfigurationchangewithoutdestroyingandre-creatingyouractivity,Androidprovidesawaytohandlethataswell.
Beawarethatconfigurationchangescantakeonmanyforms,notjustdevicerotation.Ifadevicegetsconnectedtoadock,that’salsoaconfigurationchange.Soischangingthelanguageofthedevice.Whateverthenewconfigurationis,aslongasyou’vedesignedyouractivityforthatconfiguration,Androidtakescareofmosteverythingtotransitiontoit,givingtheuseraseamlessexperience.
Thischapterwilltakeyouthroughtheprocessofaconfigurationchange,fromtheperspectivesofbothactivitiesandfragments.We’llshowyouhowtodesignyourapplicationforthosetransitionsandhowtoavoidtrapsthatcouldcauseyourapplicationtocrashormisbehave.
TheDefaultConfigurationChangeProcessTheAndroidoperatingsystemkeepstrackofthecurrentconfigurationofthedeviceit’srunningon.Configurationincludeslotsoffactors,andnewonesgetaddedallthetime.Forexample,ifadeviceispluggedintoadockingstation,thatrepresentsachangeinthedeviceconfiguration.WhenaconfigurationchangeisdetectedbyAndroid,callbacksareinvokedinrunningapplicationstotellthemachangeisoccurring,soanapplicationcanproperlyrespondtothechange.We’lldiscussthosecallbacksalittlelater,butfornowlet’srefreshyourmemorywithregardtoresources.
OneofthegreatfeaturesofAndroidisthatresourcesgetselectedforyouractivitybasedonthecurrentconfigurationofthedevice.Youdon’tneedtowritecodetofigureoutwhichconfigurationisactive;youjustaccessresourcesbyname,andAndroidgetstheappropriateresourcesforyou.Ifthedeviceisinportraitmodeandyourapplication
requestsalayout,yougettheportraitlayout.Ifthedeviceisinlandscapemode,yougetthelandscapelayout.Thecodejustrequestsalayoutwithoutspecifyingwhichoneitshouldget.Thisispowerfulbecauseasnewconfigurationfactorsgetintroduced,ornewvaluesforconfigurationfactors,thecodestaysthesame.Alladeveloperneedstodoisdecideifnewresourcesneedtobecreated,andcreatethemasnecessaryforthenewconfiguration.Then,whentheapplicationgoesthroughaconfigurationchange,Androidprovidesthenewresourcestotheapplication,andeverythingcontinuestofunctionasdesired.
Becauseofagreatdesiretokeepthingssimple,Androiddestroysthecurrentactivitywhentheconfigurationchangesandcreatesanewoneinitsplace.Thismightseemratherharsh,butit’snot.Itisabiggerchallengetotakearunningactivityandfigureoutwhichpartswouldstaythesameandwhichwouldnot,andthenonlyworkwiththepiecesthatneedtochange.
Anactivitythat’sabouttobedestroyedisproperlynotifiedfirst,givingyouachancetosaveanythingthatneedstobesaved.Whenthenewactivitygetscreated,ithastheopportunitytorestorestateusingdatafromthepreviousactivity.Foragooduserexperience,obviouslyyoudonotwantthissaveandrestoretotakeverylong.
It’sfairlyeasytosaveanydatathatyouneedsavedandthenletAndroidthrowawaytherestandstartover,aslongasthedesignoftheapplicationanditsactivitiesissuchthatactivitiesdon’tcontainalotofnon-UIstuffthatwouldtakealongtimetore-create.Thereinliesthesecrettosuccessfulconfigurationchangedesign:donotput“stuff”insideanactivitythatcannotbeeasilyre-createdduringaconfigurationchange.
Keepinmindthatourapplicationisnotbeingdestroyed,soanythingthatisintheapplicationcontext,andnotapartofourcurrentactivity,willstillbethereforthenewactivity.Singletonswillstillbeavailable,aswellasanybackgroundthreadswemighthavespunofftodoworkforourapplication.Anydatabasesorcontentprovidersthatwewereworkingwithwillalsostillbearound.Takingadvantageofthesemakesconfigurationchangesquickandpainless.Keepdataandbusinesslogicoutsideofactivitiesifyoucan.
Theconfigurationchangeprocessissomewhatsimilarbetweenactivitiesandfragments.Whenanactivityisbeingdestroyedandre-created,thefragmentswithinthatactivitygetdestroyedandre-created.Whatweneedtoworryaboutthenisstateinformationaboutourfragmentsandactivity,suchasdatacurrentlybeingdisplayedtotheuser,orinternalvaluesthatwewanttopreserve.Wewillsavewhatwewanttokeep,andpickitupagainontheothersidewhenthefragmentsandactivitiesarebeingre-created.You’llwanttoprotectdatathatcan’teasilybere-createdbynotlettingitgetdestroyedinthedefaultconfigurationchangeprocess.
TheDestroy/CreateCycleofActivitiesTherearethreecallbackstobeawareofwhendealingwithdefaultconfigurationchangesinactivities:
onSaveInstanceState()
onCreate()
onRestoreInstanceState()
ThefirstisthecallbackthatAndroidwillinvokewhenitdetectsthataconfigurationchangeishappening.Theactivityhasachancetosavestatethatitwantstorestorewhenthenewactivitygetscreatedattheendoftheconfigurationchange.TheonSaveInstanceState()callbackwillbecalledpriortothecalltoonStop().WhateverstateexistscanbeaccessedandsavedintoaBundleobject.Thisobjectwillgetpassedintobothoftheothercallbacks(onCreate()andonRestoreInstanceState())whentheactivityisre-created.Youonlyneedtoputlogicinoneortheothertorestoreyouractivity’sstate.
ThedefaultonSaveInstanceState()callbackdoessomenicethingsforyou.Forexample,itgoesthroughthecurrentlyactiveviewhierarchyandsavesthevaluesforeachviewthathasanandroid:id.ThismeansifyouhaveanEditTextviewthathasreceivedsomeuserinput,thatinputwillbeavailableontheothersideoftheactivitydestroy/createcycletopopulatetheEditTextbeforetheusergetscontrolback.Youdonotneedtogothroughandsavethisstateyourself.IfyoudooverrideonSaveInstanceState(),besuretocallsuper.onSaveInstanceState()withthebundleobjectsoitcantakecareofthisforyou.It’snottheviewsthataresaved,onlytheattributesoftheirstatethatshouldpersistacrossthedestroy/createboundary.
Tosavedatainthebundleobject,usemethodssuchasputInt()forintegersandputString()forstrings.Therearequiteafewmethodsintheandroid.os.Bundleclass;youarenotlimitedtointegersandstrings.Forexample,putParcelable()canbeusedtosavecomplexobjects.Eachputisusedwithastringkey,andyouwillretrievethevaluelaterusingthesamekeyusedtoputthevaluein.AsampleonSaveInstanceState()mightlooklikeListing9-1.
Listing9-1.SampleonSaveInstanceState()
@OverridepublicvoidonSaveInstanceState(Bundleicicle){super.onSaveInstanceState(icicle);icicle.putInt("counter",1);}
Sometimesthebundleiscallediciclebecauseitrepresentsasmallfrozenpieceofanactivity.Inthissample,youonlysaveonevalue,andithasakeyofcounter.Youcouldsavemorevaluesbysimplyaddingmoreputstatementstothiscallback.Thecountervalueinthisexampleissomewhattemporarybecauseiftheapplicationiscompletelydestroyed,thecurrentvaluewillbelost.Thiscouldhappeniftheuserturnedofftheirdevice,forexample.InChapter11,you’lllearnaboutwaystosavevaluesmorepermanently.Thisinstancestateisonlymeanttohangontovalueswhiletheapplicationisrunningthistime.Donotusethismechanismforstatethatisimportanttokeepforalongerterm.
Torestoreactivitystate,youaccessthebundleobjecttoretrievevaluesthatyoubelievearethere.Again,youusemethodsoftheBundleclasssuchasgetInt()andgetString()withtheappropriatekeypassedtotellwhichvalueyouwantback.IfthekeydoesnotexistintheBundle,avalueof0ornullispassedback(dependingonthetypeoftheobjectbeingrequested).Oryoucanprovideadefaultvalueintheappropriategettermethod.Listing9-2showsasampleonRestoreInstanceState()callback.
Listing9-2.SampleonRestoreInstanceState()
@OverridepublicvoidonRestoreInstanceState(Bundleicicle){super.onRestoreInstanceState(icicle);intsomeInt=icicle.getInt("counter",-1);//NowgodosomethingwithsomeInttorestorethe//stateoftheactivity.-1isthedefaultifno//valuewasfound.}
It’suptoyouwhetheryourestorestateinonCreate()orinonRestoreInstanceState().ManyapplicationswillrestorestateinonCreate()becausethatiswherealotofinitializationisdone.Onereasontoseparatethetwowouldbeifyou’recreatinganactivityclassthatcouldbeextended.ThedevelopersdoingtheextendingmightfinditeasiertojustoverrideonRestoreInstanceState()withthecodetorestorestate,ascomparedtohavingtooverrideallofonCreate().
What’sveryimportanttonotehereisthatyouneedtobeveryconcernedwithreferencestoactivitiesandviewsandotherobjectsthatneedtobegarbage-collectedwhenthecurrentactivityisfullydestroyed.Ifyouputsomethingintothesavedbundlethatrefersbacktotheactivitybeingdestroyed,thatactivitycan’tbegarbagecollected.Thisisverylikelyamemoryleakthatcouldgrowandgrowuntilyourapplicationcrashes.ObjectstoavoidinbundlesincludeDrawables,Adapters,Views,andanythingelsethatistiedtotheactivitycontext.InsteadofputtingaDrawableintothebundle,serializethebitmapandsavethat.Orbetteryet,managethebitmapsoutsideoftheactivityandfragmentinsteadofinside.Addsomesortofreferencetothebitmaptothebundle.Whenitcomestimetore-createanyDrawablesforthenewfragment,usethereferencetoaccesstheoutsidebitmapstoregenerateyourDrawables.
TheDestroy/CreateCycleofFragmentsThedestroy/createcycleforfragmentsisverysimilartothatofactivities.Afragmentintheprocessofbeingdestroyedandre-createdwillhaveitsonSaveInstanceState()callbackcalled,allowingthefragmenttosavevaluesinaBundleobjectforlater.OnedifferenceisthatsixfragmentcallbacksreceivethisBundleobjectwhenafragmentisbeingre-created:onInflate(),onCreate(),onCreateView(),onActivityCreated(),onViewCreated(),andonViewStateRestored().
Thelasttwocallbacksaremorerecent,fromHoneycomb3.2andJellyBean4.2respectively.Thisgivesuslotsofopportunitiestorebuildtheinternalstateofourreconstructedfragmentfromitspreviousstate.
AndroidguaranteesonlythatonSaveInstanceState()willbecalledforafragmentsometimebeforeonDestroy().ThatmeanstheviewhierarchymayormaynotbeattachedwhenonSaveInstanceState()iscalled.Therefore,don’tcountontraversingtheviewhierarchyinsideofonSaveInstanceState().Forexample,ifthefragmentisonthefragmentbackstack,noUIwillbeshowing,sonoviewhierarchywillexist.ThisisOKofcoursebecauseifnoUIisshowing,thereisnoneedtoattempttocapturethecurrentvaluesofviewstosavethem.Youneedtocheckifaviewexistsbeforetryingtosaveitscurrentvalue,andnotconsideritanerroriftheviewdoesnotexist.
Justlikewithactivities,becarefulnottoincludeitemsinthebundleobjectthatrefertoanactivityortoafragmentthatmightnotexistlaterwhenthisfragmentisbeingre-created.Keepthesizeofthebundleassmallaspossible,andasmuchaspossiblestorelong-lastingdataoutsideofactivitiesandfragmentsandsimplyrefertoitfromyouractivitiesandfragments.Thenyourdestroy/createcycleswillgothatmuchfaster,you’llbemuchlesslikelytocreateamemoryleak,andyouractivityandfragmentcodeshouldbeeasiertomaintain.
UsingFragmentManagertoSaveFragmentStateFragmentshaveanotherwaytosavestate,inadditionto,orinsteadof,Androidnotifyingthefragmentsthattheirstateshouldbesaved.WithHoneycomb3.2,theFragmentManagerclassgotasaveFragmentInstanceState()methodthatcanbecalledtogenerateanobjectoftheclassFragment.SavedState.ThemethodsmentionedintheprevioussectionsforsavingstatedosowithintheinternalsofAndroid.Whileweknowthatthestateisbeingsaved,wedonothaveanydirectaccesstoit.Thismethodofsavingstategivesyouanobjectthatrepresentsthesavedstateofafragmentandallowsyoutocontrolifandwhenafragmentiscreatedfromthatstate.
ThewaytouseaFragment.SavedStateobjecttorestoreafragmentisthroughthesetInitialSavedState()methodoftheFragmentclass.InChapter8,youlearnedthatitisbesttocreatenewfragmentsusingastaticfactorymethod(forexample,newInstance()).Withinthismethod,yousawhowadefaultconstructoriscalledandthenanargumentsbundleisattached.YoucouldinsteadcallthesetInitialSavedState()methodtosetitupforrestorationtoapreviousstate.
Thereareafewcaveatsyoushouldknowaboutthismethodofsavingfragmentstate:
Thefragmenttobesavedmustcurrentlybeattachedtothefragmentmanager.
Anewfragmentcreatedusingthissavedstatemustbethesameclasstypeasthefragmentitwascreatedfrom.
Thesavedstatecannotcontaindependenciesonotherfragments.Otherfragmentsmaynotexistwhenthesavedfragmentisre-created.
UsingsetRetainInstanceonaFragmentAfragmentcanavoidbeingdestroyedandre-createdonaconfigurationchange.IfthesetRetainInstance()methodiscalledwithanargumentoftrue,thefragmentwillberetainedintheapplicationwhenitsactivityisbeingdestroyedandre-created.Thefragment’sonDestroy()callbackwillnotbecalled,norwillonCreate().TheonDetach()callbackwillbecalledbecausethefragmentmustbedetachedfromtheactivitythat’sgoingaway,andonAttach()andonActivityCreated()willbecalledbecausethefragmentisattachedtoanewactivity.Thisonlyworksforfragmentsthatarenotonthebackstack.ItisespeciallyusefulforfragmentsthatdonothaveaUI.
Thisfeatureisverypowerfulinthatyoucanuseanon-UIfragmenttohandlereferencestoyourdataobjectsandbackgroundthreads,andcallsetRetainInstance(true)onthisfragmentsoitwon’tgetdestroyedandre-createdonaconfigurationchange.Theaddedbonusisthatduringthenormalconfigurationchangeprocess,thenon-UIfragmentcallbacksonDetach()andonAttach()willswitchtheactivityreferencefromtheoldtothenew.
DeprecatedConfigurationChangeMethodsAcoupleofmethodsonActivityhavebeendeprecated,soyoushouldnolongerusethem:
getLastNonConfigurationInstance()
onRetainNonConfigurationInstance()
Thesemethodspreviouslyallowedyoutosaveanarbitraryobjectfromanactivitythatwasbeingdestroyed,tobepassedtothenextinstanceoftheactivitythatwasbeingcreated.Althoughtheywereuseful,youshouldnowusethemethodsdescribedearlierinsteadtomanagedatabetweeninstancesofactivitiesinthedestroy/createcycle.
HandlingConfigurationChangesYourselfSofar,you’veseenhowAndroidhandlesconfigurationchangesforyou.Ittakescareofdestroyingandre-creatingactivitiesandfragments,pullinginthebestresourcesforthenewconfiguration,retaininganyuser-entereddata,andgivingyoutheopportunitytoexecutesomeextralogicinsomecallbacks.Thisisusuallygoingtobeyourbestoption.Butwhenitisn’t,whenyouhavetohandleaconfigurationchangeyourself,Androidprovidesawayout.Thisisn’trecommendedbecauseitisthencompletelyuptoyoutodeterminewhatneedstochangeduetothechange,andthenforyoutotakecareofmakingallthechanges.Asmentionedbefore,therearemanyconfigurationchangesbesidesjustanorientationchange.Luckily,youdon’tnecessarilyhavetohandleall
configurationchangesyourself.
Thefirststeptohandlingconfigurationchangesyourselfistodeclareinthe<activity>taginAndroidManifest.xmlfilewhichchangesyou’regoingtohandleusingtheandroid:configChangesattribute.Androidwillhandletheotherconfigurationchangesusingthepreviouslydescribedmethods.Youcanspecifyasmanyconfigurationchangetypesasneededbyor’ingthemtogetherwiththe‘|’symbol,likethis:
<activity...android:configChanges="orientation|keyboardHidden"...>
ThecompletelistofconfigurationchangetypescanbefoundonthereferencepageforR.attr.BeawarethatifyoutargetAPI13orhigherandyouneedtohandleorientation,youalsoneedtohandlescreenSize.
Thedefaultprocessforaconfigurationchangeistheinvokingofcallbackstodestroyandre-createtheactivityorfragment.Whenyou’vedeclaredthatyouwillhandlethespecificconfigurationchange,theprocesschangessoonlytheonConfigurationChanged()callbackisinvokedinstead,ontheactivityanditsfragments.AndroidpassesinaConfigurationobjectsothecallbackknowswhatthenewconfigurationis.Itisuptothecallbacktodeterminewhatmighthavechanged;however,sinceyoulikelyhandleonlyasmallnumberofconfigurationchangesyourself,itshouldn’tbetoohardtofigurethisout.
You’dreallyonlywanttohandleaconfigurationchangeyourselfwhenthereisverylittletobedone,whenyoucouldskipdestroyingandre-creating.Forexample,iftheactivitylayoutforportraitandlandscapeisthesamelayoutandallimageresourcesarethesame,destroyingandre-creatingtheactivitydoesn’treallyaccomplishanything.Inthiscaseitwouldbefairlysafetodeclarethatyouwillhandletheorientationconfigurationchange.Duringanorientationchangeofyouractivity,theactivitywouldremainintactandsimplyre-renderitselfintheneworientationusingtheexistingresourcessuchasthelayout,images,strings,etc.Butit’sreallynotthatbigadealtojustletAndroidtakecareofthingsifyoucan.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch09_ConfigChanges.zip.ThisZIPfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.
http://developer.android.com/guide/topics/fundamentals/activities.html#SavingActivityStateTheAndroidDeveloper’sGuide,whichdiscussessavingand
restoringstate.
http://developer.android.com/guide/topics/resources/runtime-changes.html:TheAndroidAPIGuideforHandlingRuntimeChanges.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedabouthandlingconfigurationchanges:
Activitiesbydefaultgetdestroyedandre-createdduringconfigurationchanges.Sodofragments.
Avoidputtinglotsofdataandlogicintoactivitiessoconfigurationchangesoccurquickly.
LetAndroidprovidetheappropriateresources.
Usesingletonstoholddataoutsideofactivitiestomakeiteasiertodestroyandre-createactivitiesduringconfigurationchanges.
TakeadvantageofthedefaultonSaveInstanceState()callbacktosaveUIstateonviewswithandroid:ids.
Ifafragmentcansurvivewithnoissuesacrossanactivitydestroy-and-createcycle,usesetRetainInstance()totellAndroiditdoesn’tneedtodestroyandcreatethefragment.
Chapter10
WorkingwithDialogsTheAndroidSDKoffersextensivesupportfordialogs.Adialogisasmallerwindowthatpopsupinfrontofthecurrentwindowtoshowanurgentmessage,toprompttheuserforapieceofinput,ortoshowsomesortofstatusliketheprogressofadownload.Theuserisgenerallyexpectedtointeractwiththedialogandthenreturntothewindowunderneathtocontinuewiththeapplication.Technically,Androidallowsadialogfragmenttoalsobeembeddedwithinanactivity’slayout,andwe’llcoverthataswell.
DialogsthatareexplicitlysupportedinAndroidincludethealert,prompt,pick-list,single-choice,multiple-choice,progress,time-picker,anddate-pickerdialogs.(ThislistcouldvarydependingontheAndroidrelease.)Androidalsosupportscustomdialogsforotherneeds.TheprimarypurposeofthischapterisnottocovereverysingleoneofthesedialogsbuttocovertheunderlyingarchitectureofAndroiddialogswithasampleapplication.FromthereyoushouldbeabletouseanyoftheAndroiddialogs.
It’simportanttonotethatAndroid3.0addeddialogsbasedonfragments.TheexpectationfromGoogleisthatdeveloperswillonlyusefragmentdialogs,evenintheversionsofAndroidbefore3.0.Thiscanbedonewiththefragment-compatibilitylibrary.Forthisreason,thischapterfocusesonDialogFragment.
UsingDialogsinAndroidDialogsinAndroidareasynchronous,whichprovidesflexibility.However,ifyouareaccustomedtoaprogrammingframeworkwheredialogsareprimarilysynchronous(suchasMicrosoftWindows,orJavaScriptdialogsinwebpages),youmightfindasynchronousdialogsabitunintuitive.Withasynchronousdialog,thelineofcodeafterthedialogisshowndoesnotrununtilthedialoghasbeendismissed.Thismeansthenextlineofcodecouldinterrogatewhichbuttonwaspressed,orwhattextwastypedintothedialog.InAndroidhowever,dialogsareasynchronous.Assoonasthedialoghasbeenshown,thenextlineofcoderuns,eventhoughtheuserhasn’ttouchedthedialogyet.Yourapplicationhastodealwiththisfactbyimplementingcallbacksfromthedialog,toallowtheapplicationtobenotifiedofuserinteractionwiththedialog.
Thisalsomeansyourapplicationhastheabilitytodismissthedialogfromcode,whichispowerful.Ifthedialogisdisplayingabusymessagebecauseyourapplicationisdoingsomething,assoonasyourapplicationhascompletedthattask,itcandismissthedialogfromcode.
UnderstandingDialogFragmentsInthissection,youlearnhowtousedialogfragmentstopresentasimplealertdialogand
acustomdialogthatisusedtocollectprompttext.
DialogFragmentBasicsBeforeweshowyouworkingexamplesofapromptdialogandanalertdialog,wewouldliketocoverthehigh-levelideaofdialogfragments.Dialog-relatedfunctionalityusesaclasscalledDialogFragment.ADialogFragmentisderivedfromtheclassFragmentandbehavesmuchlikeafragment.YouwillthenusetheDialogFragmentasthebaseclassforyourdialogs.Onceyouhaveaderiveddialogfromthisclasssuchas
publicclassMyDialogFragmentextendsDialogFragment{...}
youcanthenshowthisdialogfragmentMyDialogFragmentasadialogusingafragmenttransaction.Listing10-1showsacodesnippettodothis.
Listing10-1.ShowingaDialogFragment
publicclassSomeActivityextendsActivity{//....otheractivityfunctionspublicvoidshowDialog(){//constructMyDialogFragmentMyDialogFragmentmdf=MyDialogFragment.newInstance(arg1,arg2);FragmentManagerfm=getFragmentManager();FragmentTransactionft=fm.beginTransaction();mdf.show(ft,"my-dialog-tag");}//....otheractivityfunctions}
NoteWeprovidealinktoadownloadableprojectattheendofthischapterinthe“References”section.Youcanusethisdownloadtoexperimentwiththecodeandtheconceptspresentedinthischapter.
FromListing10-1,thestepstoshowadialogfragmentareasfollows:
1. Createadialogfragment.
2. Getafragmenttransaction.
3. Showthedialogusingthefragmenttransactionfromstep2.
Let’stalkabouteachofthesesteps.
ConstructingaDialogFragmentWhenconstructingadialogfragment,therulesarethesameaswhenbuildinganyother
kindoffragment.TherecommendedpatternistouseafactorymethodsuchasnewInstance()asyoudidbefore.InsidethatnewInstance()method,youusethedefaultconstructorforyourdialogfragment,andthenyouaddanargumentsbundlethatcontainsyourpassed-inparameters.Youdon’twanttodootherworkinsidethismethodbecauseyoumustmakesurethatwhatyoudohereisthesameaswhatAndroiddoeswhenitrestoresyourdialogfragmentfromasavedstate.AndallthatAndroiddoesistocallthedefaultconstructorandre-createtheargumentsbundleonit.
OverridingonCreateViewWhenyouinheritfromadialogfragment,youneedtooverrideoneoftwomethodstoprovidetheviewhierarchyforyourdialog.ThefirstoptionistooverrideonCreateView()andreturnaview.ThesecondoptionistooverrideonCreateDialog()andreturnadialog(liketheoneconstructedbyanAlertDialog.Builder,whichwe’llgettoshortly).
Listing10-2showsanexampleofoverridingtheonCreateView().
Listing10-2.OverridingonCreateView()ofaDialogFragment
publicclassMyDialogFragmentextendsDialogFragmentimplementsView.OnClickListener{.....otherfunctionspublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){//CreateaviewbyinflatingdesiredlayoutViewv=inflater.inflate(R.layout.prompt_dialog,container,false);
//youcanlocateaviewandsetvaluesTextViewtv=(TextView)v.findViewById(R.id.promptmessage);tv.setText(this.getPrompt());
//YoucansetcallbacksonbuttonsButtondismissBtn=(Button)v.findViewById(R.id.btn_dismiss);dismissBtn.setOnClickListener(this);
ButtonsaveBtn=(Button)v.findViewById(R.id.btn_save);saveBtn.setOnClickListener(this);returnv;}.....otherfunctions}
InListing10-2,youareloadingaviewidentifiedbyalayout.Thenyoulookfortwobuttonsandsetupcallbacksonthem.ThisisverysimilartohowyoucreatedthedetailsfragmentinChapter8.However,unliketheearlierfragments,adialogfragmenthasanotherwaytocreatetheviewhierarchy.
OverridingonCreateDialogAsanalternatetosupplyingaviewinonCreateView(),youcanoverrideonCreateDialog()andsupplyadialoginstance.Listing10-3suppliessamplecodeforthisapproach.
Listing10-3.OverridingonCreateDialog()ofaDialogFragment
publicclassMyDialogFragmentextendsDialogFragmentimplementsDialogInterface.OnClickListener{.....otherfunctions@OverridepublicDialogonCreateDialog(Bundleicicle){AlertDialog.Builderb=newAlertDialog.Builder(getActivity()).setTitle("MyDialogTitle").setPositiveButton("Ok",this).setNegativeButton("Cancel",this).setMessage(this.getMessage());returnb.create();}.....otherfunctions}
Inthisexample,youusethealertdialogbuildertocreateadialogobjecttoreturn.Thisworkswellforsimpledialogs.ThefirstoptionofoverridingonCreateView()isequallyeasyandprovidesmuchmoreflexibility.
AlertDialog.Builderisactuallyacarryoverfrompre-3.0Android.Thisisoneoftheoldwaystocreateadialog,andit’sstillavailabletoyoutocreatedialogswithinDialogFragments.Asyoucansee,it’sfairlyeasytobuildadialogbycallingthevariousmethodsavailable,aswe’vedonehere.
DisplayingaDialogFragmentOnceyouhaveadialogfragmentconstructed,youneedafragmenttransactiontoshowit.Likeallotherfragments,operationsondialogfragmentsareconductedthroughfragmenttransactions.
Theshow()methodonadialogfragmenttakesafragmenttransactionasaninput.YoucanseethisinListing10-1.Theshow()methodusesthefragmenttransactiontoaddthisdialogtotheactivityandthencommitsthefragmenttransaction.However,theshow()
methoddoesnotaddthetransactiontothebackstack.Ifyouwanttodothis,youneedtoaddthistransactiontothebackstackfirstandthenpassittotheshow()method.Theshow()methodofadialogfragmenthasthefollowingsignatures:
publicintshow(FragmentTransactiontransaction,Stringtag)publicintshow(FragmentManagermanager,Stringtag)
Thefirstshow()methoddisplaysthedialogbyaddingthisfragmenttothepassed-intransactionwiththespecifiedtag.Thismethodthenreturnstheidentifierofthecommittedtransaction.
Thesecondshow()methodautomatesgettingatransactionfromthetransactionmanager.Thisisashortcutmethod.However,whenyouusethissecondmethod,youdon’thaveanoptiontoaddthetransactiontothebackstack.Ifyouwantthatcontrol,youneedtousethefirstmethod.Thesecondmethodcouldbeusedifyouwantedtosimplydisplaythedialog,andyouhadnootherreasontoworkwithafragmenttransactionatthattime.
Anicethingaboutadialogbeingafragmentisthattheunderlyingfragmentmanagerdoesthebasicstatemanagement.Forexample,evenifthedevicerotateswhenadialogisbeingdisplayed,thedialogisreproducedwithoutyouperforminganystatemanagement.
Thedialogfragmentalsooffersmethodstocontroltheframeinwhichthedialog’sviewisdisplayed,suchasthetitleandtheappearanceoftheframe.RefertotheDialogFragmentclassdocumentationtoseemoreoftheseoptions;thisURLisprovidedattheendofthischapter.
DismissingaDialogFragmentTherearetwowaysyoucandismissadialogfragment.Thefirstistoexplicitlycallthedismiss()methodonthedialogfragmentinresponsetoabuttonorsomeactiononthedialogview,asshowninListing10-4.
Listing10-4.Callingdismiss()
if(someview.getId()==R.id.btn_dismiss){//usesomecallbackstoadviseclients//ofthisdialogthatitisbeingdismissed//andcalldismissdismiss();return;}
Thedialogfragment’sdismiss()methodremovesthefragmentfromthefragmentmanagerandthencommitsthattransaction.Ifthereisabackstackforthisdialogfragment,thenthedismiss()popsthecurrentdialogoutofthetransactionstackandpresentsthepreviousfragmenttransactionstate.Whetherthereisabackstackornot,callingdismiss()resultsincallingthestandarddialogfragmentdestroycallbacks,
includingonDismiss().
Onethingtonoteisthatyoucan’trelyononDismiss()toconcludethatadismiss()hasbeencalledbyyourcode.ThisisbecauseonDismiss()isalsocalledwhenadeviceconfigurationchangesandhenceisnotagoodindicatorofwhattheuserdidtothedialogitself.Ifthedialogisbeingdisplayedwhentheuserrotatesthedevice,thedialogfragmentseesonDismiss()calledeventhoughtheuserdidnotpressabuttoninthedialog.Instead,youshouldalwaysrelyonexplicitbuttonclicksonthedialogview.
IftheuserpressestheBackbuttonwhilethedialogfragmentisdisplayed,thiscausestheonCancel()callbacktofireonthedialogfragment.Bydefault,Androidmakesthedialogfragmentgoaway,soyoudon’tneedtocalldismiss()onthefragmentyourself.Butifyouwantthecallingactivitytobenotifiedthatthedialoghasbeencancelled,youneedtoinvokelogicfromwithinonCancel()tomakethathappen.ThisisadifferencebetweenonCancel()andonDismiss()withdialogfragments.WithonDismiss(),youcan’tbesureexactlywhathappenedthatcausedtheonDismiss()callbacktofire.Youmightalsohavenoticedthatadialogfragmentdoesnothaveacancel()method,justdismiss();butaswesaid,whenadialogfragmentisbeingcancelledbypressingtheBackbutton,Androidtakescareofcancelling/dismissingitforyou.
Theotherwaytodismissadialogfragmentistopresentanotherdialogfragment.Thewayyoudismissthecurrentdialogandpresentthenewoneisslightlydifferentthanjustdismissingthecurrentdialog.Listing10-5showsanexample.
Listing10-5.SettingUpaDialogforaBackStack
if(someview.getId()==R.id.btn_invoke_another_dialog){Activityact=getActivity();FragmentManagerfm=act.getFragmentManager();FragmentTransactionft=fm.beginTransaction();ft.remove(this);
ft.addToBackStack(null);//nullrepresentsnonameforthebackstacktransaction
HelpDialogFragmenthdf=HelpDialogFragment.newInstance(R.string.helptext);hdf.show(ft,"HELP");return;}
Withinasingletransaction,you’reremovingthecurrentdialogfragmentandaddingthenewdialogfragment.Thishastheeffectofmakingthecurrentdialogdisappearvisuallyandmakingthenewdialogappear.IftheuserpressestheBackbutton,becauseyou’vesavedthistransactiononthebackstack,thenewdialogisdismissedandtheprevious
dialogisdisplayed.Thisisahandywayofdisplayingahelpdialog,forexample.
ImplicationsofaDialogDismissWhenyouaddanyfragmenttoafragmentmanager,thefragmentmanagerdoesthestatemanagementforthatfragment.Thismeanswhenadeviceconfigurationchanges(forexample,thedevicerotates),theactivityisrestartedandthefragmentsarealsorestarted.YousawthisearlierwhenyourotatedthedevicewhilerunningtheShakespearesampleapplicationinchapter8.
Adevice-configurationchangedoesn’taffectdialogsbecausetheyarealsomanagedbythefragmentmanager.Buttheimplicitbehaviorofshow()anddismiss()meansyoucaneasilylosetrackofadialogfragmentifyou’renotcareful.Theshow()methodautomaticallyaddsthefragmenttothefragmentmanager;thedismiss()methodautomaticallyremovesthefragmentfromthefragmentmanager.Youmayhaveadirectpointertoadialogfragmentbeforeyoustartshowingthefragment.Butyoucan’taddthisfragmenttothefragmentmanagerandlatercallshow(),becauseafragmentcanonlybeaddedoncetothefragmentmanager.Youmayplantoretrievethispointerthroughrestoreoftheactivity.However,ifyoushowanddismissthisdialog,thisfragmentisimplicitlyremovedfromthefragmentmanager,therebydenyingthatfragment’sabilitytoberestoredandrepointed(becausethefragmentmanagerdoesn’tknowthisfragmentexistsafteritisremoved).
Ifyouwanttokeepthestateofadialogafteritisdismissed,youneedtomaintainthestateoutsideofthedialogeitherintheparentactivityorinanon-dialogfragmentthathangsaroundforalongertime.
DialogFragmentSampleApplicationInthissection,youreviewasampleapplicationthatdemonstratestheseconceptsofadialogfragment.Youalsoexaminecommunicationbetweenafragmentandtheactivitythatcontainsit.Tomakeitallhappen,youneedfiveJavafiles:
MainActivity.java:Themainactivityofyourapplication.Itdisplaysasimpleviewwithhelptextinitandamenufromwhichdialogscanbestarted.
PromptDialogFragment.java:AnexampleofadialogfragmentthatdefinesitsownlayoutinXMLandallowsinputfromtheuser.Ithasthreebuttons:Save,Dismiss(cancel),andHelp.
AlertDialogFragment.java:AnexampleofadialogfragmentthatusestheAlertBuilderclasstocreateadialogwithinthisfragment.Thisistheold-schoolwayofcreatingadialog.
HelpDialogFragment.java:Averysimplefragmentthatdisplaysahelpmessagefromtheapplication’sresources.Thespecifichelpmessageisidentifiedwhenahelpdialogobjectis
created.Thishelpfragmentcanbeshownfromboththemainactivityandthepromptdialogfragment.
OnDialogDoneListener.java:Aninterfacethatyourequireyouractivitytoimplementinordertogetmessagesbackfromthefragments.Usinganinterfacemeansyourfragmentsdon’tneedtoknowmuchaboutthecallingactivity,exceptthatitmusthaveimplementedthisinterface.Thishelpsencapsulatefunctionalitywhereitbelongs.Fromtheactivity’spointofview,ithasacommonwaytoreceiveinformationbackfromfragmentswithoutneedingtoknowtoomuchaboutthem.
Therearethreelayoutsforthisapplication:forthemainactivity,forthepromptdialogfragment,andforthehelpdialogfragment.Notethatyoudon’tneedalayoutforthealertdialogfragmentbecausetheAlertBuildertakescareofthatlayoutforyouinternally.Whenyou’redone,theapplicationlookslikeFigure10-1.
Figure10-1.Theuserinterfaceforthedialogfragmentsampleapplication
DialogSample:MainActivityLet’sgettothesourcecode,whichyoucandownloadfromthebook’swebsite(seethe“References”section).We’llusetheDialogFragmentDemoproject.OpenupthesourcecodeforMainActivity.javabeforewecontinue.
Thecodeforthemainactivityisverystraightforward.Youdisplayasimplepageoftextandsetupamenu.Eachmenuiteminvokesanactivitymethod,andeachmethoddoesbasicallythesamething:getsafragmenttransaction,createsanewfragment,andshows
thefragment.Notethateachfragmenthasauniquetagthat’susedwiththefragmenttransaction.Thistagbecomesassociatedwiththefragmentinthefragmentmanager,soyoucanlocatethesefragmentslaterbytagname.ThefragmentcanalsodetermineitsowntagvaluewiththegetTag()methodonFragment.
ThelastmethoddefinitioninthemainactivityisonDialogDone(),whichisacallbackthatispartoftheOnDialogDoneListenerinterfacethatyouractivityisimplementing.Asyoucansee,thecallbacksuppliesatagofthefragmentthatiscallingyou,abooleanvalueindicatingwhetherthedialogfragmentwascancelled,andamessage.Foryourpurposes,youmerelywanttologtheinformationtoLogCat;youalsoshowittotheuserusingToast.Toastwillbecoveredlaterinthischapter.
DialogSample:OnDialogDoneListenerSothatyoucanknowwhenadialoghasgoneaway,createalistenerinterfacethatyourdialogcallersimplement.ThecodeoftheinterfaceisinOnDialogDoneListener.java.
Thisisaverysimpleinterface,asyoucansee.Youchooseonlyonecallbackforthisinterface,whichtheactivitymustimplement.Yourfragmentsdon’tneedtoknowthespecificsofthecallingactivity,onlythatthecallingactivitymustimplementtheOnDialogDoneListenerinterface;thereforethefragmentscancallthiscallbacktocommunicatewiththecallingactivity.Dependingonwhatthefragmentisdoing,therecouldbemultiplecallbacksintheinterface.Forthissampleapplication,you’reshowingtheinterfaceseparatelyfromthefragmentclassdefinitions.Foreasiermanagementofcode,youcouldembedthefragmentlistenerinterfaceinsideofthefragmentclassdefinitionitself,thusmakingiteasiertokeepthelistenerandthefragmentinsyncwitheachother.
DialogSample:PromptDialogFragmentNowlet’slookatyourfirstfragment,PromptDialogFragment,whoselayoutisin/res/layout/prompt_dialog.xmlandJavacodeisunder/srcinPromptDialogFragment.java.
Thispromptdialoglayoutlookslikemanyyou’veseenpreviously.ThereisaTextViewtoserveastheprompt;anEditTexttotaketheuser’sinput;andthreebuttonsforsavingtheinput,dismissing(cancelling)thedialogfragment,andpoppingahelpdialog.
ThePromptDialogFragmentJavacodestartsoutlookingjustlikeyourearlierfragments.YouhaveanewInstance()staticmethodtocreatenewobjects,andwithinthismethodyoucallthedefaultconstructor,buildanargumentsbundle,andattachittoyournewobject.Next,youhavesomethingnewintheonAttach()callback.YouwanttomakesuretheactivityyoujustgotattachedtohasimplementedtheOnDialogDoneListenerinterface.Inordertotestthat,youcasttheactivitypassedintotheOnDialogDoneListenerinterface.Here’sthatcode:
try{OnDialogDoneListenertest=(OnDialogDoneListener)act;}
catch(ClassCastExceptioncce){//Hereiswherewefailgracefully.Log.e(MainActivity.LOGTAG,"Activityisnotlistening");}
Iftheactivitydoesnotimplementthisinterface,aClassCastExceptionisthrown.Youcouldhandlethisexceptionanddealwithitmoregracefully,butthisexamplekeepsthecodeassimpleaspossible.
NextupistheonCreate()callback.Asiscommonwithfragments,youdon’tbuildyouruserinterfacehere,butyoucansetthedialogstyle.Thisisuniquetodialogfragments.Youcansetboththestyleandthethemeyourself,oryoucansetjuststyleanduseathemevalueofzero(0)toletthesystemchooseanappropriatethemeforyou.Here’sthatcode:
intstyle=DialogFragment.STYLE_NORMAL,theme=0;setStyle(style,theme);
InonCreateView()youcreatetheviewhierarchyforyourdialogfragment.Justlikeotherfragments,youdonotattachyourviewhierarchytotheviewcontainerpassedin(thatis,bysettingtheattachToRootparametertofalse).Youthenproceedtosetupthebuttoncallbacks,andyousetthedialogprompttexttothepromptthatwaspassedoriginallytonewInstance().
TheonCancel()andonDismiss()callbacksarenotshownbecausealltheydoislogging;you’llbeabletoseewhenthesecallbacksfireduringthefragment’slifecycle.
Thefinalcallbackinthepromptdialogfragmentisforthebuttons.Onceagain,yougrabareferencetoyourenclosingactivityandcastittotheinterfaceyouexpecttheactivitytohaveimplemented.IftheuserpressedtheSavebutton,yougrabthetextasenteredandcalltheinterface’scallbackonDialogDone().Thiscallbacktakesthetagnameofthisfragment,abooleanindicatingwhetherthisdialogfragmentwascancelled,andamessage,whichinthiscaseisthetexttypedbytheuser.HereitisfromtheMainActivity:
publicvoidonDialogDone(Stringtag,booleancancelled,CharSequencemessage){Strings=tag+"respondswith:"+message;if(cancelled)s=tag+"wascancelledbytheuser";Toast.makeText(this,s,Toast.LENGTH_LONG).show();Log.v(LOGTAG,s);}
TofinishhandlingaclickontheSavebutton,youthencalldismiss()togetridofthedialogfragment.Rememberthatdismiss()notonlymakesthefragmentgoawayvisually,butalsopopsthefragmentoutofthefragmentmanagersoitisnolongeravailabletoyou.
IfthebuttonpressedisDismiss,youagaincalltheinterfacecallback,thistimewithno
message,andthenyoucalldismiss().Andfinally,iftheuserpressedtheHelpbutton,youdon’twanttolosethepromptdialogfragment,soyoudosomethingalittledifferent.Wedescribedthisearlier.Inordertorememberthepromptdialogfragmentsoyoucancomebacktoitlater,youneedtocreateafragmenttransactiontoremovethepromptdialogfragmentandaddthehelpdialogfragmentwiththeshow()method;thisneedstogoontothebackstack.Notice,too,howthehelpdialogfragmentiscreatedwithareferencetoaresourceID.Thismeansyourhelpdialogfragmentcanbeusedwithanyhelptextavailabletoyourapplication.
DialogSample:HelpDialogFragmentYoucreatedafragmenttransactiontogofromthepromptdialogfragmenttothehelpdialogfragment,andyouplacedthatfragmenttransactiononthebackstack.Thishastheeffectofmakingthepromptdialogfragmentdisappearfromview,butit’sstillaccessiblethroughthefragmentmanagerandthebackstack.Thenewhelpdialogfragmentappearsinitsplaceandallowstheusertoreadthehelptext.Whentheuserdismissesthehelpdialogfragment,thefragmentbackstackentryispopped,withtheeffectofthehelpdialogfragmentbeingdismissed(bothvisuallyandfromthefragmentmanager)andthepromptdialogfragmentrestoredtoview.Thisisaprettyeasywaytomakeallthishappen.Itisverysimpleyetverypowerful;itevenworksiftheuserrotatesthedevicewhilethesedialogsarebeingdisplayed.
LookatthesourcecodeoftheHelpDialogFragment.javafileanditslayout(help_dialog.xml).Thepointofthisdialogfragmentistodisplayhelptext.ThelayoutisaTextViewandaClosebutton.TheJavacodeshouldbestartingtolookfamiliartoyou.There’sanewInstance()methodtocreateanewhelpdialogfragment,anonCreate()methodtosetthestyleandtheme,andanonCreateView()methodtobuildtheviewhierarchy.Inthisparticularcase,youwanttolocateastringresourcetopopulatetheTextView,soyouaccesstheresourcesthroughtheactivityandchoosetheresourceIDthatwaspassedintonewInstance().Finally,onCreateView()setsupabutton-clickhandlertocapturetheclicksoftheClosebutton.Inthiscase,youdon’tneedtodoanythinginterestingatthetimeofdismissal.
Thisfragmentiscalledtwoways:fromtheactivityandfromthepromptdialogfragment.Whenthishelpdialogfragmentisshownfromthemainactivity,dismissingitsimplypopsthefragmentoffthetopandrevealsthemainactivityunderneath.Whenthishelpdialogfragmentisshownfromthepromptdialogfragment,becausethehelpdialogfragmentwaspartofafragmenttransactiononthebackstack,dismissingitcausesthefragmenttransactiontoberolledback,whichpopsthehelpdialogfragmentbutrestoresthepromptdialogfragment.Theuserseesthepromptdialogfragmentreappear.
DialogSample:AlertDialogFragmentWehaveonelastdialogfragmenttoshowyouinthissampleapplication:thealertdialogfragment.Althoughyoucouldcreateanalertdialogfragmentinawaysimilartothehelpdialogfragment,youcanalsocreateadialogfragmentusingtheoldAlertBuilderframeworkthathasworkedformanyreleasesofAndroid.Lookatthesourcecodein
AlertDialogFragment.java.
Youdon’tneedalayoutforthisonebecausetheAlertBuildertakescareofthatforyou.Notethatthisdialogfragmentstartsoutlikeanyother,butinsteadofanonCreateView()callback,youhaveaonCreateDialog()callback.YouimplementeitheronCreateView()oronCreateDialog()butnotboth.ThereturnfromonCreateDialog()isnotaview;it’sadialog.Ofinteresthereisthattogetparametersforthedialog,youshouldbeaccessingyourargumentsbundle.Inthisexampleapplication,youonlydothisforthealertmessage,butyoucouldaccessotherparametersthroughtheargumentsbundleaswell.
Noticealsothatwiththistypeofdialogfragment,youneedyourfragmentclasstoimplementtheDialogInterface.OnClickListener,whichmeansyourdialogfragmentmustimplementtheonClick()callback.Thiscallbackisfiredwhentheuseractsontheembeddeddialog.Onceagain,yougetareferencetothedialogthatfiredandanindicationofwhichbuttonwaspressed.Asbefore,youshouldbecarefulnottodependonanonDismiss()becausethiscouldfirewhenthereisadeviceconfigurationchange.
DialogSample:EmbeddedDialogsThere’sonemorefeatureofaDialogFragmentthatyoumayhavenoticed.Inthemainlayoutfortheapplication,underthetext,isaFrameLayoutthatcanbeusedtoholdadialog.Intheapplication’smenu,thelastitemcausesafragmenttransactiontoaddanewinstanceofaPromptDialogFragmenttothemainscreen.Withoutanymodifications,thedialogfragmentcanbedisplayedembeddedinthemainlayout,anditfunctionsasyouwouldexpect.
Onethingthatisdifferentaboutthistechniqueisthatthecodetoshowtheembeddeddialogisnotthesameasthecodetodoapop-updialog.Theembeddeddialogcodelookslikethis:
ft.add(R.id.embeddedDialog,pdf,EMBED_DIALOG_TAG);ft.commit();
ThislooksjustthesameasinChapter8,whenwedisplayedafragmentinaFrameLayout.Thistime,however,youmakesuretopassinatagname,whichisusedwhenthedialogfragmentnotifiesyouractivityoftheuser’sinput.
DialogSample:ObservationsWhenyourunthissampleapplication,makesureyoutryallthemenuoptionsindifferentorientationsofthedevice.Rotatethedevicewhilethedialogfragmentsaredisplayed.Youshouldbepleasedtoseethatthedialogsgowiththerotations;youdonotneedtoworryaboutalotofcodetomanagethesavingandrestoringoffragmentsduetoconfigurationchanges.
Theotherthingwehopeyouappreciateistheeasewithwhichyoucancommunicatebetweenthefragmentsandtheactivity.Ofcourse,theactivityhasreferences,orcanget
references,toalltheavailablefragments,soitcanaccessmethodsexposedbythefragmentsthemselves.Thisisn’ttheonlywaytocommunicatebetweenfragmentsandtheactivity.Youcanalwaysusethegettermethodsonthefragmentmanagertoretrieveaninstanceofamanagedfragment,andthencastthatreferenceappropriatelyandcallamethodonthatfragmentdirectly.Youcanevendothisfromwithinanotherfragment.Thedegreetowhichyouisolateyourfragmentsfromeachotherwithinterfacesandthroughactivities,orbuildindependencieswithfragment-to-fragmentcommunication,isbasedonhowcomplexyourapplicationisandhowmuchreuseyouwanttoachieve.
WorkingwithToastAToastislikeaminialertdialogthathasamessageanddisplaysforacertainamountoftimeandthengoesawayautomatically.Itdoesnothaveanybuttons.Soitcanbesaidthatitisatransientalertmessage.It’scalledToastbecauseitpopsupliketoastoutofatoaster.
Listing10-10showsanexampleofhowyoucanshowamessageusingToast.
Listing10-10.UsingToastforDebugging
//Createafunctiontowrapamessageasatoast//showthetoastpublicvoidreportToast(Stringmessage){Strings=MainActivity.LOGTAG+":"+message;Toast.makeText(activity,s,Toast.LENGTH_SHORT).show();}
ThemakeText()methodinListing10-10cantakenotonlyanactivitybutanycontextobject,suchastheonepassedtoabroadcastreceiveroraservice,forexample.ThisextendstheuseofToastoutsideofactivities.
Referenceswww.androidbook.com/proandroid5/projects:Thischapter’stestproject.ThenameoftheZIPfileisProAndroid5_ch10_Dialogs.zip.Thedownloadincludesanexampleofthedate-andtime-pickerdialogsinPickerDialogFragmentDemo.
http://developer.android.com/guide/topics/ui/dialogs.htmlAndroidSDKdocumentthatprovidesanexcellentintroductiontoworkingwithAndroiddialogs.Youwillfindhereanexplanationofhowtousemanageddialogsandvariousexamplesofavailabledialogs.
http://developer.android.com/reference/android/content/DialogInterface.htmlThemanyconstantsdefinedfordialogs.
http://developer.android.com/reference/android/app/AlertDialog.Builder.htmlAPIdocumentationfortheAlertDialogbuilderclass.
http://developer.android.com/reference/android/app/ProgressDialog.htmlAPIdocumentationforProgressDialog.
http://developer.android.com/guide/topics/ui/controls/pickers.htmlAnAndroidtutorialforusingthedate-pickerandtime-pickerdialogs.
SummaryThischapterdiscussedasynchronousdialogsandhowtousedialogfragments,includingthefollowingtopics:
Whatadialogisandwhyyouuseone
TheasynchronousnatureofadialoginAndroid
Thethreestepsofgettingadialogtodisplayonthescreen
Creatingafragment
Twomethodsforhowadialogfragmentcancreateaviewhierarchy
Howafragmenttransactionisinvolvedindisplayingadialogfragment,andhowtogetone
WhathappenswhentheuserpressestheBackbuttonwhileviewingadialogfragment
Thebackstack,andmanagingdialogfragments
Whathappenswhenabuttononadialogfragmentisclicked,andhowyoudealwithit
Acleanwaytocommunicatebacktothecallingactivityfromadialogfragment
Howonedialogfragmentcancallanotherdialogfragmentandstillgetbacktothepreviousdialogfragment
TheToastclassandhowitcanbeusedasasimplealertpop-up
Chapter11
WorkingwithPreferencesandSavingStateAndroidoffersarobustandflexibleframeworkfordealingwithsettings,alsoknownaspreferences.Andbysettings,wemeanthosefeaturechoicesthatausermakesandsavestocustomizeanapplicationtotheirliking.(Inthischapter,thetermssettingsandpreferenceswillbeusedinterchangeably.)Forexample,iftheuserwantsanotificationviaaringtoneorvibrationornotatall,thatisapreferencetheusersaves;theapplicationremembersthechoiceuntiltheuserchangesit.AndroidprovidessimpleAPIsthathidethemanagementandpersistingofpreferences.Italsoprovidesprebuiltuserinterfacesthatyoucanusetolettheusermakepreferenceselections.BecauseofthepowerbuiltintotheAndroidpreferencesframework,wecanalsousepreferencesformoregeneral-purposestoringofapplicationstate,toallowourapplicationtopickupwhereitleftoff,shouldourapplicationgoawayandcomebacklater.Asanotherexample,agame’shighscorescouldbestoredaspreferences,althoughyou’llwanttouseyourownUItodisplaythem.
Thischaptercovershowtoimplementyourownsettingsscreensforyourapplication,howtointeractwithAndroidsystemsettings,andhowtousesettingstosecretlysaveapplicationstate,anditalsoprovidesbest-practiceguidance.You’lldiscoverhowtomakeyoursettingslookgoodonsmallscreensaswellaslargerscreenssuchasthosefoundontablets.
ExploringthePreferencesFrameworkAndroid’spreferencesframeworkbuildsfromtheindividualsettingschoices,toahierarchyofscreensthatcontainsettingschoices.Settingscouldbebinarysettingssuchason/off,ortextinput,oranumericvalue,orcouldbeaselectionfromalistofchoices.AndroidusesaPreferenceManagertoprovidesettingsvaluestoapplications.Theframeworktakescareofmakingandpersistingchanges,andnotifyingtheapplicationwhenasettingchangesorisabouttochange.Whilesettingsarepersistedinfiles,applicationsdon’tdealdirectlywiththefiles.Thefilesarehiddenaway,andyou’llseeshortlywheretheyare.
AswithviewscoveredinChapter3,preferencescanbespecifiedwithXML,orbywritingcode.Forthischapter,you’llworkwithasampleapplicationthatdemonstratesthedifferenttypesofchoices.XMListhepreferredwaytospecifyapreference,sothatishowtheapplicationwaswritten.XMLspecifiesthelowest-levelsettings,plushowtogroupsettingstogetherintocategoriesandscreens.Forreference,thesampleapplicationforthischapterpresentsthefollowingsettingsasshowninFigure11-1.
Figure11-1.ThemainsettingsfromthesampleapppreferenceUI.Duetothescreen’sheight,ithasbeenshownwiththetopontheleftandthebottomontheright.Noticetheoverlapbetweenthetwoimages
Androidprovidesanend-to-endpreferencesframework.Thismeanstheframeworkletsyoudefineyourpreferences,displaythesetting(s)totheuser,andpersisttheuser’sselectiontothedatastore.YoudefineyourpreferencesinXMLunder/res/xml/.Toshowpreferencestotheuser,youwriteanactivityclassthatextendsapredefinedAndroidclasscalledandroid.preference.PreferenceActivityandusefragmentstohandlethescreensofpreferences.Theframeworktakescareoftherest(displayingandpersisting).Withinyourapplication,yourcodewillgetreferencestospecificpreferences.Withapreferencereference,youcangetthecurrentvalueofthepreference.
Inorderforpreferencestobesavedacrossusersessions,thecurrentvaluesmustbesavedsomewhere.TheAndroidframeworktakescareofpersistingpreferencesinanXMLfilewithintheapplication’s/data/datadirectoryonthedevice(seeFigure11-2).
Figure11-2.Pathtoanapplication’ssavedpreferences
NoteYouwillbeabletoinspectsharedpreferencesfilesintheemulatoronly.Onarealdevice,thesharedpreferencesfilesarenotreadableduetoAndroidsecurity(unlessyouhaverootprivileges,ofcourse).
Thedefaultpreferencesfilepathforanapplicationis/data/data/[PACKAGE_NAME]/shared_prefs/[PACKAGE_NAME]_preferences.xml,where[PACKAGE_NAME]isthepackageoftheapplication.Listing11-1showsthecom.androidbook.preferences.main_preferences.xmldatafileforthisexample.
Listing11-1.SavedPreferencesforOurExample
<?xmlversion='1.0'encoding='utf-8'standalone='yes'?><map><booleanname="notification_switch"value="true"/><stringname="package_name_preference">com.androidbook.win</string><booleanname="potato_selection_pref"value="true"/><booleanname="show_airline_column_pref"value="true"/><stringname="flight_sort_option">2</string><booleanname="alert_email"value="false"/><setname="pizza_toppings"><string>pepperoni</string><string>cheese</string><string>olive</string></set><stringname="alert_email_address">[email protected]</string></map>
Asyoucansee,valuesarestoredinamap,withpreferencekeysasnamestothedatavalues.Someofthevalueslookcrypticanddonotmatchwhatisdisplayedtotheuser.Forexample,thevalueforflight_sort_optionis2.Androiddoesnotstorethedisplayedtextas
thevalueofthepreference;rather,itstoresavaluethattheuserwon’tsee,thatyoucanuseindependentlyofwhattheusersees.Youwantthefreedomtochangethedisplayedtextbasedontheuser’slanguage,andyoualsowanttheabilitytotweakthedisplayedtextwhilekeepingthesamestoredvalueinthepreferencesfile.Youmightevenbeabletodosimplerprocessingofthepreferenceifthevalueisanintegerinsteadofsomedisplaystring.Whatyoudon’thavetoworryaboutisparsingthisdatafile.TheAndroidpreferencesframeworkprovidesaniceAPIfordealingwithpreferences,whichwillbedescribedinmoredetaillaterinthischapter.IfyoucomparethepreferencesmapinListing11-1withthescreenshotsinFigure11-1,youwillnoticethatnotallpreferencesarelistedwithvaluesinthepreferencesXMLdatafile.Thisisbecausethepreferencedatafiledoesnotautomaticallystoreadefaultvalueforyou.You’llseeshortlyhowtodealwithdefaultvalues.
Nowthatyou’veseenwherethevaluesaresaved,youneedtoseehowtodefinethescreenstodisplaytotheusersotheycanmakeselections.Beforeyouseehowtocollectpreferencestogetherintoscreens,you’lllearnaboutthedifferenttypesofpreferencesyoucanuse,andthenyou’llseehowtoputthemtogetherintoscreens.Eachpersistedvalueinthe/data/dataXMLfileisfromaspecificpreference.Solet’sunderstandwhateachofthesemeans.
UnderstandingCheckBoxPreferenceandSwitchPreferenceThesimplestofthepreferencesaretheCheckBoxPreferenceandSwitchPreference.Theseshareacommonparentclass(TwoStatePreference)andareeitheron(valueistrue)oroff(valueisfalse).Forthesampleapplication,ascreenwascreatedwithfiveCheckBoxPreferences,asshowninFigure11-3.Listing11-2showswhattheXMLlookslikeforaCheckBoxPreference.
Figure11-3.Theuserinterfaceforthecheckboxpreference
Listing11-2.UsingCheckBoxPreference
<CheckBoxPreferenceandroid:key="show_airline_column_pref"android:title="Airline"android:summary="ShowAirlinecolumn"/>
NoteWewillgiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.ThemainsampleapplicationiscalledPrefDemo.YoushouldrefertothatprojectuntilyoucometotheSavingStatesection.
Thisexampleshowstheminimumthat’srequiredtospecifyapreference.Thekeyisthereferenceto,ornameof,thepreference,thetitleisthetitledisplayedforthepreference,andsummaryisadescriptionofwhatthepreferenceisfororastatusofthecurrentsetting.LookingbackonthesavedvaluesinListing11-1,youwillseea<boolean>tagfor“show_airline_column_pref”(thekey),andithasanattributevalueoftrue,whichindicatesthatthepreferenceischeckedon.
WithCheckBoxPreference,thestateofthepreferenceissavedwhentheusersetsthestate.Inotherwords,whentheuserchecksorunchecksthepreferencecontrol,itsstateissavedimmediately.
TheSwitchPreferenceisverysimilarexceptthatthevisualdisplayisdifferent.Insteadofacheckboxintheuserinterface,theuserseesanon-offswitch,asshowninFigure11-1nextto“Notificationsare”.
OneotherusefulfeatureofCheckBoxPreferenceandSwitchPreferenceisthatyoucansetdifferentsummarytextdependingonwhetherit’schecked.TheXMLattributesaresummaryOnandsummaryOff.Ifyoulookinthemain.xmlfilefortheCheckBoxPreferencecalled“potato_selection_pref”youwillseeanexampleofthis.
Beforeyoulearntheotherpreferencetypes,nowwouldbeagoodtimetounderstandhowtoaccessthispreferencetoreaditsvalueandperformotheroperations.
AccessingaPreferenceValueinCodeNowthatyouhaveapreferencedefinedyouneedtoknowhowtoaccessthepreferenceincodesoyoucanreadthevalue.Listing11-3showscodetoaccesstheSharedPreferencesobjectinAndroidwherethepreferencesexist.ThiscodeisfromtheMainActivity.javafileinthesetOptionText()method.
Listing11-3.AccessingtheCheckBoxPreferenceSharedPreferencesprefs=
PreferenceManager.getDefaultSharedPreferences(this);
//Thisistheotherwaytogettothesharedpreferences://SharedPreferencesprefs=getSharedPreferences(//“com.androidbook.preferences.main_preferences”,0);booleanshowAirline=prefs.getBoolean(“show_airline_column_pref”,false);
Usingthereferencetopreferences,itisstraightforwardtoreadthecurrentvalueoftheshow_airline_column_prefpreference.AsshowninListing11-3,therearetwowaystogettothepreferences.Thefirstwayshownistogetthedefaultpreferencesforthecurrentcontext.Inthiscase,thecontextisthatoftheMainActivityofourapplication.Thesecondcase,whichisshowncommentedout,retrievesthepreferencesusingapackagename.Youcouldusewhateverpackagenameyouwantincaseyouneedtostoredifferentsetsofpreferencesindifferentfiles.
Onceyouhaveareferencetothepreferences,youcalltheappropriategettermethodwiththekeyofthepreferenceandadefaultvalue.Sinceshow_airline_column_prefisaTwoStatePreference,thevaluereturnedisaboolean.Thedefaultvalueforshow_airline_column_prefishard-codedhereasfalse.Ifthispreferencehasnotyetbeensetatall,thehard-codedvalue(false)willbeassignedtoshowAirline.However,thatbyitselfdoesnotpersistthepreferencetofalseforfutureuse,nordoesithonoranydefaultvaluethatmighthavebeensetintheXMLspecificationforthispreference.IftheXMLspecificationusesaresourcevaluetospecifythedefaultvalue,thenthesameresourcecouldbereferredtoincodetosetthedefaultvalue,asshowninthefollowingforadifferentpreference:
Stringflight_option=prefs.getString(resources.getString(R.string.flight_sort_option),resources.getString(R.string.flight_sort_option_default_value));
Noticeherethatthekeyforthepreferenceisalsousingastringresourcevalue(R.string.flight_sort_option).Thiscanbeawisechoicesinceitmakestyposlesslikely.Iftheresourcenameistypedwrongyou’llverylikelygetabuilderror.Ifyouusejustsimplestrings,itispossibleforatypotogounnoticed,exceptthatyourpreferenceswon’twork.
Weshowedonewaytoreadadefaultvalueforapreferenceincode.Androidprovidesanotherwaythatisabitmoreelegant.InonCreate(),youcandothefollowinginstead:
PreferenceManager.setDefaultValues(this,R.xml.main,false);
Then,insetOptionText(),youwouldhavedonethistoreadtheoptionvalue:
Stringoption=prefs.getString(resources.getString(R.string.flight_sort_option),null);
Thefirstcallwillusemain.xmltofindthedefaultvaluesandgeneratethepreferencesXMLdatafileforususingthedefaultvalues.Ifwealreadyhaveaninstanceofthe
SharedPreferencesobjectinmemory,itwillupdatethattoo.Thesecondcallwillthenfindavalueforflight_sort_option,becausewetookcareofloadingdefaultsfirst.
Afterrunningthiscodethefirsttime,ifyoulookintheshared_prefsfolder,youwillseethepreferencesXMLfileevenifthepreferencesscreenhasnotyetbeeninvoked.Youwillalsoseeanotherfilecalled_has_set_default_values.xml.ThisfiletellsyourapplicationthatthepreferencesXMLfilehasalreadybeencreatedwiththedefaultvalues.ThethirdargumenttosetDefaultValues()—thatis,false—indicatesthatyouwantthedefaultssetinthepreferencesXMLfileonlyifithasn’tbeendonebefore.AndroidremembersthisinformationthroughtheexistenceofthisnewXMLfile.However,Androidremembersevenifyouupgradeyourapplicationandaddnewsettingswithnewdefaultvalues,whichmeansthistrickwon’tsetthosenewdefaults.Yourbestoptionistoalwaysusearesourceforthedefaultvalue,andalwaysprovidethatresourceasthedefaultvaluewhengettingthecurrentvalueofapreference.
UnderstandingListPreferenceAlistpreferencecontainsradiobuttonsforeachoption,andthedefault(orcurrent)selectionispreselected.Theuserisexpectedtoselectoneandonlyoneofthechoices.Whentheuserchoosesanoption,thedialogisimmediatelydismissedandthechoiceissavedinthepreferencesXMLfile.Figure11-4showswhatthislookslike.
Figure11-4.TheuserinterfacefortheListPreference
Listing11-4containsanXMLfragmentthatrepresentstheflight-optionpreferencesetting.Thistimethefilecontainsreferencestostringsandtoarrays,whichwouldbethe
morecommonwaytospecifytheseratherthanhard-codingthestrings.Asmentionedbefore,thevalueofalistpreferenceasstoredintheXMLdatafileunderthe/data/data/{package}directoryisnotthesameaswhattheuserseesintheuserinterface.Thenameofthekeyisstoredinthedatafile,alongwithahiddenvaluethattheuserdoesnotsee.Therefore,togetaListPreferencetowork,thereneedstobetwoarrays:thevaluesdisplayedtotheuserandthestringsusedaskeyvalues.Thisiswhereyoucaneasilygettrippedup.Theentriesarrayholdsthestringsdisplayedtotheuser,andtheentryValuesarrayholdsthestringsthatwillbestoredinthepreferencesdataXMLfile.
Listing11-4.SpecifyingaListPreferenceinXML
<ListPreferenceandroid:key="@string/flight_sort_option"android:title="@string/listTitle"android:summary="@string/listSummary"android:entries="@array/flight_sort_options"android:entryValues="@array/flight_sort_options_values"android:dialogTitle="@string/dialogTitle"android:defaultValue="@string/flight_sort_option_default_value"/>
Theelementsbetweenthetwoarrayscorrespondtoeachotherpositionally.Thatis,thethirdelementintheentryValuesarraycorrespondstothethirdelementintheentriesarray.Itistemptingtouse0,1,2,etc.,asentryValuesbutitisnotrequired,anditcouldcauseproblemslaterwhenthearraysmustbemodified.Ifouroptionwerenumericinnature(forexample,acountdowntimerstartingvalue),thenwecouldhaveusedvaluessuchas60,120,300,andsoon.Thevaluesdon’tneedtobenumericatallaslongastheymakesensetothedeveloper;theuserdoesn’tseethesevaluesunlessyouchoosetoexposethem.Theuseronlyseesthetextfromthefirststringarrayflight_sort_options.Theexampleapplicationforthischaptershowsitbothways.
Awordofcautionhere:becausethepreferencesXMLdatafileisstoringonlythevalueandnotthetext,shouldyoueverupgradeyourapplicationandchangethetextoftheoptionsoradditemstothestringarrays,anyvaluestoredinthepreferencesXMLdatafileshouldstilllineupwiththeappropriatetextaftertheupgrade.ThepreferencesXMLdatafileiskeptduringtheapplicationupgrade.IfthepreferencesXMLdatafilehada“1”init,andthatmeant“#ofStops”beforetheupgrade,itshouldstillmean“#ofStops”aftertheupgrade.
SincetheentryValuesarrayisnotseenbytheenduser,itisbestpracticetostoreitonceandonlyoncewithinyourapplication.Therefore,makeoneandonlyone/res/values/prefvaluearrays.xmlfiletocontainthesearrays.Theentriesarrayisverylikelytobecreatedmultipletimesperapplication,fordifferentlanguagesorperhapsdifferentdeviceconfigurations.Therefore,makeseparateprefdisplayarrays.xmlfilesforeachvariationthatyouneed.Forexample,ifyourapplicationwillbeusedinEnglishandinFrench,therewillbeseparate
prefdisplayarrays.xmlfilesforEnglishandFrench.YoudonotwanttoincludetheentryValuesarrayineachoftheseotherfiles.ItisimperativethoughthattherearethesamenumbersofarrayelementsbetweenentryValuesandentriesarrays.Theelementsmustlineup.Whenyoumakechanges,becarefultokeepeverythinginalignment.Listing11-5containsthesourceofListPreferencefilesfortheexample.
Listing11-5.OtherListPreferenceFilesfromOurExample
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/values/prefvaluearrays.xml--><resources><string-arrayname="flight_sort_options_values"><item>0</item><item>1</item><item>2</item></string-array><string-arrayname="pizza_toppings_values"><item>cheese</item><item>pepperoni</item><item>onion</item><item>mushroom</item><item>olive</item><item>ham</item><item>pineapple</item></string-array><string-arrayname="default_pizza_toppings"><item>cheese</item><item>pepperoni</item></string-array></resources>
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/values/prefdisplayarrays.xml--><resources><string-arrayname="flight_sort_options"><item>TotalCost</item><item>#ofStops</item><item>Airline</item></string-array><string-arrayname="pizza_toppings"><item>Cheese</item><item>Pepperoni</item><item>Onions</item><item>PortobelloMushrooms</item><item>BlackOlives</item><item>SmokedHam</item><item>Pineapple</item>
</string-array></resources>
Also,don’tforgetthatyourdefaultvalueasspecifiedintheXMLsourcefilemustmatchanentryValueinthearrayfromprefvaluearrays.xml.
ForaListPreference,thevalueofthepreferenceisaString.Ifyouareusingnumberstrings(e.g.,0,1,1138)asentryValues,youcouldconvertthosetointegersorwhateveryouneedinyourcode,asisusedintheflight_sort_options_valuesarray.
Yourcodeislikelygoingtowanttodisplaytheuser-friendlytextfromthepreference’sentriesarray.Thisexampletookashortcut,becausearrayindiceswereusedfortheelementsinflight_sort_options_values.Bysimplyconvertingthevaluetoanint,youknowwhichstringtoreadfromflight_sort_options.Hadyouusedsomeothersetofvaluesforflight_sort_options_values,youwouldneedtodeterminetheindexoftheelementthatisyourpreferenceandthenturnaroundandusethatindextograbthetextofyourpreferencefromflight_sort_options.ListPreference’shelpermethodfindIndexOfValue()canhelpwiththis,byprovidingtheindexintothevaluesarraysoyoucantheneasilygetthecorrespondingdisplaytextfromtheentriesarray.
ReturningnowtoListing11-4,thereareseveralstringsfortitles,summaries,andmore.Thestringcalledflight_sort_option_default_valuesetsthedefaultvalueto1torepresent“#ofStops”intheexample.Itisusuallyagoodideatochooseadefaultvalueforeachoption.Ifyoudon’tchooseadefaultvalueandnovaluehasyetbeenchosen,themethodsthatreturnthevalueoftheoptionwillreturnnull.Yourcodewouldhavetodealwithnullvaluesinthiscase.
UnderstandingEditTextPreferenceThepreferencesframeworkalsoprovidesafree-formtextpreferencecalledEditTextPreference.Thispreferenceallowsyoutocapturerawtextratherthanasktheusertomakeaselection.Todemonstratethis,let’sassumeyouhaveanapplicationthatgeneratesJavacodefortheuser.Oneofthepreferencesettingsofthisapplicationmightbethedefaultpackagenametouseforthegeneratedclasses.Here,youwanttodisplayatextfieldtotheuserforsettingthepackagenameforthegeneratedclasses.Figure11-5showstheUI,andListing11-6showstheXML.
Figure11-5.UsingtheEditTextPreference
Listing11-6.AnExampleofanEditTextPreference
<EditTextPreferenceandroid:key="package_name_preference"android:title="SetPackageName"android:summary="Setthepackagenameforgeneratedcode"android:dialogTitle="PackageName"/>
WhenSetPackageNameisselected,theuserispresentedwithadialogtoinputthepackagename.WhentheOKbuttonisclicked,thepreferenceissavedtothepreferencestore.
Aswiththeotherpreferences,youcanobtainthevalueofthepreferencebycallingtheappropriategettermethod,inthiscasegetString().
UnderstandingMultiSelectListPreferenceAndfinally,apreferencecalledMultiSelectListPreferencewasintroducedinAndroid3.0.TheconceptissomewhatsimilartoaListPreference,butinsteadofonlybeingabletoselectoneiteminthelist,theusercanselectseveralornone.InListing11-1,theMultiSelectListPreferencestoresa<setname=“pizza_toppings”>taginthepreferencesXMLdatafile,insteadofasinglevalue.TheothersignificantdifferencewithaMultiSelectListPreferenceisthatthedefaultvalueisanarrayjustliketheentryValuesarray.Thatis,thearrayforthedefaultvaluesmustcontainzeroormoreoftheelementsfromtheentryValuesarrayforthispreference.Thiscanalsobeseeninthesampleapplicationforthischapter;justviewtheendofthemain.xmlfileinthe/res/xmldirectory.
TogetthecurrentvalueofaMultiSelectListPreference,usethegetStringSet()methodofSharedPreferences.Toretrievethedisplaystringsfromtheentriesarray,youwouldneedtoiteratethroughthesetofstringsthatisthevalue
ofthispreference,determinetheindexofthestring,andusetheindextoaccesstheproperdisplaystringfromtheentriesarray.
UpdatingAndroidManifest.xmlBecausetherearetwoactivitiesinthesampleapplication,weneedtwoactivitytagsinAndroidManifest.xml.ThefirstoneisastandardactivityofcategoryLAUNCHER.ThesecondoneisforaPreferenceActivity,sosettheactionnameaccordingtoconventionforintents,andsetthecategorytoPREFERENCEasshowninListing11-7.Youprobablydon’twantthePreferenceActivityshowingupontheAndroidpagewithallourotherapplications,whichiswhyyoudon’tuseLAUNCHERforit.YouwouldneedtomakesimilarchangestoAndroidManifest.xmlifyouweretoaddotherpreferenceactivities.
Listing11-7.PreferenceActivityEntryinAndroidManifest.xml<activityandroid:name=”.MainPreferenceActivity”
android:label=”@string/prefTitle”><intent-filter><actionandroid:name=“com.androidbook.preferences.main.intent.action.MainPreferences”/><categoryandroid:name=“android.intent.category.PREFERENCE”/></intent-filter></activity>
UsingPreferenceCategoryThepreferencesframeworkprovidessupportforyoutoorganizeyourpreferencesintocategories.Ifyouhavealotofpreferences,forexample,youcanusePreferenceCategory,whichgroupspreferencesunderaseparatorlabel.Figure11-6showswhatthiscouldlooklike.Noticetheseparatorscalled“MEATS”and“VEGETABLES.”Youcanfindthespecificationsforthesein/res/xml/main.xml.
Figure11-6.UsingPreferenceCategorytoorganizepreferences
CreatingChildPreferenceswithDependencyAnotherwaytoorganizepreferencesistouseapreferencedependency.Thiscreatesaparent-childrelationshipbetweenpreferences.Forexample,youmighthaveapreferencethatturnsonalerts;andifalertsareon,theremightbeseveralotheralert-relatedpreferencestochoosefrom.Ifthemainalertspreferenceisoff,theotherpreferencesarenotrelevantandshouldbedisabled.Listing11-8showstheXML,andFigure11-7showswhatitlookslike.
Listing11-8.PreferenceDependencyinXML
<PreferenceScreen><PreferenceCategoryandroid:title="Alerts">
<CheckBoxPreferenceandroid:key="alert_email"android:title="Sendemail?"/>
<EditTextPreferenceandroid:key="alert_email_address"android:layout="?android:attr/preferenceLayoutChild"android:title="EmailAddress"android:dependency="alert_email"/>
</PreferenceCategory></PreferenceScreen>
Figure11-7.Preferencedependency
PreferenceswithHeadersAndroid3.0introducedanewwaytoorganizepreferences.YouseethisontabletsunderthemainSettingsapp.Becausetabletscreenrealestateoffersmuchmoreroomthanasmartphonedoes,itmakessensetodisplaymorepreferenceinformationatthesametime.Toaccomplishthis,youusepreferenceheaders.TakealookatFigure11-8.
Figure11-8.MainSettingspagewithpreferenceheaders
Noticethatheadersappeardowntheleftside,likeaverticaltabbar.Asyouclickeachitemontheleft,thescreentotherightdisplaysthepreferencesforthatitem.InFigure11-8,Soundischosen,andthesoundpreferencesaredisplayedatright.TherightsideisaPreferenceScreenobject,andthissetupusesfragments.Obviously,weneedtodosomethingdifferentthanwhathasbeendiscussedsofarinthischapter.
ThebigchangefromAndroid3.0wastheadditionofheaderstoPreferenceActivity.ThisalsomeansusinganewcallbackwithinPreferenceActivitytodotheheaderssetup.Now,whenyouextendPreferenceActivity,you’llwanttoimplementthismethod:
publicvoidonBuildHeaders(List<Header>target){loadHeadersFromResource(R.xml.preferences,target);}
PleaserefertothePrefDemosampleapplicationforthecompletesourcecode.Thepreferences.xmlfilecontainssomenewtagsthatlooklikethis:
<preference-headersxmlns:android="http://schemas.android.com/apk/res/android<headerandroid:fragment="com.example.PrefActivity$Prefs1Fragment"android:icon="@drawable/ic_settings_sound"android:title="Sound"android:summary="Yoursoundpreferences"/>...
EachheadertagpointstoaclassthatextendsPreferenceFragment.Intheexamplejustgiven,theXMLspecifiesanicon,thetitle,andsummarytext(whichactslikeasubtitle).Prefs1FragmentisaninnerclassofPreferenceActivitythatcouldlooksomethinglikethis:
publicstaticclassPrefs1FragmentextendsPreferenceFragment{@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.sound_preferences);}}
AllthisinnerclassneedstodoispullintheappropriatepreferencesXMLfile,asshown.ThatpreferencesXMLfilecontainsthetypesofpreferencespecificationswecoveredearlier,suchasListPreference,CheckBoxPreference,PreferenceCategory,andsoon.What’sveryniceisthatAndroidtakescareofdoingtherightthingwhenthescreenconfigurationchangesandwhenthepreferencesaredisplayedonasmallscreen.Headersbehavelikeoldpreferenceswhenthescreenistoosmalltodisplaybothheadersandthepreferencescreentotheright.Thatis,youonlysee
theheaders;andwhenyouclickaheader,youthenseeonlytheappropriatepreferencescreen.
PreferenceScreensThetop-levelcontainerforpreferencesisaPreferenceScreen.BeforetabletsandPreferenceFragments,youcouldnestPreferenceScreens,andwhentheuserclickedonanestedPreferenceScreenitem,thenewPreferenceScreenwouldreplacethecurrentlydisplayedPreferenceScreen.Thisworkedfineonasmallscreen,butdoesn’tlookasgoodonatablet,especiallyifyoustartedwithheadersandfragments.WhatyouprobablywantisforthenewPreferenceScreentoappearwherethecurrentfragmentis.
TomakeaPreferenceScreenworkinsideofafragment,allyouneedtodoisspecifyafragmentclassnameforthatPreferenceScreen.Listing11-9showstheXMLfromthesampleapplication.
Listing11-9.PreferenceScreeninvokedviaaPreferenceFragment
<PreferenceScreenandroid:title="Launchanewscreenintoafragment"android:fragment="com.androidbook.preferences.main.BasicFrag"/>
Whentheuserclicksonthisitem,thecurrentfragmentisreplacedwithBasicFrag,whichthenloadsanewXMLlayoutforaPreferenceScreenasspecifiedinnested_screen_basicfrag.xml.Inthiscase,wechosenottomaketheBasicFragclassaninnerclassoftheMainPreferenceActivityclass,mainlybecausethereisnosharingneededfromtheouterclass,andtoshowyouthatyoucandoitthiswayifyouprefer.
DynamicPreferenceSummaryTextYou’veprobablyseenpreferenceswherethepreferencesummarycontainsthecurrentvalue.Thisisactuallyalittlehardertoimplementthanyoumightthink.Toaccomplishthisfeat,youcreatealistenercallbackthatdetectswhenapreferencevalueisabouttochange,andyouthenupdatethepreferencesummaryaccordingly.ThefirststepisforyourPreferenceFragmenttoimplementtheOnPreferenceChangeListenerinterface.YouthenneedtoimplementtheonPreferenceChange()callback.Listing11-10showsanexample.ThepkgPrefobjectinthecallbackwassetearliertothepreferenceintheonCreate()method.
Listing11-10.SettingUpaPreferenceListener
publicbooleanonPreferenceChange(Preferencepreference,ObjectnewValue){
finalStringkey=preference.getKey();if("package_name_preference".equals(key)){pkgPref.setSummary(newValue.toString());}...returntrue;}
YouhavetoregisterthefragmentasalistenerinonResume()usingsetOnPreferenceChangeListener(this)oneachpreferenceyouwanttolistenon,andunregisterinonPause()bycallingitagainwithnull.Noweverytimethereisapendingchangetoapreferenceyou’veregisteredfor,thiscallbackwillbeinvokedpassinginthepreferenceandthepotentialnewvalue.Thecallbackreturnsabooleanindicatingwhethertoproceedwithsettingthepreferencetothenewvalue(true)ornot(false).Assumingyouwouldreturntruetoallowthenewsetting,thisiswhereyoucanupdatethesummaryvalueaswell.Youcouldalsovalidatethenewvalueandrejectthechange.PerhapsyouwantaMultiSelectListPreferencetohaveamaximumnumberofcheckeditems.Youcouldcounttheselecteditemsinthecallbackandrejectthechangeiftherearetoomany.
SavingStatewithPreferencesPreferencesaregreatforallowinguserstocustomizeapplicationstotheirliking,butwecanusetheAndroidpreferenceframeworkformorethanthat.Whenyourapplicationneedstokeeptrackofsomedatabetweeninvocationsoftheapplication,preferencesareonewaytoaccomplishthetaskeveniftheusercan’tseethedatainpreferencescreens.PleasefindthesampleapplicationcalledSavingStateDemotofollowalongwiththecompletesourcecode.
TheActivityclasshasagetPreferences(intmode)method.This,inreality,simplycallsgetSharedPreferences()withtheclassnameoftheactivityasthetagplusthemodeaspassedin.Theresultisanactivity-specificsharedpreferencesfilethatyoucanusetostoredataaboutthisactivityacrossinvocations.AsimpleexampleofhowyoucouldusethisisshowninListing11-11.
Listing11-11.UsingPreferencestoSaveStateforanActivityfinalStringINITIALIZED=“initialized”;
privateStringsomeString;
[…]
SharedPreferencesmyPrefs=getPreferences(MODE_PRIVATE);
booleanhasPreferences=myPrefs.getBoolean(INITIALIZED,false);if(hasPreferences){
Log.v(“Preferences”,“We'vebeencalledbefore”);//Readothervaluesasdesiredfrompreferencesfile…someString=myPrefs.getString(“someString”,””);}else{Log.v(“Preferences”,“Firsttimeeverbeingcalled”);//Setupinitialvaluesforwhatwillendup//inthepreferencesfilesomeString=“somedefaultvalue”;}
[…]
//LaterwhenreadytowriteoutvaluesEditoreditor=myPrefs.edit();editor.putBoolean(INITIALIZED,true);editor.putString(“someString”,someString);//Writeothervaluesasdesirededitor.commit();
Whatthiscodedoesisacquireareferencetopreferencesforouractivityclassandcheckfortheexistenceofaboolean“preference”calledinitialized.Wewrite“preference”indoublequotationmarksbecausethisvalueisnotsomethingtheuserisgoingtoseeorset;it’smerelyavaluethatwewanttostoreinasharedpreferencesfileforusenexttime.Ifwegetavalue,thesharedpreferencesfileexists,sotheapplicationmusthavebeencalledbefore.Youcouldthenreadothervaluesoutofthesharedpreferencesfile.Forexample,someStringcouldbeanactivityvariablethatshouldbesetfromthelasttimethisactivityranorsettothedefaultvalueifthisisthefirsttime.
Towritevaluestothesharedpreferencesfile,youmustfirstgetapreferencesEditor.Youcanthenputvaluesintopreferencesandcommitthosechangeswhenyou’refinished.Notethat,behindthescenes,AndroidismanagingaSharedPreferencesobjectthatistrulyshared.Ideally,thereisnevermorethanoneEditoractiveatatime.Butitisveryimportanttocallthecommit()methodsothattheSharedPreferencesobjectandthesharedpreferencesXMLfilegetupdated.Intheexample,thevalueofsomeStringiswrittenouttobeusedthenexttimethisactivityruns.
Youcanaccess,write,andcommitvaluesanytimetoyourpreferencesfile.Possibleusesforthisincludewritingouthighscoresforagameorrecordingwhentheapplicationwaslastrun.YoucanalsousethegetSharedPreferences()callwithdifferentnamestomanageseparatesetsofpreferences,allwithinthesameapplicationoreventhesameactivity.
MODE_PRIVATEwasusedformodeinourexamplesthusfar.Becausethesharedpreferencesfilesarealwaysstoredwithinyourapplication’s/data/data/{package}directoryandthereforearenotaccessibletootherapplications,youonlyneedtouse
MODE_PRIVATE.
UsingDialogPreferenceSofar,you’veseenhowtousetheout-of-the-boxcapabilitiesofthepreferencesframework,butwhatifyouwanttocreateacustompreference?WhatifyouwantsomethingliketheslideroftheBrightnesspreferenceunderScreenSettings?ThisiswhereDialogPreferencecomesin.DialogPreferenceistheparentclassofEditTextPreferenceandListPreference.Thebehaviorisadialogthatpopsup,displayschoicestotheuser,andisclosedwithabuttonorviatheBackbutton.ButyoucanextendDialogPreferencetosetupyourowncustompreference.Withinyourextendedclass,youprovideyourownlayout,yourownclickhandlers,andcustomcodeinonDialogClosed()towritethedataforyourpreferencetothesharedpreferencesfile.
ReferenceHerearehelpfulreferencestotopicsyoumaywishtoexplorefurther:
http://developer.android.com/design/patterns/settings.htmlAndroid’sDesignGuidetoSettings.SomegoodadviceaboutlayingoutSettingsscreensandoptions.
http://developer.android.com/guide/topics/ui/settings.htmlAndroid’sAPIGuidetoSettings.ThispagedescribestheSettingsframework.
http://developer.android.com/reference/android/provider/Settings.htmlReferencepagethatliststhesettingsconstantsforcallingasystemsettingsactivity.
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforthefileProAndroid5_Ch11_Preferences.zip.ThisZIPfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribeshowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.
SummaryThischaptertalkedaboutmanagingpreferencesinAndroid:
Typesofpreferencesavailable
Readingthecurrentvaluesofpreferencesintoyourapplication
SettingdefaultvaluesfromembeddedcodeandbywritingthedefaultvaluesfromtheXMLfiletothesavedpreferencesfile
Organizingpreferencesintogroups,anddefiningdependenciesbetweenpreferences
Callbacksonpreferencestovalidatechangesandtosetdynamicsummarytext
Usingthepreferencesframeworktosaveandrestoreinformationfromanactivityacrossinvocations
Creatingacustompreference
Chapter12
UsingtheCompatibilityLibraryforOlderDevicesTheAndroidplatformhasgonethroughanimpressiveevolutionsinceitwasfirstintroducedseveralyearsago.WhiletheintentionhasalwaysbeenforAndroidtopowerlotsofdifferenttypesofdevices,itwasn’tarchitectedfromthebeginningtomeetthatgoal.Instead,theGoogleengineershaveadded,removed,andchangedAPIsinordertoprovidenewfeatures.OneofthebiggestchangeswasthecreationoffragmentsinordertohandlelargerscreensizessuchasontabletsandTVs.ButtherehavebeenotherchangessuchaswithActionBarandMenus.
ThenewAPIscreatedadifficultproblemfordeveloperswhowantedtheirapplicationstorunonthenewdeviceswiththenewAPIs,aswellasolderdevicesthatdidnothavethoseAPIs.ManyolderdevicesdonotgetAndroidupgrades.EvenifGoogleaddedthenewAPIstoarevisionoftheoldAndroidOS,theolddevicesaren’tgoingtogetthatnewrevision,becauseofthetestingandsupportrequiredfromboththedevicemanufacturerandthecellularcarrier.ThesolutionthatGooglecameupwithwastocreatecompatibilitylibrariesthatcouldbelinkedintoanapplicationsoitcouldtakeadvantageofthenewAPIfunctionalityyetstillrunonanolderversionofAndroid.ThelibraryfiguresouthowtousetheolderAPIstoimplementthenewfeatures.IfthesameapplicationrunsonanewerversionofAndroidthatalreadyhasthosenewfeatures,thecompatibilitylibrarycallsthroughtotheunderlyingAPIspresentinthatnewerversionofAndroid.
Thischapterwilldiveintothecompatibilitylibrariesandexplainhowtousethemandwhattowatchoutfor.Ifyouaren’tdevelopingapplicationsforolderversionsofAndroid,youcouldsafelyskipthischapterasyouwon’tneedthelibraries.ThelibrariesareonlyusefulifyouwanttoincludethefunctionalityofanewAPIinanapplicationthatwillrunonanoldversionofAndroidthatdoesn’thavethatnewAPI.
ItAllStartedwithTabletsTheAndroidoperatingsystemwasdoingfineuntilitcametimetosupporttablets.Thebasicbuildingblockofanapplicationwastheactivity,meanttoperformasingletaskfortheuserandtofillthescreenofthedevice.Buttabletsofferedmorerealestatesotheusercouldseeanddoafewthingsatatimeononescreen.SowithHoneycomb(Android3.0),Googleintroducedfragments.Thiswasawholenewconcept,whichchangedhowdeveloperscreatedUIsandthelogicthatranbehindthem.Andthiswouldhavebeenfine,exceptthattherewerestillplentyofAndroiddevices(e.g.,smartphones)inthewildwhichdidnotsupportfragments.WhatGooglefiguredoutisthatacompatibilitylibrarycouldbewrittentoprovidecomparableimplementationsofFragment,etc.,thatusedtheexistingAPIsintheolderversionsofAndroid.Ifanapplicationlinkedinthecompatibilitylibrary,
itcouldworkwithfragmentseventhoughtheolderversionofAndroiddidn’tsupportfragmentsintheOS.
TheGoogleengineersthenlookedatotherfeaturesandAPIsinnewAndroidandprovidedcompatibilitylibraryfeaturesandAPIstomatch,sothatthesefeaturescouldalsobeusedinolderversionsofAndroidwithouthavingtoreleaseupdatestothoseolderversionsofAndroid.InadditiontosupportforFragments,compatibilitylibrariesprovidesupportforLoaders,RenderScript,ActionBar,andothers.
Thecompatibilitylibrarydoesn’talwaysmakethingsperfectlythesamebetweenoldandnew.Forexample,thenewActivityclassisawareoffragments.Tousethecompatibilitylibrary,youmustextendtheFragmentActivityclassinsteadofActivity;itistheFragmentActivityclassthatworkswithfragmentsinoldAndroidversions.
Whenyouusethecompatibilitylibrary,youwillusethoseclassesforyourapplicationregardlessofwhichversionofAndroiditwillrunon.Inotherwords,youwouldonlyuseFragmentActivityinyourapplicationanditwilldotherightthinginallversionsofAndroid,includingAndroid3.0andlater.YouwouldnottrytoincludeinthesameapplicationbothActivityforAndroid3.0+andFragmentActivityforAndroidbelow3.0.WhenFragmentActivityisexecutingonAndroid3.0andabove,itcanprettymuchcallstraightthroughtotheunderlyingActivityclass.ThereisnorealpenaltytousingacompatibilitylibraryonarecentAndroidversion.
AddingtheLibrarytoYourProjectAsofthiswriting,therearefourcompatibilitylibraries;togetherthecollectioniscalledtheAndroidSupportLibrary,revision22.1.1:
v4—containsFragmentActivity,Fragment,Loader,andquiteafewotherclassesintroducedafterAndroid3.0.Thenumber4representsAndroidAPIversion4(i.e.,Donut1.6).ItmeansthislibrarycanbeusedforapplicationsthatrunonAndroidAPIversion4andabove.
v7—makesavailabletheActionBar,CardView,GridLayout,MediaRouter,PaletteandRecyclerViewclasses.ThislibrarycanbeusedwithAndroidAPIversion7(i.e.,Eclair2.1)andabove.Thereareactuallysixlibrarieshere:appcompat,cardview,gridlayout,mediarouter,paletteandrecyclerview
v8—addsRenderSciptcapabilitytoAndroidAPIversion8(i.e.,Froyo2.2)andabove.RenderScriptallowsforparallelizationofworkacrossdeviceprocessors(CPUcores,GPUs,DSPs)andwasintroducedinAndroidAPIversion11(i.e.,Honeycomb3.0).
v13—addssomespecialFragmentfunctionalityforthingsliketabbedandpagerinterfaces.Thislibraryalsocontainsmanyofthe
classesfromv4soitcanbeincludedinyourapplicationwithoutrequiringotherlibraries.
v17—addsLeanbackfeaturesrelatedtoAndroidTVapplications
Foracompletelistofallcompatibilityfunctionalitybyversionnumber,pleaseseethereferencesattheendofthischapter.
TodownloadtheAndroidSupportLibrarytoyourcomputer,usetheAndroidSDKManagerandlookforitatthebottomofthelistunderExtras.Ifyou’reusingAndroidStudio,downloadtheAndroidSupportRepository.Otherwise,downloadAndroidSupportLibraryinstead.ThefileswillbeplacedunderyourAndroidSDKdirectory.TheAndroidSupportLibrarycanbefoundinextras/android/support/,andtheAndroidSupportRepositorycanbefoundinextras/android/m2repository.
Asyoucanseefromtheprecedingbulletlist,notallfeaturesoftheAndroidSupportLibraryareavailableonallolderversionsofAndroid.Thereforeyoumustproperlysetandroid:minSdkVersioninyourAndroidManifest.xmlfile.Ifyouareusingacompatibilitylibraryfeaturefromv7,android:minSdkVersionshouldnotbelowerthan7.
Includingthev7SupportLibraryThere’sverylittlechancethatyou’deverwanttoincludethev4libraryandnotthev7library.Sincethev7libraryrequiresthatthev4libraryalsobeincludedtoprovidethenecessaryclassesforv7tofunctionproperly,you’llwanttoincludeboth.IfyouareusingEclipse,theADTplug-inmakesallofthisprettyeasy.WhenyoucreateanewAndroidprojectinEclipse,youspecifytheminimumversionofAndroidthatitwillrunon.IfADTthinksthatyoumightwantthecompatibilitylibraryincluded,itwillautomaticallyincludeit.
Forexample,ifyouspecifyatargetSDKof16(JellyBean4.1)butaminimumSDKof8(Froyo2.2),ADTwillautomaticallysetupanappcompatv7libraryproject,includethatlibraryprojectinyournewapplication,andalsoincludethev4libraryaswellinyourapplication.Theresourcesfromthev7libraryarethereforeavailabletoyourapplicationwithoutyouhavingtodoextrawork.However,ifyouwanttouseeitheroftheothertwov7libraries(gridlayoutand/ormediarouter),thosewillrequirealittleextrawork,aswillnowbeexplained.Bycreatingalibraryprojectandincludingthatinyourapplication,itwillincludethecompatibilitylibraryresourcesthatyourapplicationwillneed.
YouwillmanuallydosomethingsimilartowhatADTdidtoautomaticallyincludethev7appcompatlibraryintoyourproject.Tostart,youwillchooseFile Import,thenExistingAndroidCodeIntoWorkspace,thennavigatetotheextrasfolderwheretheAndroidSDKisonyourworkstation.Locatethev7gridlayoutormediarouterfolderandchoosethat.SeeFigure12-1.
Figure12-1.Importingthev7mediaroutercompatibilitylibrary
ClickFinishandyouwillgetanewlibraryproject.Ifyouchosetocreatealibraryprojectforv7mediarouter,youwillseethatitismissingsomefunctionalitysoithaserrors.Youneedtoaddinthev7appcompatlibrarytoclearthatup.Right-clickthemediarouterlibraryprojectinEclipseandchooseProperties.InthelistontheleftchooseAndroid.NowclicktheAdd…buttonintheLibrarysection.SeeFigure12-2.
Figure12-2.Addingappcompat_v7tothev7mediaroutercompatibilitylibrary
Selecttheappcompat_v7libraryandclickOK.Thatshouldclearuptheerrorsinmediarouter.Nowwhenyouwanttoincludemediarouterinyourapplicationproject,simplyfollowthesameprocedurebutright-clickyourapplicationproject,andwhenyouclicktheAdd…buttonforLibrary,chosethemediarouterlibrary.
WithAndroidStudio,addingav7compatibilitylibraryisjustaseasy.Bydefault,ifyoucreateanewprojectwithaminimumSDKvaluelessthanyourtargetSDK,youwillverylikelygetthev7appcompatlibraryaddedinautomatically.Youcancheckthisbylookingforthefollowinglineintheapp’sbuild.gradleconfigurationfileinthedependenciessection:compile'com.android.support:appcompat-v7:22.0.0'
Therefore,toaddoneoftheotherv7libraries,youwouldinsertanothersimilarcompilelinetothedependenciessection,butusetheappropriatenamesuchascardviewormediarouter.
Includingthev8SupportLibraryIfyouwanttousethev8renderscriptcompatibilitylibrary,andyoudevelopwithEclipse,yousimplyaddthefollowingthreelinestotheapplicationproject’sproject.propertiesfileregardlessofthetargetversionofyourapplication:
renderscript.target=22renderscript.support.mode=truesdk.buildtools=22.1.1
Atthetimeofthiswriting,theonlineAndroiddocumentationsaysthatyoushoulduseatargetof18andabuildtoolsof18.1.0.However,usingtheoldvaluesgeneratesanerror
sayingtouseanewerversionofbuildtools.IfyouseeerrorsintheEclipseConsoleregardingversionnumbers,tryusingalaterversionasindicatedbytheerror.
IfyoudevelopwithAndroidStudio,toincludev8renderscriptyouwouldedittheapp’sbuild.gradlefileandaddtheselineswithinthedefaultConfigsection:
renderscriptTargetApi22renderscriptSupportModeEnabledtrue
Withinyourcode,makesureyouimportfromandroid.support.v8.renderscriptratherthanandroid.renderscript.IfyouaremodifyinganexistingRenderScriptapplicationforthev8library,makesuretocleanyourproject;theJavafilesthataregeneratedfromyour.rsfilesneedtoberegeneratedtoalsousethev8library.YoucannowuseRenderScriptasusualanddeployyourapplicationtoolderversionsofAndroid.
Includingthev13SupportLibraryToincludethev13compatibilitylibraryintoyourapplicationusingEclipse,navigatetotheSDKextrasdirectoryandfindthev13jarfile.Copythisfiletothe/libsdirectoryofyourapplicationproject.Oncethev13jarfileisinplace,right-clickittopullupthemenu,andthenchooseBuildPath AddtoBuildPath.There’sagoodchanceyoualreadyhavethev4andv7appcompatlibrariesinyourapplicationcourtesyofADT.Youmaychoosetogetridofthoseifyoudon’tneedthefunctionalityfromeitherone.Forexample,iftheminimumSDKforyourapplicationisv11,youcanusethenativeActionBarclasswithouttheneedforthev7appcompatsupportlibrary.
Thev13jarfilecontainsmanyofthesameclassesasv4,soyoudon’twanttocauseanyproblemsbyhavingthesameclassesintwice.Ifyou’regoingtohaveallthreelibrariesinyourapplication(i.e.,v4,v7,andv13),thenatleastensurethatv13isorderedbeforev4.ThiscanbedoneintheConfigureBuildPathdialogbox.
Ifyou’reusingAndroidStudio,justmakesuretheSDKManagerhasdownloadedtheSupportRepository,thenaddthefollowingcompilelinetotheapp’sbuild.gradlefilejustlikeyoudoforv7libraries:
compile'com.android.support:support-v13:22.0.0'
Includingthev17SupportLibraryFinally,includingthev17compatibilitylibraryisdonethesamewayasforthev13supportlibrary.
IncludingJustthev4SupportLibraryIfyoureallymusthavethev4supportlibraryandnoneoftheothers,youwouldfollowthesameprocedureasforthev13library.
RetrofittinganAppwiththeAndroidSupportLibraryTogetabetterfeelforhowthisallworks,you’regoingtobringbackafragmentappyouworkedoninChapter8andwillmakeitworkforolderversionsofAndroidthatdon’tnativelysupportfragments.
UseFile Import,chooseGeneral,thenExistingProjectsintoWorkspace.NavigatetotheShakespeareInstrumentedprojectfromChapter8andchoosethat.Check“Copyprojectsintoworkspace”beforehittingFinish.
Nowyou’regoingtoretrofitthisapplicationtoworkonversionsofAndroidlowerthanAPIversion11.Thefollowingworkswhenyoudon’tneedresourcesfromthecompatibilitylibrary,sinceitworriesonlyaboutcopyingintheJARfile.
1. Right-clickyourprojectandchooseAndroidTools AddSupportLibrary….AcceptthelicenseandclickOK.
2. NowgointoMainActivity.javaandchangethebaseclassfromActivitytoFragmentActivity.Youneedtofixtheimportlinefromandroid.app.Activitytoandroid.support.v4.app.FragmentActivity.AlsofixtheimportsforFragment,FragmentManager,andFragmentTransactiontousetheonesfromthesupportlibrary.
3. FindthemethodcallsforgetFragmentManager()andchangethesetogetSupportFragmentManager().DothisalsoforDetailsActivity.java.
4. ForDetailsFragment.java,changetheimportforFragmenttotheoneforthesupportlibraryFragment(i.e.,android.support.v4.app.Fragment).
5. InTitlesFragment.java,changetheimportforListFragmenttotheoneforthesupportlibraryListFragment(i.e.,android.support.v4.app.ListFragment).
ThenewerversionsofAndroidusedifferentanimatorsfromoldAndroid.YoumayneedtofixanimationsinMainActivity.javaintheshowDetails()method.PickoneofthecommentedoutcallstosetCustomAnimations(),thenplaywiththeinandoutanimations.AnythingthatreliesonanObjectAnimatorclasswillnotworkonolderdevicessincethisclasswasintroducedwithAPIversion11(i.e.,Honeycomb3.0).ItwillcompilebutsincethatclasshasnotbeenimplementedinolderAndroidandhasnotbeenincludedinthecompatibilitylibraries,youwillgetaruntimeexception.Inotherwords,avoidR.animator.TryusingR.animinstead.Youcancopyintoyourprojectanim
resourcefilesthatyou’dliketouse,oryoucantryreferringtoandroid.R.animfiles.
NowyoucangointoAndroidManifest.xmlandchangetheminSdkVersionfrom11to8.Thatshouldbeallyouneedtodo.TryrunningthisapplicationonaFroyodeviceoremulator.Ifallwentwellyoushouldnowbeseeingafragment-basedapplicationrunningonapre–Android3.0OS.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
http://developer.android.com/tools/support-library/index.html:TheAndroidDeveloper’sGuideontheSupportLibrarypackage.
http://developer.android.com/tools/support-library/features.html:Androiddocumentationonthemainfeaturesofeachcompatibilitylibrary.
http://developer.android.com/tools/support-library/setup.html:Androiddocumentationonsettingupacompatibilitylibraryforyourproject,forbothEclipseandAndroidStudio.Atthetimeofthiswriting,thesepageswerenotascurrentasthischapter.However,thingschange.Ifyouexperiencetrouble,checktheonlinedocumentationorcontactthebook’sauthors.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedabouttheAndroidcompatibilitylibraries:
Togetyourapplicationworkingonthebroadestarrayofdevices,usethecompatibilitylibrariesandcodetotheirAPIsratherthanthelatestandgreatestAPIs.
Thev7supportlibrariescomewithresourcesthatmustbeincludedinyourapplicationfortheAPIstoworkproperly.
Chapter13
ExploringPackages,Processes,Threads,andHandlersInthebookthusfar,wehavefocusedontheessentialsofhowtoprogramfortheAndroidplatform.InthischapterwewanttogounderthehoodabittoaddresstheprocessandthreadingmodelforAndroidprograms.Thisdiscussionwillleadustosigningpackages,sharingdatabetweenpackages,usingcompile-timelibraries,thenatureofAndroidcomponentsandhowtheyusethreads,andfinallytheneedforhandlersandhowonecancodehandlers.
Asyougothroughthischapter,keepinmindthattheword“package”isoverloaded.SometimesitreferstotheJavalanguagepackage,andsometimesitreferstotheAPKfilesthatAndroidapplicationsaredeployedas.
UnderstandingPackagesandProcessesWewillstartwithAndroidpackagesandtheprocessmodel.WhenyoudevelopanapplicationinAndroid,youendupwithan.apkfile.Yousignthis.apkfileanddeployittothedevice.Each.apkfileisuniquelyidentifiedbyauniquejava-language-stylepackagename,asshowninthemanifestfileshowninListing13-1.
Listing13-1.ProvidingaPackageNameintheManifestFile
<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.testapp"...>...restofthexmlnodes</manifest>
Ifyouwerethedeveloperofthispackage,nooneotherthanyoucouldupdatethisapplicationonceitisdeployed.TheAndroidapplicationpackagenameisreservedforyou.Thistie-uphappenswhenyousignandregisteryourappwithvariousapppublishers.SochoosethisAndroidapplicationpackagenameverysimilartothewaythatJavapackagesarenamed.Thisneedstobeuniqueintheworld.Onceyoupublishtheapp,youcannotchangethispackagename,asthisdefinesyourapplication’sidentity.
Androidusesthepackagenameasthenameoftheprocessthatrunsthecomponentsofthispackage.AndroidalsoallocatesauniqueuserIDforthisprocesstorununder.ThisuserIDisessentiallytheuserIDfortheunderlyingLinuxOS.AsthisuserIDisdeterminedatthetimeoftheinstallonaparticulardevice,itwillbedifferentoneachdevicewhereyourappisinstalled.Youcandiscoverthisinformationbylookingatthe
detailsoftheinstalledpackagethroughthedevelopertoolsintheAndroidEmulator.Forexample,apackagedetailscreenfortheinstalledbrowserapplicationlookslikeFigure13-1.(Pleasenotethatthisimageortoolwhereyoulookthisupmayvaryfromreleasetorelease.TheimageinFigure13-1istakenfromthedevelopertoolsapplicationontheAndroidEmulator.)
Figure13-1.Androidpackagedetails
Figure13-1showsthenameoftheprocessasindicatedbytheJavapackagenameinthemanifestfileandtheuniqueuserIDallocatedtothispackage.AnyresourcescreatedbythisprocessorpackagewillbesecuredunderthatLinuxuserID.Thisscreenalsoliststhecomponentsinsidethispackage.Examplesofcomponentsareactivities,services,andbroadcastreceivers.DonotethatthisimagemayvarydependingontheAndroidrelease.Throughthesettingsofthedeviceortheemulator,youcanalsouninstallthepackagesothatitcanberemoved.
Becauseaprocessistiedtoapackagename,andapackagenameistiedtoitssignature,signaturesplayaroleinsecuringthedatabelongingtoapackage.Apackageistypicallysignedwithaself-signedPKI(PublicKeyInfrastructure)certificate.Acertificate
identifieswhotheauthorofthepackageis.Thesecertificatesneednotbeissuedbyacertificateauthority.Thismeanstheinformationinthecertificateisnotapprovedorvalidatedbyanyauthority.ThismeansonecancreateacertificatethatsaysthattheirnameisGoogle.Theonlyassuranceisthatthispackagenameisreservedtothatuserifnoonehadclaimeditinthemarketplacebefore,andanysubsequentupdatestothatpackagearegivenonlytothatuser(identifiedbythatcertificate).AllassetsthatareinstalledorcreatedthroughthispackagebelongtotheuserwhoseIDisassignedtothepackage.Ifyourintentionistoallowasetofcooperatingapplicationsthatdependonacommonsetofdata,youhaveanoptiontoexplicitlyspecifyauserIDthatisuniquetoyouandcommonforyourneeds.ThisshareduserIDisalsodefinedinthemanifestfile,similartothedefinitionofapackagename.Listing13-2showsanexample.
Listing13-2.SharedUserIDDeclaration
<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.somepackage"sharedUserId="com.androidbook.mysharedusrid"...>...therestofthexmlnodes</manifest>
MultipleapplicationscanspecifythesameshareduserIDiftheysharethesamesignature(signedwiththesamePKIcertificate).HavingashareduserIDallowsmultipleapplicationstosharedataandevenruninthesameprocess.ToavoidtheduplicationofashareduserID,useaconventionsimilartonamingaJavaclass.HerearesomeexamplesofshareduserIDsfoundintheAndroidsystem:
"android.uid.system""android.uid.phone"
NoteAsharedIDmustbespecifiedasarawstringandnotastringresource.
Asanoteofcaution,ifyouareplanningtouseshareduserIDs,therecommendationistousethemfromthestart.Otherwise,theydon’tworkwellwhenyouupgradeyourapplicationfromanonshareduserIDtoonewithasharedID.OneofthecitedreasonsisthatAndroidwillnotrunchownontheoldresourcesbecauseoftheuserIDchange.
ACodePatternforSharingDataThissectionexplorestheopportunitieswhentwoapplicationswanttoshareresourcesanddatathroughtheuseofashareduserID.Theresourcesanddataofeachpackageareownedandprotectedbythatpackage’scontextduringruntime.Youneedaccesstothecontextofthepackagefromwhichyouwanttosharetheresourcesordata.
YoucanusethecreatePackageContext()APIonanyexistingcontextobject(such
asyouractivity)togetareferencetothetargetcontextthatyouwanttointeractwith.Listing13-3providesanexample.
Listing13-3.UsingthecreatePackageContext()API
//Usetheappropriatetry/catchtodetecterrors//IdentifypackageyouwanttouseStringtargetPackageName="com.androidbook.samplepackage1";
//Decideonanappropriatecontextflagintflag=Context.CONTEXT_RESTRICTED;
//Getthetargetcontextthroughoneofyouractivities//NeedtocatchNameNotFoundExceptionActivitymyContext=......;ContexttargetContext=myContext.createPackageContext(targetPackageName,flag);
//UsecontexttoresolvefilepathsResourcesres=targetContext.getResources();Filepath=targetContext.getFilesDir();
Noticehowweareabletogetareferencetothecontextofagivenpackagenamesuchascom.androidbook.samplepackage1.ThistargetContextinListing13-3isidenticaltothecontextthatispassedtothetargetapplicationwhenthatapplicationislaunched.Asthenameofthemethodindicates(inits“create”prefix),eachcallreturnsanewcontextobject.However,thedocumentationassuresusthatthisreturnedcontextobjectisdesignedtobelightweight,meaningitdoesn’tconsumealotofmemoryandisoptimizedtoreferthetargetpackage’sresources,assets,andcode.
ThisAPIisapplicableregardlessofwhetherbothcontextsshareauserID.IfyousharetheuserID,itiswellandgood.Ifyoudon’tshareauserID,thetargetapplicationwouldneedtodeclareitsresourcesaccessibletooutsideusers.
TheCONTEXT_RESTRICTEDflagindicatesthatyouareinterestedinjustloadingtheresourcesandtheassetsandnotthecode.Sousingthisflagallowsthesystemtodetectifthelayoutscontainreferencestocallbackcode.Exampleofacallbackwouldbeabuttoninalayoutreferringtoamethodthatwouldbecalled.Thiscallbackcodeexistsinthesourcecontext.So,youwouldwantthesystemtothrowanexceptionsothatyoucandetectthatconditionorignorethatparticularXMLtag.Inessence,youaretellingthesystemthatyouareusingthecontextinarestrictedsenseandthetargetcontextisfreetomakesuitableassumptionsbasedonthatflag.Thebottomlineappearstobethatifyouareinterestedinnotusingthecodefromthetargetcontext,usethisflag.
CONTEXT_INCLUDE_CODEallowsyoutoloadJavaclassesatruntimefromthetargetcontextintoyourprocessandcallthatcode.Documentationindicatesthatyoumayreceiveasecurityexceptionifitisnotsafetoloadthecode.However,itisnotclearunderwhatcircumstancesthecodeisconsideredunsafe.Oneeducatedguessisthatthetarget
contextdoesnothaveashareduserIDasthatofthesourcecontext.YoucanovercomethisrestrictionbyalsospecifyingtheCONTEXT_IGNOR_SECURITYalongwiththeCONTEXT_INCLUDE_CODE.Thesetwoflagstogetherloadthetargetcontextcodeintothesourcecontextcodeallthetime,ignoringevenifthetargetcontextbelongstoadifferentuser.Althoughcodeisborrowedandrunsintheclientprocess,itwillnothavepermissionstothetargetcontextdata.So,besurewhatthatcodedoeswhenletlooseonyourdata.Thisapproachisoftenusedforutilitycodethatcanbeshared.
UnderstandingLibraryProjectsAswetalkthroughsharingcodeandresources,onequestionworthaskingis,willtheideaofa“library”projecthelp?StartingwithADT0.9.7Eclipseplug-in,Androidsupportstheideaoflibraryprojects.Theapproachtobuildinglibrarieshasbeenchangingabitsincethen,whilethecentralidearemainsinallrecentreleases.
AlibraryprojectisacollectionofJavacodeandresourcesthatlookslikearegularAndroidprojectbutneverendsupinan.apkfilebyitself.Instead,thecodeandresourcesofalibraryprojectbecomepartofanotherprojectandgetcompiledintothatmainproject’s.apkfile.Aslibrariesarepurelyacompile-timeconcept,eachdevelopmenttoolmaycraftthisfacilitydifferently.
Herearesomeadditionalfactsabouttheselibraryprojects:
Alibraryprojectcanhaveitsownpackagenamedistinctfromthemainapplication.
AlibraryprojectcanuseotherJARfiles.
EclipseADTwillcompilethelibraryJavasourcefilesintoaJARfilethatisthencompiledwiththeapplicationproject.
ExceptfortheJavafiles(whichbecomeajarfile),therestofthefilesbelongingtoalibraryproject(suchasresources)arekeptwiththelibraryproject.Thepresenceofthelibraryprojectisrequiredinordertocompiletheapplicationprojectthatincludesthatlibraryasadependency.
StartingwithSDKTools15.0,theresourceIDsgeneratedforlibraryprojectsintheirrespectiveR.javafilesarenotfinal.(Thisisexplainedlaterinthechapter.)
BoththelibraryprojectandthemainprojectcanaccesstheresourcesfromthelibraryprojectthroughtheirrespectiveR.javafiles.ThismeanstheIDnamesareduplicatedandavailableinbothR.javafiles.
IfyouwouldliketodistinguishresourceIDsbetweenthetwoprojects(libraryandmain),youcanusedifferentresourceprefixes,suchaslib_forthelibraryprojectresources.
Amainprojectcanreferenceanynumberoflibraryprojects.
Components,suchasanactivity,ofalibraryneedtobedefinedinthetargetmainprojectmanifestfile.Whenthisisdone,thecomponentnamefromthelibrarypackagemustbefullyqualifiedwiththelibrarypackagename.
Itisnotnecessarytodefinethecomponentsinalibrarymanifestfile,althoughitmaybeagoodpracticetoknowquicklywhatcomponentsitsupports.
CreatingalibraryprojectstartswithcreatingaregularAndroidprojectandthenchoosingtheIsLibraryflaginitspropertieswindow.
Youcansetthedependentlibraryprojectsforamainprojectthroughtheprojectpropertiesscreenaswell.
Clearly,beingalibraryproject,anynumberofmainprojectscanincludealibraryproject.
Onelibraryprojectcannotreferenceanotherlibraryprojectasofthereleases(Android4.4,API19,SDKTools19,ADT22.3),althoughthereseemstobeadesiretobeabletodosoinfuturereleases.
Tocreatealibraryproject,youstartbycreatingaregularAndroidproject.Oncetheprojectissetup,right-clicktheprojectnameandclickthepropertiescontextmenutoshowthepropertiesdialogforthelibraryproject.ThisdialogisshowninFigure13-2.(TheavailablebuildtargetsinthisfiguremayvarywithyourversionoftheAndroidSDK.)SimplyselectIsLibraryfromthisdialogtosetupthisprojectasalibraryproject.
Figure13-2.Designatingaprojectasalibraryproject
Youcanusethefollowingprojectpropertiesdialog(seeFigure13-3)toindicatethatamainprojectdependsonthelibraryprojectthatwascreatedearlier.
Figure13-3.Declaringalibraryprojectdependency
NoticetheAddbuttoninthedialog.YoucanusethistoaddthelibraryinFigure13-3asareference.Youdon’tneedtodoanythingelse.
Oncethelibraryprojectissetupasadependencyforthemainapplicationproject,thelibraryprojectappearsasacompiledJARfileintheapplicationprojectunderthenodeAndroidDependencies.
Androiddoesn’tpackageR.classfilesfromthelibrariesintheirrespectivejarfiles.Instead,itreliesonthesourceR.javafilethatisre-createdandmadeavailableinthemainapplicationprojectforeachofthelibraries.ThatmeansyouhaveanR.javafileforeachofthelibrariesinthegensubdirectoryofthemainproject.
Toavoidhard-codedconstantsbeinginthecompiledsourcecodeofthelibraries,AndroidcreatesthelibraryR.javafilessuchthatalltheconstantsinthatfilearenon-final.Duringthefinalcompilationofthemainproject,newconstantvaluesareallocatedsothattheseconstantvaluesareuniqueacrossallthelibrariesandthemainproject.Hadwegivenfinalconstantvaluesduringlibrarycompilation,thenthosenumberscouldcollidebetweenlibraries.AllocationofIDsuniquelyforagivensetofnamesmustbedoneonetime.OncethesenumbersareallocatedtotheIDsduringthecompileofthemainproject,theycan
becomefinalinthatmainproject.
ThereisanimplicationtiedtothefactthatIDsinthelibrary’sR.javafilearenotfinal.ItiscommontouseaswitchstatementtorespondtomenuitemsbasedonamenuitemID.ThislanguageconstructwillfailatcompiletimewhendoneinthelibrarycodeiftheIDsarenotfinal.Thisisbecausethecasestatementinaswitchclausehastobeanumericalconstantnumber.
So,theswitchstatementinListing13-4willnotcompileunlesstheIDs(suchasR.id.menu_item_1)areactualliteralnumbersorstaticfinals.
Listing13-4.SampleswitchStatementtoDemonstrateNon-FinalVariables
switch(menuItem.getItemId()){caseR.id.menu_item_1:Statement1;break;case0x7778888://asanexampleforR.id.menu_item_2:statement;statement;break;default:statement;statement;}
BecausetheIDsaredefinedasnon-finalforlibraryprojects,weareforcedtouseif/elsestatementsinsteadofswitch/caseclauses.Becausethesameconstantsre-createdfromthelibrary’sR.javafilesarefinal,youcanusefreelytheswitchclauseinyourfinalproject.
Asyoucansee,libraryprojectsarecompile-timeconstructs.Clearly,anyresourcesthatbelongtothelibrarygetabsorbedandmergedintothemainproject.Thereisnotaquestionofsharingatruntime,becausethereisjustonepackagefilewiththenameofthemainpackage.Inshort,librariesofferawaytoshareresourcesbetweenrelatedprojectsatcompiletime.
UnderstandingComponentsandThreadsWestartedoffthischapterestablishingthateachpackagerunsinitsownprocess.Wewillnowexplaintheorganizationofthreadswithinthisprocess.Thiswillleadustowhyweneedhandlerstooffloadtheworkfromthemainthreadandalsotocommunicatewiththemainthread.
MostcodeinanAndroidapplicationrunsinthecontextofacomponentsuchasanactivityoraservice.Mostofthetime,thereisonlyonethreadrunninginanAndroidprocess,calledthemainthread.Wewilltalkabouttheimplicationsofsharingthismainthreadamongvariouscomponents.Primarily,thiscanleadtoApplicationNotResponding
(ANR)messages(the“A”standsfor“application”andnot“annoying”).Wewillshowyouhowyoucanusehandlers,messages,andthreadstobreakthedependencyonthemainthreadwhenlong-runningoperationsareneeded.
AnAndroidprocesshasfourprimarycomponenttypes:Activity,Service,ContentProvider,andaBroadcastReceiver.MostcodeyouwriteinanAndroidapplicationispartofoneofthesecomponentsorcalledbyoneofthesecomponents.EachofthesecomponentsgetsitsownXMLnodeunderanapplicationnodespecificationintheAndroidprojectmanifestfile.Torecall,herearethesenodesinListing13-5:
Listing13-5.HowComponentsAreDeclaredintheManifestFile
<manifest…><application><activity/><service/><receiver/><provider/></application></manifest>
Withsomeexceptions(suchasexternalprocesscallstocontentproviders),Androidusesthesamethreadtoprocess(orrunthrough)codeinthesecomponents.Thisthreadiscalledthemainthreadoftheapplication.Whenthesecomponentsarecalled,thecallcanbeeitherasynchronouscall,suchaswhenyoucallacontentproviderfordata,oradeferredonethroughamessagequeue,suchaswhenyouinvokefunctionalitybycallingastartserviceorshowadialog.
Figure13-4describestherelationshipbetweenthreadsandthesefourcomponents.ThisdiagramshowshowthreadsweavethroughtheAndroidframeworkanditscomponents.Thediagramdoesnotindicatetheorderinwhichathreadmightweavethroughthevariouscomponents.Thediagramismerelyshowingthattheprocessingcontinuesfromonecomponenttoanotherinasequentialfashion.
Figure13-4.Androidcomponentsandthreadingframework
AsindicatedinFigure13-4,themainthreaddoestheheavylifting.Itrunsthroughallthecomponentsbyusingamessagequeue.Asyouselectmenusorbuttonsonthedevicescreen,thedevicewilltranslatetheseactionsasmessagesanddropthemontothemainqueueoftheprocessthatisinfocus.Themainthreadsitsinaloopandprocesseseachmessage.Ifanymessagetakesmorethanfivesecondsorso,AndroidthrowsanANRmessage.
Similarly,inresponsetoamenuitem,ifyouweretoinvokeabroadcastmessage,Androidagaindropsamessageonthemainqueueofthepackageprocessfromwhichtheregisteredreceiveristobeinvoked.Themainthreadwillcomearoundtothatmessageatalatertimetoinvokethereceiver.Themainthreaddoestheworkforabroadcastreceiveraswell.Ifthemainthreadisbusyrespondingtoamenuaction,thebroadcastreceiverwillhavetowaituntilthemainthreadgetsfreedup.
Thesameistruewithaservice.WhenyoustartalocalservicewithActivity.startServicefromamenuitem,amessageisdroppedontothemainqueue,andthemainthreadwillcomearoundtoprocessitviatheservicecode.
Callstoalocalcontentproviderareslightlydifferent.Acontentproviderstillrunsonthemainthreadforalocalcall,butacalltoitissynchronousanddoesnotusemessagequeues.
Youmayask,“WhyisitimportantwhethermostcodeinanAndroidapplicationrunsonthemainthreadorotherwise?”ThisisimportantbecausethemainthreadhastheresponsibilitytogetbacktoitsqueuesothatUIeventsarerespondedto.Asaconsequence,youshouldnotholdupthemainthread.Ifthereissomethingthatisgoingto
takelongerthanfiveseconds,youshouldgetthatdoneinaseparatethreadordeferitbyaskingthemainthreadtocomebacktoitwhenitisfreedupfromotherprocessing.Whenexternalclientsorcomponentsoutsideoftheprocessmakeacalltothecontentproviderfordata,thenthatcallisallocatedathreadfromathreadpool.Thesameistruewithexternalclientsconnectingtoservices.
Let’slookatwhathandlersareandhowtheyfunctioninthenextsection.
UnderstandingHandlersWehavebrieflyreferredtotheideaofdeferringworkonamainthreadifneeded.Thisisdonethroughhandlers.HandlersareextensivelyusedthroughoutAndroidsothatthemainUIthreadisnotheldup.Theyalsoplayaroleincommunicatingwiththemainthreadfromotherspawnedworkerthreads.
Ahandlerisamechanismtodropamessageonthemainqueue(moreprecisely,thequeueattachedtothethreadonwhichthehandlerisinstantiated)sothatthemessagecanbeprocessedatalaterpointintimebythatcirculatingthread.Themessagethatisdroppedhasaninternalreferencepointingtothehandlerthatdroppedit.
Whenthemainthreadgetsaroundtoprocessingthatmessage,itinvokesthehandlerthatdroppedthemessagethroughacallbackmethodonthehandlerobject.ThiscallbackmethodiscalledhandleMessage.Figure13-5presentsthisrelationshipbetweenhandlers,messages,andthemainthread.
Figure13-5.Handler,message,messagequeuerelationship
Figure13-5illustratesthekeyplayersthatworktogetherwhenwetalkabouthandlers:mainthread,mainthreadqueue,handler,andamessage.Outofthesefour,wearenotexposedtothemainthreadorthequeuedirectly.Weprimarilydealwiththehandlerobjectandthemessageobject.Evenbetweenthesetwo,thehandlerobjectcoordinatesmostofthework.
Althoughahandlerallowsustodropamessageontothequeue,itisthemessageobjectthatactuallyholdsareferencebacktothehandler.Themessageobjectalsoholdsadatastructurethatcanbepassedbacktothehandler.
Workingwithahandlerandmessagesisbestunderstoodthroughanexample.Fortheexample,wewillhaveamenuitemthatinvokesafunction,andthatfunction,inturn,performsanactionfivetimesatone-secondintervalsandreportsbacktotheinvokingactivityeachtime.
Ifwedidn’tmindholdingupthemainthread,wecouldhavecodedthisscenariolikethepseudocodeinListing13-6.
Listing13-6.HoldingUptheMainThreadwithaSleepMethod
publicclassSomeActivity{....othermethodsvoidrespondToMenuItem(){//ProvethatweareonthemainthreadUtils.logThreadSignature();//simulateanoperationthattakeslongerthan5secondsfor(inti=0;i<6;i++){sleepFor(1000);//putmainthreadtosleepfor1secdosomething();SomeTextView.setText("didsomething.Counter:"+Integer.toString(i));}}}
Thiswillsatisfytherequirementoftheusecase.However,ifwedothis,weareholdingupthemainthread,andweareguaranteedtohaveanANR.WecanuseahandlertoavoidtheANRinthepreviousexample.PseudocodetodothisviaahandlerwilllooklikeListing13-7.
Listing13-7.InstantiatingaHandlerfromtheMainThread
voidrespondToMenuItem(){SomeHandlerDerivedFromHandlermyHandler=newSomeHandlerDerivedFromHandler();myHandler.doDeferredWork();//invokeafunctionin1secintervals//notethatdoDeferredWork()isnotpartoftheSDK//wewillshowyouthecodeforthisshortly}
Now,thecallrespondToMenuItem()willallowthemainthreadtogobacktoitsloop.Theinstantiatedhandlerknowsthatitisinvokedonthemainthreadandhooksitselfuptothequeue.ThemethoddoDeferredWork()willscheduleworksothatthemainthreadcangetbacktothisworkonceitisfree.
Toinvestigatethisprotocol,let’sseetheactualsourcecodeforaproperhandler.ThecodeinListing13-8inthenextsectiondemonstratesthishandler,whichiscalledDeferWorkHandler.InthepreviouspseudocodeofListing13-7,theindicatedhandlerSomeHandlerDerivedFromHandlerisequivalenttothisDeferWorkHandler.Similarly,theindicatedmethoddoDeferredWork()(ofListing13-7)isimplementedontheDeferWorkHandlerinListing13-8.
Listing13-8.DeferWorkHandlerSourceCode
publicclassDeferWorkHandlerextendsHandler{//Keeptrackofhowmanytimeswesentthemessageprivateintcount=0;
//Aparentdriveractivitywecanusetoinformofstatus.privateTestHandlersDriverActivityparentActivity=null;
//Duringconstructionwetakeintheparentdriveractivity.publicDeferWorkHandler(TestHandlersDriverActivityinParentActivity){parentActivity=inParentActivity;}//Callbackmethodthatgetscalledbythemainthread
@OverridepublicvoidhandleMessage(Messagemsg){
//UsethemessageobjecttogettoitsdataStringpm=newString("messagecalled:"+count+":"+msg.getData().getString("message"));//youcanaccesstheparentactivityandinvokeUIcallsonithereparentActivity.someControl.somemethod();//exampleonly
//logictoinvokeitselfmultipletimesifneededif(count>5){return;}count++;//incrementcountsendTestMessage(1);//reinvokeagainbysendingamessage}//methodcalledbytheclient
publicvoiddoDeferredWork(){count=0;sendTestMessage(1);}//PreparingandsendingthemessagepublicvoidsendTestMessage(longinterval){Messagem=this.obtainMessage();prepareMessage(m);this.sendMessageDelayed(m,interval*1000);}publicvoidprepareMessage(Messagem){Bundleb=newBundle();
b.putString("message","HelloWorld");m.setData(b);return;}}
Let’slookatthekeyaspectsofthissourcecode.Thefirstisthatthehandlerisderivedfromthebaseclasshandler.Intheconstructorofthehandler,weuseapointertotheparentactivitysothatwecanusetheUIcontrolsoftheactivitytoreportwhatneedstobereportedortoactupon.Thenwecodeamethod(doDeferredWork)toencapsulatewhatthishandlerisexpectedtodoforus.NoticethatthedoDeferredWork( )isnotanoverriddenmethodandyoucancallthismethodwhatevernamethatyouwouldlike.ItisinthismethodthatyouworkwithmessagestoeventuallycalltheoverriddenhandleMessage( ).Also,itisinthishandleMessage( )thatyouactuallyputtherealcodethatisoriginallydeferredfromthemainthread.
Thebasehandleroffersaseriesofmethodstosendmessagestothequeuetoberespondedtolater.ThesemethodsareusedinthedoDeferredWork( ).sendMessage()andsendMessageDelayed()aretwoexamplesofthesesendmethods.sendMessageDelayed(),whichweusedintheexample,allowsustodropamessageonthemainqueuewithagivenamountoftimedelay.sendMessage(),incontrast,dropsthemessageimmediatelytobeprocessedwhenthemainthreadgetsaroundtoit.
WhenyoucallsendMessage()orsendMessageDelayed(),youwillneedaninstanceofthemessageobject.Itisbestthatyouaskthehandlertogiveittoyou,becausewhenthehandlerreturnsthemessageobject,ithidesitselfinthebellyofthemessage.Thatway,whenthemainthreadcomesalong,itknowswhichhandlertocallbasedsolelyonthemessage.InListing13-8,themessageisobtainedusingthefollowingcode:
Messagem=this.obtainMessage();
Thevariablethisreferstoisthehandlerobjectinstance.Asthenameindicates,themethoddoesnotcreateanewmessagebutinsteadgetsonefromaglobalmessagepool.Atalaterpoint,oncethismessageisprocessed,itwillberecycled.ThemethodobtainMessage()hasthevariationsshowninListing13-9.
Listing13-9.ConstructingaMessageThroughaHandler
obtainMessage();obtainMessage(intwhat);obtainMessage(intwhat,Objectobject);obtainMessage(intwhat,intarg1,intarg2)obtainMessage(intwhat,intarg1,intarg2,Objectobject);
Eachmethodvariationsetsthecorrespondingfieldsonthemessageobject.Therearesomerestrictionsontheobjectargumentwhenthemessagecrossesprocessboundaries.Insuchcases,itneedstobeParcelable.Itismuchsaferandcompatibleinsuchcasesto
usethesetData()methodexplicitlyonthemessageobject,whichtakesaBundle.InListing13-8,wehaveusedsetData().Youareencouragedtousearg1orarg2insteadifwhatyouareintendingtopassaresimpleindicatorsthatcanbeaccommodatedwithintegervalues.
Theargumentwhat(inListing13-9)allowsyoutodequeuemessageorenquireiftherearemessagesofthistypeinthequeue.Seetheoperationsonthehandlerclassformoredetails.
Onceweobtainamessagefromthehandler,wecanoptionallymodifythedatacontentsofthatmessage.Inourexample,wehaveusedthesetData()functionbypassingitaBundleobject.Afterwehavecategorizedoridentifiedthedataofthemessage,wecansendthemessagetothequeuethroughsendMessage()orsendMessageDelayed().Whenthesemethodsarecalled,themainthreadwillreturntoattendingthequeue.
Oncethemessagesaredeliveredtothequeue,thehandlersitsandwaits(figurativelyspeaking)untilthemainthreadretrievesthosemessagesandcallsthehandler’shandleMessage().
Ifyouwanttoseethishandlerandmainthreadinteractionmoreclearly,youcanwritealogcatmessagewhenyouaresendingthemessageandinthehandleMessage()callback.YouwillnoticethetimestampsdifferasthemainthreadwouldhavetakenafewmoremillisecondstocomebacktothehandleMessage()method.
Inourexample,eachhandleMessage(),afterprocessingonemessage,sendsanothermessagetothequeuesothatitcanbecalledagain.Itdoesthisfivetimes,andwhenthecounterreachesfive,itquitssendingmessagestothequeue.Thisisonewaytobreakuptheworkintomultiplechunks,althoughtherearebetterwaystodothiseitherthroughaworkerthreadorthroughaclassAsyncTask.TheessentialAsyncTaskiscoveredinthenextchapter.Let’scovertheexplicitworkerthreadsoptionbrieflynow.
UsingWorkerThreadsWhenweuseahandlerliketheoneintheprevioussection,thecodeisstillexecutedonthemainthread.EachcalltohandleMessage()stillshouldreturnwithinthetimestipulationsofthemainthread(inotherwords,eachmessageinvocationshouldcompleteinlessthanfivesecondstoavoidApplicationNotResponding).Ifyourgoalistoextendthattimeofexecutionfurther,youwillneedtostartaseparatethread,keepthethreadrunninguntilitfinishesthework,andallowforthatsubthreadtoreportbacktothemainactivity,whichisrunningonthemainthread.Thistypeofasubthreadisoftencalledaworkerthread.
Itisano-brainertostartaseparatethreadwhilerespondingtoamenuitem.However,theclevertrickistoallowtheworkerthreadtopostamessagetothequeueofthemainthreadthatsomethingishappeningandthatthemainthreadshouldlookatitwhenitgetstothatmessage.ItisalsoanerrortocallUImethodsonanon-UIthread.So,youwillneedthis
handlerthatistiedtothemainthreadtocallUImethodsfromaworkerthread.
Areasonablesolutionthatinvolvesaworkerthreadisasfollows:
1. Createahandlerinthemainthreadwhilerespondingtothemenuitem.Keepitaside.
2. Createaseparatethread(aworkerthread)thatdoestheactualwork.Passthehandlerfromstep1totheworkerthread.Thishandlerallowstheworkerthreadtocommunicatewiththemainthread.
3. Theworkerthreadcodecannowdotheactualworkforlongerthanfivesecondsand,whiledoingit,cancallthehandlertosendstatusmessagestocommunicatewiththemainthread.
4. Thesestatusmessagesnowgetprocessedbythemainthread,becausethehandlerbelongedtothemainthread.Themainthreadcanprocessthesemessageswhiletheworkerthreadisdoingitswork.
Youcanseethesamplecodeforthisinteractioninthedownloadableprojectforthischapter.AnalternateandprobablymorestraightforwardwaytocommunicatewiththeUIthreadfromaworkerthreadistogetholdoftheactivitypointerandcallthemethodActivity.runOnUiThread(Runnableaction).OfcourseyouneedtocreateaRunnableobjectforcoordination.
ReferencesHerearesomeusefullinkstofurtherstrengthenyourunderstandingofthischapter:
http://developer.android.com/guide/publishing/app-signing.html:Amust-readforsigning.apkfiles.
http://developer.android.com/guide/developing/projects/projects-eclipse.html:PrimarySDKreferenceforAndroidlibraries.
http://developer.android.com/guide/topics/fundamentals.htmlSDKreferenceonAndroidcomponentlifecycles.
http://www.androidbook.com/item/3493:Alayman’sintroductiontowhatitmeanstosigndigitally.
http://www.androidbook.com/item/3279:OurresearchonunderstandingAndroidpackages.Youwillseehowtosign.apkfiles,furtherlinkstohowtosharedatabetweenpackages,moreonshareduserIDs,andinstructionstoinstallanduninstallpackages.
http://www.androidbook.com/item/3908:OurresearchnotesonallaspectsofAndroidlibrarysupport,includingolderscreenshots,newerscreenshots,usefulURLs,samplecode,and
more.
http://android-developers.blogspot.com/2011/10/changes-to-library-projects-in-android.html:WhathaschangedinlibrariesatthetimeofAndroid4.0andthereasonsforthechange?Thisblogalsotalksaboutfuturedirectionsforworkingwithlibraries.
http://tools.android.com/tips/non-constant-fields:Insightfuldiscussionoftheroleofnon-finalvariablesandhowtheyaffectswitchstatements.
http://tools.android.com/knownissues:AndroiddocumentationofknownissuesintheSDKToolsandtheADTreleases.AlsonotethedomainnameofthisURL;thissiteisdedicatedtoallaspectsofAndroidtooling.
http://docs.oracle.com/javase/7/docs/technotes/tools/windows/keytool.htmlExcellentdocumentationonkeytool,jarsigner,andthesigningprocessitself.
http://www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforafilecalledProAndroid5_Ch13_TestAndroidLibraries.zip.ThisZIPfilecontainstwoprojects:onealibraryandtheotherthatusesthislibrary.AlsotakealookataprojectcalledProAndroid5_Ch13_TestHandlers.zip,whichcontainsthecodetoworkwithhandlersincludingworkerthreads.
SummaryThischaptergaveyouaquickrun-downonhowpackages,processes,components,andthreadsinteractinanAndroidapplication.Thischapterhasalsodocumentedthelibrarysupportforsharingassetsbetweenmultipleapplications.Thischapteralsohasintroducedhandlers,akeyconceptintheAndroidSDK.Inthenextchapter,wewillgivedetailedcoverageofAsyncTask,whichcombinestheworkerthreadsandhandlersintoasimplerprogrammingabstractiontouse.
Chapter14
BuildingandConsumingServicesTheAndroidplatformprovidesacompletesoftwarestack.Thismeansyougetanoperatingsystemandmiddleware,aswellasworkingapplications(suchasaphonedialer).Alongsideallofthis,youhaveanSDKthatyoucanusetowriteapplicationsfortheplatform.Thusfar,we’veseenthatwecanbuildapplicationsthatdirectlyinteractwiththeuserthroughauserinterface.Wehavenot,however,discussedbackgroundservicesorthepossibilitiesofbuildingcomponentsthatruninthebackground.
Inthischapter,wearegoingtofocusonbuildingandconsumingservicesinAndroid.Firstwe’lldiscussconsumingHTTPservices,andthenwe’llcoveranicewaytodosimplebackgroundtasks,andfinallywe’lldiscussinterprocesscommunication—thatis,communicationbetweenapplicationsonthesamedevice.
ConsumingHTTPServicesAndroidapplicationsandmobileapplicationsingeneralaresmallappswithalotoffunctionality.Oneofthewaysthatmobileappsdeliversuchrichfunctionalityonsuchasmalldeviceisthattheypullinformationfromvarioussources.Forexample,mostAndroidsmartphonescomewiththeMapsapplication,whichprovidessophisticatedmappingfunctionality.We,however,knowthattheapplicationisintegratedwiththeGoogleMapsAPIandotherservices,whichprovidemostofthesophistication.
Thatsaid,itislikelythattheapplicationsyouwritewillalsoleverageinformationfromotherapplicationsandAPIs.AcommonintegrationstrategyistouseHTTP.Forexample,youmighthaveaJavaservletavailableontheInternetthatprovidesservicesyouwanttoleveragefromoneofyourAndroidapplications.HowdoyoudothatwithAndroid?Interestingly,theAndroidSDKshipswithavariationofApache’sHttpClient(http://hc.apache.org/httpcomponents-client-ga/),whichisuniversallyused.TheAndroidversionhasbeenmodifiedforAndroid,buttheAPIsareverysimilartotheAPIsintheApacheversion.
TheApacheHttpClientisacomprehensiveHTTPclient.ItoffersfullsupportfortheHTTPprotocol.Inthissection,wewilldiscussusingtheHttpClienttomakeHTTPGETandHTTPPOSTcalls.IfyouareworkingwithRESTfulservices,youwouldprobablyusetheotherHTTPoperationsaswell(PUT,DELETE,etc.).
UsingtheHttpClientforHTTPGETRequestsHere’soneofthegeneralpatternsforusingtheHttpClient:
1. CreateanHttpClient(orgetanexistingreference).
2. InstantiateanewHTTPmethod,suchasPostMethodorGetMethod.
3. SetHTTPparameternames/values.
4. ExecutetheHTTPcallusingtheHttpClient.
5. ProcesstheHTTPresponse.
Listing14-1showshowtoexecuteanHTTPGETusingtheHttpClient.
NoteWegiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.Also,becausethecodeattemptstousetheInternet,youwillneedtoaddandroid.permission.INTERNETtoyourmanifestfilewhenmakingHTTPcallsusingHttpClient.
Alsonotethatinthefollowingexamples,allwebservicescallsshouldbeputintobackgroundthreadssoasnottoblockthemainUIthread.Seelaterinthischapter,aswellasChapter15,foranexcellentdeep-diveonhowtodothat.Forthepurposesofthischapter,thosedetailsareexcludedtohelpwithunderstandingservices.
Listing14-1.UsingHttpClientandHttpGet:HttpGetDemo.java
publicclassHttpGetDemoextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
BufferedReaderin=null;try{
HttpClientclient=newDefaultHttpClient();HttpGetrequest=newHttpGet("http://code.google.com/android/");HttpResponseresponse=client.execute(request);
in=newBufferedReader(newInputStreamReader(response.getEntity().getContent()));
StringBuffersb=newStringBuffer("");Stringline="";StringNL=System.getProperty("line.separator");while((line=in.readLine())!=null){sb.append(line+NL);
}in.close();
Stringpage=sb.toString();System.out.println(page);}catch(Exceptione){e.printStackTrace();}finally{if(in!=null){try{in.close();}catch(IOExceptione){e.printStackTrace();}}}}}
TheHttpClientisabletoconsumethevariousHTTPrequesttypes,suchasHttpGet,HttpPost,andsoon.Listing14-1usestheHttpClienttogetthecontentsofthehttp://code.google.com/android/URL.TheactualHTTPrequestisexecutedwiththecalltoclient.execute().Afterexecutingtherequest,thecodereadstheentireresponseintoastringobject.NotethattheBufferedReaderisclosedinthefinallyblock,whichalsoclosestheunderlyingHTTPconnection.
ForourexampleweembeddedtheHTTPlogicinsideofanactivity,butwedon’tneedtobewithinthecontextofanactivitytouseHttpClient.YoucanuseitfromwithinthecontextofanyAndroidcomponentoruseitaspartofastand-aloneclass.Infact,youshouldn’tuseHttpClientdirectlywithinanactivity,becauseawebcallcouldtakeawhiletocompleteandcauseanApplicationNotResponding(ANR)pop-up.We’llcoverthattopiclaterinthischapter.Fornowwe’regoingtocheatalittlesowecanfocusonhowtomakeHttpClientcalls.
ThecodeinListing14-1executesanHTTPrequestwithoutpassinganyHTTPparameterstotheserver.Youcanpassname/valueparametersaspartoftherequestbyappendingname/valuepairstotheURL,asshowninListing14-2.
Listing14-2.AddingParameterstoanHTTPGETRequest
HttpGetrequest=newHttpGet("http://somehost/Upload.aspx?one=value1&two=value2");client.execute(request);
WhenyouexecuteanHTTPGET,theparameters(namesandvalues)oftherequestarepassedaspartoftheURL.Passingparametersthiswayhassomelimitations.Namely,thelengthofaURLshouldbekeptbelow2,048characters.Ifyouhavemorethanthisamount
ofdatatosubmit,youshoulduseHTTPPOSTinstead.ThePOSTmethodismoreflexibleandpassesparametersaspartoftherequestbody.
UsingtheHttpClientforHTTPPOSTRequests(aMultipartExample)MakinganHTTPPOSTcallisverysimilartomakinganHTTPGETcall(seeListing14-3).ThisexampleiscalledSimpleHTTPPost.
Listing14-3.MakinganHTTPPOSTRequestwiththeHttpClient
HttpClientclient=newDefaultHttpClient();HttpPostrequest=newHttpPost("http://www.androidbook.com/akc/display");List<NameValuePair>postParameters=newArrayList<NameValuePair>();postParameters.add(newBasicNameValuePair("url","DisplayNoteIMPURL"));postParameters.add(newBasicNameValuePair("reportId","4788"));postParameters.add(newBasicNameValuePair("ownerUserId","android"));postParameters.add(newBasicNameValuePair("aspire_output_format","embedded-xml"));UrlEncodedFormEntityformEntity=newUrlEncodedFormEntity(postParameters);request.setEntity(formEntity);HttpResponseresponse=client.execute(request);
ThecodeinListing14-3wouldreplacethethreelinesinListing14-1wheretheHttpGetisused.Everythingelsecouldstaythesame.TomakeanHTTPPOSTcallwiththeHttpClient,youhavetocalltheexecute()methodoftheHttpClientwithaninstanceofHttpPost.WhenmakingHTTPPOSTcalls,yougenerallypassURL-encodedname/valueformparametersaspartoftheHTTPrequest.TodothiswiththeHttpClient,youhavetocreatealistthatcontainsinstancesofNameValuePairobjectsandthenwrapthatlistwithaUrlEncodedFormEntityobject.TheNameValuePairwrapsaname/valuecombination,andtheUrlEncodedFormEntityclassknowshowtoencodealistofNameValuePairobjectssuitableforHTTPcalls(generallyPOSTcalls).AfteryoucreateaUrlEncodedFormEntity,youcansettheentitytypeoftheHttpPosttotheUrlEncodedFormEntityandthenexecutetherequest.
InListing14-3,wecreatedanHttpClientandtheninstantiatedtheHttpPostwiththeURLoftheHTTPendpoint.Next,wecreatedalistofNameValuePairobjectsandpopulateditwithseveralname/valueparameters.WethencreatedaUrlEncodedFormEntityinstance,passingthelistofNameValuePairobjectsto
itsconstructor.Finally,wecalledthesetEntity()methodofthePOSTrequestandthenexecutedtherequestusingtheHttpClientinstance.
HTTPPOSTisactuallymuchmorepowerfulthanthis.WithanHTTPPOST,wecanpasssimplename/valueparameters,asshowninListing14-3,aswellascomplexparameterssuchasfiles.HTTPPOSTsupportsanotherrequest-bodyformatknownasamultipartPOST.WiththistypeofPOST,youcansendname/valueparametersasbefore,alongwitharbitraryfiles.Unfortunately,theversionofHttpClientshippedwithAndroiddoesnotdirectlysupportmultipartPOST.Toachievethisgoalinthepast,we’verecommendedthatyougrabthreeotherlibraries:ApacheCommonsIO,Mime4j,andHttpMime.
NowwerecommendthatyoudownloadtheIonlibrary,whichhastwodependencies.Allthreejarfilescanbefoundatthesetwosites:
https://github.com/koush/ion#jars(ionandandroidasync)
https://code.google.com/p/google-gson/downloads/list(gson)
Listing14-4demonstratesamultipartPOSTusingAndroid.ThisexampleiscalledMultipartHTTPPost.
Listing14-4.MakingaMultipartPOSTCall
publicclassTestMultipartPostextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
try{Ion.with(this,"http://www.androidbook.com/akc/update/PublicUploadTest").setMultipartParameter("field1","Thisisfieldnumber1").setMultipartParameter("field2","Field2isshorter").setMultipartFile("datafile",newFile(Environment.getExternalStorageDirectory()+"/testfile.txt")).asString().setCallback(newFutureCallback<String>(){@OverridepublicvoidonCompleted(Exceptione,Stringresult){System.out.println(result);}});
}catch(Exceptione){//DosomethingaboutexceptionsSystem.out.println("Gotexception:"+e);}}}
NoteThemultipartexampleusesseveral.jarfilesthatarenotincludedaspartoftheAndroidruntime.Toensurethatthe.jarfileswillbepackagedaspartofyour.apkfile,youneedtoaddthemasexternal.jarfilesinEclipse.Todothis,right-clickyourprojectinEclipse,selectProperties,chooseJavaBuildPath,selecttheLibrariestab,andthenselectAddExternalJARs.
Followingthesestepswillmakethe.jarfilesavailableduringcompiletimeaswellasruntime.
ToexecuteamultipartPOSTusingtheIonlibrary,yousimplyputtogethertheappropriatecallstobuildaURL,addparameters,definethereturntype,andsetupacallbackmethod.ThiswillrunasynchronouslyandthecallbackwillbeinvokedontheUIthreadoncearesponseisreceivedfromthewebserver.Intheexample,theresultstringiswrittentoLogCat.YourapplicationisprobablygoingtoreceivebackaJsonObjectwhichthecallbackwouldthenprocess.ButrealizethattheresponsefromthewebserverhasalreadybeenconvertedintoaJsonObjectforyou,makingtheprocessinginthecallbackthatmucheasier.Listing14-4addsthreepartstotherequest:twostringpartsandatextfile.Torunthisexampleyourselfyouwillneedtoputatestfile.txtfileontotheexternalstorageareaofyourdeviceoremulator.
Finally,ifyouarebuildinganapplicationthatrequiresyoutopassamultipartPOSTtoawebresource,you’lllikelyhavetodebugthesolutionusingadummyimplementationoftheserviceonyourlocalworkstation.Whenyou’rerunningapplicationsonyourlocalworkstation,normallyyoucanaccessthelocalmachinebyusinglocalhostorIPaddress127.0.0.1.WithAndroidapplications,however,youwillnotbeabletouselocalhost(or127.0.0.1)becausethedeviceoremulatorwillbeitsownlocalhost.Youdon’twanttopointthisclienttoaserviceontheAndroiddevice;youwanttopointtoyourworkstation.Torefertoyourdevelopmentworkstationfromtheapplicationrunninginthedeviceoremulator,you’llhavetouseyourworkstation’sIPaddressintheURL.
SOAP,JSON,andXMLParsersWhataboutSOAP?TherearelotsofSOAP-basedwebservicesontheInternet,buttodate,GooglehasnotprovideddirectsupportinAndroidforcallingSOAPwebservices.GoogleinsteadprefersREST-likewebservices,seeminglytoreducetheamountofcomputingrequiredontheclientdevice.However,thetradeoffisthatthedevelopermustdomoreworktosenddataandtoparsethereturneddata.Ideally,youwillhavesome
optionsforhowyoucaninteractwithyourwebservices.SomedevelopershaveusedthekSOAP2developerkittobuildSOAPclientsforAndroid.Wewon’tbecoveringthatapproach,butit’soutthereifyou’reinterested.
NoteTheoriginalkSOAP2sourceislocatedhere:http://ksoap2.sourceforge.net/.Theopensourcecommunityhas(thankfully!)contributedaversionofkSOAP2forAndroid,andyoucanfindoutmoreaboutithere:http://code.google.com/p/ksoap2-android/.
Oneapproachthat’sbeenusedsuccessfullyistoimplementyourownservicesontheInternet,whichcantalkSOAP(orwhatever)tothedestinationservice.ThenyourAndroidapplicationonlyneedstotalktoyourservices,andyounowhavecompletecontrol.Ifthedestinationserviceschange,youmightbeabletohandlethatwithouthavingtoupdateandreleaseanewversionofyourapplication.You’donlyhavetoupdatetheservicesonyourserver.Asidebenefitofthisapproachisthatyoucouldmoreeasilyimplementapaidsubscriptionmodelforyourapplication.Ifauserletstheirsubscriptionlapse,youcanturnthemoffatyourserver.
AndroiddoeshavesupportforJavaScriptObjectNotation(JSON).Thisisafairlycommonmethodofpackagingdatabetweenawebserverandaclient.TheJSONparsingclassesmakeitveryeasytounpackdatafromaresponsesoyourapplicationcanactonit.OrdigdeeperintotheGsonpackagereferencedearlierinthischapter.GsonisaJSONJavalibraryfromGoogle,anditsmainbenefitishoweasyitistoparseJSONinputintoJavaobjects,andviceversa.It’salsoveryfast.
AndroidalsohasacoupleofXMLparsersthatyoucanusetointerprettheresponsesfromtheHTTPcalls;therecommendedoneisXMLPullParser.
DealingwithExceptionsDealingwithexceptionsispartofanyprogram,butsoftwarethatmakesuseofexternalservices(suchasHTTPservices)mustpayadditionalattentiontoexceptionsbecausethepotentialforerrorsismagnified.ThereareseveraltypesofexceptionsthatyoucanexpectwhilemakinguseofHTTPservices.Thesearetransportexceptions,protocolexceptions,andtimeouts.Youshouldunderstandwhentheseexceptionscouldoccur.
Transportexceptionscanoccurduetoanumberofreasons,butthemostlikelyscenariowithamobiledeviceispoornetworkconnectivity.Protocolexceptions(e.g.,ClientProtocolException)areexceptionsattheHTTPprotocollayer.Theseincludeauthenticationerrors,invalidcookies,andsoon.Youcanexpecttoseeprotocolexceptionsif,forexample,youhavetosupplylogincredentialsaspartofyourHTTPrequestbutfailtodoso.Timeouts,withrespecttoHTTPcalls,comeintwoflavors:connectiontimeoutsandsockettimeouts.Aconnectiontimeout(e.g.,ConnectTimeoutException)canoccuriftheHttpClientisnotabletoconnecttotheHTTPserver—if,forexample,theserverisnotavailable.Asockettimeout(e.g.,SocketTimeoutException)canoccuriftheHttpClientfailstoreceivearesponse
withinadefinedtimeperiod.Inotherwords,theHttpClientwasabletoconnecttotheserver,buttheserverfailedtoreturnaresponsewithintheallocatedtimelimit.
Nowthatyouunderstandthetypesofexceptionsthatmightoccur,howdoyoudealwiththem?Fortunately,theHttpClientisarobustframeworkthattakesmostoftheburdenoffyourshoulders.Infact,theonlyexceptiontypesthatyou’llhavetoworryaboutaretheonesthatyou’llbeabletomanageeasily.TheHttpClienttakescareoftransportexceptionsbydetectingtransportissuesandretryingrequests(whichworksverywellwiththistypeofexception).Protocolexceptionsareexceptionsthatcangenerallybeflushedoutduringdevelopment.Timeoutsarethemostlikelyexceptionsthatyou’llhavetodealwith.Asimpleandeffectiveapproachtodealingwithbothtypesoftimeouts—connectiontimeoutsandsockettimeouts—istowraptheexecute()methodofyourHTTPrequestwithatry/catchandthenretryifafailureoccurs.
WhenusingtheHttpClientaspartofareal-worldapplication,youneedtopaysomeattentiontomultithreadingissuesthatmightcomeup.Let’sdelveintothesenow.
AddressingMultithreadingIssuesTheexampleswe’veshownsofarcreatedanewHttpClientforeachrequest.Inpractice,however,youcouldcreateoneHttpClientfortheentireapplicationandusethatforallofyourHTTPcommunication.It’spossibletoassociateaconnectionpoolwiththisHttpClient,whichyou’llnowsee.WithoneHttpClientservicingallofyourHTTPrequests,youshouldpayattentiontomultithreadingissuesthatcouldsurfaceifyoumakesimultaneousrequeststhroughthesameHttpClient.Fortunately,theHttpClientprovidesfacilitiesthatmakethiseasy—allyouhavetodoiscreatetheDefaultHttpClientusingaThreadSafeClientConnManager,asshowninListing14-5.ThisexampleprojectisHttpSingleton.
Listing14-5.CreatinganHttpClientforMultithreading:CustomHttpClient.java
publicclassCustomHttpClient{privatestaticHttpClientcustomHttpClient;
/**AprivateConstructorpreventsinstantiation*/privateCustomHttpClient(){}
publicstaticsynchronizedHttpClientgetHttpClient(){if(customHttpClient==null){HttpParamsparams=newBasicHttpParams();HttpProtocolParams.setVersion(params,HttpVersion.HTTP_1_1);HttpProtocolParams.setContentCharset(params,HTTP.DEFAULT_CONTENT_CHARSET);HttpProtocolParams.setUseExpectContinue(params,
true);HttpProtocolParams.setUserAgent(params,System.getProperty("http.agent")//Couldalsohaveusedthefollowingwhichisbrowser-orientedasopposedto//device-oriented://newWebView(getApplicationContext()).getSettings().getUserAgentString());
ConnManagerParams.setTimeout(params,1000);
HttpConnectionParams.setConnectionTimeout(params,5000);HttpConnectionParams.setSoTimeout(params,10000);
SchemeRegistryschReg=newSchemeRegistry();schReg.register(newScheme("http",PlainSocketFactory.getSocketFactory(),80));schReg.register(newScheme("https",SSLSocketFactory.getSocketFactory(),443));ClientConnectionManagerconMgr=newThreadSafeClientConnManager(params,schReg);
customHttpClient=newDefaultHttpClient(conMgr,params);}returncustomHttpClient;}
publicObjectclone()throwsCloneNotSupportedException{thrownewCloneNotSupportedException();}}
IfyourapplicationneedstomakemorethanafewHTTPcalls,youshouldcreateanHttpClientthatservicesallyourHTTPrequests.Thesimplestwaytodothisistocreateasingletonclassthatcanbeaccessedfromanywhereintheapplication,aswe’veshownhere.ThisisafairlystandardJavapatterninwhichwesynchronizeaccesstoagettermethod,andthatgettermethodreturnstheoneandonlyHttpClientobjectforthesingleton,creatingitthefirsttimeasnecessary.
Now,takealookatthegetHttpClient()methodofCustomHttpClient.ThismethodisresponsibleforcreatingoursingletonHttpClient.Wesetsomebasicparameters,sometimeoutvalues,andtheschemesthatourHttpClientwillsupport
(thatis,HTTPandHTTPS).NoticethatwhenweinstantiatetheDefaultHttpClient(),wepassinaClientConnectionManager.TheClientConnectionManagerisresponsibleformanagingHTTPconnectionsfortheHttpClient.BecausewewanttouseasingleHttpClientforalltheHTTPrequests(requeststhatcouldoverlapifwe’reusingthreads),wecreateaThreadSafeClientConnManager.
WealsoshowyouasimplerwayofcollectingtheresponsefromtheHTTPrequest,usingaBasicResponseHandler.ThecodeforouractivitythatusesourCustomHttpClientisinListing14-6.
Listing14-6.UsingOurCustomHttpClient:HttpActivity.java
publicclassHttpActivityextendsActivity{privateHttpClienthttpClient;@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
httpClient=CustomHttpClient.getHttpClient();getHttpContent();}
publicvoidgetHttpContent(){try{HttpGetrequest=newHttpGet("http://www.google.com/");Stringpage=httpClient.execute(request,newBasicResponseHandler());System.out.println(page);}catch(IOExceptione){//covers://ClientProtocolException//ConnectTimeoutException//ConnectionPoolTimeoutException//SocketTimeoutExceptione.printStackTrace();}}}
Forthissampleapplication,wedoasimpleHTTPgetoftheGooglehomepage.WealsouseaBasicResponseHandlerobjecttotakecareofrenderingthepageasabig
String,whichwethenwriteouttoLogCat.Asyoucansee,addingaBasicResponseHandlertotheexecute()methodisveryeasytodo.
YoumaybetemptedtotakeadvantageofthefactthateachAndroidapplicationhasanassociatedApplicationobject.Bydefault,ifyoudon’tdefineacustomapplicationobject,Androidusesandroid.app.Application.Here’stheinterestingthingabouttheapplicationobject:therewillalwaysbeexactlyoneapplicationobjectforyourapplication,andallofyourcomponentscanaccessit(usingtheglobalcontextobject).ItispossibletoextendtheApplicationclassandaddfunctionalitysuchasourCustomHttpClient.However,inourcasethereisreallynoreasontodothiswithintheApplicationclassitself,andyouwillbemuchbetteroffnotmessingwiththeApplicationclasswhenyoucansimplycreateaseparatesingletonclasstohandlethistypeofneed.
FunwithTimeoutsThereareotherterrificadvantagestosettingupasingleHttpClientforourapplicationtouse.Wecanmodifythepropertiesofitinoneplace,andeveryonecantakeadvantageofit.Forexample,ifwewanttosetupcommontimeoutvaluesforourHTTPcalls,wecandothatwhencreatingourHttpClientbycallingtheappropriatesetterfunctionsagainstourHttpParamsobject.PleaserefertoListing14-5andthegetHttpClient()method.Noticethattherearethreetimeoutswecanplaywith.Thefirstisatimeoutfortheconnectionmanager,anditdefineshowlongweshouldwaittogetaconnectionoutoftheconnectionpoolmanagedbytheconnectionmanager.Inourexample,wesetthisto1second.Abouttheonlytimewemighteverhavetowaitisifallconnectionsfromthepoolareinuse.Thesecondtimeoutvaluedefineshowlongweshouldwaittomakeaconnectionoverthenetworktotheserverontheotherend.Here,weusedavalueof2seconds.Andlastly,wesetasockettimeoutvalueto4secondstodefinehowlongweshouldwaittogetdatabackforourrequest.
Correspondingtothethreetimeoutsdescribedpreviously,wecouldgetthesethreeexceptions:ConnectionPoolTimeoutException,ConnectTimeoutException,orSocketTimeoutException.AllthreeoftheseexceptionsaresubclassesofIOException,whichwe’veusedinourHttpActivityinsteadofcatchingeachsubclassexceptionseparately.
Ifyouinvestigateeachoftheparameter-settingclassesthatweusedingetHttpClient(),youmightdiscoverevenmoreparametersthatyouwouldfinduseful.
We’vedescribedforyouhowtosetupanHttpClientwithapoolofconnectionsforuseacrossyourapplication.Andtheimplicationisthateverytimeyouneedtouseaconnection,thevarioussettingswillapplytoyourparticularneeds.Butwhatifyouwantdifferentsettingsforaparticularmessage?Thankfully,there’saneasywaytodothataswell.WeshowedyouhowtouseanHttpGetoranHttpPostobjecttodescribetherequesttobemadeacrossthenetwork.InasimilarwaytohowwesetHttpParamsonourHttpClient,youcansetHttpParamsonbothHttpGetandHttpPost
objects.ThesettingsyouapplyatthemessagelevelwilloverridethesettingsattheHttpClientlevelwithoutchangingtheHttpClientsettings.Listing14-7showswhatthismightlooklikeifwewantedtohaveasockettimeoutof1minuteinsteadof4secondsforoneparticularrequest.YouwouldusetheselinesinplaceofthelinesinthetryblockofgetHttpContent()inListing14-6.
Listing14-7.OverridingtheSocketTimeoutattheRequestLevel
HttpGetrequest=newHttpGet("http://www.google.com/");HttpParamsparams=request.getParams();HttpConnectionParams.setSoTimeout(params,60000);//1minuterequest.setParams(params);Stringpage=httpClient.execute(request,newBasicResponseHandler());System.out.println(page);
UsingtheHttpURLConnectionAndroidprovidesanotherwaytodealwithHTTPservices,andthatisusingthejava.net.HttpURLConnectionclass.ThisisnotunliketheHttpClientclasseswe’vejustcovered,butHttpURLConnectiontendstorequiremorestatementstogetthingsdone.HttpURLConnectionisalsonotthreadsafe.Ontheotherhand,thisclassismuchsmallerandlightweightthanHttpClient,soyoucansimplycreatetheonesyouneed.StartingwiththeGingerbreadrelease,itisalsofairlystable,soyoushouldconsideritforappsonmorerecentdeviceswhenyoujustneedbasicHTTPfeaturesandyouwantacompactapplication.
UsingtheAndroidHttpClientAndroid2.2introducedanewsubclassofHttpClientcalledAndroidHttpClient.TheideabehindthisclassistomakethingseasierforthedeveloperofAndroidappsbyprovidingdefaultvaluesandlogicappropriateforAndroidapps.Forexample,thetimeoutvaluesfortheconnectionandthesocket(thatis,operation)defaultto20secondseach.TheconnectionmanagerdefaultstotheThreadSafeClientConnManager.Forthemostpart,itisinterchangeablewiththeHttpClientweusedinthepreviousexamples.Thereareafewdifferences,though,thatyoushouldbeawareof:
TocreateanAndroidHttpClient,youinvokethestaticnewInstance()methodoftheAndroidHttpClientclass,likethis:
AndroidHttpClienthttpClient=AndroidHttpClient.newInstance("my-http-agent-string");
NoticethattheparametertothenewInstance()methodisanHTTPagentstring.Youmostlikelydon’twanttohardcodethis,soyouhavetwooptionsasfollows,whichunfortunatelycanreturndifferentstrings.Thesecondoneisprobablytheoneyouwanttouseasitlooksmorelikewhatabrowserwouldsend(atleastinourexperiments).
//Thefirstoptionisadevice-levelagentstringStringhttpAgent=System.getProperty("http.agent");//Thissecondoptionlookslikeabrowser’sagentstringhttpAgent=newWebView(context).getSettings().getUserAgentString();
Ofcourse,youarealsofreetobuildyourownagentstringusinganythingavailabletoyourapp;it’stheserverthat’sgoingtoparseittobetterunderstandthedevice,andifyoucontroltheserver,youcanusewhatevervaluesyousentfromtheapp.
Whenexecute()iscalledonthisclient,youmustbeinathreadseparatefromthemainUIthread.Thismeansthatyou’llgetanexceptionifyousimplyattempttoreplaceourpreviousHttpClientwithanAndroidHttpClient.ItisbadpracticetomakeHTTPcallsfromthemainUIthread,soAndroidHttpClientwon’tletyou.We’llbecoveringthreadingissuesinthenextsection.
Youmustcallclose()ontheAndroidHttpClientinstancewhenyouaredonewithit.Thisissomemorycanbefreedupproperly.
Therearesomehandystaticmethodsfordealingwithcompressedresponsesfromaserver,including
modifyRequestToAcceptGzipResponse(HttpRequestrequest)
getCompressedEntity(byte[]data,ContentResolverresolver)
getUngzippedContent(HttpEntityentity)
Onceyou’veacquiredaninstanceoftheAndroidHttpClient,youcannotmodifyanyparametersettingsinit,norcanyouaddanyparametersettingstoit(suchastheHTTPprotocolversion,forexample).YouroptionsaretooverridesettingswithintheHttpGet
objectasshownpreviouslyortonotusetheAndroidHttpClient.
ThisconcludesourdiscussionofusingHTTPserviceswiththeHttpClient.ForagreattutorialonusingHttpClientandtheseotherconcepts,pleasecheckouttheApachesiteathttp://hc.apache.org/httpcomponents-client-ga/tutorial/html/.
We’veshownyouhowtooperatewithHTTP-basedservices.Butwhatifwewantedtorunsomebackgroundprocessingthatlastedlongerthanashortwhile,orwhatifwewantedtoinvokesomenon-UIfunctionalitythatexistsinanotherAndroidapplication?Fortheseneeds,Androidprovidesservices.Wewilldiscussthemnext.
UsingAndroidServicesAndroidsupportstheconceptofservices.Servicesarecomponentsthatruninthebackground,withoutauserinterface.YoucanthinkofthesecomponentsassimilartoWindowsservicesorUnixdaemons.Similartothesetypesofservices,Androidservicescanbealwaysavailablebutdon’thavetobeactivelydoingsomething.Moreimportant,Androidservicescanhavelifecyclesseparatefromactivities.Whenanactivitypauses,stops,orgetsdestroyed,theremaybesomeprocessingthatyouwanttocontinue.Servicesaregoodforthattoo.
Androidsupportstwotypesofservices:localservicesandremoteservices.Alocalserviceisaservicethatisonlyaccessibletotheapplicationthatishostingit,anditisnotaccessiblefromotherapplicationsrunningonthedevice.Generally,thesetypesofservicessimplysupporttheapplicationthatishostingtheservice.Aremoteserviceisaccessiblefromotherapplicationsonthedeviceinadditiontotheapplicationhostingtheservice.RemoteservicesdefinethemselvestoclientsusingAndroidInterfaceDefinitionLanguage(AIDL).We’regoingtotalkaboutbothofthesetypesofservices,althoughinthenextfewchapters,we’regoingdeepintolocalservices.Therefore,wewillintroducethemherebutnotspendthatmuchtimeonthem.We’llcoverremoteservicesinmoredetailinthischapter.
UnderstandingServicesinAndroidTheAndroidServiceclassisawrapperofsortsforcodethathasservice-likebehavior.However,aServiceobjectdoesnotcreateitsownthreadsautomatically.ForaServiceobjecttousethreads,thedevelopermustmakeithappen.Thismeansthatwithoutaddingthreadingtoaservice,thecodeoftheservicewillrunonthemainthread.Ifourserviceisperformingoperationsthatwillcompletequickly,thiswon’tbeaproblem.Ifourservicemightrunforawhile,wedefinitelywanttoinvolvethreading.KeepinmindthereisnothingwrongwithusingAsyncTaskstodothreadingwithinservices.
Androidsupportstheconceptofaservicefortworeasons:
First,toallowyoutoimplementbackgroundtaskseasily.
Second,toallowyoutodointerprocesscommunicationbetweenapplicationsrunningonthesamedevice.
ThesetworeasonscorrespondtothetwotypesofservicesthatAndroidsupports:localservicesandremoteservices.Anexampleofthefirstcasemightbealocalserviceimplementedaspartofane-mailapplication.Theservicecouldhandlethesendingofanewe-mailtothee-mailserver,completewithattachmentsandretries.Asthiscouldtakeawhiletocomplete,aserviceisanicewayofwrappingupthatfunctionalitysothemainthreadcankickitoffandgetbacktotheuser.Plus,ifthee-mailactivitygoesaway,youstillwantthesente-mailstobedelivered.Anexampleofthesecondcase,aswe’llseelater,isalanguagetranslationapplication.Supposeyouhaveseveralapplicationsrunningonadevice,andyouneedaservicetoaccepttextthatneedstobetranslatedfromonelanguagetoanother.Ratherthanrepeatthelogicineveryapplication,youcouldwritearemotetranslationserviceandhavetheapplicationstalktotheservice.
AlocalservicegetsinitializedeitherbyaclientbindingtoitusingbindService(),orbyaclientstartingitusingstartService().RemoteservicesaretypicallyalwaysinitializedwithbindService().Aboundservicegetsinstantiatedwhenthefirstclientbindstoit,anddestroyedwhenthelastclientunbindsfromit.Asclientscomeinandoutoftheforeground,theycanbindandunbindasneeded,toensurethattheserviceisn’trunningunnecessarily.Thishelpstopreservebatterylife.However,itisunwisetobindinonResume()andunbindinonPause()becausethatcouldcausealotofunnecessarystartingandstoppingoftheservice.It’sbettertobindandunbindinonCreate()andonDestroy(),orinonStart()andonStop().BindingisonlyallowedfromanApplicationContext,anActivity,anotherService,oraContentProvider.ThatmeansnotfromFragmentsandnotfromBroadcastReceivers.
WhenaserviceisinsteadstartedwithstartService(),itwillremainrunninguntilitisstopped,eitherbyaclientorbytellingitselftostop.Foralocalservicethatwantstoperformworkinthebackground,considerinstantiatingitwithstartService()soitcanremainrunningeveniftheactivitythatstarteditgoesaway.TechnicallyaBroadcastReceivercanstartaserviceusingstartService(),sincetheservicecanthencontinuetoexistoncetheshort-livedBroadcastReceiverterminates.Ifyoudocreateaservicethatwillruninthebackgroundevenwhentheactivitieshavegoneaway,youmaywanttoimplementonBind()forwhentheuserwantstoregaincontroloftheservice.Anewactivitycouldbindtotheexistingserviceandthencallservicemethodsonit.
Thereareexamplesoflocalservicesthatdonotcreatebackgroundthreads,butthismaynotbeveryusefulinpractice.Aservicedoesnotinherentlycreateanythreads,socodeofaservicewillbydefaultrunonthemainUIthread.Theremaynotbeanyrealadvantagetowrappingthiscodeinaservicethen,sinceyoucouldjustcallmethodsofaclasstoexecutethatlogic.Itismorecommonforalocalservicetohaveitsownthreadsofexecution,whichcanbestartedeitherwhenthefirstclientbindstoit,orbecauseofastartService()command.
Now,wecanbeginadetailedexaminationofthetwotypesofservices.Wewillstartbytalkingaboutlocalservicesandthendiscussremoteservices.Asmentionedbefore,localservicesareservicesthatarecalledonlybytheapplicationthathoststhem.Remote
servicesareservicesthatsupportaremoteprocedurecall(RPC)mechanism.Theseservicesallowexternalclients,onthesamedevice,toconnecttotheserviceanduseitsfacilities.Therearetwomainwaysofcallingremoteservices:usinganAIDLinterfaceandusingaMessenger.Bothwillbecovered.
NoteThesecondtypeofserviceinAndroidisknownbyseveralnames:remoteservice,AIDL-supportingservice,AIDLservice,externalservice,andRPCservice.Thesetermsallrefertothesametypeofservice—onethat’smeanttobeaccessedremotelybyotherapplicationsrunningonthedevice.
UnderstandingLocalServicesLocalservicesareservicesthataregenerallystartedviaContext.startService().Oncestarted,thesetypesofserviceswillcontinuetorununtilaclientcallsContext.stopService()ontheserviceortheserviceitselfcallsstopSelf().NotethatwhenContext.startService()iscalledandtheservicehasnotalreadybeencreated,thesystemwillinstantiatetheserviceandcalltheservice’sonStartCommand()method.KeepinmindthatcallingContext.startService()aftertheservicehasbeenstarted(thatis,whileitexists)willnotresultinanotherinstanceoftheservice,butwillreinvoketherunningservice’sonStartCommand()method.Hereareacoupleofexamplesoflocalservices:
Aservicetomonitorsensordatafromthedeviceanddoanalysis,issuingalertsifacertainconditionisrealized.Thisservicemightrunconstantly.
Atask-executorservicethatletsyourapplication’sactivitiessubmitjobsandqueuethemforprocessing.Thisservicemightonlyrunforthedurationoftheoperationtosubmitthejob.
Listing14-8demonstratesalocalservicebyimplementingaservicethatexecutesbackgroundtasks.We’llendupwithfourartifactsrequiredtocreateandconsumetheservice:BackgroundService.java(theserviceitself),main.xml(alayoutfilefortheactivity),MainActivity.java(anactivityclasstocalltheservice),andAndroidManifest.xml.Listing14-8onlycontainsBackgroundService.java.We’lldissectthiscodefirstandthenmoveontotheotherthree.
Listing14-8.ImplementingaLocalService:BackgroundService.java
publicclassBackgroundServiceextendsService{privatestaticfinalStringTAG="BackgroundService";privateNotificationManagernotificationMgr;privateThreadGroupmyThreads=newThreadGroup("ServiceWorker");
@Override
publicvoidonCreate(){super.onCreate();
Log.v(TAG,"inonCreate()");notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);displayNotificationMessage("BackgroundServiceisrunning");}
@OverridepublicintonStartCommand(Intentintent,intflags,intstartId){super.onStartCommand(intent,flags,startId);
intcounter=intent.getExtras().getInt("counter");Log.v(TAG,"inonStartCommand(),counter="+counter+",startId="+startId);
newThread(myThreads,newServiceWorker(counter),"BackgroundService").start();
returnSTART_STICKY;}
classServiceWorkerimplementsRunnable{privateintcounter=-1;publicServiceWorker(intcounter){this.counter=counter;}
publicvoidrun(){finalStringTAG2="ServiceWorker:"+Thread.currentThread().getId();//dobackgroundprocessinghere…we'lljustsleep…try{Log.v(TAG2,"sleepingfor10seconds.counter="+counter);Thread.sleep(10000);Log.v(TAG2,"...wakingup");}catch(InterruptedExceptione){
Log.v(TAG2,"...sleepinterrupted");}}}
@OverridepublicvoidonDestroy(){Log.v(TAG,"inonDestroy().Interruptingthreadsandcancellingnotifications");myThreads.interrupt();notificationMgr.cancelAll();super.onDestroy();}
@OverridepublicIBinderonBind(Intentintent){Log.v(TAG,"inonBind()");returnnull;}
privatevoiddisplayNotificationMessage(Stringmessage){
PendingIntentcontentIntent=PendingIntent.getActivity(this,0,newIntent(this,MainActivity.class),0);
Notificationnotification=newNotificationCompat.Builder(this).setContentTitle(message).setContentText("Touchtoturnoffservice").setSmallIcon(R.drawable.emo_im_winking).setTicker("Startingup!!!")//.setLargeIcon(aBitmap).setContentIntent(contentIntent).setOngoing(true).build();
notificationMgr.notify(0,notification);}}
ThestructureofaServiceobjectissomewhatsimilartoanactivity.ThereisanonCreate()methodwhereyoucandoinitialization,andanonDestroy()whereyoudocleanup.Servicesdon’tpauseorresumethewayactivitiesdo,sowedon’tuseonPause()oronResume()methods.Inthisexample,wewon’tbebindingtothe
localservice,butbecauseServicerequiresanimplementationoftheonBind()method,weprovideonethatsimplyreturnsnull.It’sworthmentioningthatyoucouldhavealocalservicethatimplementsonBind()anddoesnotuseonStartCommand().
GoingbacktoouronCreate()method,wedon’tneedtodomuchexcepttonotifytheuserthatthisservicehasbeencreated.WedothisusingtheNotificationManager.You’veprobablynoticedthenotificationbaratthetopleftofanAndroidscreen.Bypullingdownonthis,theusercanviewmessagesofimportance,andbytouchingnotificationscanactonthenotifications,whichusuallymeansreturningtosomeactivityrelatedtothenotification.Withservices,becausetheycanberunning,oratleastexisting,inthebackgroundwithoutavisibleactivity,therehastobesomewayfortheusertogetbackintouchwiththeservice,perhapstoturnitoff.Therefore,wecreateaNotificationobject,populateitwithaPendingIntent,whichwillgetusbacktoourcontrolactivity,andpostit.ThisallhappensinthedisplayNotificationMessage()method.NotethatourNotificationobjectneedstoexistaslongasourserviceexists,soweusesetOngoing(true)tokeepitinthenotificationslistuntilweclearitourselvesfromourservice’sonDestroy()method.ThemethodweusedinonDestroy()toclearournotificationiscancelAll()ontheNotificationManager.
There’sanotherthingyouneedtohaveforthisexampletowork.You’llneedtocreateadrawablenamedemo_im_winkingandplaceitwithinyourproject’sdrawablefolder.AgoodsourceofdrawablesforthisdemonstrationpurposeistolookundertheAndroidplatformfolderatAndroidSDK/platforms/<version>/data/res/drawable,where<version>istheversionyou’reinterestedin.Unfortunately,youcan’treliablyrefertoAndroidsystemdrawablesfromyourcode,soyou’llneedtocopywhatyouwantovertoyourproject’sdrawablesfolder.Ifyouchooseadifferentdrawablefileforyourexample,justgoaheadandrenametheresourceIDintheconstructorfortheNotification.
WhenanintentissentintoourserviceusingstartService(),onCreate()iscalledifnecessary,andouronStartCommand()methodiscalledtoreceivethecaller’sintent.Inourcase,we’renotgoingtodoanythingspecialwithit,excepttounpackthecounteranduseittostartabackgroundthread.Inareal-worldservice,wewouldexpectanydatatobepassedtousviatheintent,andthiscouldincludeURIs,forexample.NoticetheuseofaThreadGroupwhencreatingtheThread.Thiswillprovetobeusefullaterwhenwewanttogetridofourbackgroundthreads.AlsonoticethestartIdparameter.ThisissetforusbyAndroidandisauniqueidentifieroftheservicecallssincethisservicewasstarted.
OurServiceWorkerclassisatypicalrunnableandiswheretheworkhappensforourservice.Inourparticularcase,we’resimplyloggingsomemessagesandsleeping.We’realsocatchinganyinterruptionsandloggingthem.Onethingwe’renotdoingismanipulatingtheuserinterface.We’renotupdatinganyviewsforexample.Becausewe’renotonthemainthreadanymore,wecannottouchtheUIdirectly.TherearewaysforourServiceWorkertoeffectchangesintheuserinterface,andwe’llgetintothosedetailsinthenextfewchapters.
ThelastitemtopayattentiontoinourBackgroundServiceistheonDestroy()method.Thisiswhereweperformthecleanup.Forourexample,wewanttogetridofthethreadswecreatedearlier,ifanyarestillaround.Ifwedon’tdothis,theycouldsimplyhangaroundandtakeupmemory.Second,wewanttogetridofournotificationmessage.Becauseourserviceisgoingaway,there’snolongeranyneedfortheusertogettotheactivitytogetridofit.Inareal-worldapplication,however,wemightwanttokeepourworkersworking.Ifourserviceissendinge-mails,wecertainlydon’twanttosimplykilloffthethreads.Ourexampleisoverlysimple,becauseweimplythroughtheuseoftheinterrupt()methodthatyoucaneasilykilloffbackgroundthreads.Inreality,however,themostyoucandoisinterrupt.Thiswon’tnecessarilykilloffathread,though.Therearedeprecatedmethodsforkillingthreads,butyoushouldnotusethese.Theycancausememoryandstabilityproblemsforyouandyourusers.Interruptingworksinourexample,becausewe’redoingsleeps,whichcanbeinterrupted.
It’sworthwhiletakingalookattheThreadGroupclassbecauseitprovideswaysforyoutogetaccesstoyourthreads.WecreatedasingleThreadGroupobjectwithinourserviceandthenusedthatwhencreatingourindividualthreads.WithinouronDestroy()methodoftheservice,wesimplyinterrupt()ontheThreadGroup,anditissuesaninterrupttoeachthreadintheThreadGroup.
Sothereyouhavethemakingsofasimplelocalservice.Beforeweshowyouthecodeforouractivity,Listing14-9showstheXMLlayoutfileforouruserinterface.
Listing14-9.ImplementingaLocalService:main.xml
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><Buttonandroid:id="@+id/startBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="StartService"android:onClick="doClick"/><Buttonandroid:id="@+id/stopBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="StopService"android:onClick="doClick"/></LinearLayout>
We’regoingtoshowtwobuttonsontheuserinterface,onetodostartService()andtheothertodostopService().WecouldhavechosentouseaToggleButton,butthenyouwouldnotbeabletocallstartService()multipletimesinarow.Thisisan
importantpoint.Thereisnotaone-to-onerelationshipbetweenstartService()andstopService().WhenstopService()iscalled,theserviceobjectwillbedestroyed,andallthreadscreatedfromallstartService()callsshouldalsogoaway.Now,let’slookatthecodeforouractivityinListing14-10.
Listing14-10.ImplementingaLocalService:MainActivity.java
publicclassMainActivityextendsActivity{privatestaticfinalStringTAG="MainActivity";privateintcounter=1;
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}
publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.startBtn:Log.v(TAG,"Startingservice…counter="+counter);Intentintent=newIntent(MainActivity.this,BackgroundService.class);intent.putExtra("counter",counter++);startService(intent);break;caseR.id.stopBtn:stopService();}}
privatevoidstopService(){Log.v(TAG,"Stoppingservice…");if(stopService(newIntent(MainActivity.this,BackgroundService.class)))Log.v(TAG,"stopServicewassuccessful");elseLog.v(TAG,"stopServicewasunsuccessful");}
@OverridepublicvoidonDestroy(){stopService();
super.onDestroy();}}
OurMainActivitylooksalotlikeotheractivitiesyou’veseen.There’sasimpleonCreate()tosetupouruserinterfacefromthemain.xmllayoutfile.There’sadoClick()methodtohandlethebuttoncallbacks.Inourexample,we’recallingstartService()whentheStartServicebuttonispressed,andwe’recallingstopService()whentheStopServicebuttonispressed.Whenwestarttheservice,wewanttopassinsomedata,whichwedoviatheintent.WechosetopassthedataintheExtrasbundle,butwecouldhaveaddeditusingsetData()ifwehadaURI.Whenwestoptheservice,wechecktoseethereturnresult.Itshouldnormallybetrue,butiftheservicewasnotrunning,wecouldgetareturnoffalse.Last,whenouractivitydies,wewanttostoptheservice,sowealsostoptheserviceinouronDestroy()method.There’sonemoreitemtodiscuss,andthat’stheAndroidManifest.xmlfile,whichweshowinListing14-11.
Listing14-11.ImplementingaLocalService:AndroidManifest.xml
<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.services.simplelocal"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="8"/><applicationandroid:icon="@drawable/icon"android:label="@string/app_name"><activityandroid:name=".MainActivity"android:label="@string/app_name"android:launchMode="singleTop"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity><serviceandroid:name="BackgroundService"/></application>
</manifest>
Inadditiontoourregular<activity>tagsinthemanifestfile,wenowhavea<service>tag.Becausethisisalocalservicethatwe’recallingexplicitlyusingtheclassname,wedon’tneedtoputmuchintothe<service>tag.Allthatisrequiredisthenameofourservice.Butthereisoneotherthingtopointoutaboutthismanifestfile.Our
servicecreatesanotificationsothattheusercangetbacktoourMainActivityif,forexample,theuserpressedtheHomekeyonMainActivitywithoutstoppingtheservice.
TheMainActivityisstillthere;it’sjustnotvisible.OnewaytogetbacktotheMainActivityistoclickthenotificationthatourservicecreated.ThenotificationmanagerdeliversourintentbacktoourapplicationandwouldnormallycauseanewinstanceofMainActivitytohandlethenewintent.Topreventthisfromhappening,wesetanattributeinourmanifestfileforMainActivitycalledandroid:launchMode,andwesetittosingleTop.ThiswillhelpensurethattheexistinginvisibleMainActivitywillbebroughtforwardanddisplayed,ratherthancreatinganotherMainActivity.
Whenyourunthisapplication,youwillseeourtwobuttons.ByclickingtheStartServicebutton,youwillbeinstantiatingtheserviceandcallingonStartCommand().OurcodelogsseveralmessagestoLogCat,soyoucanfollowalong.GoaheadandclickStartServiceseveraltimesinarow,evenquickly.Youwillseethreadscreatedtohandleeachrequest.You’llalsonoticethatthevalueofcounterispassedalongthroughtoeachServiceWorkerthread.WhenyoupresstheStopServicebutton,ourservicewillgoaway,andyou’llseethelogmessagesfromourMainActivity’sstopService()method,fromourBackgroundService’sonDestroy()method,andpossiblyfromServiceWorkerthreadsiftheygotinterrupted.
Youshouldalsonoticethenotificationmessagewhentheservicehasbeenstarted.Withtheservicerunning,goaheadandpresstheBackbuttonfromourMainActivityandnoticethatthenotificationmessagedisappears.Thismeansourservicehasgoneawayalso.TorestartourMainActivity,clickStartServicetogettheservicegoingagain.Now,presstheHomebutton.OurMainActivitydisappearsfromview,butthenotificationremains,meaningourserviceisstillinexistence.Goaheadandclickthenotification,andyou’llagainseeourMainActivity.
Notethatourexampleusesanactivitytointerfacewiththeservice,butanycomponentinyourapplicationcanusetheservice.Thisincludesotherservices,activities,genericclasses,andsoon.Alsonotethatourservicedoesnotstopitself;itreliesontheactivitytodothatforit.Therearesomemethodsavailabletoaservicetoallowtheservicetostopitself,namelystopSelf()andstopSelfResult().Obviously,ifwehavemultipleclientsforthisservice,wewouldn’twanttheservicetobestoppedbyoneiftheothersarestillusingit.Forastartedservicewithmultipleclients,itismorelikelyyou’dputlogicintheserviceitselftodecidewhentheservicecanorshouldbestopped,andtheservicewoulduseoneofthestop*()methodstodothat.
OurBackgroundServiceisatypicalexampleofaservicethatisusedbythecomponentsoftheapplicationthatishostingtheservice.Inotherwords,theapplicationthatisrunningtheserviceisalsotheonlyconsumer.Becausetheservicedoesnotsupportclientsfromoutsideitsprocess,theserviceisalocalservice.ThecriticalmethodsofalocalserviceareonCreate(),onStartCommand(),onBind(),stop*(),andonDestroy().
There’sanotheroptionwithalocalservice,andthatisforthecasewhereyou’llonlyhaveoneinstanceoftheservicewithonebackgroundthread.Inthiscase,intheonCreate()methodoftheBackgroundService,wecouldcreateathreadthatdoestheservice’sheavylifting.WecouldcreateandstartthethreadinonCreate()ratherthanonStartCommand().WecoulddothisbecauseonCreate()iscalledonlyonce,andwewantthethreadtobecreatedonlyonceduringthelifeoftheservice.Onethingwewouldn’thaveinonCreate(),though,isthecontentoftheintentpassedbystartService().Ifweneedthat,wemightaswellusethepatternasdescribedpreviously,andwe’djustknowthatonStartCommand()shouldbecalledonlyonce.
Androidhasyetanotherwaytoimplementalocalservicethatincludesabackgroundthreadautomatically:theIntentService.AsubclassofService,IntentServicereceivestheincomingIntentfromastartService()call,createsabackground(worker)threadforyou,andinvokesthecallbackonHandleIntent(Intentintent).Ifanotherintentisdeliveredtothisservicebeforetheworkerthreadhascompletedtheearlierintent,thenewintentwillsitandwaituntilthepreviousintenthasbeenprocessed,atwhichtimethenextintentinthequeuewillgetpassedtotheonHandleIntent()method.Whenallintentsfromtheinboundqueuehavecompletedprocessing,theservicewillstopitself(noneedforyoutodothat).
Thisconcludesourintroductiontolocalservices.Rememberthatwe’llgetintomoredetailsoflocalservicesinsubsequentchapters.Let’smoveontoAIDLservices—themorecomplicatedtypeofservice.
UnderstandingAIDLServicesIntheprevioussection,weshowedyouhowtowriteanAndroidservicethatisconsumedbytheapplicationthathoststheservice.Now,wearegoingtoshowyouhowtobuildaservicethatcanbeconsumedbyotherprocessesviaremoteprocedurecall(RPC).AswithmanyotherRPC-basedsolutions,inAndroidyouneedaninterfacedefinitionlanguage(IDL)todefinetheinterfacethatwillbeexposedtoclients.IntheAndroidworld,thisIDLiscalledAndroidInterfaceDefinitionLanguage(AIDL).Tobuildaremoteservice,youdothefollowing:
1. WriteanAIDLfilethatdefinesyourinterfacetoclients.TheAIDLfileusesJavasyntaxandhasan.aidlextension.UsethesamepackagenameinsideyourAIDLfileasthepackageforyourAndroidproject.
2. AddtheAIDLfiletoyourEclipseprojectunderthesrcdirectory.TheAndroidEclipseplug-inwillcalltheAIDLcompilertogenerateaJavainterfacefromtheAIDLfile(theAIDLcompileriscalledaspartofthebuildprocess).
3. Implementaservice,andreturntheinterfacefromtheonBind()method.
4. AddtheserviceconfigurationtoyourAndroidManifest.xml
file.Thesectionsthatfollowshowyouhowtoexecuteeachstep.
DefiningaServiceInterfaceinAIDLTodemonstrateanexampleofaremoteservice,wearegoingtowriteastock-quoterservice.Thisservicewillprovideamethodthattakesatickersymbolandreturnsthestockvalue.TowritearemoteserviceinAndroid,thefirststepistodefinetheserviceinterfacedefinitioninanAIDLfile.Listing14-12showstheAIDLdefinitionofIStockQuoteService.ThisfilegoesintothesameplaceasaregularJavafilewouldforyourStockQuoteServiceproject.
Listing14-12.TheAIDLDefinitionoftheStock-QuoterService
//ThisfileisIStockQuoteService.aidlpackagecom.androidbook.services.stockquoteservice;interfaceIStockQuoteService{doublegetQuote(Stringticker);}
TheIStockQuoteServiceacceptsthestock-tickersymbolasastringandreturnsthecurrentstockvalueasadouble.WhenyoucreatetheAIDLfile,theAndroidEclipseplug-inrunstheAIDLcompilertoprocessyourAIDLfile(aspartofthebuildprocess).IfyourAIDLfilecompilessuccessfully,thecompilergeneratesaJavainterfacesuitableforRPCcommunication.NotethatthegeneratedfilewillbeinthepackagenamedinyourAIDLfile—com.androidbook.services.stockquoteserviceinthiscase.
Listing14-13showsthegeneratedJavafileforourIStockQuoteServiceinterface.ThegeneratedfilewillbeputintothegenfolderofourEclipseproject.
Listing14-13.TheCompiler-GeneratedJavaFile
/**Thisfileisauto-generated.DONOTMODIFY.*Originalfile:C:\\android\\StockQuoteService\\src\\com\\androidbook\\services\\stockquoteservice\\IStockQuoteService.aidl*/packagecom.androidbook.services.stockquoteservice;importjava.lang.String;importandroid.os.RemoteException;importandroid.os.IBinder;importandroid.os.IInterface;importandroid.os.Binder;importandroid.os.Parcel;publicinterfaceIStockQuoteServiceextendsandroid.os.IInterface{
/**Local-sideIPCimplementationstubclass.*/publicstaticabstractclassStubextendsandroid.os.Binderimplementscom.androidbook.services.stockquoteservice.IStockQuoteService{privatestaticfinaljava.lang.StringDESCRIPTOR="com.androidbook.services.stockquoteservice.IStockQuoteService";/**Constructthestubatattachittotheinterface.*/publicStub(){this.attachInterface(this,DESCRIPTOR);}/***CastanIBinderobjectintoanIStockQuoteServiceinterface,*generatingaproxyifneeded.*/publicstaticcom.androidbook.services.stockquoteservice.IStockQuoteService
asInterface(android.os.IBinderobj){if((obj==null)){returnnull;}android.os.IInterfaceiin=(android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);if(((iin!=null)&&(iininstanceofcom.androidbook.services.stockquoteservice.IStockQuoteService))){return((com.androidbook.services.stockquoteservice.IStockQuoteService)iin);}returnnewcom.androidbook.services.stockquoteservice.IStockQuoteService.Stub.Proxy(obj);}publicandroid.os.IBinderasBinder(){returnthis;}@OverridepublicbooleanonTransact(intcode,android.os.Parceldata,android.os.Parcelreply,intflags)throwsandroid.os.RemoteException{switch(code){
caseINTERFACE_TRANSACTION:{reply.writeString(DESCRIPTOR);returntrue;}caseTRANSACTION_getQuote:{data.enforceInterface(DESCRIPTOR);java.lang.String_arg0;_arg0=data.readString();double_result=this.getQuote(_arg0);reply.writeNoException();reply.writeDouble(_result);returntrue;}}returnsuper.onTransact(code,data,reply,flags);}privatestaticclassProxyimplementscom.androidbook.services.stockquoteservice.IStockQuoteService{privateandroid.os.IBindermRemote;Proxy(android.os.IBinderremote){mRemote=remote;}publicandroid.os.IBinderasBinder(){returnmRemote;}publicjava.lang.StringgetInterfaceDescriptor(){returnDESCRIPTOR;}publicdoublegetQuote(java.lang.Stringticker)throwsandroid.os.RemoteException{android.os.Parcel_data=android.os.Parcel.obtain();android.os.Parcel_reply=android.os.Parcel.obtain();double_result;try{_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(ticker);mRemote.transact(Stub.TRANSACTION_getQuote,_data,_reply,0);_reply.readException();
_result=_reply.readDouble();}finally{_reply.recycle();_data.recycle();}return_result;}}staticfinalintTRANSACTION_getQuote=(IBinder.FIRST_CALL_TRANSACTION+0);}publicdoublegetQuote(java.lang.Stringticker)throwsandroid.os.RemoteException;}
Notethefollowingimportantpointsregardingthegeneratedclasses:
TheinterfacewedefinedintheAIDLfileisimplementedasaninterfaceinthegeneratedcode(thatis,thereisaninterfacenamedIStockQuoteService).
AstaticabstractclassnamedStubextendsandroid.os.BinderandimplementsIStockQuoteService.Notethattheclassisanabstractclass.
AninnerclassnamedProxyimplementstheIStockQuoteServicethatproxiestheStubclass.
TheAIDLfilemustresideinthepackagewherethegeneratedfilesaresupposedtobe(asspecifiedintheAIDLfile’spackagedeclaration).
Now,let’smoveonandimplementtheAIDLinterfaceinaserviceclass.
ImplementinganAIDLInterfaceIntheprevioussection,wedefinedanAIDLfileforastock-quoterserviceandgeneratedthebindingfile.Now,wearegoingtoprovideanimplementationofthatservice.Toimplementtheservice’sinterface,weneedtowriteaclassthatextendsandroid.app.ServiceandimplementstheIStockQuoteServiceinterface.Theclasswearegoingtowritewe’llcallStockQuoteService.Toexposetheservicetoclients,ourStockQuoteServicewillneedtoprovideanimplementationoftheonBind()method,andwe’llneedtoaddsomeconfigurationinformationtotheAndroidManifest.xmlfile.Listing14-14showsanimplementationoftheIStockQuoteServiceinterface.ThisfilealsogoesintothesrcfolderoftheStockQuoteServiceproject.
Listing14-14.TheIStockQuoteServiceServiceImplementation
publicclassStockQuoteServiceextendsService{privatestaticfinalStringTAG="StockQuoteService";publicclassStockQuoteServiceImplextendsIStockQuoteService.Stub{@OverridepublicdoublegetQuote(Stringticker)throwsRemoteException{Log.v(TAG,"getQuote()calledfor"+ticker);return20.0;}}
@OverridepublicvoidonCreate(){super.onCreate();Log.v(TAG,"onCreate()called");}
@OverridepublicvoidonDestroy(){super.onDestroy();Log.v(TAG,"onDestroy()called");}
@OverridepublicIBinderonBind(Intentintent){Log.v(TAG,"onBind()called");returnnewStockQuoteServiceImpl();}}
TheStockQuoteService.javaclassinListing14-14resemblesthelocalBackgroundServicewecreatedearlier,butwithouttheNotificationManager.TheimportantdifferenceisthatwenowimplementtheonBind()method.RecallthattheStubclassgeneratedfromtheAIDLfilewasanabstractclassandthatitimplementedtheIStockQuoteServiceinterface.Inourimplementationoftheservice,wehaveaninnerclassthatextendstheStubclasscalledStockQuoteServiceImpl.Thisclassservesastheremote-serviceimplementation,andaninstanceofthisclassisreturnedfromtheonBind()method.Withthat,wehaveafunctionalAIDLservice,althoughexternalclientscannotconnecttoityet.
Toexposetheservicetoclients,weneedtoaddaservicedeclarationintheAndroidManifest.xmlfile,andthistime,weneedanintentfiltertoexposetheservice.Listing14-15showstheservicedeclarationfortheStockQuoteService.The<service>tagisachildofthe<application>tag.
Listing14-15.ManifestDeclarationfortheIStockQuoteService
<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.services.stockquoteservice"android:versionCode="1"android:versionName="1.0"><applicationandroid:icon="@drawable/icon"android:label="@string/app_name"><serviceandroid:name="StockQuoteService"><intent-filter><actionandroid:name="com.androidbook.services.stockquoteservice.IStockQuoteService"/></intent-filter></service></application><uses-sdkandroid:minSdkVersion="4"/></manifest>
Aswithallservices,wedefinetheservicewewanttoexposewitha<service>tag.ForanAIDLservice,wealsoneedtoaddan<intent-filter>withan<action>entryfortheserviceinterfacewewanttoexpose.
Withthisinplace,wehaveeverythingweneedtodeploytheservice.WhenyouarereadytodeploytheserviceapplicationfromEclipse,justgoaheadandchooseRunAsthewayyouwouldforanyotherapplication.EclipsewillcommentintheConsolethatthisapplicationhasnoLauncher,butitwilldeploytheappanyway,whichiswhatwewant.Let’snowlookathowwewouldcalltheservicefromanotherapplication(onthesamedevice,ofcourse).
CallingtheServicefromaClientApplicationWhenaclienttalkstoaservice,theremustbeaprotocolorcontractbetweenthetwo.WithAndroid,thecontractisinourAIDLfile.Sothefirststepinconsumingaserviceistotaketheservice’sAIDLfileandcopyittoyourclientproject.WhenyoucopytheAIDLfiletotheclientproject,theAIDLcompilercreatesthesameinterface-definitionfilethatwascreatedwhentheservicewasimplemented(intheservice-implementationproject).Thisexposestotheclientallofthemethods,parameters,andreturntypesontheservice.Let’screateanewprojectandcopytheAIDLfile:
1. CreateanewAndroidprojectnamedStockQuoteClient.Useadifferentpackagename,suchascom.androidbook.stockquoteclient.UseMainActivityfortheCreateActivityfield.
2. CreateanewJavapackageinthisprojectnamedcom.androidbook.services.stockquoteserviceinthesrcdirectory.
3. CopytheIStockQuoteService.aidlfilefromtheStockQuoteServiceprojecttothisnewpackage.Notethatafteryoucopythefiletotheproject,theAIDLcompilerwillgeneratetheassociatedJavafile.
Theserviceinterfacethatyouregenerateservesasthecontractbetweentheclientandtheservice.ThenextstepistogetareferencetotheservicesowecancallthegetQuote()method.Withremoteservices,wehavetocallthebindService()methodratherthanthestartService()method.Listing14-16showsanactivityclassthatactsasaclientoftheIStockQuoteServiceservice.Listing14-17containsthelayoutfilefortheactivity.
Listing14-16showsourMainActivity.javafile.Realizethatthepackagenameoftheclientactivityisnotthatimportant—youcanputtheactivityinanypackageyou’dlike.However,theAIDLartifactsthatyoucreatearepackage-sensitivebecausetheAIDLcompilergeneratescodefromthecontentsoftheAIDLfile.
Listing14-16.AClientoftheIStockQuoteServiceService
publicclassMainActivityextendsActivity{privatestaticfinalStringTAG="StockQuoteClient";privateIStockQuoteServicestockService=null;privateToggleButtonbindBtn;privateButtoncallBtn;
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
bindBtn=(ToggleButton)findViewById(R.id.bindBtn);callBtn=(Button)findViewById(R.id.callBtn);}
publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.bindBtn:if(((ToggleButton)view).isChecked()){
bindService(newIntent(IStockQuoteService.class.getName()),serConn,Context.BIND_AUTO_CREATE);}else{unbindService(serConn);callBtn.setEnabled(false);}break;caseR.id.callBtn:callService();break;}}
privatevoidcallService(){try{doubleval=stockService.getQuote("ANDROID");Toast.makeText(MainActivity.this,"Valuefromserviceis"+val,Toast.LENGTH_SHORT).show();}catch(RemoteExceptionee){Log.e("MainActivity",ee.getMessage(),ee);}}
privateServiceConnectionserConn=newServiceConnection(){
@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){Log.v(TAG,"onServiceConnected()called");stockService=IStockQuoteService.Stub.asInterface(service);bindBtn.setChecked(true);callBtn.setEnabled(true);}
@OverridepublicvoidonServiceDisconnected(ComponentNamename){Log.v(TAG,"onServiceDisconnected()called");bindBtn.setChecked(false);callBtn.setEnabled(false);stockService=null;
}};
protectedvoidonDestroy(){Log.v(TAG,"onDestroy()called");if(callBtn.isEnabled())unbindService(serConn);super.onDestroy();}}
TheactivitydisplaysourlayoutandgrabsareferencetotheCallServicebuttonsowecanproperlyenableitwhentheserviceisrunninganddisableitwhentheserviceisstopped.WhentheuserclickstheBindbutton,theactivitycallsthebindService()method.Similarly,whentheuserclicksUnBind,theactivitycallstheunbindService()method.NoticethatthreeparametersarepassedtothebindService()method:anIntentwiththenameoftheAIDLservice,aServiceConnectioninstance,andaflagtoautocreatetheservice.
Listing14-17.TheIStockQuoteServiceServiceClientLayout
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<ToggleButtonandroid:id="@+id/bindBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textOff="Bind"android:textOn="Unbind"android:onClick="doClick"/>
<Buttonandroid:id="@+id/callBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="CallService"android:enabled="false"android:onClick="doClick"/></LinearLayout>
Withaboundservice,suchasanAIDLservice,youneedtoprovideanimplementationoftheServiceConnectioninterface.Thisinterfacedefinestwomethods:onecalledbythesystemwhenaconnectiontotheservicehasbeenestablishedandonecalledwhentheconnectiontotheservicehasbeendestroyed.Inouractivityimplementation,wedefinetheServiceConnectionfortheIStockQuoteService.Whenwecallthe
bindService()method,wepassinthereferencetothis(i.e.,serConn).Whentheconnectiontotheserviceisestablished,theonServiceConnected()callbackisinvoked,andwethenobtainareferencetotheIStockQuoteServiceusingtheStubandenabletheCallServicebutton.
NotethatthebindService()callisanasynchronouscall.Itisasynchronousbecausetheprocessorservicemightnotberunningandthusmighthavetobecreatedorstarted.Andwecannotwaitonthemainthreadfortheservicetostart.BecausebindService()isasynchronous,theplatformprovidestheServiceConnectioncallback,soweknowwhentheservicehasbeenstartedandwhentheserviceisnolongeravailable.TheseServiceConnectioncallbackswillrunonthemainthreadthough,sothey’llhaveaccesstotheUIcomponentsifnecessary.
PleasenoticetheonServiceDisconnected()callback.Thisdoesnotgetinvokedwhenweunbindfromtheservice.ItisinvokediftheservicecrashesorifAndroiddecidestokilltheservice,forexampleifmemoryisgettinglow.Ifthiscallbackfires,weshouldnotthinkthatwe’restillconnected,andwemightneedtoreinvokethebindService()call.ThatiswhywechangethestatusofourbuttonsintheUIwhenthiscallbackisinvoked.Butnoticewesaid“wemightneedtoreinvokethebindService()call.”AndroidcouldrestartourserviceforusandinvokeouronServiceConnected()callback.Youcantrythisyourselfbyrunningtheclient,bindingtotheservice,andusingDDMStodoaStopontheStockQuoteServiceapplication.
Whenyourunthisexample,watchthelogmessagesinLogCattogetafeelforwhatisgoingonbehindthescenes.
Inourserviceexamplesthusfar,wehavestrictlydealtwithpassingsimpleJavaprimitivetypes.Androidservicesactuallysupportpassingcomplextypes,too.Thisisveryuseful,especiallyforAIDLservices,becauseyoumighthaveanopen-endednumberofparametersthatyouwanttopasstoaservice,andit’sunreasonabletopassthemallassimpleprimitives.Itmakesmoresensetopackagethemascomplextypesandthenpassthemtotheservice.
Let’sseehowwecanpasscomplextypestoservices.
PassingComplexTypestoServicesPassingcomplextypestoandfromservicesrequiresmoreworkthanpassingJavaprimitivetypes.Beforeembarkingonthiswork,youshouldgetanideaofAIDL’ssupportfornonprimitivetypes:
AIDLsupportsStringandCharSequence.
AIDLallowsyoutopassotherAIDLinterfaces,butyouneedtohaveanimportstatementforeachAIDLinterfaceyoureference(evenifthereferencedAIDLinterfaceisinthesamepackage).
AIDLallowsyoutopasscomplextypesthatimplementtheandroid.os.Parcelableinterface.Youneedtohavean
importstatementinyourAIDLfileforthesetypes.
AIDLsupportsjava.util.Listandjava.util.Map,withafewrestrictions.TheallowabledatatypesfortheitemsinthecollectionincludeJavaprimitive,String,CharSequence,andandroid.os.Parcelable.YoudonotneedimportstatementsforListorMap,butyoudoneedthemfortheParcelables.
Nonprimitivetypes,otherthanString,requireadirectionalindicator.Directionalindicatorsincludein,out,andinout.inmeansthevalueissetbytheclient,outmeansthevalueissetbytheservice,andinoutmeansboththeclientandservicesetthevalue.Androidavoidsserializingthevaluesifthey’renotflowingintheindicateddirection,whichhelpsoverallperformance.
TheParcelableinterfacetellstheAndroidruntimehowtoserializeanddeserializeobjectsduringthemarshallingandunmarshallingprocess.Listing14-18showsaPersonclassthatimplementstheParcelableinterface.
Listing14-18.ImplementingtheParcelableInterface
//ThisfileisPerson.javapackagecom.androidbook.services.stock2;importandroid.os.Parcel;importandroid.os.Parcelable;
publicclassPersonimplementsParcelable{privateintage;privateStringname;publicstaticfinalParcelable.Creator<Person>CREATOR=newParcelable.Creator<Person>(){publicPersoncreateFromParcel(Parcelin){returnnewPerson(in);}
publicPerson[]newArray(intsize){returnnewPerson[size];}};
publicPerson(){}
privatePerson(Parcelin){readFromParcel(in);}
@OverridepublicintdescribeContents(){return0;}
@OverridepublicvoidwriteToParcel(Parcelout,intflags){out.writeInt(age);out.writeString(name);}
publicvoidreadFromParcel(Parcelin){age=in.readInt();name=in.readString();}
publicintgetAge(){returnage;}
publicvoidsetAge(intage){this.age=age;}
publicStringgetName(){returnname;}
publicvoidsetName(Stringname){this.name=name;}}
Togetstartedonimplementingthis,createanewAndroidProjectinEclipsecalledStockQuoteService2.ForCreateActivity,useanameofMainActivity,anduseapackageofcom.androidbook.services.stock2.ThenaddthePerson.javafilefromListing14-18tothecom.androidbook.services.stock2packageofournewproject.
TheParcelableinterfacedefinesthecontractforthemarshallingandunmarshallingofobjects.UnderlyingtheParcelableinterfaceistheParcelcontainerobject.TheParcelclassisafastserialization/deserializationmechanismspeciallydesignedforinterprocesscommunicationwithinAndroid.Theclassprovidesmethodsthatyouusetoflattenyourmemberstothecontainerandtoexpandthemembersbackfromthecontainer.Toproperlyimplementanobjectforinterprocesscommunication,wehavetodothefollowing:
1. ImplementtheParcelableinterface.ThismeansthatyouimplementwriteToParcel()andreadFromParcel().Thewritemethodwillwritetheobjecttotheparcel,andthereadmethodwillreadtheobjectfromtheparcel.Notethattheorderinwhichyouwritepropertiesmustbethesameastheorderinwhichyoureadthem.
2. AddastaticfinalpropertytotheclasswiththenameCREATOR.Thepropertyneedstoimplementtheandroid.os.Parcelable.Creator<T>interface.
3. ProvideaconstructorfortheParcelablethatknowshowtocreatetheobjectfromtheParcel.
4. DefineaParcelableclassinan.aidlfilethatmatchesthe.javafilecontainingthecomplextype.TheAIDLcompilerwilllookforthisfilewhencompilingyourAIDLfiles.AnexampleofaPerson.aidlfileisshowninListing14-19.ThisfileshouldbeinthesameplaceasPerson.java.
NoteSeeingParcelablemighthavetriggeredthequestion,whyisAndroidnotusingthebuilt-inJavaserializationmechanism?ItturnsoutthattheAndroidteamcametotheconclusionthattheserializationinJavaisfartooslowtosatisfyAndroid’sinterprocess-communicationrequirements.SotheteambuilttheParcelablesolution.TheParcelableapproachrequiresthatyouexplicitlyserializethemembersofyourclass,butintheend,yougetamuchfasterserializationofyourobjects.
AlsorealizethatAndroidprovidestwomechanismsthatallowyoutopassdatatoanotherprocess.Thefirstistopassabundletoanactivityusinganintent,andthesecondistopassaParcelabletoaservice.Thesetwomechanismsarenotinterchangeableandshouldnotbeconfused.Thatis,theParcelableisnotmeanttobepassedtoanactivity.Ifyouwanttostartanactivityandpassitsomedata,useaBundle.ParcelableismeanttobeusedonlyaspartofanAIDLdefinition.
Listing14-19.AnExampleofaPerson.aidlFile
//ThisfileisPerson.aidlpackagecom.androidbook.services.stock2;parcelablePerson;
Youwillneedan.aidlfileforeachParcelableinyourproject.Inthiscase,wehavejustoneParcelable,whichisPerson.Youmaynoticethatyoudon’tgetaPerson.javafilecreatedinthegenfolder.Thisistobeexpected.Wealreadyhavethisfilefromwhenwecreateditpreviously.
Now,let’susethePersonclassinaremoteservice.Tokeepthingssimple,wewill
modifyourIStockQuoteServicetotakeaninputparameteroftypePerson.TheideaisthatclientswillpassaPersontotheservicetotelltheservicewhoisrequestingthequote.ThenewIStockQuoteService.aidllookslikeListing14-20.
Listing14-20.PassingParcelablestoServices
//ThisfileisIStockQuoteService.aidlpackagecom.androidbook.services.stock2;importcom.androidbook.services.stock2.Person;
interfaceIStockQuoteService{StringgetQuote(inStringticker,inPersonrequester);}
ThegetQuote()methodnowacceptstwoparameters:thestock’stickersymbolandaPersonobjecttospecifywhoismakingtherequest.NotethatwehavedirectionalindicatorsontheparametersbecausetheparametersincludenonprimitivetypesandthatwehaveanimportstatementforthePersonclass.ThePersonclassisalsointhesamepackageastheservicedefinition(com.androidbook.services.stock2).
TheserviceimplementationnowlookslikeListing14-21,withtheMainActivitylayoutinListing14-22.
Listing14-21.TheStockQuoteService2Implementation
packagecom.androidbook.services.stock2;//ThisfileisStockQuoteService2.java
importandroid.app.Notification;importandroid.app.NotificationManager;importandroid.app.PendingIntent;importandroid.app.Service;importandroid.content.Intent;importandroid.os.IBinder;importandroid.os.RemoteException;
publicclassStockQuoteService2extendsService{privateNotificationManagernotificationMgr;
publicclassStockQuoteServiceImplextendsIStockQuoteService.Stub{publicStringgetQuote(Stringticker,Personrequester)throwsRemoteException{return"Hello"+requester.getName()+"!Quotefor"+ticker+"is20.0";
}}
@OverridepublicvoidonCreate(){super.onCreate();
notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
displayNotificationMessage("onCreate()calledinStockQuoteService2");}
@OverridepublicvoidonDestroy(){displayNotificationMessage("onDestroy()calledinStockQuoteService2");//ClearallnotificationsfromthisservicenotificationMgr.cancelAll();super.onDestroy();}
@OverridepublicIBinderonBind(Intentintent){displayNotificationMessage("onBind()calledinStockQuoteService2");returnnewStockQuoteServiceImpl();}
privatevoiddisplayNotificationMessage(Stringmessage){PendingIntentcontentIntent=PendingIntent.getActivity(this,0,newIntent(this,MainActivity.class),0);
Notificationnotification=newNotificationCompat.Builder(this).setContentTitle("StockQuoteService2").setContentText(message).setSmallIcon(R.drawable.emo_im_happy).setTicker(message)//.setLargeIcon(aBitmap).setContentIntent(contentIntent).setOngoing(true)
.build();
notificationMgr.notify(R.id.app_notification_id,notification);}}
Listing14-22.TheStockQuoteService2Layout
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Thisiswheretheservicecouldaskforhelp."/></LinearLayout>
Thedifferencesbetweenthisimplementationandthepreviousonearethatwebroughtbackthenotifications,andwenowreturnthestockvalueasastringandnotadouble.ThestringreturnedtotheusercontainsthenameoftherequesterfromthePersonobject,whichdemonstratesthatwereadthevaluesentfromtheclientandthatthePersonobjectwaspassedcorrectlytotheservice.
Thereareafewotherthingsthatneedtobedonetomakethiswork:
1. Findtheemo_im_happy.pngimagefilefromunderAndroidSDK/platforms/android-19/data/res/drawable-mdpi,andcopyittothe/res/drawabledirectoryofourproject.Orchangethenameoftheresourceinthecode,andputwhateverimageyouwantinthedrawablesfolder.
2. Addanew<itemtype=“id”name=“app_notification_id”/>tagtothe/res/values/strings.xmlfile.
3. WeneedtomodifytheapplicationintheAndroidManifest.xmlfileasshowninListing14-23.
Listing14-23.Modified<application>inAndroidManifest.xmlFileforStockQuoteService2
<?xmlversion="1.0"encoding="utf-8"?><manifest
xmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.services.stock2"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="8"/><applicationandroid:icon="@drawable/icon"android:label="@string/app_name"><activityandroid:name=".MainActivity"android:label="@string/app_name"android:launchMode="singleTop"><intent-filter><actionandroid:name="android.intent.action.MAIN"/></intent-filter></activity><serviceandroid:name="StockQuoteService2"><intent-filter><actionandroid:name="com.androidbook.services.stock2.IStockQuoteService"/></intent-filter></service></application>
</manifest>
WhileitisOKtousethedotnotationforourandroid:name=”.MainActivity”attribute,itisnotOKtousedotnotationinsideofour<action>taginsidetheservice’s<intent-filter>tag.Weneedtospellitout;otherwise,ourclientwillnotfindtheservicespecification.
Last,we’llusethedefaultMainActivity.javafilethatsimplydisplaysabasiclayoutwithasimplemessage.Weshowedyouearlierhowtolaunchtotheactivityfromanotification.Thisactivitywouldservethatpurposealsoinreallife,butforthisexample,we’llkeepthatpartsimple.Nowthatwehaveourserviceimplementation,let’screateanewAndroidprojectcalledStockQuoteClient2.Usecom.daveforthepackageandMainActivityfortheactivityname.ToimplementaclientthatpassesthePersonobjecttotheservice,weneedtocopyeverythingthattheclientneedsfromtheserviceprojecttotheclientproject.Thereneedstobeanewsrcpackagecalledcom.androidbook.services.stock2toreceivethesecopiedfiles.Inourpreviousexample,allweneededwastheIStockQuoteService.aidlfile.WealsoneedtocopythePerson.javaandPerson.aidlfiles,becausethePersonobjectisnowpartoftheinterface.Afteryoucopythesethreefilestothecom.androidbook.services.stock2srcpackageoftheclientproject,modifymain.xmlaccordingtoListing14-24,andmodifyMainActivity.javaaccordingtoListing14-25.Orsimplyimportthisprojectfromthesourcecodeonourwebsite.
Listing14-24.Updatedmain.xmlforStockQuoteClient2
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent">
<ToggleButtonandroid:id="@+id/bindBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textOff="Bind"android:textOn="Unbind"android:onClick="doClick"/><Buttonandroid:id="@+id/callBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="CallService"android:enabled="false"android:onClick="doClick"/></LinearLayout>
Listing14-25.CallingtheServicewithaParcelable
packagecom.dave;//ThisfileisMainActivity.javaimportcom.androidbook.services.stock2.IStockQuoteService;importcom.androidbook.services.stock2.Person;
publicclassMainActivityextendsActivity{
protectedstaticfinalStringTAG="StockQuoteClient2";privateIStockQuoteServicestockService=null;privateToggleButtonbindBtn;privateButtoncallBtn;
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
bindBtn=(ToggleButton)findViewById(R.id.bindBtn);callBtn=(Button)findViewById(R.id.callBtn);}
publicvoiddoClick(Viewview){switch(view.getId()){
caseR.id.bindBtn:if(((ToggleButton)view).isChecked()){bindService(newIntent(IStockQuoteService.class.getName()),serConn,Context.BIND_AUTO_CREATE);}else{unbindService(serConn);callBtn.setEnabled(false);}break;caseR.id.callBtn:callService();break;}}
privatevoidcallService(){try{Personperson=newPerson();person.setAge(47);person.setName("Dave");Stringresponse=stockService.getQuote("ANDROID",person);Toast.makeText(MainActivity.this,"Valuefromserviceis"+response,Toast.LENGTH_SHORT).show();}catch(RemoteExceptionee){Log.e("MainActivity",ee.getMessage(),ee);}}
privateServiceConnectionserConn=newServiceConnection(){
@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){Log.v(TAG,"onServiceConnected()called");stockService=IStockQuoteService.Stub.asInterface(service);bindBtn.setChecked(true);callBtn.setEnabled(true);}
@Override
publicvoidonServiceDisconnected(ComponentNamename){Log.v(TAG,"onServiceDisconnected()called");bindBtn.setChecked(false);callBtn.setEnabled(false);stockService=null;}};
protectedvoidonDestroy(){if(callBtn.isEnabled())unbindService(serConn);super.onDestroy();}}
Thisisnowreadytorun.Remembertosendovertheservicetothedeviceoremulatorbeforeyousendovertheclienttorun.TheuserinterfaceshouldlooklikeFigure14-1.
Figure14-1.UserinterfaceofStockQuoteClient2
Let’stakealookatwhatwe’vegot.Asbefore,webindtoourservice,andthenwecaninvokeaservicemethod.TheonServiceConnected()methodiswherewegettoldthatourserviceisrunning,sowecanthenenabletheCallServicebuttonsothebuttoncaninvokethecallService()method.Asshown,wecreateanewPersonobjectandsetitsAgeandNameproperties.Wethenexecutetheserviceanddisplaytheresultfromtheservicecall.TheresultlookslikeFigure14-2.
Figure14-2.ResultfromcallingtheservicewithaParcelable
Noticethatwhentheserviceiscalled,yougetanotificationinthestatusbar.Thisiscomingfromtheserviceitself.WebrieflytouchedonNotificationsearlierasawayforaservicetocommunicatetotheuser.Normally,servicesareinthebackgroundanddonotdisplayanysortofUI.Butwhatifaserviceneedstointeractwiththeuser?Whileit’stemptingtothinkthataservicecaninvokeanactivity,aserviceshouldneverinvokeanactivitydirectly.Aserviceshouldinsteadcreateanotification,andthenotificationshouldbehowtheusergetstothedesiredactivity.Thiswasshowninourlastexercise.We
definedasimplelayoutandactivityimplementationforourservice.Whenwecreatedthenotificationwithintheservice,wesettheactivityinthenotification.Theusercantouchthenotification,anditwilltaketheusertoouractivitythatispartofthisservice.Thiswillallowtheusertointeractwiththeservice.
Notificationsaresavedsothatyoucangettothembypullingdownfromthestatusbartoseethem.NotethefactthatwereusethesameIDforeverymessage.Thismeansthatweareupdatingtheoneandonlynotificationeverytime,ratherthancreatingnewnotificationentries.Therefore,ifyougototheNotificationsscreeninAndroidafterclickingBind,CallAgain,andUnbindafewtimes,youwillonlyseeonemessageinNotifications,anditwillbethelastonesentbyStockQuoteService2.IfweuseddifferentIDs,wecouldhavemultiplenotificationmessages,andwecouldupdateeachoneseparately.Notificationscanalsobesetwithadditionaluser“prompts”suchassound,lights,and/orvibration.
Itisalsousefultoseetheartifactsoftheserviceprojectandtheclientthatcallsit(seeFigure14-3).
Figure14-3.Theartifactsoftheserviceandtheclient
Figure14-3showstheEclipseprojectartifactsfortheservice(left)andtheclient(right).NotethatthecontractbetweentheclientandtheserviceconsistsoftheAIDLartifactsand
theParcelableobjectsexchangedbetweenthetwoparties.ThisisthereasonthatweseePerson.java,IStockQuoteService.aidl,andPerson.aidlonbothsides.BecausetheAIDLcompilergeneratestheJavainterface,stub,proxy,andsoonfromtheAIDLartifacts,thebuildprocesscreatestheIStockQuoteService.javafileontheclientsidewhenwecopythecontractartifactstotheclientproject.Nowyouknowhowtoexchangecomplextypesbetweenservicesandclients.Let’sbrieflytouchonanotherimportantaspectofcallingservices:synchronousversusasynchronousserviceinvocation.
Allofthecallsthatyoumakeonservicesaresynchronous.Thisbringsuptheobviousquestion:Doyouneedtoimplementallofyourservicecallsinaworkerthread?Notnecessarily.Onmostotherplatforms,it’scommonforaclienttouseaservicethatisacompleteblackbox,sotheclientwouldhavetotakeappropriateprecautionswhenmakingservicecalls.WithAndroid,youwilllikelyknowwhatisintheservice(generallybecauseyouwrotetheserviceyourself),soyoucanmakeaninformeddecision.Ifyouknowthatthemethodyouarecallingisdoingalotofheavylifting,youshouldconsiderusingasecondarythreadtomakethecall.Ifyouaresurethatthemethoddoesnothaveanybottlenecks,youcansafelymakethecallontheUIthread.Ifyouconcludethatit’sbesttomaketheservicecallwithinaworkerthread,youcancreatethethreadandthencalltheservice.YoucanthencommunicatetheresulttotheUIthread.
MessengersandHandlersThereisonemorewaytocommunicatewithaserviceinAndroid,andthatiswithMessengersandHandlers.ThismechanismisbuiltontopofAIDLservices,butwithoutyouhavingtoseeordealwithAIDL.LikeAIDLservices,youuseitwhentheserviceisinaseparateprocessfromtheclient.BoththeclientandtheservicewillimplementaMessengerandaHandler,andproceedtosendmessagesbackandforth.Youdon’tneedtospecifyany.aidlfiles;everythingiscodedinyourJavaclasses.Thisisafairlycommonwaytodointer-processservicecallsonAndroid,andissignificantlyeasierthanmessingwithAIDLyourself.
Here’saquickoverviewofhowitworks.Theclientbindstotheservice,andsetsupaMessengerandHandlertoreceiveresponsesfromtheservice.AcallbackwithintheHandlertakescareofmessagessentbackbytheservice.TheclientalsocreatesaMessengertosendmessagestotheservice.Ontheserviceside,there’sasimilarMessengerandHandlertoreceivetheincomingmessagesfromclients.AmessagecominginfromaclientincludesaMessengertouseforanyrepliestothatclient.Therefore,theservicecreatesonlyoneMessengerwhileaclientcreatestwo.Theclientsideisasynchronous,withtheserviceresponsecominglater.AproblemwiththeservicecallgeneratesaRemoteExceptionthattheclientcancaptureandactupon.
Let’sseeanexampleofhowthisworks.Thissampleapplicationhastwoparts:aMessengerClientandaMessengerService.Theywillrunasseparateprocessesonadevice.Theclientwilluseanon-UIfragmenttocontaintheserviceclientconnection.Thismeanstheclientactivitycangoawayandberecreatedduetoaconfigurationchange,andtheunderlyingserviceconnectionremainsinplace.Thisisapreferredwaytoconnect
toaservicefromanactivitysinceyoudon’twanttohavetoreconstructtheserviceclientconnectionjustbecausethedevicehasbeenrotatedforexample.Listing14-26showsthesignificantcodefromMessengerService.javatosetuptheHandlerandMessenger.Forafulllisting,refertotheMessengerServicesourceprojectforthischapter.
Listing14-26.Messenger/Handler-BasedServiceCode
publicclassMessengerServiceextendsService{NotificationManagermNM;ArrayList<Messenger>mClients=newArrayList<Messenger>();intmValue=0;publicstaticfinalintMSG_REGISTER_CLIENT=1;publicstaticfinalintMSG_UNREGISTER_CLIENT=2;publicstaticfinalintMSG_SET_SIMPLE_VALUE=3;publicstaticfinalintMSG_SET_COMPLEX_VALUE=4;publicstaticfinalStringTAG="MessengerService";/***Handlerofincomingmessagesfromclients.*/classIncomingHandlerextendsHandler{@OverridepublicvoidhandleMessage(Messagemsg){switch(msg.what){caseMSG_REGISTER_CLIENT:mClients.add(msg.replyTo);Log.v(TAG,"Registeringclient");break;caseMSG_UNREGISTER_CLIENT:mClients.remove(msg.replyTo);Log.v(TAG,"Unregisteringclient");break;caseMSG_SET_SIMPLE_VALUE:mValue=msg.arg1;Log.v(TAG,"Receivingarg1:"+mValue);showNotification("Receivedarg1:"+mValue);for(inti=mClients.size()-1;i>=0;i--){try{mClients.get(i).send(Message.obtain(null,MSG_SET_SIMPLE_VALUE,mValue,0));}catch(RemoteExceptione){//Theclientisdead.Removeitfromthelist;//wearegoingthroughthelistfrombacktofront
//sothisissafetodoinsidetheloop.mClients.remove(i);}}break;caseMSG_SET_COMPLEX_VALUE:BundlemBundle=msg.getData();Log.v(TAG,"Receivingbundle:");if(mBundle!=null){showNotification("Gotcomplexmsg:myDouble="+mBundle.getDouble("myDouble"));for(Stringkey:mBundle.keySet()){Log.v(TAG,""+key);}}break;default:Log.v(TAG,"Gotsomeothermessage:"+msg.what);super.handleMessage(msg);}}}
//TargetforclientstosendmessagestoIncomingHandler.finalMessengermMessenger=newMessenger(newIncomingHandler());
@OverridepublicvoidonCreate(){mNM=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
//Displayanotificationaboutusstarting.Log.v(TAG,"Serviceisstarting");showNotification(getText(R.string.remote_service_started));}
@OverridepublicvoidonDestroy(){//Cancelthepersistentnotification.mNM.cancel(R.string.remote_service_started);
//Telltheuserwestopped.
Toast.makeText(this,R.string.remote_service_stopped,Toast.LENGTH_SHORT).show();}
/***Whenbindingtotheservice,wereturnaninterfacetoourmessenger*forsendingmessagestotheservice.*/@OverridepublicIBinderonBind(Intentintent){returnmMessenger.getBinder();}
/***Showanotificationwhilethisserviceisrunning.Notethat*wedon'tincludeanintentsincewe'rejustaservicehere.The*servicestopswhentheclienttellsitto.*/privatevoidshowNotification(CharSequencetext){Notificationnotification=newNotificationCompat.Builder(this).setContentTitle("MessengerService").setContentText(text).setSmallIcon(android.R.drawable.ic_dialog_info).setTicker(text).setOngoing(true).build();
mNM.notify(R.string.remote_service_started,notification);}
}
Inthissample,clientsregisterwiththeservice,unregisterwiththeservice,sendasimplemessage,orsendacomplexmessage.Whenaclientregisters,theserviceremembersitbysavingthepassed-inclientMessenger(i.e.,msg.replyTo)inmClients.Ifasimplemessageisreceived,theservicesendsacopyofthereceivedargumentvaluetoallknownclients.NoticehowrepliesaresenttoeachclientusingtheMessengersinmClientsthatcamefromeachclient.TheMessage’swhatfieldisjustaninttoindicatewhatserviceoperationisbeingcalled.Baseduponthewhatoperation,theservicewillextracttheappropriatearguments.SinceaMessageobjecthastwointargumentsavailable,thesimplecaseusesjustoneofthoseMessagefields.Whenmorecomplexdatamustbesent,aBundleobjectiscreated,populated,andattachedtotheMessagefortransmissiontothe
service.
Beawarethataservicehasa1MBbufferforpasseddata(inandout)forallin-processservicecalls,soyou’llwanttokeepMessagedatatoaminimum.Iftherearealotofsimultaneousservicecalls,youcouldexceedthebufferandgetaTransactionTooLargeException.
Ontheclientside,there’saMainActivityandaClientFrag(thenon-UIfragment).Forsimplicity’ssaketheactivityprovidestheUItotheuserwithoutusingaUIfragment.Listing14-27showstheMainActivity.Forafulllistingoftheproject,pleaseseetheMessengerClientprojectforthischapter.
Listing14-27.Messenger/Handler-BasedClientActivityCode
publicclassMainActivityextendsFragmentActivityimplementsISampleServiceClient{
protectedstaticfinalStringTAG="MessengerClient";privateTextViewmCallbackText;privateClientFragclientFrag;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
mCallbackText=(TextView)findViewById(R.id.callback);
//Getanon-UIfragmenttohandletheserviceinterface.//Ifouractivitygetsdestroyedandrecreated,thefragment//willstillbearoundandwejustneedtore-fetchit.if((clientFrag=(ClientFrag)getSupportFragmentManager().findFragmentByTag("clientFrag"))==null){updateStatus("CreatingaclientFrag.Noserviceyet.");clientFrag=ClientFrag.getInstance();getSupportFragmentManager().beginTransaction().add(clientFrag,"clientFrag").commit();}else{updateStatus("FoundexistingclientFrag,willuseit");}}
publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.startBtn:clientFrag.doBindService();break;caseR.id.stopBtn:clientFrag.doUnbindService();break;caseR.id.simpleBtn:clientFrag.doSendSimple();break;caseR.id.complexBtn:clientFrag.doSendComplex();break;}}
@OverridepublicvoidupdateStatus(Stringstatus){mCallbackText.setText(status);}}
Noticehowthere’snomentionofaserviceinthisactivity,justaTextView,buttons,andaclientfragment.TheupdateStatus()methodisasaresultoftheISampleServiceClientinterfacethatthisactivityimplements,andallitneedstodoissetthetextintheUIaspassedin.Thebuttonssimplyinvokeamethodoftheclientfragment.Inarealapplication,therewouldbemorebusinessandUIlogicinthisactivityorinotherfragmentsthatareseparatefromtheservicecall.
Theclientfragmentiswherethefunis.Listing14-28showsthecodefromtheclientfragment.
Listing14-28.Messenger/Handler-BasedClientFragmentCode
publicclassClientFragextendsFragment{privatestaticfinalStringTAG="MessengerClientFrag";staticprivateClientFragmClientFrag=null;//applicationcontextwillbeusedtobindtotheservicebecause//fragmentscan'tbindandactivitiescangoaway.privateContextappContext=null;
//Messengerforsendingtoservice.MessengermService=null;//Flagindicatingwhetherwehavecalledbindontheservice.
booleanmIsBound;
//Instantiationmethodfortheclientfragment.Wejustwantone//andweusesetRetainInstance(true)soithangsaroundduring//configurationchanges.publicstaticClientFraggetInstance(){if(mClientFrag==null){mClientFrag=newClientFrag();mClientFrag.setRetainInstance(true);}returnmClientFrag;}
//HandlerforresponsemessagesfromtheserviceclassIncomingHandlerextendsHandler{@OverridepublicvoidhandleMessage(Messagemsg){switch(msg.what){caseMessengerService.MSG_SET_SIMPLE_VALUE:updateStatus("Receivedfromservice:"+msg.arg1);break;default:break;}super.handleMessage(msg);}}
//NeedaMessengertoreceiveresponses.Sendthiswiththe//Messagestotheservice.finalMessengermMessenger=newMessenger(newIncomingHandler());
privateServiceConnectionmConnection=newServiceConnection(){publicvoidonServiceConnected(ComponentNameclassName,IBinderservice){//Thisiscalledwhentheconnectionwiththeservicehasbeen//established,givingustheserviceobjectwecanuseto//interactwiththeservice.Weare
communicatingwithour//servicethroughaMessenger,sogetaclient-side//representationofthatfromtherawserviceobject.mService=newMessenger(service);updateStatus("Attached.");
//Wewanttomonitortheserviceforaslongasweare//connectedtoit.Thisisnotstrictlynecessary.You//donotneedtoregisterwiththeservicebeforeusing//it.Butifthisfailedyou'dhaveanearlywarning.try{Messagemsg=Message.obtain(null,MessengerService.MSG_REGISTER_CLIENT);msg.replyTo=mMessenger;mService.send(msg);}catch(RemoteExceptione){//Inthiscasetheservicehascrashedbeforewecouldeven//doanythingwithit;wecancountonsoonbeing//disconnected(andthenreconnectedifitcanberestarted)//sothereisnoneedtodoanythinghere.Log.e(TAG,"Couldnotestablishaconnectiontotheservice:"+e);}}
publicvoidonServiceDisconnected(ComponentNameclassName){//Thisiscalledwhentheconnectionwiththeservicehasbeen//unexpectedlydisconnected—thatis,itsprocesscrashed.mService=null;updateStatus("Disconnected.");}};
publicvoiddoBindService(){//Establishaconnectionwiththeservice.Weusethe
Stringname//oftheservicesinceitexistsinaseparateprocessandwedo//notwanttorequiretheservicejarintheclient.Wealsograb//theapplicationcontextandbindtheservicetothatsincethe//activitycontextcouldgoawayonaconfigurationchangebutthe//applicationcontextwillalwaysbethere.appContext=getActivity().getApplicationContext();if(mIsBound=appContext.bindService(newIntent("com.androidbook.messengerservice.MessengerService"),mConnection,Context.BIND_AUTO_CREATE)){updateStatus("Boundtoservice.");}else{updateStatus("Bindattemptfailed.");}}
publicvoiddoUnbindService(){if(mIsBound){//Ifwehavereceivedtheservice,andhenceregisteredwith//it,thennowisthetimetounregister.Notethatthe//replyTovalueisonlyusedbytheservicetounregister//thisclient.Noresponsemessagewillcomebacktotheclient.if(mService!=null){try{Messagemsg=Message.obtain(null,MessengerService.MSG_UNREGISTER_CLIENT);msg.replyTo=mMessenger;mService.send(msg);}catch(RemoteExceptione){//Thereisnothingspecialweneedtodoiftheservice//hascrashed.}}
//Detachourexistingconnection.appContext.unbindService(mConnection);mIsBound=false;updateStatus("Unbound.");}}
//Ifyoucansimplifyandsendonlyoneortwointegers,this//istheeasywaytodoit.publicvoiddoSendSimple(){try{Messagemsg=Message.obtain(null,MessengerService.MSG_SET_SIMPLE_VALUE,this.hashCode(),0);mService.send(msg);updateStatus("Sendingsimplemessage.");}catch(RemoteExceptione){Log.e(TAG,"Couldnotsendasimplemessagetotheservice:"+e);}}
//Ifyouhavemorecomplexdata,throwitintoaBundleand//addittotheMessage.CanalsopassParcelablesifyoulike.publicvoiddoSendComplex(){try{Messagemsg=Message.obtain(null,MessengerService.MSG_SET_COMPLEX_VALUE);BundlemBundle=newBundle();mBundle.putString("stringArg","Thisisastringtopass");mBundle.putDouble("myDouble",1138L);mBundle.putInt("myInt",42);msg.setData(mBundle);mService.send(msg);updateStatus("Sendingcomplexmessage.");}catch(RemoteExceptione){Log.e(TAG,"Couldnotsendacomplexmessagetotheservice:"+e);}}
privatevoidupdateStatus(Stringstatus){//MakesurethelateststatusisupdatedintheGUI,
which//ishandledbytheparentactivity.ISampleServiceClientuiContext=(ISampleServiceClient)getActivity();if(uiContext!=null){uiContext.updateStatus(status);}}}
Theclientfragmentcodeisfairlystraightforward.WhentheuserclickstheBindServicebutton,theclientfragmentbindstotheremoteserviceandsetsuptheServiceConnection.Bindingisdonefromtheapplicationcontext.Thisispreferredbecausefragmentscan’tbindservices,butactivitiesandapplicationscan.However,becausetheactivitycouldgoawayduringaconfigurationchange,it’sbettertobindtotheapplicationwhichwillalwaysbethere.WhentheServiceConnectiongetsconnected,anoutgoingMessengerissetuptosendtheMSG_REGISTER_CLIENTregisterclientmessagetotheservice.Theclientdoesnotwaitforareplyfromtheservicebutdoesgobacktowaitingfortheuser’snextinteraction.ThispreventsthedreadedANRpop-up.PressingSendSimplecreatesasimplemessageandsendsit.
Forasimplemessage,theservicedoesareplymessagewhichisreceivedbytheclient’shandlerandprocessed.AlltheclienthandlerdoesisupdatetheTextViewwiththevaluereceivedfromtheservice.Noticethattheclientfragmentusestheparentactivity’sISampleServiceClientinterfacetocallanappropriatemethodtoupdatetheUI.Thisisbecausetheclientfragmentisnon-UIandwewouldprefernottoembedUIlogicwithinit.Aninterfacekeepstheclientfragmentseparatefromtheactivityandmakesiteasytolettheactivitygoawayandcomebackduringaconfigurationchange.PressingSendComplexcreatesamessagewithabundlecontainingseveraldifferentvalues,whichissenttotheservice.Theservicewillusethedoublevalueinanotificationtoprovethatthevaluewasproperlytransmittedfromclienttoservice.Theservicedoesnotsendareplymessageforthecomplexmessage.
Onethingtobeawareofwiththismechanismforinter-processservicecalls:theservice’shandlerisworkingfromaqueueofincomingmessagesandisthereforesingle-threadedbydefault.Therewillnotbemultiplethreadsprocessingtheincomingservicemessagesunlessyoucreatesomeyourself.Sincetheclientisnotblockingonareplyfromtheservice,aclientapplicationwouldnotcrashiftheservicetookawhiletorespondtoamessage.However,itwouldbesomethingtokeepinmindifyou’llhavemultipleclientsoftheservice.AIDLservicescanmoreeasilyhandlerequestssimultaneously,soitmightbeabetterchoiceifyouneedmorepredictableresponsetimes.
Ifyourclientissendingmessagestomultipleservices,youcoulduseasingleMessenger/Handlerpairtoprocessreplymessagesfromthoseservices.YoujusthavetoputthatsameMessengerintoeachoutboundMessageandeachserviceshouldreplyback.
Theotherthingtobeawareofisthattheclienthasnoguaranteethattheservicewilleverrespond.TherearenotimeoutsinherentinaMessenger/Handlerinteraction.YouwillbenotifiedthroughonServiceDisconnected()iftheservicedies,butnotifithangs
ortakestoolong.Therefore,tobesurethattheservicerespondsinatimelyfashion,theclientcouldchoosetosetatimer,oranalarmtowakeitupagain.Ifthereplycomesbackbeforethetimer/alarmgoesoff,theclienthandlercouldclearit.Ifthetimer/alarmwakesuptheclient,itmeanstheservicetooktoolongandtheclientcouldthentakeappropriateaction.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch14_Services.zip.ThisZIPfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneoftheseZIPfiles.
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/:GreattutorialsonusingtheHttpClientclasses,includingauthenticationandtheuseofcookies.
http://developer.android.com/guide/components/bound-services.html:AndroidDeveloperGuideonBoundServices.
SummaryThischapterwasallaboutservices,specifically:
WetalkedaboutconsumingexternalHTTPservicesusingtheApacheHttpClient.
WithregardtousingtheHttpClient,weshowedyouhowtodoHTTPGETcallsandHTTPPOSTcalls.
WealsoshowedyouhowtodomultipartPOSTs.
YoulearnedthatSOAPcanbedonefromAndroid,butit’snotthepreferredwaytocallwebservices.
WetalkedabouthowyoucouldsetupanInternetproxytomanageaSOAPserviceonyourapplication’sbehalffromaserversomewhere,soyourapplicationcanuseRESTfulservicestoyourproxyandkeeptheapplicationsimpler.
Wethencoveredexceptionhandlingandthelikelytypesofexceptionsthatyourapplicationislikelytoexperience(timeoutsmostly).
YousawhowtousetheThreadSafeClientConnManagertoshareacommonHttpClientinsideyourapplication.
Youlearnedhowtocheckandsettimeoutvaluesforconnectionstothenetwork.
Wecoveredacoupleofoptionsformakingconnectionstowebservices,includingHttpURLConnectionandAndroidHttpClient.
Weexplainedthedifferencebetweenlocalservicesandremoteservices.Localservicesareservicesthatareconsumedbythecomponents(suchasactivities)inthesameprocessastheservice.Remoteservicesareserviceswhoseclientsareoutsidetheprocesshostingtheservices.
Youlearnedthateventhoughaserviceismeanttobeonaseparatethread,itisstilluptothedevelopertocreateandmanagethebackgroundthreadsassociatedwithservices.
Youdiscoveredhowtostartandstoplocalservices,andhowtocreateandbindtoaremoteservice.
YousawhowtheNotificationManagerisusedtotrackrunningservices.
Wecoveredhowtopassdatatoaservice,usingParcelablesforthecomplextypes.
YoulearnedhowtouseMessengersandHandlerstocallremoteservices.
Chapter15
AdvancedAsyncTaskandProgressDialogsInChapter13,wecoveredhandlersandworkerthreadstorunlong-runningtaskswhilethemainthreadkepttheUIhouseinorder.AndroidSDKhasrecognizedthisasapatternandabstractedthehandlerandthreaddetailsintoautilityclasscalledAsyncTask.YoucanuseAsyncTasktoruntasksthattakelongerthanfivesecondsinthecontextofUI.(Wewillcoverhowtorunreallylong-runningtasks,rangingfromminutestoevenhours,through“Long-RunningReceiversandServices”inChapter16.)
ThischapterwillstartwiththebasicsofanAsyncTaskandmovetothecodeneededtopresentprogressdialogsandprogressbarsthatshowthestatusofanAsyncTaskcorrectlyevenifthedevicechangesitsconfiguration.Let’sstartbyintroducingtheAsyncTaskthroughpseudocodeinListing15-1.
Listing15-1.UsagePatternforanAsyncTaskbyanActivity
publicclassMyActivity{voidrespondToMenuItem(){//menuhandlerperformALongTask();}voidperformALongTask(){//usinganAsyncTask
//DerivefromanAsyncTask,andInstantiatethisAsyncTaskMyLongTaskmyLongTask=newMyLongTask(...CallBackObjects…);myLongTask.execute(...someargs…);//starttheworkonaworkerthread//havethemainthreadgetbacktoitsUIbusiness}
//HearbackfromtheAsyncTaskvoidsomeCallBackFromAsyncTask(SomeParameterizedTypex){
//AlthoughinvokedbytheAsyncTaskthiscoderunsonthemainthread.//reportbacktotheuseroftheprogress}}
UseofanAsyncTaskstartswithextendingfromAsyncTaskfirstliketheMyLongTaskinListing15-1.OnceyouhavetheAsyncTaskobjectinstantiated,youcancallexecute()methodonthatobject.Theexecute()methodinternallystartsaseparatethreadtodotheactualwork.TheAsyncTaskimplementationwillinturn
invokeanumberofcallbackstoreportthebeginningofthetask,theprogressofthetask,andtheendofthetask.Listing15-2showspseudocodetoextendanAsyncTaskandthemethodsthatneedtobeoverridden.(Pleasenotethatthisispseudocodeandnotintendedtobecompiled.The@overrideannotationisaddedtoexplicitlystatethattheyareoverriddenfromthebaseclass.)
Listing15-2.ExtendinganAsyncTask:AnExample
publicclassMyLongTaskextendsAsyncTask<String,Integer,Integer>{
//...constructorsstuff//Callingexecute()willresultincallingallofthesemethods@Override
voidonPreExecute(){}//Runsonthemainthread
//Thisiswhereyoudoalltheworkandrunsontheworkerthread@Override
IntegerdoInBackground(String…params){}
//Runsonthemainthreadagainonceitfinishes@Override
voidonPostExecute(Integerresult){}
//Runsonthemainthread@Override
voidonProgressUpdate(Integer…progressValuesArray){}
//....othermethods}
execute()methodinListing15-1iscalledonthemainthread.ThiscallwilltriggeraseriesofmethodsinListing15-2,startingwithonPreExecute().TheonPreExecute()iscalledonthemainthreadaswell.Youcanusethismethodtosetupyourenvironmenttoexecutethetask.Youcanalsousethismethodtosetupadialogboxorinitiateaprogressbartoindicatetotheuserthattheworkhasstarted.AfterthecompletionoftheonPreExecute(),execute()methodwillreturnandthemainthreadoftheactivitycontinueswithitsUIresponsibilities.Bythattimetheexecute()wouldhavespawnedanewworkerthreadsothatdoInBackground()methodisscheduledtobeexecutedonthatworkerthread.YouwilldoallyourheavyliftinginthisdoInBackground()method.Asthismethodrunsonaworkerthread,themainthreadisnotaffectedandyouwillnotgetthe“applicationnotresponding”message.FromthedoInBackground()methodyouhaveafacility(youwillseethisshortly)tocalltheonProgressUpdate()toreporttheprogress.ThisonProgressUpdate()methodrunsonthemainthreadsothatyoucanaffecttheUIonthemainthread.
EssentialsofaSimpleAsyncTaskLet’sgetintothedetailsofextendingtheAsyncTask.TheAsyncTaskclassuses
genericstoprovidetypesafetytoitsmethods,includingtheoverriddenmethods.Youcanseethesegenericswhenyoulookatthepartialdefinition(Listing15-3)oftheAsyncTaskclass.(PleasenotethatListing15-3isanextremelypruned-downversionoftheAsyncTaskclass.It’sreallyjusttheelementsofitsinterfacemostcommonlyusedbyclientcode.)
Listing15-3.AQuickLookattheAsyncTaskClassDefinition
publicclassAsyncTask<Params,Progress,Result>{
//AclientwillcallthismethodAsyncTask<Params,Progress,Result>execute(Params…params);
//Doyourworkhere.FrequentlytriggersonProgressUpdate()ResultdoInBackGround(Params…params);
//Callback:AftertheworkiscompletevoidonPostExecute(Resultresult);
//Callback:AstheworkisprogressingvoidonProgressUpdate(Progress…progressValuesArray);
}
StudyingListing15-3,youcanseethattheAsyncTask(throughgenerics)needsthefollowingthreeparameterizedtypes(Params,Progress,andResult)whenyouextendit.Let’sexplainthesetypesbriefly:
Params(Thetypeofparameterstotheexecute()method):WhenextendingAsyncTask,youwillneedtoindicatethetypeofparametersthatyouwillpasstotheexecute()method.IfyousayyourParamstypeisString,thentheexecute()methodwillexpectanynumberofstringsseparatedbycommasinitsinvocationsuchasexecute(s1,s2,s3)orexecute(s1,s2,s3,s4,s5).
Progress(Parametertypestotheprogresscallbackmethod):ThistypeindicatesthearrayofvaluespassedbacktothecallerwhilereportingprogressthroughthecallbackonProgressUpdate(Progress…progressValuesArray).Theabilitytopassanarrayofprogressvaluesallowssituationswheremultipleaspectsofataskcanbemonitoredandreportedon.Forexample,thisfeaturecouldbeusedifanAsyncTaskisworkingonmultiplesubtasks.
Result(TypeusedtoreporttheresultthroughonPostExecute()method):ThistypeindicatesthetypeofthereturnvaluethatissentbackasthefinalresultfromtheexecutionthroughthecallbackonPostExecute(Result
finalResult).
KnowingnowtheneededgenerictypesforanAsyncTask,supposewedecideonthefollowingparametersforourspecificAsyncTask:Params:AString,Result:Anint,Progress:AnInteger.Then,wecandeclareanextendedAsyncTaskclassasshowninListing15-4.
Listing15-4.ExtendingtheGenericAsyncTaskThroughConcreteTypes
publicclassMyLongTaskextendsAsyncTask<String,Integer,Integer>
{//...otherconstructorsstuff//...othermethods//ConcretemethodsbasedontheparameterizedtypesprotectedIntegerdoInBackground(String…params);protectedvoidonPostExecute(Integerresult);protectedvoidonProgressUpdate(Integer…progressValuesArray);
//....othermethods}
NoticehowthisconcreteclassinListing15-4,MyLongTask,hasdisambiguatedthetypenamesandarrivedatfunctionsignaturesthataretypesafe.
ImplementingYourFirstAsyncTaskLet’snowlookatasimple,butcomplete,implementationofMyLongTask.WehaveamplycommentedthecodeinListing15-5inlinetoindicatewhichmethodsrunonwhichthread.AlsopayattentiontotheconstructorofMyLongTaskwhereitreceivesobjectreferencesofthecallingcontext(usuallyanactivity)andalsoaspecificsimpleinterfacesuchasIReportBacktologprogressmessages.
TheIReportBackinterfaceisnotcriticaltoyourunderstandingbecauseitismerelyawrappertoalog.SameistruewiththeUtilsclassaswell.Youcanseetheseadditionalclassesinbothofthedownloadableprojectsforthischapter.TheURLforthedownloadableprojectsisinthereferencessectionattheendofthischapter.Listing15-5showsthecompletecodeforMyLongTask.
Listing15-5.CompleteSourceCodeforImplementinganAsyncTask
//ThefollowingcodeisinMyLongTask.java(ProAndroid5_Ch15_TestAsyncTask.zip)//Usemenuitem:TestAsync1toinvokethiscodepublicclassMyLongTaskextendsAsyncTask<String,Integer,Integer>
{IReportBackr;//aninterfacetoreportbacklog
messagesContextctx;//TheactivitytostartadialogpublicStringtag=null;//DebugtagProgressDialogpd=null;//Tostart,report,andstopaprogressdialog
//ConstructornowMyLongTask(IReportBackinr,ContextinCtx,StringinTag){r=inr;ctx=inCtx;tag=inTag;}//RunsonthemainuithreadprotectedvoidonPreExecute(){
Utils.logThreadSignature(this.tag);pd=ProgressDialog.show(ctx,"title","InProgress…",true);}//Runsonthemainuithread.TriggeredbypublishProgresscalledmultipletimesprotectedvoidonProgressUpdate(Integer…progress){
Utils.logThreadSignature(this.tag);Integeri=progress[0];r.reportBack(tag,"Progress:"+i.toString());}protectedvoidonPostExecute(Integerresult){
//RunsonthemainuithreadUtils.logThreadSignature(this.tag);r.reportBack(tag,"onPostExecuteresult:"+result);pd.cancel();}//Runsonaworkerthread.Mayevenbeapooliftherearemoretasks.protectedIntegerdoInBackground(String…strings){
Utils.logThreadSignature(this.tag);for(Strings:strings){Log.d(tag,"Processing:"+s);}for(inti=0;i<3;i++){Utils.sleepForInSecs(2);publishProgress(i);//thiscallsonProgressUpdate}return1;//thisvalueisthenpassedtotheonPostExecuteasinput}}
WewillgointothedetailsofeachofthemethodshighlightedinListing15-5after
coveringbrieflyhowaclientwouldmakeuseof(orcall)MyLongTask.
CallinganAsyncTaskOncewehavetheclassMyLongTaskimplemented,aclientwillutilizethisclassasshowninListing15-6.
Listing15-6.CallinganAsyncTask
//YouwillfindthisclassAsyncTester.java(ProAndroid5_Ch15_TestAsyncTask.zip)//Usemenuitem:TestAsync1toinvokethiscodevoidrespondToMenuItem(){//Aninterfacetologsomemessagesbacktotheactivity//Seedownloadableprojectifyouneedthedetails.IReportBackreportBackObject=this;Contextctx=this;//activityStringtag="Task1";//debugtag
//InstantiateandexecutethelongtaskMyLongTaskmlt=newMyLongTask(reportBackObject,ctx,tag);
mlt.execute("String1","String2","String3");
}
Noticehowtheexecute()methodiscalledinListing15-6.BecausewehaveindicatedoneofthegenerictypesasaStringandthattheexecute()methodstakesavariablenumberofargumentsforthistype,wecanpassanynumberofstringstotheexecute()method.IntheexampleinListing15-6,wehavepassedthreestringarguments.Youcanpassmoreorlessasyouneed.
Oncewecalltheexecute()methodontheAsyncTask,thiswillresultinacalltotheonPreExecute()methodfollowedbyacalltothedoInBackground()method.ThesystemwillalsocalltheonPostExecute()callbackoncethedoInBackground()methodfinishes.RefertoListing15-5forhowthesemethodsareimplemented.
UnderstandingtheonPreExecute( )CallbackandProgressDialogGoingbacktoMyLongTaskimplementationinListing15-5,intheonPreExecute()methodwestartedaprogressdialogtoindicatethatthetaskisinprogress.Figure15-1showsanimageofthatdialog.(UsemenuitemTestAsync1toinvokethisviewfromprojectdownloadProAndroid5_Ch15_TestAsyncTask.zip.)
Figure15-1.AsimpleprogressdialoginteractingwithanAsyncTask
Thecodesegment(takenfromListing15-5)thatshowstheprogressdialogisreproducedinListing15-7.
Listing15-7.ShowinganIndeterminateProgressDialog
pd=ProgressDialog.show(ctx,"title","InProgress…",true);
Thevariablepdwasalreadydeclaredintheconstructor(seeListing15-5).ThiscallinListing15-7willcreateaprogressdialoganddisplayitasshowninFigure15-1.Thelastargumenttotheshow()methodinListing15-7indicatesifthedialogisindeterminate(whetherthedialogcanestimatebeforehandhowmuchworkthereis).Wewillcoverthedeterministiccaseinalatersection.
NoteShowingprogressofanAsyncTaskreliablyisquiteinvolved.Thisisbecauseanactivitycancomeandgo,becauseofeitheraconfigurationchangeoranotherUItakingprecedence.Wewillcoverthisessentialneedandsolutionslaterinthechapter.
UnderstandingthedoInBackground( )MethodAllthebackgroundworkcarriedoutbytheAsyncTaskisdoneinthedoInBackground()method.ThismethodisorchestratedbytheAsyncTasktorunonaworkerthread.Asaresult,thisworkisallowedtotakemorethanfiveseconds,unliketheworkdoneonamainthread.
InourexamplefromListing15-5,inthedoInBackground()methodwesimplyretrieveeachoftheinputstringstothetaskasiftheyareanarray.Inthismethoddefinitionwehaven’tdefinedanexplicitstringarray.However,thesingleargumenttothisfunctionisdefinedasavariable-lengthargument,asshowninListing15-8.
Listing15-8.doInBackground()MethodSignature
protectedIntegerdoInBackground(String…strings)
Javathentreatstheargumentasifitisanarrayinsidethefunction.SoinourcodeinthedoInBackground()method,wereadeachofthestringsandlogthemtoindicatethatweknowwhattheyare.Wethenwaitlongenoughtosimulatealong-runningoperation.Becausethismethodisrunninginaworkerthread,wehavenoaccesstotheUIfunctionalityofAndroidfromthisworkerthread.Forinstance,youwon’tbeabletoupdateanyViewsdirectlyevenifyouhaveaccesstothemfromthisthread.YoucannotevensendaToastfromhere.Thenexttwomethodsallowustoovercomethis.
TriggeringonProgressUpdate( )throughpublishProgress( )InthedoInBackground()method,youcantriggeronProgressUpdate()bycallingthepublishProgress()method.ThetriggeredonProgressUpdate()methodthenrunsonthemainthread.ThisallowstheonProgressUpdate()methodtoupdateUIelementssuchasViewsappropriately.YoucanalsosendaToastfromhere.InListing15-5,wesimplylogamessage.Oncealltheworkisdone,wereturnfromthedoInBackground()methodwitharesultcode.
UnderstandingtheonPostExecute( )MethodTheresultcodefromthedoInBackground()methodisthenpassedtotheonPostExecute()callbackmethod.Thiscallbackisalsoexecutedonthemainthread.Inthismethod,wetelltheprogressdialogtoclose.Beingonthemainthread,youcanaccessanyUIelementsinthismethodwithnorestrictions.
UpgradingtoaDeterministicProgressDialogInthepreviousexampleinListing15-5,weusedaprogressdialog(Figure15-1)thatdoesn’ttelluswhatportionoftheworkiscomplete.Thisprogressdialogiscalledan
indeterminateprogressdialog.Ifyousettheindeterminatepropertytofalseonthisprogressdialog,youwillseeaprogressdialogthattracksprogressinsteps.ThisisshowninFigure15-2.(Usemenuitem“TestAsync2”toinvokethisviewfromprojectdownloadProAndroid5_Ch15_TestAsyncTask.zip.)
Figure15-2.Aprogressdialogshowingexplicitprogress,interactingwithanAsyncTask
Listing15-9showstheprevioustaskfromListing15-5rewrittentochangethebehavioroftheprogressdialogtoadeterministicprogressdialog.WehavealsoaddedanonCancelListenertoseeifweneedtocancelthetaskoncancellingthedialog.AusercanclickthebackbuttoninFigure15-2tocancelthedialog.KeyportionsofthecodearegiveninListing15-9(forthefullcode,seethedownloadfileProAndroid5_Ch15_TestAsyncTask.zip).
Listing15-9.ALongTaskUtilizingaDeterministicProgressDialog
//FollowingcodeisinMyLongTask1.java(ProAndroid5_Ch15_TestAsyncTask.zip)//Usemenuitem:TestAsync2toinvokethiscode
publicclassMyLongTask1extendsAsyncTask<String,Integer,Integer>implementsOnCancelListener{//..othercodetakenfromListing15-5//AlsorefertothejavaclassMyLongTask1.javainthedownloadableproject//forfullcodelisting.protectedvoidonPreExecute(){//....othercodepd=newProgressDialog(ctx);pd.setTitle("title");pd.setMessage("InProgress…");pd.setCancelable(true);pd.setOnCancelListener(this);pd.setIndeterminate(false);pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);pd.setMax(5);pd.show();}publicvoidonCancel(DialogInterfaced){r.reportBack(tag,"CancelCalled");this.cancel(true);}//..othercodetakenfromListing15-5}
NoticehowwehavepreparedtheprogressdialoginListing15-9.Inthiscasewehaven’tusedthestaticmethodshow(),incontrasttowhatwedidinListing15-5,ontheprogressdialog.Instead,weexplicitlyinstantiatedtheprogressdialog.Thevariablectxstandsforthecontext(oractivity)inwhichthisUIprogressdialogoperates.Thenweindividuallysetthepropertiesonthedialog,includingitsdeterministicorindeterminatebehavior.ThemethodsetMax()indicateshowmanystepstheprogressdialoghas.Wehavealsopassedtheselfreference(theAsyncTaskitself)asalistenerwhencancelistriggered.Inthecancelcallback,weexplicitlyissueacancelontheAsyncTask.Thecancel()methodwilltrytostoptheworkerthreadifwecallitwiththebooleanargumentoffalse.Abooleanargumentoftruewillforce-stoptheworkerthread.
AsyncTaskandThreadPoolsConsiderthecodeinListing15-10,whereamenuitemisinvokingtwoAsyncTasksoneaftertheother.
Listing15-10.InvokingTwoLong-RunningTasks
voidrespondToMenuItem(){
MyLongTaskmlt=newMyLongTask(this.mReportTo,this.mContext,"Task1");mlt.execute("String1","String2","String3");
MyLongTaskmlt1=newMyLongTask(this.mReportTo,this.mContext,"Task2");mlt1.execute("String1","String2","String3");}
Hereweareexecutingtwotasksonthemainthread.Youmayexpectthatboththetasksgetstartedclosetoeachother.Thedefaultbehavior,however,isthatthesetasksrunsequentiallyusingasinglethreaddrawnoutofapoolofthreads.Ifyouwantaparallelexecution,youcanusetheexecuteOnExecutor()methodontheAsyncTask.SeeSDKdocsfordetailsonthismethod.AlsoaspertheSDKdocumentation,itisnotvalidtocalltheexecute()methodmorethanonceonasingleAsyncTask.Ifyouwantthatbehavior,youhavetoinstantiateanewtaskandcalltheexecute()methodagain.
IssuesandSolutionsforCorrectlyShowingtheProgressofanAsyncTaskIfyourprimarygoalwiththischapteristolearnjusttheessentialsofAsyncTask,thenwhatwehavecoveredsofarissufficient.However,therearesomeissueswhenanAsyncTaskispairedwithaprogressdialogasshowninthepreviouslistingssofar.OneofthoseissuesisthatanAsyncTaskwilllosethecorrectactivityreferencewhenthedeviceisrotated,therebyalsolosingitsreferencetotheprogressdialog.Theotherissueisthattheprogressdialogweusedearlierinthecodeisnotamanageddialog.Let’sunderstandtheseissuesnow.
DealingwithActivityPointersandDeviceRotationTheactivitypointerthatisheldbytheAsyncTaskbecomesstalewhentheactivityisre-createdbecauseofaconfigurationchange.ThisisbecauseAndroidhascreatedanewactivityandtheoldactivityisnolongershownonthescreen.Soholdingontotheoldactivityanditscorrespondingdialogisbadforacoupleofreasons.ThefirstisthattheuserisnotseeingthatactivityordialogthattheAsyncTaskistryingtoupdate.ThesecondreasonisthattheoldactivityneedstobegarbagecollectedandyouarestoppingitfromgettinggarbagecollectedbecausetheAsyncTaskisholdingontoitsreference.IfyouweretobesmartanduseaJavaweakreferencefortheoldactivity,thenyouwouldn’tleakmemorybutyouwouldgetanullpointerexception.Thecaseofstalepointeristruenotonlyoftheactivitypointerbutanyotherpointerthatindirectlypointstotheactivity.
Therearetwowaystoaddressthestaleactivityreferenceissue.Therecommendedwayistouseheadlessretainedfragments.(FragmentsarecoveredinChapter8.Retainedfragmentsarefragmentsthatstayaroundwhiletheactivityisre-createdduetoa
configurationchange.Thesefragmentsarealsocalledheadlessbecausetheydon’tnecessarilyhavetoholdanyUI.)Anotherwaytosolvethestaleactivitypointersistousetheretainedobjectscallbackfromtheactivity.Wewillpresentbothoftheseapproachesforaddressingthestaleactivitypointerissue.
DealingwithManagedDialogsEvenifweareabletosolvethestaleactivityreferenceissueandreestablishtheconnectivitytothecurrentactivity,thereisaflawinthewayprogressdialogswereusedsofarinthischapter.WehaveinstantiatedaProgressDialogdirectly.AProgressDialogcreatedinthismannerisnota“managed”dialog.Ifitisnotamanageddialog,theactivitywillnotre-createthedialogwhenthedeviceundergoesrotationoranyotherconfigurationchange.So,whenthedevicerotatestheAsyncTaskisstillrunninguninterruptedbutthedialogwillnotshowup.Thereareacoupleofwaystosolvethisproblemaswell.TherecommendedwayisnottouseprogressdialogsbutinsteaduseanembeddedUIcontrolintheactivityitself,suchasaprogressbar.Becauseaprogressbarispartoftheactivityviewhierarchy,thehopeisthatitwillbere-created.Althoughaprogressbarsoundsgood,therearetimeswhenamodalprogressdialogmakesmoresense.Forexample,thatwouldbethecaseifyoudon’twanttheusertointeractwithanyotherpartoftheactivitywhiletheAsyncTaskisrunning.Inthosecases,weseelittlecontradictioninusingfragmentdialogsinsteadofprogressbars.
It’stimewestepintothesolutionstodealwiththeactivityreferencesissuesandthemanageddialogsissue.Wewillpresentthreedifferentsolutions.Thefirstoneusesretainedobjectsandfragmentdialogs.Thesecondoneusesheadlessretainedfragmentsandfragmentdialogs.Thethirdsolutionusesheadlessretainedfragmentsandprogressbars.
TestingScenariosforaWell-BehavedProgressDialogOfthethreesolutionswehaveinthischapter,whicheveryouusetocorrectlydisplayaprogressdialogforanAsyncTask,thesolutionshouldworkinallofthefollowingtestscenarios:
1. Withoutanorientationchangetheprogressdialogmuststart,showitsprogress,end,andalsocleanupthereferencetotheAsyncTask.Thismustworkrepeatedlytoshowthattherearenovestigesleftfromthepreviousrun.
2. Thesolutionshouldhandletheorientationchangeswhilethetaskisinthemiddleofitsexecution.Therotationshouldre-createthedialogandshowprogresswhereitleftoff.ThedialogshouldproperlyfinishandcleanuptheAsyncTaskreference.Thismustworkrepeatedlytoshowthattherearenovestigesleftbehind.
3. Thebackshouldbedisabledwhenthetaskstartstorun.
4. GoingHomeshouldbeallowedevenwhenthetaskisinthemiddle
ofexecution.
5. GoingHomeandrevisitingtheactivityshouldshowthedialogandcorrectlyreflectthecurrentprogress,andtheprogressshouldneverbelessthantheonebefore.
6. GoingHomeandrevisitingtheactivityalsoshouldworkwhenthetaskfinishesbeforereturning.ThedialogshouldbeproperlydismissedandtheAsyncTaskreferenceremoved.
ThissetoftestcasesshouldalwaysbeperformedforallactivitiesdealingwithAsyncTasks.Nowthatwehavelaidouthoweachsolutionshouldsatisfy,let’sstartwiththefirstsolution,theonethatusesretainedobjectsandfragmentdialogs.
UsingRetainedObjectsandFragmentDialogsInthisfirstsolution,let’sshowyouhowtouseretainedobjectsandfragmentdialogsfordisplayingprogresscorrectlyforanAsyncTask.Thissolutioninvolvesthefollowingsteps:
1. TheactivitymustkeeptrackofanexternalobjectthroughitsonRetainNonConfigurationInstance()callback.Thisexternalobjectmuststickaroundanditsreferencevalidatedastheactivityisclosedandbroughtback.Thatiswhythisobjectisreferredtoasaretainedobject.ThisretainedobjectcaneitherbetheAsyncTaskobjectitselforanintermediateobjectthatholdsareferencetotheAsyncTask.Let’scallthatarootretainedactivity-dependentobject(orarootRADO).Itiscalleda“root”becausetheonRetainNonConfigurationInstance()canuseonlyoneretainedobjectreference.
2. ArootRADOthenwillhaveapointertotheAsyncTaskandcansetandresettheactivitypointeronAsyncTaskastheactivitycomesandgoes.So,thisrootRADOactsasanintermediarybetweentheactivityandtheAsyncTask.
3. TheAsyncTaskthenwillinstantiateafragmentprogressdialoginsteadofaplainnon-managedprogressdialog.TheAsyncTaskwillusetheactivitypointerthatissetbytherootRADOtoaccomplishthis,asyouwillneedanactivitytocreateafragmentincludingafragmentdialog.
4. Theactivitywillre-createthedialogfragmentasitrotatesandkeepsitsstateproperlybecausedialogfragmentsaremanaged.TheAsyncTaskcanproceedtoincrementprogressonthefragmentdialogaslongastheactivityissetandavailable.Notethatthisdialogfragmentisnot,byitself,aretainedfragment.Itgetsre-createdaspartoftheactivitylifecycle.
5. ThefragmentdialogcanfurtherdisallowthecancelonitsothattheusercannotgobacktotheactivityfromthedialogwhiletheAsyncTaskisinprogress.
6. However,ausercangoHomebytappingHomeanduseotherapps.Thiswillpushouractivity,andthedialogwithit,intothebackground.Thismustbehandled.Whentheuserreturnstotheactivityorapp,thedialogcancontinuetoshowtheprogress.TheAsyncTaskmustknowhowtodismissthefragmentdialogifthetaskfinisheswhiletheactivityishidden.Beingafragmentdialog,dismissingthisdialogthrowsaninvalidstateexceptioniftheactivityisnotintheforeground.So,theAsyncTaskhastowaituntiltheactivityisreopenedandintherightstatetodismissthedialog.
ExploringCorrespondingKeyCodeSnippetsWewillpresentnowthekeypiecesofcodethatareusedtoimplementtheoutlinedapproach.TherestoftheimplementationcanbefoundinthedownloadableprojectProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zipforthischapter.Asallsolutionsforthisproblemrequirethedialogtobeafragmentdialog,sothatthedialogcanbemanaged,Listing15-11presentsthesourcecodeofthisfragmentdialogfirst.
Listing15-11.EncapsulatingaProgressDialoginaDialogFragment
//ThefollowingcodeisinProgressDialogFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)/***ADialogFragmentthatencapsulatesaProgressDialog.*Thisisnotexpectedtobearetainedfragmentdialog.*Getsre-createdasactivityrotatesfollowinganyfragmentprotocol.*/publicclassProgressDialogFragmentextendsDialogFragment{
privatestaticStringtag="ProgressDialogFragment";ProgressDialogpd;//WillbesetbyonCreateDialog
//ThisgetscalledfromADOssuchasretainedfragments//typicallydonewhenactivityisattachedbacktotheAsyncTaskprivateIFragmentDialogCallbacksfdc;publicvoidsetDialogFragmentCallbacks(IFragmentDialogCallbacks
infdc){
Log.d(tag,"attachingdialogcallbacks");fdc=infdc;}
//Thisisadefaultconstructor.Calledbytheframework
allthetime//forreintroduction.publicProgressDialogFragment(){
//Shouldbesafeformetosetcancelableasfalse;//wonderifthatiscarriedthroughrebirth?this.setCancelable(false);}//Onewayfortheclienttoattachinthebeginningwhenthefragmentisreborn.//ThereattachmentisdonethroughsetFragmentDialogCallbacks//Thisisashortcut.Yourcompilerifenabledforlintmaythrowanerror.//YoucanusethenewInstancepatternandsetbundle(seethefragmentschapter)publicProgressDialogFragment(IFragmentDialogCallbacksinfdc){
this.fdc=infdc;this.setCancelable(false);}/***Thiscangetcalledmultipletimeseachtimethefragmentis*re-created.Sostoringthedialogreferenceinalocalvariableshouldbesafe*/@OverridepublicDialogonCreateDialog(BundlesavedInstanceState){
Log.d(tag,"InonCreateDialog");pd=newProgressDialog(getActivity());pd.setTitle("title");pd.setMessage("InProgress…");pd.setIndeterminate(false);pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);pd.setMax(15);returnpd;}//Calledwhenthedialogisdismissed.Ishouldtellmycorrespondingtask//tocloseordotherightthing!Thisisdonethroughcallbacktofdc//fdc:fragmentdialogcallbackscouldbetheTask,orActivityortherootRADO//SeeListing15-12toseehowFDCisimplementedbythetask@OverridepublicvoidonDismiss(DialogInterfacedialog){
super.onDismiss(dialog);
Log.d(tag,"Dialogdismissed");if(fdc!=null){fdc.onDismiss(this,dialog);}}@OverridepublicvoidonCancel(DialogInterfacedialog){
super.onDismiss(dialog);Log.d(tag,"Dialogcancelled");if(fdc!=null){fdc.onCancel(this,dialog);}}//willbecalledbyaclientlikethetaskpublicvoidsetProgress(intvalue){
pd.setProgress(value);}}
CodeinListing15-11showshowtowraparegularnon-managedProgressDialoginamanagedfragmentdialog.WeextendaDialogFragmentandoverrideitsonCreateDialog()toreturntheProgressDialogobject.Inadditiontothatbasicfeature,weaddedtheabilitytomonitorwhentheprogressdialoggetsdismissedorcancelled.WealsoprovideasetProgress()methodonthewrappedclasstocallthesetProgress()ontheinternalProgressDialog.YoucanseethesourcecodefortheIFragmentDialogCallbacksinthedownloadableproject(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip),asitisnotthatcriticaltounderstandingthisfragmentprogressdialog.
Let’sseenowhowanAsyncTaskcancreateandcontrolthisfragmentprogressdialog.Listing15-12presentsthepseudocodefortheAsyncTaskinordertoaidthisunderstanding.Forthecompletesourcecode,refertodownloadableproject.
Listing15-12.PseudocodeforanAsyncTaskThatUsesaFragmentProgressDialog
//ThefollowingcodeisinMyLongTaskWithRADO.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithADOspublicclassMyLongTaskWithRADOextendsAsyncTask<String,Integer,Integer>implementsIRetainedADO,IFragmentDialogCallbacks{//....othercode@OverridepublicvoidonPreExecute(){
//....othercode//gettheactivityasitwouldhavebeensetbytherootRADO
Activityact=this.getActivity();
//CreatetheprogressdiaolgProgressDialogFragmentpdf=newProgressDialogFragment();//theshowmethodwilladdandcommitthefragmentdialogpdf.show(act.getFragmentManager(),this.PROGRESS_DIALOG_FRAGMENT_TAG_NAME);}@OverridepublicvoidonProgressUpdate(){
//ifactivityisavailable,getthefragmentdialogfromit//callsetProgress()onit//otherwiseignoretheprogress}@OverridepublicvoidonPostExecute(){
//ifactivityisinagoodstate//dismissthedialogandtelltherootRADOtodropthepointertotheAsyncTask//ifnotrememberitthroughaflagtocloseitwhenyoucomeback}@Overridepublicvoidattach(){
//calledwhentheactivityisback//checktoseeifyouaredone//ifsodismissthedialogandremoveyourselffromtheRADO//ifnotcontinuetoupdatetheprogress}}
BecausethisAsyncTaskimplementstheideaofaretainedactivity-dependentobject(IRetainedADO),itknowswhentheactivityisavailableandwhenitisnot.Italsoknowsthestateoftheactivity,suchaswhethertheUIisreadyornot.Althoughittakessomecodetoimplementactivity-dependentobjects(ADOs),itisnotahardconcept.WeleaveittoyouduetospaceconsiderationstolookintothedownloadableprojectProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zipandseehowthisisdone.
ThisAsyncTaskinListing15-12alsotakesoverthemanagementofitsfragmentdialogsothatitactslikeacohesiveunitandtherebydoesn’tcontaminatethemainactivitywiththedetailsofthisAsyncTask.AnotherkeydetailinListing15-12iswhathappenswhenthedialogisdismissedastheAsyncTaskfinishes.Atthisinstantiftheactivityishidden,ornotthereduetorotation,itisimportanttodismissthedialogwhentheactivityisre-created.Inordertodothis,theonPostExecute()remembersthelaststateoftheAsyncTaskwhetheritisdoneorinprogress.ThisAsyncTaskthenwaitsforthe
attach()method,whichgetscalledwhentheUIreadyactivityisreattachedtothisADO.Onceintheattach()method,theAsyncTaskcanthendismissthefragmentdialog.
YoucandownloadtheprojectnamedProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zipandseehowtheinteractionpresentedinListing15-12isfullyrealized.
Thisparticularapproachofusingretainedobjectsisabitinvolvedwhencomparedtousingretainedfragmentsinstead.ButithastheeleganceofsolvingitinamoregenericformusingtheideaofADOs,betheyfragmentsorotherwise.Wehavelinksinthereferencesectionthatoutlinethisideaandprovidebackground.Withthat,let’sturnourattentiontotherecommendedideaofretainedfragmentsinoursecondsolution.
UsingRetainedFragmentsandFragmentDialogsInthesecondsolution,wewillstickwiththefragmentdialogsbutwewilluseheadlessretainedfragmentsinsteadofsimpleretainedobjects.Androidhasdeprecatedtheretainedobjectsinfavorofretainedfragments.InAndroidaretainedobjectisjustanobjectandhasnoin-builtabilitytotrackthestateoftheactivity.(ThisiswhywehadtoinventtheframeworkofADOsontop.)ThisdeficiencyisnottherewiththeintroductionoffragmentsinlaterreleasesofAndroid.AlthoughfragmentsaretightlywovenintothefabricofUI,theycanexistwithoutUIaswell.Thesearecalledheadlessfragments.Inadditiontobeingabletotrackthestateoftheactivity,fragmentscanalsoberetained,muchlikeretainedobjects.
OutliningtheRetainedFragmentsApproachTheapproachinthissolutionistouseaheadlessretainedfragmentasananchortocommunicatebetweentheactivityandtheAsyncTask.Herearethekeyaspectsofthisapproach:
1. Continuetouseafragmentprogressdialog,asinthesolutionbefore.
2. HavetheactivitycreateaheadlessretainedfragmentwhichthenholdsapointertotheAsyncTask.Thisheadlessretainedfragmenttakestheplaceoftheretainedobjectintheprevioussolution.Beingaretainedfragment,thefragmentobjectsticksaroundwhiletheactivityisre-createdwithanewpointer.TheAsyncTaskthenalwaysreliesontheretainedfragmenttoretrievethemostcurrentactivitypointer.
3. TheAsyncTaskreliesontheheadlessretainedfragmenttobeinformedoftheactivitystatetoaccomplishallofthetestcasesindicatedintheprevioussolution.
ExploringCorrespondingKeyCodeSnippets
Wehavealreadyshownyouthecodeforthefragmentdialogduringtheprevioussolution.Aswecontinuetousethesameobjectinthissolution,wewillfocusontheretainedfragmentandalsohowtheAsyncTaskusesthefragmentdialogthroughtheretainedfragment.
Inthesampleprogram(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)wehaveprovidedinthedownload,wecalledtheretainedfragmentAsyncTesterFragment.Listing15-13showsthepseudocodeforthisclass,whichdemonstrates,amongotherthings,whatmakesthisclassaheadlessfragment.
Listing15-13.PseudocodeforaHeadlessFragment
//ThefollowingcodeisinAsyncTesterFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithFragmentpublicclassAsyncTesterFragment
extendsFragment(oranotherobjectthatisderivedfromFragment){//NoneedtooverridethekeyonCreateView()method
//whichotherwisewouldhavereturnedaviewloadedfromalayout.//ThushavingnoViewmakesthisfragmentaheadlessfragment
//UsethisnametoregisterwiththeactivitypublicstaticStringFRAGMENT_NAME=“AsyncTesterRetainedFragment”;
//Localvariablefortheasynctask.Youcanuseamenutostartworkonthistask//NullifythisreferencewhentheasynctaskfinishesMyLongTaskWithFragmentDialogtaskReference;
//Haveaninitmethodtohelpwithinheritancepublicvoidinit(arg1,arge2,etc){super.init(arg1,…);//ifthereisonesetArguments(….);//orpassthebundletothesuperinit}publicstaticAsyncTesterFragmentnewInstance(arg1,arg2,…){AsyncTesterFragmentf=newAsyncTesterFragment();f.init(arg1,arg2,…);}//havemorestaticmethodstocreatethefragment,locatethefragmentetc.
}
TherearethreethingsworthmentioningaboutthecodeinListing15-13.BynotoverridingtheonCreateView(),thisfragmentbecomesaheadlessfragment.Becauseafragmentgetsre-createdusingitsdefaultconstructor,wefollowedthenewInstance()patternandalsoextendedthatpatterntouseinit()methodswhichcanbevirtualandinherited.ThislatterapproachisusefulifyouareextendingtheFragmentclassinadeeperhierarchy.
Listing15-14showsastaticmethodonthisAsyncTesterFragmentobjectthatcancreatethisfragment,makeitretainitsstate,andthenregisteritwiththeactivity.
Listing15-14.RegisteringaFragmentasaRetainedFragment
//ThefollowingcodeisinAsyncTesterFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithFragmentpublicstaticAsyncTesterFragmentcreateRetainedAsyncTesterFragment(Activityact){AsyncTesterFragmentfrag=AsyncTesterFragment.newInstance();frag.setRetainInstance(true);
FragmentManagerfm=act.getFragmentManager();FragmentTransactionft=fm.beginTransaction();ft.add(frag,AsyncTesterFragment.FRAGMENT_TAG);ft.commit();returnfrag;}
Oncethisretainedfragmentisavailablewiththeactivity,itcanberetrievedanytimeandaskedtostartanAsyncTask.Listing15-15showsthepseudocodefortheAsyncTaskthatisabletointeractwiththisretainedfragmenttocontrolthefragmentdialog
Listing15-15.AnAsyncTaskThatUsesaFragmentDialogThroughaRetainedFragment
//ThefollowingcodeisinMyLongTaskWithFragment.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:FlipDialogwithFragmentpublicclassMyLongTaskWithFragmentextendsAsyncTask<String,Integer,Integer>{//...othercode//ThefollowingreferencepassedinandsetfromtheconstructorAsyncTesterFragmentretainedFragment;
//....othercode@OverrideprotectedvoidonPreExecute(){
....othercode//gettheactivityfromtheretainedfragmentActivityact=retainedFragment.getActivity();//CreatetheprogressdialogProgressDialogFragmentpdf=newProgressDialogFragment();//theshowmethodwilladdandcommitthefragmentdialogpdf.show(act.getFragmentManager(),this.PROGRESS_DIALOG_FRAGMENT_TAG_NAME);}@OverrideprotectedvoidonProgressUpdate(){//ifactivityisavailable,getthefragmentdialogfromit,callsetProgress()onit//otherwiseignoretheprogress}@OverrideprotectedvoidonPostExecute(){//ifactivityisinagoodstate//dismissthedialogandtelltherootRADOtodropthepointertotheAsyncTask//ifnotrememberitthroughaflagtocloseitwhenyoucomeback}@Overridepublicvoidattach(){//calledwhentheactivityisback.checktoseeifthistaskisdone//ifsodismissthedialogandremoveyourselffromtheretainedfragment//ifnotcontinuetoupdatetheprogress}@OverrideprotectedIntegerdoInBackground(String…strings){//Dotheactualworkherewhichoccursonaseparatethread}}
ThisAsyncTaskinListing15-15behavesmuchliketheAsyncTaskthatusedtheretainedobject.Oncethistaskknowshowtogetaccesstotheprogressdialogfragmentfromtheretainedfragment,itisprettystraightforwardtosettheprogressonit.Asbefore,thistaskalsoneedstoknowwhentheactivityisreattachedincasethetaskisdonebeforehand.Ifthishappens,theAsyncTaskneedstorememberthisandclosethedialogonreattach.ThepseudocodeinListing15-15satisfiesallthetestconditionssetforthearlier.
Thisconcludesoursecondsolution.Let’sshiftnowtothethirdsolution,wherewewilluseaprogressbarinsteadofaprogressdialogtoshowtheprogressofanAsyncTask.
UsingRetainedFragmentsandProgressBarsAndroidSDKdocumentationonProgressDialog(http://developer.android.com/guide/topics/ui/dialogs.html)isrecommendingthatweuseaProgressBarinanumberofscenariosinsteadasabetterpractice.Thepurportedreasonisthataprogressbarislessintrusive,asitallowsinteractionwithotherareasoftheactivity.Aprogressbar,likeaprogressdialog,canbeindeterminateorfixedinduration.Itcanalsobeacontinuouslyrevolvingcircleorahorizontalbar.YoucanfindthesemodesbylookingupthedocsforProgressBar.Listing15-16givesaquickrundownofasamplingofProgressBarstylesinalayoutfile.
Listing15-16.DifferentWaystoStyleaProgressBarinaLayoutFile
//Thefollowingcodeisinspb_show_progressbars_activity_layout.xml//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanseetheseprogressbarsthroughmenuitem:ShowProgressbars<!--Aregularprogressbar-Alargespinningcircle--><ProgressBarandroid:id="@+id/tpb_progressBar1"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/background_light"/>
<!--Smallspinningcircle--><ProgressBarandroid:id="@+id/tpb_progressBar4"style="?android:attr/progressBarStyleSmall"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/background_light"/>
<!--HorizontalindefiniteProgressbar:aline--><ProgressBarandroid:id="@+id/tpb_progressBar3"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:indeterminate="true"/>
<!--HorizontalfixeddurationProgressbar:aline--><ProgressBarandroid:id="@+id/tpb_progressBar3"style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"android:layout_height="wrap_content"android:indeterminate="false"android:max="50"android:progress="10"/>
Figure15-3showshowtheprogressbarlayoutsshowninListing15-16lookwhenloadedintoanactivity.Eachtypeofprogressbarislabeledtoindicateitsmodeorbehavior.(UsemenuitemShowProgressBarstoinvokethisviewfromprojectdownloadProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip.)
Figure15-3.AsamplingofprogressbarsinAndroid
OutliningtheProgressBarApproachTheapproachtoreporttheprogressofanAsyncTaskthroughaprogressbarissimilartothepreviousapproachthatusedaretainedheadlessfragmentandafragmentprogressdialog.
1. Asintheprevioussolution,havetheactivitycreateaheadlessretainedfragmentthatholdsapointertotheAsyncTask.
2. Embedtheprogressbarintheactivitylayout.AsyncTaskwillgettothisprogressbarthroughtheheadlessretainedfragment.
3. TheAsyncTaskreliesontheheadlessretainedfragmenttobe
informedoftheactivitystatetoaccomplishallofthetestcasesindicatedearlier.
WalkingThroughCorrespondingKeyCodeSnippetsLet’swalkthroughthekeycodesnippetsthatyouwouldneedtomakethissolutionwork.Let’sbeginwiththelocalvariablestheAsyncTaskholdstointeractwiththeretainedfragmentandtheactivity(Listing15-17).
Listing15-17.LocalVariablesofanAsyncTasktoWorkwithaProgressBar
//ThefollowingcodeisinMyLongTaskWithProgressBar.java//(ProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip)//Youcanstartthistaskthroughmenuitem:TestProgressBarpublicclassMyLongTaskWithProgressBarextendsAsyncTask<String,Integer,Integer>implementsIWorkerObject{publicStringtag=null;//DebugtagprivateMonitoredFragmentretainedFragment;//ReferencetotheretainedfragmentintcurProgress=0;//Totrackcurrentprogress....
Listing15-18showshowtheAsyncTaskinitializestheprogressbarwhenitstarts.
Listing15-18.InitializingaProgressBar
//PartofMyLongTaskWithProgressBar.javaprivatevoidshowProgressBar(){Activityact=retainedFragment.getActivity();ProgressBarpb=(ProgressBar)act.findViewById(R.id.tpb_progressBar1);pb.setProgress(0);pb.setMax(15);pb.setVisibility(View.VISIBLE);}
Listing15-19showshowtheAsyncTasksetstheprogressontheprogressbarafterlocatingit.
Listing15-19.SettingProgressonaProgressBar
//PartofMyLongTaskWithProgressBar.javaprivatevoidsetProgressOnProgressBar(inti){this.curProgress=i;ProgressBarpbar=getProgressBar();if(pbar==null){
Log.d(tag,"Activityisnotavailabletosetprogress");return;}pbar.setProgress(i);}
ThemethodgetProgressBar()thatlocatestheactivityisquitesimple;youjustusethefind()methodtolocatetheProgressBarview.Iftheactivityisnotavailableduetodevicerotation,theProgressBarreferencewillbenullandwewillignoresettingtheprogress.Listing15-20showshowtheAsyncTaskclosestheprogressbar.
Listing15-20.ClosingtheProgressBaronAsyncTaskCompletion
//PartofMyLongTaskWithProgressBar.javaprivatevoidcloseProgressBar(){ProgressBarpbar=getProgressBar();if(pbar==null){Log.d(tag,"Sorryprogressbarisnulltocloseit!");return;}//Dismissthedialogpbar.setVisibility(View.GONE);detachFromParent();}
OncetheProgresBarisremovedfromtheview,thecodeinListing15-20informstheretainedfragmentthatitcanletgooftheAsyncTaskpointershoulditbeholdingit.Dependingonhowtheretainedfragmentholdsthispointer,thisstepmayormaynotbeneeded.Butitisagoodpracticetotelltheparentitnolongerneedstoholdontoareferencethatitdoesn’tneedanymore.So,Listing15-21showshowtheAsyncTaskinformstheparentthatitnolongerneedstoholdapointertotheAsyncTask.
Listing15-21.InformingClients,LiketheRetainedFragment,oftheCompletionofAsyncTask
//TotellthecalledobjectthatI,theAsyncTask,havefinished//TheActivityorretainedfragmentcanactasaclienttothisAsyncTask//AsyncTaskisimaginedtobeaWorkerObjectandhenceunderstandstheIWorkerObjectClient
//MyLongTaskWithProgressBarimplementsIWorkerObject//AsyncTesterFragmentimplementstheIWorkerObjectClient
//CodebelowistakenfromMyLongTaskWithProgressBar.java//ThisimplementstheIWorkerObjectcontract
IWorkerObjectClientclient=null;intworkerObjectPassbackIdentifier=-1;
publicvoidregisterClient(IWorkerObjectClientwoc,intinWorkerObjectPassbackIdentifier){client=woc;this.workerObjectPassbackIdentifier=inWorkerObjectPassbackIdentifier;}privatevoiddetachFromParent(){if(client==null){Log.e(tag,"Youhavefailedtoregisteraclient.");return;}//clientisavailableclient.done(this,workerObjectPassbackIdentifier);}
AddressingKeyDifferenceswiththeProgressBarSolutionTherearesomeunexpecteddifferencesyoumustbeawareofwhenweuseaprogressbarinsteadofaprogressdialog.
Initially,inthelayoutfile,visibilityoftheprogressbarissettoGONEsothatitrepresentsthestatethattheAsyncTaskhasnotevenstarted.OncetheAsyncTaskstartsitwillsetthevisibilitytoVISIBLEandsubsequentlysettheprogressasitgoesalong.However,whentheactivityisre-created,thestatemanagementoftheactivityrequiresthatthecontrolisvisiblecomingoutoftheonCreate()method.BecauseinthelayoutthevisibilityissettobeGONE,theactivitywillnotrestoretheprogressbarstateandyouwillnotseetheprogressbarwhenthedeviceisrotated.Becauseofthis,theAsyncTaskneedstotakeoverthecontrolofthisprogressbarstatemanagementandreinitializeitproperlywhentheactivityisreattached.Listing15-22showshowwedothisintheAsyncTaskcode.
Listing15-22.ManagingtheProgressBarStatefromtheAsyncTask
//TakenfromMyLongTaskWithProgressBar.java//OnactivitystartpublicvoidonStart(Activityact){//dismissdialogifneededif(bDoneFlag==true){Log.d(tag,"OnmystartInoticeIwasdoneearlier");closeProgressBar();return;}Log.d(tag,"Iamreattached.Iamnotdone");setProgressBarRightOnReattach();
}privatevoidsetProgressBarRightOnReattach(){ProgressBarpb=getProgressBar();pb.setMax(15);pb.setProgress(curProgress);pb.setVisibility(View.VISIBLE);}
TheonStart()methodinListing15-22iscalledbytheretainedfragmentontheAsyncTaskwhentheactivityisreattachedtotheretainedfragmentandthefragmentdetectsthattheactivity’sUIisreadytobeused.
Anotherdifferencewhenusingaprogressbaristhebehaviorofthebackbutton.Unlikeaprogressdialog,fortheactivity,youmaywanttoallowthebackbutton.Asthebackbuttoncompletelyremovestheactivity,youmaywanttotakethisopportunitytocancelthetask.ThereleaseResources()methodinListing15-23iscalledbytheretainedfragmentwhenitdetectsthattheactivityisnotgoingtobebackbymonitoringtheisFinishing()flagintheonDestroy()method.
Listing15-23.CancellingtheAsyncTaskonActivityBack
//TakenfromMyLongTaskWithProgressBar.javapublicvoidreleaseResources(){cancel(true);//cancelthetaskdetachFromParent();//removemyself}
AllthreesolutionsoutlinedinthislatterpartofthechapterwillworktocorrectlyshowtheprogressofanAsyncTask.TheSDK-recommendedapproachistousetheProgressBarastherightUIcomponenttodisplaytheprogress.Ourpreferenceforquicktasksthattakejustasecondortwoistousetheprogressbarsaswell.Forataskthattakesalittlelonger—andyoudon’twanttheusertodisturbthestateoftheUI—thenusetheProgressDialoginconjunctionwithaheadlessretainedfragment.Whenyoursolutionsrequireadeephierarchyofobjects,thenuseoftheADOframeworkcouldcomehandyirrespectiveofwhetheryouusethemthroughretainedfragmentsorthroughtheretainedobjects.YoucanseeallofthesolutionsoutlinedherefullyimplementedinthedownloadableprojectProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.zip.
TherearefurtherconsiderationsiftheAsyncTaskweredoingupdatesandchangingstate.Ifthatisthecase,youmaywanttouseabackgroundservicesothatitcanberestartediftheprocessistobereclaimedandrestartedlater.Theapproachespresentedhereareadequateforquick-tomedium-levelreadsasyouareexpectingtheusertowait.However,forlonger-timereadsorwrites,youmaywanttoadaptaservice-basedsolution.
References
Thefollowingreferenceswillhelpyoulearnmoreaboutthetopicsdiscussedinthischapter:
http://developer.android.com/reference/android/os/AsyncTask.htmlAkeyresourcethatdefinitivelydocumentsthebehaviorofAsyncTask.
http://www.shanekirk.com/2012/04/asynctask-missteps/:Anotherlookatawell-behavedAsyncTask.
http://www.androidbook.com/item/3536:ResearchnotesonAsyncTaskthatwegatheredinpreparingthischapter.
http://www.androidbook.com/item/3537:AndroidusesJavagenericsofteninitsAPI.ThisURLdocumentsafewbasicsonJavagenericstogetyoustarted.
http://www.androidbook.com/fragments:Asthischapterhasdemonstrated,toworkwithanAsyncTaskauthoritativelyyouneedtoknowalotaboutactivitylifecycle,fragments,theirlifecycle,headlessfragments,configurationchanges,fragmentdialogs,AsyncTask,ADOs,andmore.ThisURLhasanumberofarticlesfocusingonalltheseareas.
http://www.androidbook.com/item/4660:ADOisanabstractionthatoneofourauthorsespousedasahandytooltodealwithconfigurationchange.ThisURLdocumentswhatADOsareandhowtheycouldbeused,anditalsoprovidesapreliminaryimplementation.
http://www.androidbook.com/item/4674:ThisURLdocumentsthebackground,helpfulURLs,codesnippets,andhelpfulhintstoworkwithaProgressBar.
http://www.androidbook.com/item/4680:ThisURLhasagoodbitofresearchonactivitylifecycleintheeventofconfigurationchanges.
http://www.androidbook.com/item/4665:Itisquitehardtowriteprogramsthatworkwellwhendevicesrotate.ThisURLoutlinessomebasictestcasesyoumustsuccessfullyrunforvalidatingAsyncTask.
http://www.androidbook.com/item/4673:ThisURLsuggestsanenhancedpatternforconstructinginheritedfragments.
http://www.androidbook.com/item/4629:Thebestwaytounderstandafragment,includingaretainedfragment,istostudyitscallbacksdiligently.ThisURLprovidesdocumentedsamplecodeforalltheimportantcallbacksofafragment.
http://www.androidbook.com/item/4668:Thebestwaytounderstandanactivitylifecycleisstudyitscallbacksdiligently.ThisURLprovidesdocumentedsamplecodeforalltheimportantactivitycallbacks.
http://www.androidbook.com/item/3634:ThisURLoutlinesourresearchonfragmentdialogs.
http://www.androidbook.com/proandroid5/projects:AlistofdownloadableprojectsfromthisbookisatthisURL.Forthischapter,lookforazipfilenamedProAndroid5_Ch15_TestAsyncTask.zipandalsoProAndroid5_Ch15_TestAsyncTaskWithConfigChanges.Thelatterzipfileistheonethatimplementsthethreeproposedsolutionsforawell-behavedAsyncTask.
SummaryInthischapter,inadditiontocoveringAsyncTask,wehaveintroducedyoutoprogressdialogs,progressbars,headlessretainedfragments,andADOs.Readingthischapter,younotonlyunderstoodAsyncTaskbutalsogottoapplyyourunderstandingofactivitylifecycleandadeepunderstandingoffragments.Wehavealsodocumentedasetofkeytestcasesthatmustbesatisfiedforawell-behavedAndroidapplication.
Chapter16
BroadcastReceiversandLong-RunningServicesAbroadcastreceiverisanothercomponentinanAndroidprocess,alongwithactivities,contentproviders,andservices.Abroadcastreceiverisacomponentthatcanrespondtoabroadcastmessagesentbyaclient.Thismessageismodeledasanintent.Further,abroadcastmessage(intent)canberespondedtobymorethanonereceiver.
AclientcomponentsuchasanactivityoraserviceusesthesendBroadcast(intent)method,availableontheContextclass,tosendabroadcast.ReceivingcomponentsofthebroadcastintentwillneedtoinheritfromaBroadcastReceiverclassavailableintheAndroidSDK.Thesebroadcastreceiversneedtoberegisteredinthemanifestfilethroughareceivercomponenttagtoindicatethatthereceiverisinterestedinrespondingtoacertaintypeofbroadcastintent.
SendingaBroadcastListing16-1showssamplecodethatsendsabroadcastevent.Thiscodecreatesanintentwithauniqueintentactionstring,putsanextrafieldcalledmessageonit,andcallsthesendBroadcast()method.Puttingtheextraontheintentisoptional.
Listing16-1.BroadcastinganIntent
//Thiscodeisinclass:TestBCRActivity.java//Project:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zipprivatevoidtestSendBroadcast(Activityactivity){//CreateanintentwithauniqueactionstringStringuniqueActionString="com.androidbook.intents.testbc";IntentbroadcastIntent=newIntent(uniqueActionString);
//Allowstandalonecross-processesthathavebroadcastreceivers//inthemtobestartedeventhoughtheyareinstoppedstate.broadcastIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
broadcastIntent.putExtra("message","Helloworld");activity.sendBroadcast(broadcastIntent);}
InListing16-1,theactionisanarbitraryidentifierthatsuitsyourneeds.Tomakethisactionstringunique,youmaywanttouseanamespacesimilartoaJavapackage.Also,wewilltalkaboutthecross-processFLAG_INCLUDE_STOPPED_PACKAGESlaterinthischapterinthesectioncalled“Out-of-ProcessReceivers.”
CodingaSimpleReceiverListing16-2showsabroadcastreceiverthatcanrespondtothebroadcastedintentfromListing16-1.
Listing16-2.SampleBroadcastReceiverCode
//ThisclassisinTestBroadcastReceiverprojectinthedownload//Thedownloadforthischapteris:ProAndroid5_Ch16_TestReceivers.zippublicclassTestReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="TestReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d("TestReceiver","intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);}}
Creatingabroadcastreceiverisquitesimple.ExtendtheBroadcastReceiverclassandoverridetheonReceive()method.Weareabletoseetheintentinthereceiverandextractthemessagefromit.Nextweneedtoregisterthebroadcastreceiverinthemanifestfileasareceiver.
RegisteringaReceiverintheManifestFileListing16-3showshowyoucandeclareareceiverastherecipientoftheintentwhoseactioniscom.androidbook.intents.testbc.
Listing16-3.AReceiverDefinitionintheManifestFile
<!--Infilename:AndroidManifest.xmlProject:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zip--><manifest><application>...
<activity>...</activity>...<receiverandroid:name=".TestReceiver"><intent-filter><actionandroid:name="com.androidbook.intents.testbc"/></intent-filter></receiver>...</application></manifest>
Thereceiverelementisachildnodeoftheapplicationelementliketheothercomponentnodessuchasanactivity.
Withthereceiver(Listing16-2)anditsregistrationinthemanifestfile(Listing16-3)available,youcaninvokethereceiverusingtheclientcodeinListing16-1.WehaveincludedareferencetothedownloadableZIPfileProAndroid5_Ch16_TestReceivers.zipforthischapterattheendofthischapter.ThisZIPfilehastwoprojects.ThecodereferencedsofarisintheprojectTestBroadcastReceiver.
AccommodatingMultipleReceiversTheideaofabroadcastisthattherecouldbemorethanonereceiver.Let’sreplicateTestReceiver(seeListing16-2)asTestReceiver2andseeifbothcanrespondtothesamebroadcastmessage.ThecodeforTestReceiver2ispresentedinListing16-4.
Listing16-4.SourcecodeforTestReceiver2
//Filename:TestReceiver2.java//Project:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassTestReceiver2extendsBroadcastReceiver{privatestaticfinalStringtag="TestReceiver2";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d(tag,"intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);}}
AddthisreceivertoyourmanifestfileasshowninListing16-5.
Listing16-5.TestReceiver2DefinitionintheManifestFile
<!--Infilename:AndroidManifest.xmlProject:TestBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zip--><receiverandroid:name=".TestReceiver2"><intent-filter><actionandroid:name="com.androidbook.intents.testbc"/></intent-filter></receiver>
Now,ifyoufireofftheeventasinListing16-1,bothreceiverswillbecalled.
WehaveindicatedinChapter13thatthemainthreadrunsallthebroadcastreceiversthatbelongtoasingleprocess.Youcanprovethisbyprintingoutthethreadsignatureineachofthereceivers,includingthemainlineinvokingcode.Youwillseethesamethreadrunningthroughthiscodesequentially.ThesendBroadcast()queuesthebroadcastmessageandletsthemainthreadgetbacktoitsqueue.Responsetothisqueuedmessagebyareceiveriscarriedoutbythesamemainthreadinorder.Whentherearemultiplereceivers,itisnotgooddesigntorelyontheorderofexecutionastowhichreceiverisinvokedfirst.
WorkingwithOut-of-ProcessReceiversTheintentionofabroadcastismorelikelythattheprocessrespondingtoitisanunknownoneandseparatefromtheclientprocess.Youcanprovethisbyreplicatingoneofyourreceiverspresentedsofarandcreatingaseparate.apkfilefromit.ThenwhenyoufireofftheeventfromListing16-1,youwillseethatboththein-processreceivers(thosethatareinthesameprojector.apkfile)andout-of-processreceivers(thosethatareinaseparate.apkfile)areinvoked.YouwillalsoseethroughtheLogCatmessagesthatthein-processandout-of-processreceiversrunintheirrespectivemainthreads.
However,afterAPI12(Android3.1)therearesomewrinklesaroundbroadcastreceiversthatareinexternalprocesses.ThisisduetothelaunchmodeladaptedbytheSDKforsecurityconcerns.Youcanreadaboutthismoreinoneofthereferencelinksprovidedforthischapter.Withthischangeanapplicationwheninstalledwillbeinastoppedstate.Intentsthatcanstartcomponentscannowspecifytotargetthoseapplicationsthatareonlyinstartedstate.Bydefaulttheoldbehaviorpersists.However,forbroadcastintentsthesystemautomaticallyaddsaflagtoexcludeapplicationsthatareinstoppedstate.Toovercomethepreviouspoint,onecanexplicitlysetanintentflagonthebroadcastintenttoincludethosestoppedapplicationsasvalidtargets.ThisiswhatyouseeincodeListing16-1.
Wehaveincludedanadditionalseparatestand-aloneprojectcalledStandaloneBroadcastReceiverinthechapter’sdownloadableZIPfileProAndroid5_Ch16_TestReceivers.ziptotestthisconcept.Totryit,youhave
todeployboththeinvokingprojectTestBroadcastReceiverandthestand-alonereceiver’sprojectStandloneBroadcastReceiverontheemulator.YoucanthenusetheTestBroadcastReceiverprojecttosendthebroadcasteventandmonitortheLogCatforthereceiversrespondingfromtheStandaloneBroadcastReceiver.
UsingNotificationsfromaReceiverBroadcastreceiversoftenneedtocommunicatetotheuseraboutsomethingthathappenedorasastatus.Thisisusuallydonebyalertingtheuserthroughanotificationiconinthesystem-widenotificationbar.Wewillnowshowyouhowtocreateanotificationfromabroadcastreceiver,sendit,andviewitthroughthenotificationmanager.
MonitoringNotificationsThroughtheNotificationManagerAndroidshowsiconsofnotificationsasalertsinthenotificationarea.ThenotificationareaislocatedatthetopofdeviceinastripthatlookslikeFigure16-1.ThelookandplacementofthenotificationareamaychangebasedonwhetherthedeviceisatabletoraphoneandmayattimesalsochangebasedonAndroidrelease.
Figure16-1.Androidnotificationiconstatusbar
ThenotificationareashowninFigure16-1iscalledthestatusbar.Itcontainssystemindicatorssuchasbatterystrength,signalstrength,andsoon.Whenwedeliveranotification,thenotificationwillappearasaniconintheareashowninFigure16-1.ThenotificationiconisillustratedinFigure16-2.
Figure16-2.Statusbarshowinganotificationicon
Thenotificationiconisanindicatortotheuserthatsomethingneedstobeobserved.Toseethefullnotification,youhavetoholdafingerontheiconanddragthetitlestripshowninFigure16-2downlikeacurtain.Thiswillexpandthenotificationarea,asshowninFigure16-3.
Figure16-3.Expandednotificationview
IntheexpandedviewofthenotificationinFigure16-3,yougettoseethedetailssuppliedtothenotification.Youcanclickthenotificationdetailtofireofftheintenttobringupthefullapplicationtowhichthenotificationbelongs.Youcanusethisviewtoclearnotifications.Alsodependingonthedeviceandreleasetheremaybealternatewaysofopeningthenotifications.Let’sseenowhowtogenerateanotificationiconliketheoneshowninFigures16-2and16-3.
SendingaNotificationWhenyoucreateanotificationobject,itneedstohavethefollowingelements:
Anicontodisplay
Tickertextlike“helloworld”
Thetimewhenitisdelivered
Onceyouhaveanotificationobjectconstructed,yougetthenotificationmanagerreferencebyaskingthecontextforasystemservicenamedContext.NOTIFICATION_SERVICE.Thenyouaskthenotificationmanagertosendthenotification.Listing16-6hasthesourcecodeforabroadcastreceiverthatsendsthenotificationshowninFigures16-2and16-2.
Listing16-6.AReceiverThatSendsaNotification
//Filename:NotificationReceiver.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassNotificationReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="NotificationReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){
Log.d(tag,"intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);this.sendNotification(context,message);}privatevoidsendNotification(Contextctx,Stringmessage){//Getthenotificationmanager
Stringns=Context.NOTIFICATION_SERVICE;NotificationManagernm=(NotificationManager)ctx.getSystemService(ns);//PrepareNotificationObjectDetails
inticon=R.drawable.robot;CharSequencetickerText="Hello";longwhen=System.currentTimeMillis();//Gettheintenttofirewhenthenotificationisselected
Intentintent=newIntent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.google.com"));PendingIntentpi=PendingIntent.getActivity(ctx,0,intent,0);//Createthenotificationobjectthroughthebuilder
Notificationnotification=newNotification.Builder(ctx).setContentTitle("title").setContentText(tickerText).setSmallIcon(icon).setWhen(when).setContentIntent(pi)
.setContentInfo("AddtionalInformation:ContentInfo").build();//Sendnotification
//Thefirstargumentisauniqueidforthisnotification.//Thisidallowsyoutocancelthenotificationlater//Thisidalsoallowsyoutoupdateyournotification//bycreatinganewnotificationandresendingitagainstthatid//Thisidisuniquewithinthisapplicationnm.notify(1,notification);}}
Thecontentviewofanotificationisdisplayedwhenthenotificationisexpanded.ThisiswhatyouseeinFigure16-2.ThecontentviewneedstobeaRemoteViewsobject.However,wedon’tpassacontentviewdirectly.BasedontheparameterspassedtotheBuilderobject,theBuilderobjectcreatesanappropriateRemoteViewsobjectandsetsitonthenotification.TheBuilderinterfacealsohasamethodtodirectlysetthecontentviewasawholeifneeded.
Thestepsfordirectlyusingremoteviewsforacontentviewofanotificationareasfollows:
1. Createalayoutfile.
2. CreateaRemoteViewsobjectusingthepackagenameandthelayoutfileID.
3. CallsetContent()ontheNotification.Builderobjectbeforecallingthebuild()methodtocreatethenotificationobject,whichisthensenttothenotificationmanager.
Keepinmindthatonlyalimitedsetofcontrolsmayparticipateinaremoteview,suchasFrameLayout,LinearLayout,RelativeLayout,AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar,TextView.
ThecodeinListing16-6createsanotificationusingtheBuilderobjecttosettheimplicitcontentview(throughtitleandtext)andtheintenttofire(inourcase,thisintentisthebrowserintent).AnewnotificationcanbecreatedtoberesentthroughthenotificationmanagerinordertoupdatethepreviousinstanceofitusingtheuniqueIDofthenotification.TheIDofthenotification,whichissetto1inListing16-6,isuniquewithinthisapplicationcontext.Thisuniquenessallowsustocontinuouslyupdatewhatishappeningtothatnotificationandalsocancelitifneeded.
Youmayalsowanttolookatthevariousflagsavailablewhilecreatinganotification,suchasFLAG_NO_CLEARandFLAG_ONGOING_EVENT,tocontrolthepersistenceofthesenotifications.YoucanusethefollowingURLtochecktheseflags:
http://developer.android.com/reference/android/app/Notification.html
StartinganActivityinaBroadcastReceiverAlthoughyou’readvisedtousethenotificationmanagerwhenauserneedstobeinformed,Androiddoesallowyoutospawnanactivityexplicitly.YoucandothisbyusingtheusualstartActivity()methodbutwiththefollowingflagsaddedtotheintentthatisusedastheargumenttothestartActivity():
Intent.FLAG_ACTIVITY_NEW_TASK
Intent.FLAG_FROM_BACKGROUND
Intent.FLAG_ACTIVITY_SINGLE_TOP
ExploringLong-RunningReceiversandServicesSofar,wehavecoveredthehappypathofbroadcastreceiverswheretheexecutionofabroadcastreceiverisunlikelytotakemorethan10seconds.Theproblemspaceisabitcomplicatedifwewanttoperformtasksthattakelongerthan10seconds.
Tounderstandwhy,let’sreviewafewfactsaboutbroadcastreceivers:
Abroadcastreceiver,likeothercomponentsofanAndroidprocess,runsonthemainthread.HenceholdingupthecodeinabroadcastreceiverwillholdupthemainthreadandwillresultinANR.Thetimelimitonabroadcastreceiveris10secondscomparedto5secondsforanactivity.Itisabitofareprieve,butnotverymuch.
Theprocesshostingthebroadcastreceiverwillstartandterminatealongwiththebroadcastreceiverexecution.Hencetheprocesswillnotstickaroundafterthebroadcastreceiver’sonReceive()methodreturns.Ofcourse,thisisassumingthattheprocesscontainsonlythebroadcastreceiver.Iftheprocesscontainsothercomponents,suchasactivitiesorservices,thatarealreadyrunning,thenthelifetimeoftheprocesstakesthesecomponentlifecyclesintoaccountaswell.
Unlikeaserviceprocess,abroadcastreceiverprocesswillnotgetrestarted.
Ifabroadcastreceiverweretostartaseparatethreadandreturntothemainthread,Androidwillassumethattheworkiscompleteandwillshutdowntheprocesseveniftherearethreadsrunning,bringingthosethreadstoanabruptstop.
Androidautomaticallyacquiresapartialwakelockwheninvokinga
broadcastserviceandreleasesitwhenitreturnsfromtheserviceinthemainthread.AwakelockisamechanismandanAPIclassavailableintheSDKtokeepthedevicefromgoingtosleeportowakeitupifitisalreadyasleep.
Giventhesepredicates,howcanweexecutelonger-runningcodeinresponsetoabroadcastevent?
UnderstandingLong-RunningBroadcastReceiverProtocolTheanswerliesinresolvingthefollowing:
WewillclearlyneedaseparatethreadsothatthemainthreadcangetbackandavoidANRmessages.
TostopAndroidfromkillingtheprocessandhencetheworkerthread,weneedtotellAndroidthatthisprocesscontainsacomponent,suchasaservice,withalifecycle.Soweneedtocreateorstartthatservice.Theserviceitselfcannotdirectlydotheworkformorethan5secondsbecausethathappensonthemainthread,sotheserviceneedstostartaworkerthreadandletthemainthreadgo.
Forthedurationoftheworkerthread’sexecution,weneedtoholdontothepartialwakelocksothatthedevicewon’tgotosleep.Apartialwakelockwillallowthedevicetoruncodewithoutturningonthescreenandsoon,whichallowsforlongerbatterylife.
Thepartialwakelockmustbeobtainedinthemainlinecodeofthereceiver;otherwise,itwillbetoolate.Forexample,youcannotdothisintheservice,becauseitmaybetoolatebetweenthestartService()beingissuedbythebroadcastreceiverandtheonStartCommand()ofaservicethatbeginsexecution.
Becausewearecreatingaservice,theserviceitselfcanbebroughtdownandbroughtbackupbecauseoflow-memoryconditions.Ifthishappens,weneedtoacquirethewakelockagain.
WhentheworkerthreadstartedbytheonStartCommand()methodoftheservicecompletesitswork,itneedstotelltheservicetostopsothatitcanbeputtobedandnotbroughtbacktolifebyAndroid.
Itisalsopossiblethatmorethanonebroadcasteventcanoccur.Giventhat,weneedtobecautiousabouthowmanyworkerthreadsweneedtospawn.
Giventhesefacts,therecommendedprotocolforextendingthelifeofabroadcastreceiverisasfollows:
1. Geta(static)partialwakelockintheonReceive()methodofthebroadcastreceiver.Thepartialwakelockneedstobestatictoallowcommunicationbetweenthebroadcastreceiverandtheservice.Thereisnootherwayofpassingareferenceofthewakelocktotheservice,astheserviceisinvokedthroughadefaultconstructorthattakesnoparameters.
2. Startalocalservicesothattheprocesswon’tbekilled.
3. Intheservice,startaworkerthreadtodothework.DonotdotheworkintheonStart()methodoftheservice.Ifyoudo,youarebasicallyholdingupthemainthreadagain.
4. Whentheworkerthreadisdone,telltheservicetostopitselfeitherdirectlyorthroughahandler.
5. Havetheserviceturnoffthestaticwakelock.
UnderstandingIntentServiceRecognizingtheneedforaservicetonotholdupthemainthread,AndroidhasprovidedautilitylocalserviceimplementationcalledIntentServicetooffloadworktoaworkerthreadsothatthemainthreadcanbereleasedafterschedulingtheworktothesubthread.Underthisscheme,whenyoucallstartService()onanIntentService,theIntentServicewillqueuethatrequesttoasubthreadusingalooperandahandlersothataderivedmethodoftheIntentServiceiscalledtodotheactualworkonasingleworkerthread.
HereiswhattheAPIdocumentationforIntentServicesays:
IntentService is a base class for Services that handle asynchronous requests(expressed as Intents) on demand. Clients send requests throughstartService(Intent)calls;theserviceisstartedasneeded,handleseachIntentinturnusingaworkerthread,andstopsitselfwhenitrunsoutofwork.This“workqueue processor” pattern is commonly used to offload tasks from anapplication'smainthread.TheIntentServiceclassexiststosimplifythispatternand takecareof themechanics.Touse it, extend IntentServiceand implementonHandleIntent(Intent). IntentServicewill receive the Intents, launchaworkerthread,andstoptheserviceasappropriate.Allrequestsarehandledonasingleworker thread—they may take as long as necessary (and will not block theapplication'smainloop),butonlyonerequestwillbeprocessedatatime.
ThisideaisdemonstratedusingasimpleexampleinListing16-7.YouextendtheIntentServiceandprovidewhatyouwanttodointheonHandleIntent()method.
Listing16-7.UsingIntentService
//YoucanseefileTest30SecBCRService.javaforexample//Project:StandaloneBroadcastReceiver,Download:
ProAndroid5_Ch16_TestReceivers.zippublicclassMyServiceextendsIntentService{publicMyService(){super("some-java-package-like-name-used-for-debugging");}protectedvoidonHandleIntent(Intentintent){//logthreadsignatureifyouwanttoseethatitisrunningonaseparatethread//Ex:Utils.logThreadSignature("MyService");//dotheworkinthissubthread//andreturn}}
Onceyouhaveaservicelikethis,youcanregisterthisserviceinthemanifestfileanduseclientcodetoinvokethisserviceascontext.startService(newIntent(context,MyService.class)).ThiswillresultinacalltoonHandleIntent()inListing16-7.YouwillnoticethatifyouweretousethecommentedoutmethodUtils.logThreadSignature()inListing16-7inyouractualcode,itwillprinttheIDoftheworkerthreadandnotthemainthread.YoucanseetheUtilsclassintheprojectanddownloadreferenceslistedinthecommentssectionofListing16-7.
ExtendingIntentServiceforaBroadcastReceiverFromtheperspectiveofabroadcastreceiver,anIntentServiceisawonderfulthing.Itletsusexecutelong-runningcodewithoutblockingthemainthread.Notonlythat,beingaservice,anIntentServiceprovidesaprocessthatkeepsrunningwhenthebroadcastcodereturns.SocanweusetheIntentServicefortheneedsofalong-runningoperation?Yesandno.
Yes,becausetheIntentServicedoestwothings:First,itkeepstheprocessrunningbecauseitisaservice.Second,itletsthemainthreadgoandavoidsrelatedANRmessages.
Tounderstandthe“no”answer,youneedtounderstandwakelocksabitmore.Whenabroadcastreceiverisinvokedthroughanalarmmanager,thedevicemaynotbeon.Sothealarmmanagerpartiallyturnsonthedevice(justenoughtorunthecodewithoutanyUI)bymakingacalltothepowermanagerandrequestingawakelock.Thewakelockgetsreleasedassoonasthebroadcastreceiverreturns.
ThisleavestheIntentServiceinvocationwithoutawakelock,sothedevicemaygotosleepbeforetheactualcoderuns.However,IntentService,beingageneral-purposeextensiontoaservice,itdoesnotacquireawakelock.Soweneedfurtherprops
ontopofanIntentService.Weneedanabstraction.
MarkMurphyhascreatedavariantoftheIntentServicecalledWakefulIntentServicethatkeepsthesemanticsofusinganIntentServicebutalsoacquiresthewakelockandreleasesitproperlyunderavarietyofconditions.Youcanlookathisimplementationathttp://github.com/commonsguy/cwac-wakeful.
ExploringLong-RunningBroadcastServiceAbstractionWakefulIntentServiceisagoodabstraction.However,wewanttogoastepfurthersothatourabstractionparallelsthemethodofextendingIntentServiceasinListing16-7anddoeseverythingthatanIntentServicedoesbutalsoprovidesfewmorebenefits:
PasstheoriginalintentthatwaspassedtothebroadcastreceivertotheoverriddenmethodonHandleIntent.Thisallowsustolargelyhidethebroadcastreceiver,simulatingaprogrammingexperiencethataserviceisstartedinresponsetoabroadcastmessage.Thisisreallythegoalforthisabstractionwhilesomeextrasarethrownin.
Acquireandreleasewakelocks(similartoWakefulIntentService).
Dealwithaservicebeingrestarted.
Allowauniformwaytodealwiththewakelockformultiplereceiversandmultipleservicesinthesameprocess.
WewillcallthisabstractclassALongRunningNonStickyBroadcastService.Asthenamesuggests,wewantthisservicetoallowforlong-runningwork.Itwillalsobespecificallybuiltforabroadcastreceiver.Thisservicewillalsobenonsticky(wewillexplainthisconceptlaterinthechapter,butbriefly,thisindicatesthatAndroidwillnotstarttheserviceiftherearenomessagesinthequeue).ToallowforthebehaviorofanIntentService,itwillextendtheIntentServiceandoverridetheonHandleIntentmethod.
Combiningtheseideas,theabstractALongRunningNonStickyBroadcastServiceservicewillhaveasignaturethatlookslikeListing16-8.
Listing16-8.Long-RunningServiceAbstractIdea
publicabstractclassALongRunningNonStickyBroadcastServiceextendsIntentService{//...otherimplementationdetails
//thefollowingmethodwillbecalledbytheonHandleIntentofIntentService//thisiswheretheactualworkhappensinthisderivedabstractclassprotectedabstractvoidhandleBroadcastIntent(IntentbroadcastIntent);
//...otherimplementationdetails
}
TheimplementationdetailsforthisALongRunningNonStickyBroadcastServiceareatouchinvolved,andwewillcoverthemsoonafterweexplainwhywearegoingafterthistypeofservice.Wewanttodemonstratefirsttheutilityandsimplicityofhavingit.
OncewehavethisabstractclassofListing16-8,theMyServiceexampleinListing16-7canberewrittenasinListing16-9.
Listing16-9.Long-RunningServiceSampleUsage
publicclassMyServiceextendsALongRunningNonStickyBroadcastService{//..otherimplementationdetailsprotectedvoidhandleBroadcastIntent(IntentbroadcastIntent){//Youcanusethefollowingmethodtoseewhichthreadrunsthiscode//Utils.logThreadSignature("MyService");//dotheworkhere//andreturn}//..otherimplementationdetails}
ThesimplicityofListing16-9isthatthiscodeisinvokedassoonasaclientfiresoffabroadcastintent.Especiallythefactthatyouarereceivingdirectly,unmodified,thesameintentthatinvokedthebroadcastreceiver.Itisasifthebroadcastreceiverhasdisappearedfromthesolution.
Asyoucansee,youcanextendthisnewlong-runningserviceclass(justlikeIntentServiceandWakefulIntentService)andoverrideasinglemethodanddoverylittletonothinginthebroadcastreceiver.Yourworkwillbedoneinaworkerthread(thankstoIntentService)withoutblockingthemainthread.
Listing16-9isasimpleexampledemonstratingtheconcept.Let’sturntoamorecompleteimplementationthatimplementsalong-runningservicethatcanrunfor60secondsinresponsetoabroadcastevent(provingthatwecanrunformorethan10secondsandavoidanANRmessage).WewillcallthisserviceappropriatelyTest60SecBCRService(BCRstandsforbroadcastreceiver),anditsimplementationisshowninListing16-10.
Listing16-10.SourcecodeforTest60SecBCRService
//Filename:Test30SecBCRService.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassTest60SecBCRServiceextendsALongRunningNonStickyBroadcastService{publicstaticStringtag="Test60SecBCRService";//RequiredbyIntentServicetopasstheclassnamefordebugneedspublicTest60SecBCRService(){super("com.androidbook.service.Test60SecBCRService");}/*Performlong-runningoperationsinthismethod.*Thisisexecutedinaseparatethread.*/@OverrideprotectedvoidhandleBroadcastIntent(IntentbroadcastIntent){//UtilsclassisinthedownloadprojectmentionedUtils.logThreadSignature(tag);Log.d(tag,"Sleepingfor60secs");//Usethethreadtosleepfor60secondsUtils.sleepForInSecs(60);Stringmessage=broadcastIntent.getStringExtra("message");Log.d(tag,"Jobcompleted");Log.d(tag,message);}}
Asyoucansee,thiscodesuccessfullysimulatesdoingworkfor60secondsandstillavoidstheANRmessage.TheutilitymethodsinListing16-10areself-explanatoryandavailableinthedownloadprojectsforthischapter.TheprojectnameanddownloadfilenameareinthecommentssectionofthecodeinListing16-10.
DesigningALong-RunningReceiverOncewehavethelong-runningserviceinListing16-10,weneedtobeabletoinvoketheservicefromabroadcastreceiver.Againwearegoingafteranabstractiontohidethebroadcastreceiverasmuchaspossible.
Thefirstgoalofalong-runningbroadcastreceiveristodelegatetheworktothelong-runningservice.Todothis,thelong-runningreceiverwillneedtheclassnameofthelong-runningservicetoinvokeit.Thesecondgoalistoacquireawakelock.Thethirdgoalistotransfertheoriginalintentthatthebroadcastreceiverisinvokedontotheservice.WewilldothisbystickingtheoriginalintentasaParcelableintheintentextras.Wewilluseoriginal_intentasthenameforthisextra.Thelong-runningservicethenextractsoriginal_intentandpassesittotheoverriddenmethodofthelong-runningservice
(youwillseethislaterintheimplementationofthelong-runningservice).Thisfacilitythusgivestheimpressionthatthelong-runningserviceisindeedanextensionofthebroadcastreceiver.
Letusabstractoutthesethreethingsandprovideabaseclass.Theonlybitofinformationthislong-runningreceiverabstractionneedsisthenameofthelong-runningserviceclass(LRSClass)throughanabstractmethodcalledgetLRSClass().
Puttingtheseneedstogether,sourcecodefortheimplementationoftheabstractclassALongRunningReceiverisinListing16-11.
Listing16-11.ALongRunningReceiverAbstraction
//Filename:ALongRunningReceiver.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicabstractclassALongRunningReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="ALongRunningReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d(tag,"Receiverstarted");//LightedGreenRoomabstractstheAndroidWakeLock//tokeepthedevicepartiallyon.//Inshortthisisequivalenttoturningon//oracquiringthewakelock.LightedGreenRoom.setup(context);startService(context,intent);Log.d(tag,"Receiverfinished");}privatevoidstartService(Contextcontext,Intentintent){IntentserviceIntent=newIntent(context,getLRSClass());serviceIntent.putExtra("original_intent",intent);context.startService(serviceIntent);}/**Overridethismethodtoreturnthe*"class"objectbelongingtothe*nonstickyserviceclass.*/publicabstractClassgetLRSClass();}
Intheprecedingbroadcastreceivercode,youseereferencestoaclasscalledLightedGreenRoom.Thisisawrapperaroundastaticwakelock.Inadditiontobeingawakelock,thisclasstriestocatertoworkingwithmultiplereceivers,multipleservices,
etc.,sothatallwaki-nessisproperlycoordinated.Forthepurposeofunderstanding,youcantreatitasifitisastaticwakelock.ThisabstractioniscalledaLightedGreenRoombecauseitisaimedatsavingpowerforthedevicelikethevarious“green”movements.Furthermoreitiscalled“Lighted”becauseitstartsoffbeing“lighted”firstasthebroadcastreceiverturnsitonassoonasitiskickedoff.Thelastservicetouseitwillturnitoff.
Oncethereceiverabstractionisavailable,you’llneedareceiverthatworkshandinhandwiththe60-secondlong-runningserviceinListing16-11.SuchareceiverisprovidedinListing16-12.
Listing16-12.ASampleLong-RunningBroadcastReceiver,Test60SecBCR
//Filename:Test60SecBCR.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicclassTest60SecBCRextendsALongRunningReceiver{@OverridepublicClassgetLRSClass(){Utils.logThreadSignature("Test60SecBCR");returnTest60SecBCRService.class;}}
JustliketheserviceabstractioninListings16-10and16-11,thecodeinListing16-12usesanabstractionforthebroadcastreceiver.ThereceiverabstractionstartstheserviceindicatedbytheserviceclassreturnedbythegetLRSClass()method.
Thusfar,wehavedemonstratedwhyweneededthetwoimportantabstractionstoimplementlong-runningservicesinvokedbybroadcastreceivers:
ALongRunningNonStickyBroadcastService(Listing16-8)
ALongRunningReceiver(Listing16-11)
AbstractingaWakeLockwithLightedGreenRoomAsmentionedearlier,theprimarypurposeoftheLightedGreenRoomabstractionistosimplifytheinteractionwiththewakelock,andawakelockisusedtokeepthedeviceonduringbackgroundprocessing.Youreallydon’tneedthedetailsoftheimplementationoftheLightedGreenRoom,butmerelyitsinterfaceandthecallsthataremadeagainstit.JustkeepinmindthatitisathinwrapperaroundtheAndroidSDKwakelock.Initssimplestimplementation,itcanjustbeassimpleasturningthewakelockon(acquire)andoff(release).Listing16-13showshowawakelockisusedtypicallyasstatedintheSDK.
Listing16-13.PsuedocodeforworkingwiththeWakeLockAPI
//Getaccesstothepowermanagerservice
PowerManagerpm=
(PowerManager)inCtx.getSystemService(Context.POWER_SERVICE);
//GetholdofawakelockPowerManager.WakeLockwl=pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,tag);
//Acquirethewakelockwl.acquire();
//dosomework//whilethisworkisbeingdonethedevicewillbeonpartially
//releasetheWakelockwl.release();
Giventhisinteraction,thebroadcastreceiverissupposedacquirethelock,andwhenthelong-runningserviceisfinished,itneedstoreleasethelock.Assaidearlierthereisnogoodwaytopassthewakelockvariabletotheservicefromthebroadcastreceiver.Theonlywaytheserviceknowsaboutthiswakelockistouseastaticorapplication-levelvariable.
Anotherdifficultyinacquiringandreleasingawakelockisthereferencecount.Asabroadcastreceiverisinvokedmultipletimes,iftheinvocationsoverlap,therearegoingtobemultiplecallstoacquirethewakelock.Similarly,therearegoingtobemultiplecallstorelease.Ifthenumberofacquireandreleasecallsdon’tmatch,wewillendupwithawakelockthatatworstkeepsthedeviceonforfarlongerthanneeded.Also,whentheserviceisnolongerneededandthegarbagecollectionruns,ifthewakelockcountsaremismatched,therewillbearuntimeexceptionintheLogCat.TheseissueshavepromptedustodoourbesttoabstractthewakelockasaLightedGreenRoomtoensureproperusage.Therewillbeoneoftheseobjectsperprocessthatkeepsawakelockandensuresitisturnedonandoffproperly.Theincludedprojecthasanimplementationforthisclass.Ifyoufindthatcodetoocomplicatedduetothenumberofconditionsittakesintoaccount,youcanjuststartwithasimplestaticvariableandturnitonandoffastheservicestartsandclosesandrefineittosuityourparticularconditions.
Areasonableapproachforthebroadcastreceiverandtheservicetocommunicatewitheachotheristhroughastaticvariable.InsteadofmakingWakeLockstatic,wehavemadetheentireLightedGreenRoomastaticinstance.However,everyothervariableinsideLightedGreenRoomstayslocalandnonstatic.
EverypublicmethodofLightedGreenRoomisalsoexposedasastaticmethodforconvenience.Wehaveusedtheconventionofnamingthesemethodsstartingwith“s_”.Youcanchoose,instead,togetridofthestaticmethodsanddirectlycallthesingleobjectinstanceofLightedGreenRoom.
ImplementingaLong-RunningServiceTopresentthelong-runningserviceabstraction,wehavetotakeonemoredetourtoexplainthelifetimeofaserviceandhowitrelatestotheimplementationofonStartCommand.Thisisthemethodthatisultimatelyresponsibleforstartingtheworkerthreadandthesemanticsofaservice.
WhenaserviceisstartedthroughstartService,theservicegetscreatedfirst,anditsonStartCommandmethodiscalled.Androidhasprovisionstokeepthisprocessinmemorysothattheservicecanbecompletedevenwhenservingmultipleincomingclientrequests.However,underdemandingmemoryconditions,AndroidmaychoosetoreclaimtheprocessandcalltheonDestroy()methodoftheservice.
NoteAndroidtriestocalltheonDestroy()methodforaservicetoreclaimitsresourceswhentheserviceisnotexecutingitsonCreate(),onStart(),oronDestroy()method,orinotherwordswhentheserviceisidle.
However,unlikeanactivitythatisshutdown,aserviceisscheduledtorestartagainwhenresourcesareavailableiftherearependingstartServiceintentsinthequeue.TheservicewillbewokenupandthenextintentdeliveredtoitviaonStartCommand().Ofcourse,onCreate()willbecalledwhentheserviceisbroughtback.
Becauseservicesareautomaticallyrestartediftheyarenotexplicitlystopped,itisreasonabletothinkthat,unlikeactivitiesandothercomponents,aservicecomponentisfundamentallyastickycomponent.
UnderstandingaNonstickyServiceAservicewillnotbeautomaticallyrestartedifaclientexplicitlycallsstopService.Dependingonhowmanyclientsarestillconnected,thisstopServicemethodcanmovetheserviceintoastoppedstate,atwhichtimetheservice’sonDestroymethodiscalledandtheservicelifecycleiscomplete.Onceaservicehasbeenstoppedlikethisbyitslastclient,theservicewillnotbebroughtback.
Thisprotocolworkswellwheneverythinghappensasperdesign,wherestartandstopmethodsarecalledandexecutedinsequenceandwithoutamiss.PriortoAndroid2.0,deviceshaveseenalotofserviceshangingaroundandclaimingresourceseventhoughtherewasnoworktobedone,meaningAndroidbroughttheservicesbackintomemoryeventhoughtherewerenomessagesinthequeue.ThiswouldhavehappenedwhenstopServicewasnotinvokedeitherbecauseofanexceptionorbecausetheprocesswastakenoutbetweenonStartCommandandstopService.
Android2.0introducedasolutionsothatwecanindicatetothesystem,iftherearenopendingintents,thatitshouldn’tbotherrestartingtheservice.Thisisdonebyreturningthenonstickyflag(Service.START_NOT_STICKY)fromonStartCommand.
However,nonstickyisnotreallythatnonsticky.Evenifwemarktheserviceasnonsticky,
iftherearependingintents,Androidwillbringtheservicebacktolife.Thissettingappliesonlywhentherearenopendingintents.
UnderstandingaStickyServiceWhatdoesitmeanforaservicetobereallystickythen?Thestickyflag(Service.START_STICKY)meansthatAndroidshouldrestarttheserviceeveniftherearenopendingintents.Whentheserviceisrestarted,callonCreateandonStartCommandwithanullintent.Thiswillgivetheserviceanopportunity,ifneedbe,tocallstopSelfifthatisappropriate.Theimplicationisthataservicethatisstickyneedstodealwithnullintentsonrestarts.
UnderstandingRedeliverIntentsOptionLocalservicesinparticularfollowapatternwhereonStartandstopSelfarecalledinpairs.AclientcallsonStart.Theservice,whenitfinishesthatwork,callsstopSelf.Ifaservicetakes,say,30minutestocompleteatask,itwillnotcallstopSelffor30minutes.Meanwhile,theserviceisreclaimedduetolow-memoryconditionsandhigher-priorityjobs.Ifweusethenonstickyflag,theservicewillnotwakeup,andwewouldneverhavecalledstopSelf.
Manytimes,thisisOK.However,ifyouwanttomakesurewhetherthesetwocallshappenforsure,youcantellAndroidnottounqueuethestarteventuntilstopSelfiscalled.Thisensuresthatwhentheserviceisreclaimed,thereisalwaysapendingeventunlessthestopSelfiscalled.Thisiscalledredelivermode,anditcanbeindicatedinreplytotheonStartCommandmethodbyreturningtheService.START_REDELIVER_INTENTflag.
CodingaLong-RunningServiceNowthatyouhavethebackgroundonIntentService,service-startflags,andthelightedgreenroom,we’rereadytotakealookatthelong-runningserviceinListing16-14.
Listing16-14.ALong-RunningServiceAbstraction
//Filename:ALongRunningNonStickyBroadcastService.java//Project:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zippublicabstractclassALongRunningNonStickyBroadcastServiceextendsIntentService{publicstaticStringtag="ALongRunningBroadcastService";//Thisiswhatyouoverridetodoyourwork
protectedabstractvoidhandleBroadcastIntent(IntentbroadcastIntent);
publicALongRunningNonStickyBroadcastService(Stringname){super(name);}/**Thismethodcanbeinvokedundertwocircumstances*1.Whenabroadcastreceiverissuesa"startService"*2.whenandroidrestartsthisserviceduetopending"startService"intents.**Incase1,thebroadcastreceiverhasalready*setupthe"lightedgreenroom"andtherebygottenthewakelock**Incase2,weneedtodothesame.*/@OverridepublicvoidonCreate(){super.onCreate();
//Setupthegreenroom//Thesetupiscapableofgettingcalledmultipletimes.LightedGreenRoom.setup(this.getApplicationContext());
//Itispossiblethatmorethanoneserviceofthistypeisrunning.//Knowingthenumberwillallowustocleanupthewakelocksinondestroy.LightedGreenRoom.s_registerClient();}@OverridepublicintonStartCommand(Intentintent,intflag,intstartId){//CalltheIntentService"onstart"super.onStart(intent,startId);
//TellthegreenroomthereisavisitorLightedGreenRoom.s_enter();
//markthisasnonsticky//Means:Don'trestarttheserviceifthereareno//pendingintents.returnService.START_NOT_STICKY;}/*
*NotethatthismethodcallrunsinasecondarythreadsetupbytheIntentService.**OverridethismethodfromIntentService.*Retrievetheoriginalbroadcastintent.*Callthederivedclasstohandlethebroadcastintent.*finallytellthelightedroomthatyouareleaving.*ifthisisthelastvisitorthenthelock*willbereleased.*/@OverridefinalprotectedvoidonHandleIntent(Intentintent){try{IntentbroadcastIntent=intent.getParcelableExtra("original_intent");handleBroadcastIntent(broadcastIntent);}finally{//releasethewakelockifyouarethelastoneLightedGreenRoom.s_leave();}}/*IfAndroidreclaimsthisprocess,thismethodwillreleasethelock*irrespectiveofhowmanyvisitorsthereare.*/@OverridepublicvoidonDestroy(){super.onDestroy();//Doanycleanup,ifneeded,whenaservicenolongerneedsawakelockLightedGreenRoom.s_unRegisterClient();}}
ThisclassextendsIntentServiceandgetsallthebenefitsofaworkerthreadassetupbyIntentService.Inaddition,itspecializestheIntentServicefurthersothatitissetupasanonstickyservice.Fromadeveloper’sperspective,theprimarymethodtofocusonistheabstracthandleBroadcastIntent()method.Listing16-15showsyouhowtosetupthereceiverandthecorrespondingserviceinthemanifestfile.
Listing16-15.TheLong-RunningReceiverandServiceDefinition
<!--Infilename:AndroidManifest.xmlProject:StandaloneBroadcastReceiver,Download:ProAndroid5_Ch16_TestReceivers.zip
--><manifest…>......<application….><receiverandroid:name=".Test60SecBCR"><intent-filter><actionandroid:name="com.androidbook.intents.testbc"/></intent-filter></receiver><serviceandroid:name=".Test60SecBCRService"/></application>.....<uses-permissionandroid:name="android.permission.WAKE_LOCK"/></manifest>
Noticethatyouwillneedthewakelockpermissiontorunthislong-runningreceiverabstraction.Completesourcecodeforallofthereceiversandlong-runningservicesisavailableinthedownloadableprojectsforthischapter.Listing16-15bringsouttheessenceofthelong-runningservicesinvokedbyabroadcastreceiver.ThisabstractionstatesthatyouwriteacoupleoflinestocreateareceiverliketheTest60SecBCR(Listing16-12),andthenwriteajavamethodsimilartotheoneincodeTest60SecBCRService(Listing16-10).Giventhereceiverandthejavamethodthatyouwanttorunforalongtime,youcanexecutethatmethodinresponsetothebroadcastevent.ThisabstractionensuresthatthemethodthencanrunaslongasittakeswithoutproducinganARM.Theabstractiontakescareofa)keepingtheprocessalive,b)callingtheservice,c)takingcareofthewakelock,andd)transferringthebroadcastintenttotheservice.Intheendthisabstractionsimulates“callingamethodthatcanexecutewithouttimelimits”fromabroadcastevent.
AdditionalTopicsinBroadcastReceiversDuetospacelimitations,wearenotabletocoverallaspectsofbroadcastreceiversinthisbook.Onetopicwehaven’tcoveredatallisthesecurityopportunitiesavailabletorestrictbothsendingandreceivingbroadcasts.Youcanusetheexportattributeonareceivertoallowwhetheritcanbeinvokedfromexternalprocessesornot.Youcanalsoenableordisableareceivereitherthroughthemanifestfileorprogrammatically.WehavealsonotcoveredamethodcalledsendOrderBroadcastthatfacilitatescallingbroadcastreceiversinanorderincludingchainingthem.YoucanreadupontheseaspectsfromthemainAPIdocsfortheBroadcastReceiverclass.
Furthermore,inversion4oftheAndroidsupportlibrarySDKthereisaclasscalledLocalBroadcastManagerthatisusedtooptimizecallstobroadcastreceiversthatarestrictlylocal.Beinglocal,allsecuritylimitationsneednotbeconsidered.AspertheSDK,thereisalsosystem-leveloptimizationforwhenthisclassisused.
Alsoinversion4oftheAndroidsupportlibrarySDK,thereisaclasscalledWakefulBroadcastReceiverthatencapsulatessomeofthesameconceptsthatwehavecoveredforlong-runningserviceneeds.
ReferencesHerearehelpfulreferencestothetopicsthatarecoveredinthischapter:
http://developer.android.com/reference/android/content/BroadcastReceiver.htmlTheBroadcastReceiverAPI.YouwillfindatthislinkmoreaboutorderedbroadcastsandabouttheBroadcastReceiverlifecycle.Thisisanexcellentresource.
http://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.htmlAndroidAPIreference.
http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.htmlAndroidAPIreference.
http://developer.android.com/reference/android/app/Service.htmlTheServiceAPI.Thisreferenceisespeciallygoodtohavewhileworkingwithlong-runningservices.
http://developer.android.com/reference/android/app/NotificationManager.htmlTheNotificationManagerAPI.
http://developer.android.com/reference/android/app/Notification.htmlTheNotificationAPI.Youwillseeherethevariousoptionsavailableforworkingwithanotification,suchascontentviewsandsoundeffects.
http://developer.android.com/reference/android/widget/RemoteViews.htmlTheRemoteViewsAPI.RemoteViewsareusedtoconstructcustomdetailedviewsofnotifications.
http://www.androidbook.com/item/3514:Authors’researchonlong-runningservices.
http://www.androidbook.com/item/3482:Authors’researchonbroadcastreceivers.Thisnotealsoexplainshowtostartanactivityfromareceiver.
http://www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsfromthisbook.Forthischapter,lookforaZIPfilenamedProAndroid5_Ch16_TestReceivers.zip.ThisZIPfilehastwoprojects:TestBroadcastReceiverandStandaloneBroadcastReceiver.Thelatterisdependentontheformer,soinstalltheminthatorder.Thesourcecodesnippetsin
thischapterareannotatedwiththeirfilenamesandinwhatprojectstheyareavailable.
SummaryInthischapter,wehavecoveredbroadcastreceivers,notificationmanagers,andtheroleofserviceabstractioninputtingbroadcastreceiverstotheirbestuse.Wealsohavegivenapracticalabstractiontosimulatelongrunningbroadcastservicesoutofbroadcastreceivers.
Chapter17
ExploringtheAlarmManagerInAndroidanintentobjectisusedtostartaUIactivity,abackgroundservice,orabroadcastreceiver.Normallytheseintentsaretriggeredbyuseractions.InAndroidyoucanalsousealarmstotriggerbroadcastintents,mindyou,onlybroadcastintents.Theinvokedbroadcastreceiversthencanchoosetostartanactivityoraservice.
InthischapteryouwilllearnaboutthealarmmanagerAPI.AlarmmanagerAPIisusedtoscheduleabroadcastintenttogooffataparticulartime.Wewillrefertothisprocessofschedulingabroadcastintentataparticulartimeassettinganalarm.
Wewillalsoshowyouhowtoschedulealarmsthatrepeatatregularintervals.Wewillshowyouhowtocancelalarmsthatarealreadyset.
Whenanintentobjectisstoredtobeusedatalatertime,itiscalledapendingintent.Asalarmmanagersusependingintentsallthetimeyouwillgettoseetheusageandintricaciesofpendingintentsaswellinthischapter.
SettingUpaSimpleAlarmWewillstartthechapterwithsettinganalarmataparticulartimeandhavingitcallabroadcastreceiver.Oncethebroadcastreceiverisinvoked,youcanusetheinformationfromChapter16toperformbothsimpleandlong-runningoperationsinthatbroadcastreceiver.
GettingaccesstothealarmmanagerissimpleandisshowninListing17-1.
Listing17-1.GettingAccesstoanAlarmManager
//Infilename:SendAlarmOnceTester.javaAlarmManageram=(AlarmManager)
anyContextObject.getSystemService(Context.ALARM_SERVICE);
ThevariableanyContextObjectreferstoacontextobject.Forexample,ifyouareinvokingthiscodefromanactivitymenu,thecontextvariablewillbetheactivity.Tosetthealarmforaparticulardateandtime,youwillneedaninstanceintimeidentifiedbyaJavaCalendarobject.Listing17-2showsautilityfunctionthatgivesyouacalendarobjectforsomespecifiedtimeinstantafterthecurrenttime.
Listing17-2.AFewUsefulCalendarUtilities
//Infilename:Utils.javapublicclassUtils{publicstaticCalendargetTimeAfterInSecs(intsecs){
Calendarcal=Calendar.getInstance();cal.add(Calendar.SECOND,secs);returncal;}}
Inthedownloadableprojectforthischapter,youwillseelotmorecalendar-basedutilitiestoarriveatatimeinstanceinanumberofways.Now,weneedareceivertosetagainstthealarmthatweareplanningtoset.AsimplereceiverisshowninListing17-3.
Listing17-3.TestReceivertoTestAlarmBroadcasts
//Infilename:TestReceiver.javapublicclassTestReceiverextendsBroadcastReceiver{privatestaticfinalStringtag="TestReceiver";@OverridepublicvoidonReceive(Contextcontext,Intentintent){Log.d(tag,"intent="+intent);Stringmessage=intent.getStringExtra("message");Log.d(tag,message);}}
Youwillneedtoregisterthisreceiverinthemanifestfileusingthe<receiver>tag,asshowninListing17-4.ReceiversarecoveredindetailinChapter16.
Listing17-4.RegisteringaBroadcastReceiver
<!--Infilename:AndroidManifest.xml--><receiverandroid:name=".TestReceiver"/>
InAndroidanalarmisreallyabroadcastintentthatisscheduledforalatertime.Whatreceivercomponentthisintentshouldinvokeisexplicitly(throughitsclassname)specifiedintheintent.Listing17-5showsanintentthatcanbeusedtoinvokethebroadcastreceiverthatwehadinListing17-3.
Listing17-5.CreatinganIntentPointingtoTestReceiver
//Infilename:SendAlarmOnceTester.javaIntentintent=newIntent(mContext,TestReceiver.class);intent.putExtra("message","SingleShotAlarm");
Wealsohaveanopportunitytoloadtheintentwith“extras”whilecreatingthisintent.Becauseanalarmmanagerstoresanintentforalateruse,weneedtocreateapendingintentoutofthisintentofListing17-5.Listing17-6showshowtocreateapendingintentfromastandardintent.
Listing17-6.CreatingaPendingIntent
//Infilename:SendAlarmOnceTester.javaPendingIntentpendingIntent=
PendingIntent.getBroadcast(mContext,//context,oractivity,orservice1,//requestid,usedfordisambiguatingthisintentintent,//intenttobedelivered0);//pendingintentflags
NoticethatwehaveaskedthePendingIntentclasstoconstructapendingintentthatissuitableforabroadcastexplicitly.TheothervariationsofcreatingapendingintentarelistedinListing17-7:
Listing17-7.MultipleAPIsforCreatingaPendingIntent
//usefultostartanactivityPendingIntentactivityPendingIntent=PendingIntent.getActivity(..args..);//usefultostartaservicePendingIntentservicePendingIntent=PendingIntent.getService(..args..);
InListing17-7,argumentstothemethodsgetActivity()andgetService()aresimilartotheargumentstothegetBroadcast()methodinListing17-6.Notethatalarmsrequireabroadcastpendingintentandnotanactivitypendingintentoraservicependingintent.
Wewilldiscusstherequestidargument,whichwesetto1inListing17-6,ingreaterdetaillaterinthechapter.Briefly,itisusedtoseparatetwointentobjectsthatareequalinallotherrespects.
Pendingintentflagshavelittleornoinfluenceonthealarmmanager.Recommendationistousenoflagsatallanduse0fortheirvalues.Theseintentflagsaretypicallyusefulincontrollingthelifetimeofthependingintent.However,inthiscase,thelifetimeismaintainedbythealarmmanager.Forexample,tocancelapendingintent,youaskthealarmmanagertocancelit.
OncewehavethetimeinstanceinmillisecondsasaCalendarobjectandthependingintentpointingtothereceiver,wecansetupanalarmbycallingtheset()methodofthealarmmanager.ThisisshowninListing17-8.
Listing17-8.UsingtheAlarmManagerset()Method
//Infilename:SendAlarmOnceTester.javaCalendarcal=Utils.getTimeAfterInSecs(30);//...othercodethatgetsthependingintentetcam.set(AlarmManager.RTC_WAKEUP,cal.getTimeInMillis(),pendingIntent);
Thefirstargumenttotheset()methodindicatesthewakeupnatureofthealarmandalsothereferenceclockthatwearegoingtobeusingforthealarm.Possiblevaluesforthis
argumentareAlarmManager.RTC_WAKEUP,AlarmManager.RTC,AlarmManager.ELAPSED_REALTIME,AlarmManager.ELAPSED_REALTIME_WAKEUP.
Theelapsedwordintheseconstantsreferstothetimeinmillisecondssincethedeviceisrecentlybooted.So,itreferstothedeviceclock.TheRTCtimereferstothehumanclock/timethatyouseeonthedevicewhenyoucheckyourclockonthedevice.TheWAKEUPwordintheseconstantsreferstothenatureofthealarm,suchaswhetherthealarmshouldwakeupthedeviceorjustdeliveritatthefirstopportunitywhenthedeviceeventuallywakesup.Takentogether,theRTC_WAKEUPindicatestheuseofreal-timeclockandthedeviceshouldwakeup.TheconstantELAPSED_REALTIMEmeansusethedeviceclockanddon’twakeupthedevice;instead,deliverthealarmatthefirstopportunity.
WhenthismethodofListing17-8iscalled,thealarmmanagerwillinvoketheTestReceiverinListing17-3,30secondsafterthecalendartimewhenthemethodwascalledandalsowakesupthedeviceifitisasleep.
SettingOffanAlarmRepeatedlyLet’snowconsiderhowwecansetanalarmthatgoesofrepeatedly;seeListing17-9.
Listing17-9.SettingaRepeatingAlarm
publicvoidsendRepeatingAlarm(){Calendarcal=Utils.getTimeAfterInSecs(30);
//GetanintenttoinvokethereceiverIntentintent=newIntent(this.mContext,TestReceiver.class);intent.putExtra("message","RepeatingAlarm");
intrequestid=2;PendingIntentpi=this.getDistinctPendingIntent(intent,requestid);//Schedulethealarm!AlarmManageram=(AlarmManager)this.mContext.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),5*1000,//5secsrepeatpi);}
protectedPendingIntentgetDistinctPendingIntent(Intentintent,int
requestId){
PendingIntentpi=PendingIntent.getBroadcast(mContext,//context,oractivityrequestId,//requestidintent,//intenttobedelivered0);returnpi;}
KeyelementsofthecodeinListing17-9arehighlighted.ArepeatingalarmissetbyinvokingthesetRepeating()methodonthealarmmanagerobject.Theprimaryinputtothismethodisapendingintentpointingtoareceiver.WehaveusedthesameintentthatwascreatedinListing17-5,theonepointingtotheTestReceiver.However,whenwemakeapendingintentoutoftheintentinListing17-5,wealtertheuniquerequestcodetoavalueof2.Ifwedon’tdothis,wewillseeabitofoddbehaviorwhichweshallexplainnow.Sayweintendtoinvokethesamereceiverthroughtwodifferentalarms:onealarmthatgoesoffonlyonceandanotheralarmthatgoesoffrepeatedly.Becausebothalarmstargetthesamereceivertheyneedtobeusinganintentthatpointstothesamereceiver.Twointentsthatpointtothesamereceiver,withoutanyotherdifferencebetweenthem,isconsideredthesameintent.So,whenwetellthealarmmanagertosetthealarmonintent1asaone-timealarmandthensetthealarmonintent2asarepeatedalarm,wemightbeundertheimpressionthattheyaretwodifferentalarms.Internally,however,bothalarmspointtothesameintentvalue,asintent1andintent2arethesameintheirvalues.Thisiswhyanalarmispracticallythesameasitsintentonwhichitisset(especiallybyvalue).Asaresult,thelateralarmoverridesthefirstalarmiftheintentsareequivalent.
Again,twointentsareconsideredthesameiftheyhavethesameaction,type,data,categories,orclass.Theextrasarenotincludedinfiguringouttheuniquenessofintents.Further,twopendingintentsareconsideredthesameiftheirunderlyingintentsarethesameandtherequestIDsmatch.BecausewecanusetherequestIDtodistinguishtwopendingintents,thecodeinListing17-8overcomesthesimilarityofsourceintentsbyusingtherequestidargument.ThisrequestidargumenttothePendingIntentAPIwillseparateonependingintentfromtheotherpendingintentwhenallelsematches.
Thisallshouldmakesenseifyouweretoseethependingintent(byvaluenotbyitsJavaobjectreference)itselfasthealarmonwhichyouaresettingdifferenttimes.
CancellinganAlarmCodeinListing17-10isusedtocancelanalarm.
Listing17-10.CancellingaRepeatingAlarm
publicvoidcancelRepeatingAlarm(){//Getanintentthatwasoriginally//usedtoinvokeTestReceiverclassIntentintent=newIntent(this.mContext,
TestReceiver.class);
//Tocancel,extraisnotnecessarytobefilledin//intent.putExtra("message","RepeatingAlarm");
PendingIntentpi=this.getDistinctPendingIntent(intent,2);
//Cancelthealarm!AlarmManageram=(AlarmManager)
this.mContext.getSystemService(Context.ALARM_SERVICE);am.cancel(pi);}
Tocancelanalarm,wehavetoconstructapendingintentfirstandthenpassittothealarmmanagerasanargumenttothecancel()method.However,youmustpayattentiontomakesurethatthePendingIntentisconstructedtheexactsamewaywhensettingthealarm,includingtherequestcodeandtargetedreceiver.
Inconstructingthecancelintent,youcanignoretheintentextrasfromtheoriginalintent(Listing17-10),becauseintentextrasdon’tplayaroleintheuniquenessofanintent,andhencecancellingthatintent.
UnderstandingExactnessofAlarmsPriortoAPI19,Androidfiredthealarmsascloseaspossibletothespecifiedtime.SinceAPI19,alarmsthatareclosetoeachotherarebundledforbatterylife.Ifyouneedtheolderbehavior,thereisaversionofset()methodcalledsetExact().ThereisalsoamethodcalledsetWindow()thatallowsroomforefficienciesandalsoallowsaguaranteedwindow.Similarly,themethodsetRepeating()isnowinexact.UnlikethesetExact()method,thereisnoexactversionforsetRepeating().Ifyouhavesuchaneed,youhavetousethesetExact()andrepeatityourselfmultipletimes.
UnderstandingPersistenceofAlarmsAnothernoteonalarmsisthattheyarenotsavedacrossdevicereboots.Thismeansyouwillneedtosavethealarmsettingsandpendingintentsinapersistentstoreandreregisterthembasedondevicerebootbroadcastactions,andpossiblytime-changebroadcastactions(e.g.,intent.ACTION_BOOT_COMPLETED,intent.ACTION_TIME_CHANGED,intent.ACTION_TIMEZONE_CHANGED).
References
Thefollowingreferenceswillhelpyoulearnmoreaboutthetopicsdiscussedinthischapter:
http://developer.android.com/reference/android/app/AlarmManager.htmlThealarmmanagerAPI.Youwillseeheresignaturesformethodslikeset,setRepeating,andcancel.
http://developer.android.com/reference/android/app/PendingIntent.htmlHowtoconstructapendingintent.Don’tpaytoomuchattentiontothependingintentflags;theyarenotthatcriticaltothealarmmanager.
http://androidbook.com/item/1040:Quickexamplesandreferencesforworkingwithdateandtimeclasses.
http://androidbook.com/item/3503:Ourresearchonalarmmanagers.
http://androidbook.com/proandroid5/projects:Alistofdownloadableprojectsfromthisbook.Forthischapter,lookforaZIPfilenamedProAndroid5_Ch17_TestAlarmManager.zip.
SummaryThischapterexploredtheAlarmManagerAPI,whichyouusetosetupandcancelalarms.Thischaptershowedyouhowtoconnectanalarmtoabroadcastservice.Thischapteralsoshowedyouhowalarmsarecloselyrelatedtointents.
Chapter18
Exploring2DAnimationAnimationallowsanobjectonascreentochangeitscolor,position,size,ororientationovertime.AnimationcapabilitiesinAndroidarepractical,fun,andsimple.Theyareusedfrequentlyinapplications.
Android2.3andpriorreleasessupportthreetypesofanimation:frame-by-frameanimation,whichoccurswhenaseriesofframesisdrawnoneaftertheotheratregularintervals;layoutanimation,whereyouanimatethelayoutoftheviewsinsideacontainersuchaslistsandtables;andviewanimation,inwhichanyviewcanbeanimated.Inlayoutanimationthefocusisnotanygivenviewbutthewayviewscometogethertoformthecompositelayout.Android3.0enhancedanimationbyextendingittoanyJavapropertyincludingthepropertiesofUIelements.Wewillcoverthepre-2.3featuresfirstandthencoverthe3.0featuresrightafter.Bothfeaturesareapplicablebasedonyourusecase.
ExploringFrame-by-FrameAnimationFrame-by-frameanimationiswhereaseriesofimagesareshowninsuccessionatquickintervalssothatthefinaleffectisthatofanobjectmovingorchanging.Figure18-1showsasetofcircleseachwithaballatadifferentposition.Withafewoftheseimages(whicharetheframes)youcanuseanimationtohavetheballgoingaroundthecircle.
Figure18-1.Exampleimageframesforanimation
EachcircleinFigure18-1isaseparateimage.Givetheimageabasenameofcolored_ballandstoreeightoftheseimagesinthe/res/drawablesubdirectorysothatyoucanaccessthemusingtheirresourceIDs.Thenameofeachimagewillhavethepatterncolored-ballN,whereNisthedigitrepresentingtheimagenumber.TheanimationactivityweareplanningwilllooklikeFigure18-2.
Figure18-2.Aframe-by-frameanimationtestharness
PrimarycontrolinFigure18-2istheanimationviewshowingtheballplacedonanoval/circle.Buttonatthetopisusedtostartandstoptheanimation.Thereisadebugscratchpadatthetoptologevents.Listing18-1showsthelayoutusedtocreatetheactivityinFigure18-2.
Listing18-1.XMLLayoutFilefortheFrameAnimationExample
<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/layout/frame_animations_layout.xmlDownload:ProAndroid5_ch18_TestFrameAnimation.zip--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:id="@+id/textViewId1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="DebugScratchPad"/><Buttonandroid:id="@+id/startFAButtonId"android:layout_width="fill_parent"
android:layout_height="wrap_content"android:text="StartAnimation"/><ImageViewandroid:id="@+id/animationImage"android:layout_width="fill_parent"android:layout_height="wrap_content"/></LinearLayout>
Thefirstcontrolisthedebug-scratchtextcontrol,whichisasimpleTextView.Youthenaddabuttontostartandstoptheanimation.ThelastviewistheImageView,whichisusedtoplaytheanimation.
InAndroid,frame-by-frameanimationisimplementedthroughtheclassAnimationDrawable.ThisclassisaDrawable.Theseobjectsarecommonlyusedasbackgroundsforviews.AnimationDrawable,inadditiontobeingaDrawable,cantakealistofotherDrawableresources(likeimages)andrenderthematspecifiedintervals.TousethisAnimationDrawableclass,startwithasetofDrawableresources(forexample,asetofimages)placedinthe/res/drawablesubdirectory.YouwillthenconstructanXMLfilethatdefinestheAnimationDrawableusingalistoftheseimages(seeListing18-2).ThisXMLfileneedstobeplacedinthe/res/drawablesubdirectoryaswell.
Listing18-2.XMLFileDefiningtheListofFramestoBeAnimated
<!--filename:/res/drawable/frame_animation.xmlDownload:ProAndroid5_ch18_TestFrameAnimation.zip--><animation-listxmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false"><itemandroid:drawable="@drawable/colored_ball1"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball2"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball3"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball4"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball5"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball6"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball7"android:duration="50"/><itemandroid:drawable="@drawable/colored_ball8"android:duration="50"/></animation-list>
Eachframepointstooneofthecolored-ballimagesyouhaveassembledthroughtheirresourceIDs.Theanimation-listtaggetsconvertedintoanAnimationDrawableobjectrepresentingthecollectionofimages.YouthenneedtosetthisAnimationDrawableasabackgroundresourceforourImageViewcontrolintheactivitylayout.AssumingthatthefilenameforthisXMLfileisframe_animation.xmlandthatitresidesinthe/res/drawablesubdirectory,youcanusethefollowingcodetosettheAnimationDrawableasthebackgroundoftheImageView:
view.setBackgroundResource(R.drawable.frame_animation);//SeeListing18-3
Withthiscode,AndroidrealizesthattheresourceIDR.drawable.frame_animationisanXMLresourceandaccordinglyconstructsasuitableAnimationDrawableJavaobjectforitbeforesettingitasthebackground.Oncethisisset,youcanaccessthisAnimationDrawableobjectbydoingagetontheviewobjectlikethis:
ObjectbackgroundObject=view.getBackground();AnimationDrawablead=(AnimationDrawable)backgroundObject;
OnceyouhavetheAnimationDrawableobject,youcanuseitsstart()andstop()methodstostartandstoptheanimation.Herearetwootherimportantmethodsonthisobject:
setOneShot(boolean);addFrame(drawable,duration);
ThesetOneShot(true)methodrunstheanimationonceandthenstops.TheaddFrame()methodaddsanewframeusingaDrawableobjectandsetsitsdisplayduration.ThefunctionalityoftheaddFrame()methodresemblesthatoftheXMLtagandroid:drawableinListing18-2.Putthisalltogethertogetthecompletecodeforourframe-by-frameanimationactivityofFigure18-1.
Listing18-3.CompleteCodefortheFrame-by-FrameAnimationTestHarness
//filename:FrameAnimationActivity.java//Download:ProAndroid5_ch18_TestFrameAnimation.zippublicclassFrameAnimationActivityextendsActivity{
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.frame_animations_layout);this.setupButton();}privatevoidsetupButton(){Buttonb=(Button)this.findViewById(R.id.startFAButtonId);
b.setOnClickListener(newButton.OnClickListener(){publicvoidonClick(Viewv){animate();}});}privatevoidanimate(){ImageViewimgView=(ImageView)findViewById(R.id.animationImage);imgView.setVisibility(ImageView.VISIBLE);imgView.setBackgroundResource(R.drawable.frame_animation);
AnimationDrawableframeAnimation=(AnimationDrawable)imgView.getBackground();if(frameAnimation.isRunning()){frameAnimation.stop();}else{frameAnimation.stop();frameAnimation.start();}}}//eof-class
animate()methodinListing18-3locatestheImageViewintheactivityandsetsitsbackgroundtotheAnimationDrawableidentifiedbytheresourceR.drawable.frame_animation.ThisanimationresourceIDpointstotheearlieranimationdefinitioninListing18-3.TherestofthecodeinthemethodretrievesthisAnimationDrawableobjectandcallsanimationmethodsonthatobject.InthesameListing18-3,Start/Stopbuttonissetupsothatifanimationisrunning,thebuttoncouldstopit;ifanimationisnotrunning,thebuttoncouldstartit.IfyousettheoneshotattributeintheanimationdefinitioninListing18-2totrue,theanimationstopsafteronce.
ExploringLayoutAnimationLayoutAnimationisusedtoanimatetheviewsinanAndroidlayout.YoucanusethistypeofanimationforexamplewithcommonlayoutcontrolslikeListViewandGridView.Unlikeframe-by-frameanimation,layoutanimationisnotachievedthroughrepeatingframesbutbychangingthetransformationmatrixofaview.EveryviewinAndroidhasatransformationmatrixthatmapstheviewtothescreen.Bychangingthismatrixyoucanaccomplishscaling,rotation,andmovement(translation)oftheview.Thistypeofanimationthatreliesonchangingpropertiesandredrawinganimageisreferredtoastweeninganimation.EssentiallyLayoutAnimationistweeninganimationofthetransformationmatrixoftheviewsinalayout.ALayoutAnimationthatisspecifiedonalayoutisappliedtoalltheviewsinthatlayout.
Thesearethetweeninganimationtypesthatcanbeappliedtoalayout:
Scaleanimation:Usedtomakeaviewsmallerorlargereitheralongthexaxis,ontheyaxis,oronboth.Youcanalsospecifythepivotpointaroundwhichyouwanttheanimationtotakeplace.
Rotateanimation:Usedtorotateaviewaroundapivotpointbyacertainnumberofdegrees.
Translateanimation:Usedtomoveaviewalongthexaxisortheyaxis.
Alphaanimation:Usedtochangethetransparencyofaview.
TheseanimationsaredefinedasXMLfilesinthe/res/animsubdirectory.Listing18-4showsascaleanimationdeclaredinanXMLfile.
Listing18-4.AScaleAnimationDefinedinanXMLFileat/res/anim/scale.xml
<setxmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_interpolator"><scaleandroid:fromXScale="1"android:toXScale="1"android:fromYScale="0.1"android:toYScale="1.0"android:duration="500"android:pivotX="50%"android:pivotY="50%"android:startOffset="100"/></set>
ParametersintheanimationXMLshavea“from”anda“to”flavortoindicatestartandendvaluesofthatproperty.Otherpropertiesofananimationalsoincludeanimationdurationandatimeinterpolator.InterpolatorsdeterminetherateofchangeoftheanimatedargumentsuchasscaleinListing18-4duringanimation.Wewillcoverinterpolatorsshortly.XMLfileinListing18-4canbeassociatedwithalayouttoanimatethatlayout’sconstituentviews.
NoteAnimationsliketheScaleanimationinListing18-4arerepresentedasJavaclassesintheandroid.view.animationpackage.JavadocumentationfortheseclassesdescribesnotonlyJavamethodsbutalsotheallowedXMLargumentsforeachtypeofanimation.
WecanusetheListViewinFigure18-3totestanumberoflayoutanimations.Thisactivityiswhatyouseewhenyourunthesampleprojectforthischapter.ProAndroid5_ch18_TestLayoutAnimation.zip
Figure18-3.TheListViewtobeAnimated
ThelayoutforthisactivityisinListing18-5.
Listing18-5.ListViewXMLLayoutFile
<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/layout/list_layout.xmlproject:ProAndroid5_ch18_TestLayoutAnimation.zip--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><ListViewandroid:id="@+id/list_view_id"android:layout_width="fill_parent"android:layout_height="fill_parent"/></LinearLayout>
Listing18-5showsasimpleLinearLayoutwithasingleListViewinit.Theactivitycodetoshowthelayoutfrom18-5asFigure18-3isinListing18-6.
Listing18-6.Layout-AnimationActivityCode
//filename:LayoutAnimationActivity.java//project:ProAndroid5_ch18_TestLayoutAnimation.zippublicclassLayoutAnimationActivityextendsActivity{
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.list_layout);setupListView();}privatevoidsetupListView(){String[]listItems=newString[]{"Item1","Item2","Item3","Item4","Item5","Item6",};ArrayAdapter<String>listItemAdapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,listItems);ListViewlv=(ListView)this.findViewById(R.id.list_view_id);lv.setAdapter(listItemAdapter);}}
Let’sseenowhowtoapplyscaleanimationfromListing18-4tothisListView.TheListViewrequiresanotherXMLfilethatactsasamediatorbetweenitselfandthescaleanimationinListing18-4.ThisisbecausetheanimationsdefinedinListing18-4aregenericandapplytoanyview.Ontheotherhandalayoutisacollectionofviews.SothemediatorlayoutanimationXMLfileinListing18-7reusesthegenericanimationXMLfileandspecifiestheadditionalattributesthatareapplicabletoacollectionofviews.ThismediatorlayoutanimationXMLfileisshowninListing18-9.
Listing18-7.Layout-ControllerXMLFile
<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/anim/list_layout_controller.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><layoutAnimationxmlns:android="http://schemas.android.com/apk/res/android"
android:delay="100%"android:animationOrder="reverse"android:animation="@anim/scale"/>
ThisXMLfileneedstobein/res/animsubdirectory.ThisXMLfilespecifiesthattheanimationinthelistshouldproceedinreverse,andtheanimationforeachitemshouldstartwitha100%delaywithrespecttothetotalanimationduration.A100%durationensuresthattheanimationofoneitemiscompletebeforetheanimationofthenextitemstarts.Youcanchangethispercentagetosuittheneedsofyouranimation.Anythingless
than100%willresultinanoverlappinganimationofitems.ThismediatorXMLfilealsoreferstotheindividualanimationfile,scale.xml(Listing18-4)throughtheresourcereference@anim/scale.Listing18-8showshowtoattachtheanimationofListing18-4totheactivitylayoutofListing18-5viathemediatorofListing18-7.
Listing18-8.TheUpdatedCodeforthelist_layout.xmlFile
<?xmlversion="1.0"encoding="utf-8"?><!--filename:/res/layout/list_layout.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><ListViewandroid:id="@+id/list_view_id"android:persistentDrawingCache="animation|scrolling"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layoutAnimation="@anim/list_layout_controller"/></LinearLayout>
InListing18-8android:layoutAnimationisthetagthatpointstothemediatingXMLfileofListing18-7,whichinturnpointstothescale.xmlofListing18-5.InListing18-8AndroidSDKdocsrecommendsettingthepersistentDrawingCachetagonthelistviewtooptimizeforanimationandscrolling.IfyouweretoruntheapplicationProAndroid5_ch18_TestLayoutAnimation.zip,youwouldseethescaleanimationtakeeffectontheindividuallistitemsastheactivitygetsloaded.Wehavesettheanimationdurationto500mssothatscalechangecanbeobservedaseachlistitemisdrawn.
Withthissampleprogramyoucanexperimentwithdifferentanimationtypes.YoucantryalphaanimationwiththecodeinListing18-9.
Listing18-9.Thealpha.xmlFiletoTestAlphaAnimation
<?xmlversion="1.0"encoding="utf-8"?><!--file:/res/anim/alpha.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><alphaxmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"android:fromAlpha="0.0"android:toAlpha="1.0"android:duration="1000"/>
Alphaanimationcontrolsfading(ofcolor).InListing18-9alphaanimationcolorgoes
frominvisibletofullintensityin1second.Don’tforgettochangethemediatorXMLfile(seeListing18-7)topointtothenewanimationfileifyouintendtousethesamemediatorfile.
Listing18-10showsananimationthatcombinesachangeinpositionwithachangeincolorgradient.
Listing18-10.CombiningTranslateandAlphaAnimationsThroughanAnimationSet
<?xmlversion="1.0"encoding="utf-8"?><!--file:/res/anim/alpha_translate.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><setxmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"><translateandroid:fromYDelta="-100%"android:toYDelta="0"android:duration="500"/><alphaandroid:fromAlpha="0.0"android:toAlpha="1.0"android:duration="500"/></set>
NoticetwoanimationsareintheanimationsetofListing18-10.Translateanimationwillmovethetextfromtoptobottominitscurrentlyallocateddisplayspace.Alphaanimationwillchangethecolorgradientfrominvisibletovisibleasthetextitemdescendsintoitsslot.Toseethisanimationinaction,changethelayoutAnimationmediatorXMLfilewithreferencetofilename@anim/alpha_translate.xml.Listing18-11showsthedefinitionforarotationanimation.
Listing18-11.RotateAnimationXMLFile
<!--file:/res/anim/rotate.xml(ProAndroid5_ch18_TestLayoutAnimation.zip)--><rotatexmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"android:fromDegrees="0.0"android:toDegrees="360"android:pivotX="50%"android:pivotY="50%"android:duration="500"/>
Listing18-11spinseachtextiteminthelistonefullcirclearoundthemidpointofthetextitem.Let’stalkabouttheinterpolatorsthatyouhaveseenusedintheanimationXMLfiles.
UnderstandingInterpolatorsInterpolatorstellhowapropertychangesovertimefromitsstartvaluetotheendvalue.Willthechangeoccurlinearlyorexponentially?Willthechangestartquicklyandslowdowntowardtheend?
AlphaanimationinListing18-9identifiestheinterpolatoras
accelerate_interpolator.ThereisacorrespondingJavaobjectthatdefinesthebehaviorofthisinterpolator.Aswe’vespecifiedthisinterpolatorasaresourcereferenceinListing18-9,theremustbeafilecorrespondingtothe@anim/accelerate_interpolatorthatdescribeswhatthisJavaobjectisandwhatadditionalparametersitmighttake.Listing8-12showstheresourceXMLfiledefinitionpointedtobytheresourcereference@android:anim/accelerate_interpolator:
Listing18-12.AnInterpolatorDefinitionasanXMLResource
<accelerateInterpolatorxmlns:android="http://schemas.android.com/apk/res/android"factor="1"/>
YoucanseethisXMLfileinthesubdirectory/res/anim/accelerate_interpolator.xmlintherootAndroidSDKpackage.(Caution:Thisfilecouldlookdifferentlydependingontherelease.)TheaccelerateInterpolatorXMLtagcorrespondstotheJavaclassandroid.view.animation.AccelerateInterpolator.YoucanlookupthecorrespondingJavadocumentationtoseewhatXMLtagsareavailable.Thisinterpolator’sgoalistoprovideamultiplicationfactorgivenatimeintervalbasedonahyperboliccurve.ThesourcecodesnippetinListing18-13forthisinterpolatorillustratesthis.(Caution:ThiscodecouldlookdifferentdependingontheAndroidrelease.)
Listing18-13.SampleCodefromAccelerateInterpolatorintheCoreAndroidSDK
publicfloatgetInterpolation(floatinput){if(mFactor==1.0f){return(float)(input*input);}else{return(float)Math.pow(input,2*mFactor);}}
EveryinterpolatorimplementsthegetInterpolationmethoddifferently.IncaseoftheAccelerateInterpolator,iftheinterpolatorissetupintheresourcefilewithafactorof1.0,itwillreturnthesquareoftheinputateachinterval.Otherwise,itwillreturnapoweroftheinputthatisfurtherscaledbythefactoramount.Ifthefactoris1.5,youwillseeacubicfunctioninsteadofasquarefunction.
ThesupportedinterpolatorsincludeAccelerateDecelerateInterpolator,AccelerateInterpolator,CycleInterpolator,DecelerateInterpolator,LinearInterpolator,AnticipateInterpolator,AnticipateOvershootInterpolator,BounceInterpolator,andOvershootInterpolator.
Toseehowflexibletheseinterpolatorscanbe,takeaquicklookinListing18-14atthe
BounceInterpolatorwhichbouncestheobject(thatis,movesitbackandforth)towardtheendoftheanimationcycle:
Listing18-14.BounceInterpolatorImplementationintheCoreAndroidSDK
publicclassBounceInterpolatorimplementsInterpolator{privatestaticfloatbounce(floatt){returnt*t*8.0f;}publicfloatgetInterpolation(floatt){t*=1.1226f;if(t<0.3535f)returnbounce(t);elseif(t<0.7408f)returnbounce(t-0.54719f)+0.7f;elseif(t<0.9644f)returnbounce(t-0.8526f)+0.9f;elsereturnbounce(t-1.0435f)+0.95f;}}
YoucanfindthebehaviorofthesevariousinterpolatorsdescribedatthefollowingURL:
http://developer.android.com/reference/android/view/animation/package-summary.html
JavadocumentationforeachoftheseclassesalsopointsouttheXMLtagsavailabletocontrolthem.
ExploringViewAnimationThroughviewanimationyoucananimateaviewbymanipulatingitstransformationmatrix.Atransformationmatrixislikealensthatprojectsaviewontothedisplay.Atransformationmatrixcanaffecttheprojectedviewsscale,size,position,andcolor.
Identitytransformationmatrixpreservestheoriginalview.Youstartwithanidentitymatrixandapplyaseriesofmathematicaltransformationsinvolvingsize,position,andorientation.Youthensetthefinalmatrixasthetransformationmatrixfortheviewyouwanttotransform.
Androidexposesthetransformationmatrixforaviewbyallowingregistrationofananimationobjectwiththatview.Theanimationobjectwillbepassedtothetransformationmatrix.
ConsiderFigure18-4asademonstrationofviewanimation.TheStartAnimationbuttonanimatesthelistviewtostartsmallinthemiddleofthescreenandgraduallyfillthefullspace.Listing18-15showstheXMLlayoutfileusedforthisactivity.
Figure18-4.AViewAnimationActivity
Listing18-15.XMLLayoutFilefortheView-AnimationActivity
<?xmlversion="1.0"encoding="utf-8"?><!--filen:at/res/layout/list_layout.xml(ProAndroid5_ch18_TestViewAnimation.zip)--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><Buttonandroid:id="@+id/btn_animate"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="StartAnimation"/><ListViewandroid:id="@+id/list_view_id"android:persistentDrawingCache="animation|scrolling"android:layout_width="fill_parent"android:layout_height="fill_parent"/></LinearLayout>
Listing18-16showstheactivitycodethatloadsthislayout.
Listing18-16.CodefortheView-AnimationActivity,BeforeAnimation
//filename:ViewAnimationActivity.java(ProAndroid5_ch18_TestViewAnimation.zip)publicclassViewAnimationActivityextendsActivity{
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.list_layout);setupListView();this.setupButton();}privatevoidsetupListView(){String[]listItems=newString[]{"Item1","Item2","Item3","Item4","Item5","Item6",};ArrayAdapter<String>listItemAdapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,listItems);ListViewlv=(ListView)this.findViewById(R.id.list_view_id);lv.setAdapter(listItemAdapter);}privatevoidsetupButton(){Buttonb=(Button)this.findViewById(R.id.btn_animate);b.setOnClickListener(newButton.OnClickListener(){publicvoidonClick(Viewv){//animateListView();}});}}
WiththiscodeyouwillseetheUIaslaidoutinFigure18-4.ToaddanimationtotheListViewshowninFigure18-4weneedaclassthatderivesfromandroid.view.animation.Animation.Listing18-17showsthisclass.
Listing18-17.CodefortheViewAnimationClass
//filename:ViewAnimation.javaproject:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationextendsAnimation{
@Override
publicvoidinitialize(intwidth,intheight,intparentWidth,intparentHeight){super.initialize(width,height,parentWidth,parentHeight);setDuration(2500);setFillAfter(true);setInterpolator(newLinearInterpolator());}@OverrideprotectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){finalMatrixmatrix=t.getMatrix();matrix.setScale(interpolatedTime,interpolatedTime);}}
InListing18-7initializemethodisacallbackmethodwiththedimensionsoftheview.Animationparameterscanbeinitializedhere.Heretheanimationdurationissetto2.5seconds.WehavesettheanimationeffecttoremainintactaftertheanimationcompletesbysettingFillAftertotrue.We’vesetalinearinterpolator.Allofthesepropertiescomefromthebaseandroid.view.animation.Animationclass.
ThemainpartoftheanimationoccursintheapplyTransformationmethod.AndroidSDKcallsthismethodagainandagaintosimulateanimation.EverytimeAndroidcallsthemethod,interpolatedTimehasadifferentvalue.Thisvaluechangesfrom0to1dependingonwheretheanimationisinthe2.5-seconddurationthatyousetduringinitialization.WheninterpolatedTimeis1,theanimationisattheend.Ourgoalinthismethodistochangethetransformationmatrixthatisavailablethroughthetransformationobjectcalledt.Firstgetthematrixandchangesomethingaboutit.Whentheviewgetspainted,thenewmatrixwilltakeeffect.MethodsavailableontheMatrixobjectaredocumentedintheSDKat
http://developer.android.com/reference/android/graphics/Matrix.html
InListing18-17,thecodethatchangesthematrixis
matrix.setScale(interpolatedTime,interpolatedTime);
setScalemethodtakestwoparameters:thescalingfactorinthexdirectionandthescalingfactorintheydirection.BecauseinterpolatedTimegoesbetween0and1,youcanusethatvaluedirectlyasthescalingfactor.Atthestartoftheanimation,thescalingfactoris0inboththexandydirections.Halfwaythroughtheanimation,thisvaluewillbe0.5inbothxandydirections.Attheendofanimation,theviewwillbeatitsfullsizebecausethescalingfactorwillbe1inboththexandydirections.TheendresultofthisanimationisthattheListViewstartsouttinyandgrowstofullsize.Listing18-18showsthefunctionyouneedtoaddtheactivityclassinListing18-15andcallitfromthebuttonclick.
Listing18-18.CodefortheView-AnimationActivity,IncludingAnimation
privatevoidanimateListView(){ListViewlv=(ListView)this.findViewById(R.id.list_view_id);lv.startAnimation(newViewAnimation());}
NoteInthissectiononViewAnimationwearegoingtosuggestalternateimplementationsfortheViewAnimationclassofListing18-18.InthesuppliedprojecttherearevariationsofthisclassavailableasViewAnimation,ViewAnimation1,ViewAnimation2,andViewAnimation3.Codesnippetsinthesubsequentdiscussionswillindicateincommentswhichoftheseclassesholdthatcode.Thereisonlyonemenuiteminthesampleprojectforanimation.TotesteachvariationyouhavetoreplacetheViewAnimation()classinListing18-18withtherespectiveversionofitandreruntheprogramtoseethealteredanimation.
WhenyourunthecodewiththeViewAnimationclassasinListing18-17,youwillnoticesomethingodd.Insteadofuniformlygrowinglargerfromthemiddleofthescreen,theListViewgrowslargerfromthetop-leftcorner.Thisisbecausetheoriginformatrixoperationsisatthetop-leftcorner.Togetthedesiredeffect,youfirsthavetomovethewholeviewsothattheview’scentermatchestheanimationcenter(topleft).Then,youapplythematrixandmovetheviewbacktothepreviouscenter.TherewrittencodefromListing18-16fordoingthisisshowninListing18-19.
Listing18-19.ViewAnimationusingpreTranslateandpostTranslate
//filename:ViewAnimation1.javaproject:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationextendsAnimation{
floatcenterX,centerY;publicViewAnimation(){}
@Overridepublicvoidinitialize(intwidth,intheight,intparentWidth,intparentHeight){super.initialize(width,height,parentWidth,parentHeight);centerX=width/2.0f;centerY=height/2.0f;setDuration(2500);setFillAfter(true);setInterpolator(newLinearInterpolator());}@OverrideprotectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){finalMatrixmatrix=t.getMatrix();
matrix.setScale(interpolatedTime,interpolatedTime);matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);}}
ThepreTranslateandpostTranslatemethodssetupamatrixbeforethescaleoperationandafterthescaleoperation.Thisisequivalenttomakingthreematrixtransformationsintandem.ConsiderthefollowingcodeinListing18-20
Listing18-20.StandardpatternforPreandPostTranslateofTransformationMatrices
matrix.setScale(interpolatedTime,interpolatedTime);matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);
CodeinListing18-20isequivalentto
movetoadifferentcenterscaleitmovetotheoriginalcenter
Youwillseethispatternofpreandpostappliedoften.YoucanalsoaccomplishthisresultusingothermethodsontheMatrixclass,butthistechniqueiscommonasitissuccinct.
TheMatrixclassallowsyounotonlytoscaleaviewbutalsotomoveitaroundthroughtranslatemethodsorchangeitsorientationthroughrotatemethods.Youcanexperimentwiththesemethodsandseetheresultinganimations.Theanimationspresentedinthepreceding“LayoutAnimation”sectionareallimplementedinternallyusingthemethodsonthisMatrixclass.
UsingCameratoProvideDepthPerceptionin2DThegraphicspackageinAndroidprovidesanothertransformationmatrix–relatedfeaturethroughtheCameraclass.Thisclassprovidesdepthperceptiontoa2Dview.YoucantakeourListViewexampleandmoveitbackfromthescreenby10pixelsalongthezaxisandrotateitby30degreesaroundtheyaxis.Listing18-21isanexampleofmanipulatingthetransformationmatrixusingaCamera.
Listing18-21.UsingCameraObject
//filename:ViewAnimation2.javaproject:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationextendsAnimation{
floatcenterX,centerY;Cameracamera=newCamera();publicViewAnimation(floatcx,floatcy){centerX=cx;centerY=cy;
}@Overridepublicvoidinitialize(intwidth,intheight,intparentWidth,intparentHeight){super.initialize(width,height,parentWidth,parentHeight);setDuration(2500);setFillAfter(true);setInterpolator(newLinearInterpolator());}@OverrideprotectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){finalMatrixmatrix=t.getMatrix();camera.save();camera.translate(0.0f,0.0f,(1300-1300.0f*interpolatedTime));camera.rotateY(360*interpolatedTime);camera.getMatrix(matrix);
matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);camera.restore();}}
ThiscodeanimatestheListViewbyfirstplacingtheview1,300pixelsbackonthezaxisandthenbringingitbacktotheplanewherethezcoordinateis0.Whiledoingthis,thecodealsorotatestheviewfrom0to360degreesaroundtheyaxis.Thecamera.translate(x,y,z)methodinListing18-21tellsthecameraobjecttotranslatetheviewsuchthatwheninterpolatedTimeis0(atthebeginningoftheanimation),thezvaluewillbe1300.Astheanimationprogresses,thezvaluewillgetsmallerandsmalleruntiltheend,whentheinterpolatedTimebecomes1andthezvaluebecomes0.
Themethodcamera.rotateY(360*interpolatedTime)takesadvantageof3Drotationaroundanaxisbythecamera.Atthebeginningoftheanimation,thisvaluewillbe0.Attheendoftheanimation,itwillbe360.
Themethodcamera.getMatrix(matrix)takestheoperationsperformedontheCamerasofarandimposesthoseoperationsonthematrixthatispassedin.Oncethecodedoesthat,thematrixhasthetranslationsitneedstogettheendeffectofhavingaCamera.NowtheCameraobjectisnolongernecessarybecausethematrixhasalltheoperationsembeddedinit.Then,youdothepreandpostonthematrixtoshiftthecenterandbringitback.Attheend,yousettheCameratoitsoriginalstatethatwassavedearlier.WiththiscodeinListing18-21youwillseetheListViewarrivingfromthecenteroftheviewinaspinningmannertowardthefrontofthescreen.Asthisversion
ofViewAnimationtakesadditionalconstructionarguments,Listing18-22showshowtoinvokethisversionoftheAnimationView:
Listing18-22.ViewAnimationusingpreTranslateandpostTranslate
//filename:ViewAnimationActivity.java//project:ProAndroid5_ch18_TestViewAnimation.zipListViewlv=(ListView)this.findViewById(R.id.list_view_id);
floatcx=(float)(lv.getWidth()/2.0);floatcy=(float)(lv.getHeight()/2.0);lv.startAnimation(newViewAnimation(cx,cy));
Aspartofourdiscussionaboutviewanimation,weshowedyouhowtoanimateanyviewbyextendinganAnimationclassandthenapplyingittoaview.Inadditiontolettingyoumanipulatematrices(bothdirectlyandindirectlythroughaCameraclass),theAnimationclassalsoletsyoudetectvariousstagesinananimation.Wewillcoverthisnext.
ExploringtheAnimationListenerClassAndroidSDKhasalistenerinterface,AnimationListener,tomonitoranimationevents.Listing18-23demonstratestheseanimationeventsbyimplementingtheAnimationListenerinterface.
Listing18-23.AnImplementationoftheAnimationListenerInterface
//filename:ViewAnimationListener.java//project:ProAndroid5_ch18_TestViewAnimation.zippublicclassViewAnimationListenerimplementsAnimation.AnimationListener{
publicViewAnimationListener(){}publicvoidonAnimationStart(Animationanimation){Log.d("AnimationExample","onAnimationStart");}publicvoidonAnimationEnd(Animationanimation){Log.d("AnimationExample","onAnimationEnd");}publicvoidonAnimationRepeat(Animationanimation){Log.d("AnimationExample","onAnimationRepeat");}}
TheViewAnimationListenerclassinListing18-23justlogsmessages.CodeinListing18-24showshowtoattachananimationlistenertoanAnimationobject.
Listing18-24.AttachinganAnimationListenertoanAnimationObject
privatevoidanimateListView(){ListViewlv=(ListView)this.findViewById(R.id.list_view_id);
//Initwidth,heightandassumingViewAnimationfromListing18-21ViewAnimationanimation=newViewAnimation(width,height);animation.setAnimationListener(newViewAnimationListener());lv.startAnimation(animation);}
NotesonTransformationMatricesAsyouhaveseeninthischapter,matricesarekeytotransformingviewsandanimations.Let’sexploresomekeymethodsoftheMatrixclass.
Matrix.reset():Resetsamatrixtoanidentitymatrix,whichcausesnochangetotheviewwhenapplied
Matrix.setScale(…args..):Changessize
Matrix.setTranslate(…args..):Changespositiontosimulatemovement
Matrix.setRotate(…args..):Changesorientation
Matrix.setSkew(…args..):Distortsaview
Thelastfourmethodshaveinputparameters.
Youcanmultiplymatricestogethertocompoundtheeffectofindividualtransformations.InListing18-25,considerthreematrices,m1,m2,andm3,whichareidentitymatrices:
Listing18-25.ViewAnimationusingpreTranslateandpostTranslate
m1.setScale(..scaleargs..);m2.setTranslate(..translateargs..)m3.setConcat(m1,m2)
Transformingaviewbym1andthentransformingtheresultingviewwithm2isequivalenttotransformingthesameviewbym3.Notethatm3.setConcat(m1,m2)isdifferentfromm3.setConcat(m2,m1).setConcat(matrix1,matrix2)multipliestwomatricesinthatgivenorder.
YouhavealreadyseenthepatternusedbythepreTranslateandpostTranslatemethodstoaffectmatrixtransformation.Infact,preandpostmethodsarenotuniquetotranslate,andyouhaveversionsofpreandpostforeveryoneofthesettransformationmethods.Ultimately,apreTranslatesuchasm1.preTranslate(m2)isequivalentto
m1.setConcat(m2,m1)
Inasimilarmanner,themethodm1.postTranslate(m2)isequivalentto
m1.setConcat(m1,m2)
ConsiderthecodeinListing18-26
Listing18-26.PreandPostTranslatePattern
matrix.setScale(interpolatedTime,interpolatedTime);matrix.preTranslate(-centerX,-centerY);matrix.postTranslate(centerX,centerY);
ThecodeinthisListing18-26isequivalenttothecodeinListing18-27
Listing18-27.EquivalenceofPreandPostTranslatePattern
MatrixmatrixPreTranslate=newMatrix();matrixPreTranslate.setTranslate(-centerX,-centerY);
MatrixmatrixPostTranslate=newMatrix();matrixPostTranslate.setTranslate(centerX,centerY);
matrix.setConcat(matrixPreTranslate,matrix);matrix.setConcat(matrix,matrixPostTranslate);
ExploringPropertyAnimations:TheNewAnimationAPITheanimationAPIisoverhauledin3.0and4.0ofAndroid.Thisnewapproachtoanimationsiscalledpropertyanimation.ThepropertyanimationAPIisextensiveanddifferentenoughtorefertothepreviousanimationAPI(priorto3.x)asthelegacyAPIeventhoughthepreviousapproachisstillvalidandnotdeprecated.TheoldanimationAPIisinthepackageandroid.view.animation.ThenewanimationAPIisinthepackageandroid.animation.KeyconceptsinthenewpropertyanimationAPIare:
Animators
Valueanimators
Objectanimators
Animatorsets
Animatorbuilders
Animationlisteners
Propertyvalueholders
Typeevaluators
Viewpropertyanimators
Layouttransitions
AnimatorsdefinedinXMLfiles
Wewillcovermostoftheseconceptsintherestofthechapter.
UnderstandingPropertyAnimationThepropertyanimationapproachchangesthevalueofapropertyovertime.Thispropertycanbeanything,suchasastand-aloneinteger,afloat,oraspecificpropertyofanobjectsuchasaview.Forexample,youcanchangeanintvaluefrom10to200overatimeperiodof5secondsbyusingananimatorclasscalledValueAnimator(seeListing18-28).
Listing18-28.ASimpleValueAnimator
//file:TestBasicValueEvaluator.java(ProAndroid5_ch18_TestPropertyAnimation.zip)//Defineananimatortochangeanintvaluefrom10to200
ValueAnimatoranim=ValueAnimator.ofInt(10,200);
//setthedurationfortheanimationanim.setDuration(5000);//5seconds,default300ms
//Provideacallbacktomonitorthechangingvalueanim.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){publicvoidonAnimationUpdate(ValueAnimatoranimation){Integervalue=(Integer)animation.getAnimatedValue();//thiscodegetscalledmanymanytimesfor5seconds.//Thevaluewillrangefrom10to200}});anim.start();
Theideaiseasytograsp.AValueAnimatorisamechanismtodosomethingevery10ms(thisisthedefaultframerate).Althoughthisisthedefaultframerate,dependingonthesystemloadyoumaynotgetcalledthatmanytimes.Fortheexamplegivenwecouldexpecttogetcalled500timesoveraspanof5seconds.Onanemulatorourtestsshowthatitmaybeasfewas10times.Howeverthelastcallwillbeclosetothe5-secondduration.
Inthecorrespondingcallbackthatiscalledforeveryframe(every10ms),youcanchoosetoupdateavieworanyotheraspecttoaffectanimation.InadditiontotheonAnimationUpdate,otherusefulcallbacksareavailableonthegeneralpurposeAnimator.AnimatorListenerinterface(Listing18-28)fromtheAndroidSDK
whichcanbeattachedtotheValueAnimatorthroughitsbaseclassAnimator.SoonaValueAnimator,youcandoaddListener(Animator.AnimatorListenerlistener).SeeListing18-29.
Listing18-29.AnimatorListenerCallbackInterface
publicstaticinterfaceAnimator.AnimatorListener{abstractvoidonAnimationStart(Animatoranimation);abstractvoidonAnimationRepeat(Animatoranimation);abstractvoidonAnimationCancel(Animatoranimation);abstractvoidonAnimationEnd(Animatoranimation);}
YoucanusethesecallbacksinListing18-29tofurtheractonobjectsofinterestduringorafterananimation.
PropertyAnimationreliesontheavailabilityofanandroid.os.Looperonthethreadthatisinitiatingtheanimation.ThisisgenerallythecasefortheUIthread.ThecallbackshappenontheUIthreadaswellwhentheanimatingthreadisthemainthread.
AsyouuseValueAnimatorsandtheirlisteners,keepinmindthelifetimeoftheseobjects.EvenifyouletthereferenceofaValueAnimatorgofromyourlocalscope,theValueAnimatorwillliveonuntilitfinishestheanimation.IfyouweretoaddalistenerthenallthereferencesthatthelistenerholdsarealsovalidforthelifetimeoftheValueAnimator.
PlanningaTestBedforPropertyAnimationStartingwiththebasicideaofvalueanimators,Androidprovidesanumberofderivedwaystoanimateanyarbitraryobject,especiallyviews.Todemonstratethesemechanisms,wewilltakeasimpletextviewinalinearlayoutandanimateitsalphaproperty(simulatingtransparencyanimation)andalsothexandypositions(simulatingmovement).WewilluseFigure18-5asananchortoexplainpropertyanimationconcepts.
Figure18-5.ActivitytodemonstratePropertyanimations
EachbuttoninFigure18-5usesaseparatemechanismtoanimatethetextviewatthebottomofthefigure.Themechanismswewilldemonstrateareasfollows:
Button1:Usingobjectanimators,fadeoutandfadeinaviewalternativelyattheclickofabutton.
Button2:UsinganAnimatorSet,runafade-outanimationfollowedbyfade-inanimationinasequentialmanner.
Button3:UseanAnimatiorSetBuilderobjecttotiemultipleanimationstogetherina“before,”“after,”or“with”relationship.Usethisapproachtorunthesameanimationasbutton2.
Button4:DefineanXMLfileforbutton2’ssequenceanimationandattachittothetextviewforthesameanimationaffect.
Button5:UsingaPropertyValuesHolderobject,animatemultiplepropertiesofthetextviewinthesameanimation.Wewillchangethexandyvaluestomovethetextviewfrombottomrighttotopleft.
Button6:UseViewPropertyAnimatortomovethetextviewfrombottomrighttotopleft(sameanimationasbutton5).
Button7:UseaTypeEvaluatoroncustompointobjectstomovethetextviewfrombottomrighttotopleft(sameanimationasbutton5).
Button8:Usekeyframestoaffectmovementandalsoalphachangesonthetextview(sameanimationasbutton5,butstaggered).
ConstructingtheactivityinFigure18-5isstraightforward.YoucanseethelayoutforthisactivityandtheactivitycodeinthedownloadprojectfileProAndroid5_ch18_TestPropertyAnimation.zip.Let’sstartwiththefirstbutton.
AnimatingViewswithObjectAnimatorsThefirstbuttoninFigure18-5(Fadeout:Animator)invokesthetoggleAnimation(View)methodthatisinListing18-30.
Listing18-30.BasicViewAnimationwithObjectAnimators
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtoggleAnimation(ViewbtnView){
ButtontButton=(Button)btnView;//Thebuttonwehavepressed//m_tv:isthepointertothetextview//Animatethealphafromcurrentvalueto0thiswillmakeitinvisibleif(m_tv.getAlpha()!=0){ObjectAnimatorfadeOut=ObjectAnimator.ofFloat(m_tv,"alpha",0f);fadeOut.setDuration(5000);fadeOut.start();tButton.setText("FadeIn");}//Animatethealphafromcurrentvalueto1thiswillmakeitvisibleelse{ObjectAnimatorfadeIn=ObjectAnimator.ofFloat(m_tv,"alpha",1f);fadeIn.setDuration(5000);fadeIn.start();
tButton.setText("Fadeout");}}
ThecodeinListing18-30firstexaminesthealphavalueofthetextview.Ifthisvalueisgreaterthan0,thenthecodeassumesthatthetextviewisvisibleandrunsafade-outanimation.Attheendofthefade-outanimation,thetextviewwillbeinvisible.Ifthealphavalueofthetextviewis0,thenthecodeassumesthetextviewisinvisibleandrunsafade-inanimationtomakethetextviewvisibleagain.
TheObjectAnimatorcodeinListing18-30isreallysimple.AnObjectAnimatorisobtainedonthetextview(m_tv)usingthestaticmethodofFloat().Thefirstargumenttothismethodisanobject(m_tv).ThesecondargumentisthepropertynameoftheobjectthatyouwanttheObjectAnimatortomodifyoranimate.Inthecaseofthetextviewm_tvthispropertynameisalpha.Thetargetobjectneedstohaveapublicmethodtomatchthisname.Forapropertynamedalpha,thecorrespondingviewobjectneedstohavethefollowingsetmethod:
view.setAlpha(floatf);
ThethirdargumenttoofFloat()isthevalueofthepropertyattheendoftheanimation.Ifyouspecifyafourthargument,thenthethirdargumentisthestartingvalueandthefourthisthetargetvalue.Youcanpassmoreargumentsaslongastheyareallfloats.Theanimationwillusethosevaluesasintermediatevaluesintheanimationprocess.
Ifyouspecifyjustthe“to”value,thenthe“from”valueistakenfromthecurrentvaluebyusing
view.getAlpha();
Whenyouplaythisanimation,thetextviewwillgraduallydisappearfirst.ThecodeinListing18-30thenrenamesthebuttonto“Fadein.”Nowifyouclickthebuttonagain,whichisnowcalled“Fadein,”thesecondanimationinListing18-30isrun,andthetextviewwillappeargraduallyoveraperiodof5seconds.
AchievingSequentialAnimationwithAnimatorSetButton2inFigure18-5runstwoanimationsoneaftertheother:afade-outfollowedbyafade-in.Wecoulduseanimationlistenercallbackstowaitforthefirstanimationtofinishandthenstartthesecondanimation.ThereisanautomatedwaytorunanimationsintandemthroughtheclassAnimatorSettogetthesameeffect.Button2demonstratesthisinListing18-31.
Listing18-31.SequentialAnimationThroughanAnimatorSet
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidsequentialAnimation(ViewbView){
ObjectAnimatorfadeOut=ObjectAnimator.ofFloat(m_tv,"alpha",0f);
ObjectAnimatorfadeIn=ObjectAnimator.ofFloat(m_tv,"alpha",1f);AnimatorSetas=newAnimatorSet();as.playSequentially(fadeOut,fadeIn);as.setDuration(5000);//5secsas.start();}
InListing18-31,wehavecreatedtwoanimators:afade-outanimatorandafade-inanimator.Thenwecreatedananimatorsetandtellittoplaybothanimationssequentially.
YoucanalsochoosetoplayanimationstogetherusingananimatorsetbycallingthemethodplayTogether().Bothofthesemethods,playSequentially()andplayTogether(),cantakeavariablenumberofAnimatorobjects.
Whenyouplaythisanimation,thetextviewwillgraduallydisappearandthenreappear,muchliketheanimationyousawearlier.
SettingAnimationRelationshipswithAnimatorSet.BuilderAnimatorSetalsoprovidesabitmoreelaboratewaytolinkanimationsthroughautilityclasscalledAnimatorSet.Builder.Listing18-32demonstratesthis.
Listing18-32.UsinganAnimatorSetBuilder
//filename:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestAnimationBuilder(Viewv){
ObjectAnimatorfadeOut=ObjectAnimator.ofFloat(m_tv,"alpha",0f);ObjectAnimatorfadeIn=ObjectAnimator.ofFloat(m_tv,"alpha",1f);AnimatorSetas=newAnimatorSet();//play()returnsthenestedclass:AnimatorSet.Builderas.play(fadeOut).before(fadeIn);as.setDuration(5000);//5secsas.start();}
TheplaymethodonanAnimatorSetreturnsaclasscalledAnimatorSet.Builder.Thisispurelyautilityclass.Themethodsonthisclassareafter(animator),before(animator),andwith(animator).Thisclassisinitializedwiththefirstanimatoryousupplythroughtheplaymethod.Everyothercallonthisobjectiswithrespecttothisoriginalanimator.ConsiderListing18-33:
Listing18-33.UsingAnimationSet.Builder
AnimatorSet.Builderbuilder=someSet.play(main_animator).before(animator1);
Withthiscodeanimator1willplayafterthemain_animator.Whenwesaybuilder.after(animator2),theanimationofanimator2willplaybeforemain_animator.Themethodwith(animator)playstheanimationstogether.
ThekeypointwithanAnimationBuilderisthattherelationshipestablishedviabefore(),after(),andwith()isnotchainedbutonlytiedtotheoriginalanimatorthatwasobtainedfromplay()method.Also,theanimationstart()methodisnotonthebuilderobjectbutontheoriginalanimatorset.WhenyouplaythisanimationthroughButton3,thetextviewwillgraduallydisappearandthenreappear,muchasinthepreviousanimation.
UsingXMLtoLoadAnimatorsItisonlytobeexpectedthattheAndroidSDKallowsanimatorstobedescribedinXMLresourcefiles.AndroidSDKhasanewresourcetypecalledR.animatortodistinguishanimatorresourcefiles.TheseXMLfilesarestoredinthe/res/animatorsubdirectory.Listing18-34isanexampleofananimatorsetdefinedinanXMLfile.
Listing18-34.AnAnimatorXMLResourceFile
<?xmlversion="1.0"encoding="utf-8"?><!--file:/res/animator/fadein.xml(ProAndroid5_ch18_TestPropertyAnimation.zip)--><setxmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially"><objectAnimatorandroid:interpolator="@android:interpolator/accelerate_cubic"android:valueFrom="1"android:valueTo="0"android:valueType="floatType"android:propertyName="alpha"android:duration="5000"/><objectAnimatorandroid:interpolator="@android:interpolator/accelerate_cubic"android:valueFrom="0"android:valueTo="1"android:valueType="floatType"android:propertyName="alpha"android:duration="5000"/></set>
YouwillnaturallywonderwhatXMLnodesareavailableforyoutodefinetheseanimations.Asof4.0theallowedXMLtagsareasfollows:
animator:BindstoValueAnimator
objectAnimator:BindstoObjectAnimator
set:BindstoAnimatorSet
YoucanseeabasicdiscussionofthesetagsatthefollowingAndroidSDKURL:
http://developer.android.com/guide/topics/graphics/prop-animation.html#declaring-xml
ThecompleteXMLreferencefortheanimationtagscanbefoundatthefollowingURL:
http://developer.android.com/guide/topics/resources/animation-resource.html#Property
OnceyouhavethisXMLfile,youcanplaythisanimationusingthemethodshowninListing18-35.
Listing18-35.LoadinganAnimatorXMLResourceFile
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidsequentialAnimationXML(ViewbView){
AnimatorSetset=(AnimatorSet)AnimatorInflater.loadAnimator(this,R.animator.fadein);set.setTarget(m_tv);set.start();}
NoticehowitisnecessarytoloadtheanimationXMLfilefirstfollowedbyexplicitlysettingtheobjecttoanimate.Inourcase,theobjecttoanimateisthetextviewrepresentedbym_tv.ThemethodinListing18-35iscalledbybutton4(FadeOut/FadeInXML).Whenthisanimationruns,thetextviewwillfadeoutfirstandthenreappearbyfadingin,justasinthepreviousalphaanimations.
UsingPropertyValuesHolderSofar,wehaveseenhowtoanimateasinglevalueinasingleanimation.TheclassPropertyValuesHolderletsusanimatemultiplevaluesduringtheanimationcycle.Listing18-36demonstratestheuseofthePropertyValuesHolderclass.
Listing18-36.UsingthePropertyValueHolderClass
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestPropertiesHolder(Viewv){
//Getthecurrentcoordinatesofthetextview.//Thisallowsustoknowstartingandendingpositionstoanimatefloath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();
//Settheviewtothebottomrightasastartingpointm_tv.setX(w);m_tv.setY(h);
//fromtherightbottomanimate"x"toitsoriginalposition:topleftPropertyValuesHolderpvhX=PropertyValuesHolder.ofFloat("x",x);
//fromtherightbottomanimate"y"toitsoriginalpositionPropertyValuesHolderpvhY=PropertyValuesHolder.ofFloat("y",y);
//whenyoudonotspecifythefromposition,theanimationwilltakethecurrentposition//asthefromposition.
//Telltheobjectanimatortoconsiderboth//"x"and"y"propertiestoanimatetotheirrespectivetargetvalues.ObjectAnimatoroa=ObjectAnimator.ofPropertyValuesHolder(m_tv,pvhX,pvhY);
//setthedurationoa.setDuration(5000);//5secs
//hereisawaytosetaninterpolatoronanyanimatoroa.setInterpolator(newAccelerateDecelerateInterpolator());oa.start();}
APropertyValuesHolderclassholdsapropertynameanditstargetvalue.ThenyoucandefinemanyofthesePropertyValuesHolderswiththeirownpropertytoanimate.YoucansupplythissetofPropertyValuesHolderstotheobjectanimator.Theobjectanimatorwillthensetthesepropertiestotheirrespectivevaluesonthetargetobject.Witheachrefreshoftheanimation,allthevaluesfromeachPropertyValuesHolderwillbeappliedallatonce.Thisismoreefficientthanapplyingmultipleanimationsinparallel.
Button5inFigure18-5runsthecodeinListing18-36.Whenthisanimationruns,thetextviewwillemergefrombottomrightandmigratetowardthetopleftin5seconds.
UnderstandingViewPropertiesAnimationAndroidSDKhasanoptimizedapproachtoanimatevariouspropertiesofviews.ThisisdonethroughaclasscalledViewPropertyAnimator.Listing18-37usesthisclasstomovethetextviewfrombottomrighttotopleft.
Listing18-37.UsingaViewPropertyAnimator
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestViewAnimator(Viewv){
//Remembercurrentboundariesfloath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();
//Positiontheviewatbottomrightm_tv.setX(w);m_tv.setY(h);
//GetaViewPropertyAnimatorfromthetextviewViewPropertyAnimatorvpa=m_tv.animate();
//Setasmanytargetvaluesyouwanttosetvpa.x(x);vpa.y(y);
//Setdurationandinterpolatorsvpa.setDuration(5000);//2secsvpa.setInterpolator(newAccelerateDecelerateInterpolator());
//TheanimationautomaticallystartswhentheUIthreadgetstoit.//Noneedtoexplicitlycallthestartmethod.//vpa.start();}
ThestepstouseViewPropertyAnimatorareasfollows:
1. GetaViewPropertyAnimatorbycallingtheanimate()methodonaview.
2. UsetheViewPropertyAnimatorobjecttosetvariousfinalpropertiesofthatview,suchasx,y,scale,alpha,andsoon.
3. LettheUIthreadproceedbyreturningfromthefunction.Theanimationwillautomaticallystart.
Thisanimationisinvokedbybutton6.Whenthisanimationruns,thetextviewwillmigratefrombottomrighttotopleft.
UnderstandingTypeEvaluatorsAswehaveseen,anobjectanimatordirectlysetsaparticularvalueonatargetobjectwitheachanimationcycle.Thesevaluessofarhavebeensinglepointvaluessuchasfloats,ints,andsoon.Whathappensifyourtargetobjecthasapropertythatisanobjectitself?Thisiswheretypeevaluatorscomeintoplay.
Toillustratethisconsideraviewonwhichwewanttosettwovaluessuchas‘x’and‘y’.Listing18-35showshowweencapsulatearegularviewforwhichweknowhowtochangexandy.TheencapsulationwillallowtheanimationtocallonceforbothxandythroughthePointFabstractionavailableintheAndroidgraphicspackage.WewillprovideasetPoint(PointF)methodandthen,insidethatmethod,parseoutxandyandsetthemontheview.TakealookatListing18-38.
Listing18-38.AnimatingaViewThroughaTypeEvaluator
//file:AnimatableView.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicclassMyAnimatableView{
PointFcurPoint=null;Viewm_v=null;publicMyAnimatableView(Viewv){curPoint=newPointF(v.getX(),v.getY());m_v=v;}publicPointFgetPoint(){returncurPoint;}publicvoidsetPoint(PointFp){curPoint=p;m_v.setX(p.x);m_v.setY(p.y);}}
IncodeListing18-38TypeEvaluatorisahelperobjectthatknowshowtosetacompositevaluesuchasatwo-dimensionalorthree-dimensionalpointduringananimationcycle.Inascenarioinvolvingcompositefields(representedasanobject),anObjectAnimatorwilltakethestartingcompositevalue(likethePointFobjectwhichisacompositeofxandy),anendingcompositevalueandpassthemtoaTypeEvaluatorhelperobjecttogettheintermediateobjectvalue.Thiscompositevalueisthensetonthetargetobject.Listing18-39showshowaTypeEvlautorcalculatesthisintermediatevaluethroughitsevaluatemethod.
Listing18-39.CodingaTypeEvaluator
//file:MyPointEvaluator.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicclassMyPointEvaluatorimplementsTypeEvaluator<PointF>{
publicPointFevaluate(floatfraction,PointFstartValue,PointFendValue){PointFstartPoint=(PointF)startValue;PointFendPoint=(PointF)endValue;returnnewPointF(startPoint.x+fraction*(endPoint.x-startPoint.x),
startPoint.y+fraction*(endPoint.y-startPoint.y));}}
FromListing18-39youcanseethatyouneedtoinheritfromtheTypeEvaluatorinterfaceandimplementtheevaluate()method.Inthismethod,youwillbepassedthefractionoftheanimation’stotalprogress.Youcanusethatfractiontoadjustyourintermediatecompositevalueandreturnitasatypedvalue.
Listing18-40showshowanObjectAnimatorusesMyAnimatableViewandtheMyPointEvaluatortoanimatecompositevaluesforaView.
Listing18-40.UsingaTypeEvaluator
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestTypeEvaluator(Viewv){
floath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();
PointFstartingPoint=newPointF(w,h);PointFendingPoint=newPointF(x,y);
//m_atv:Youwillneedthiscodeinyouractivityearlierasalocalvariable:MyAnimatableViewm_atv=newMyAnimatableView(m_tv);
ObjectAnimatorviewCompositeValueAnimator=ObjectAnimator.ofObject(m_atv,"point",newMyPointEvaluator(),startingPoint,endingPoint);
viewCompositeValueAnimator.setDuration(5000);viewCompositeValueAnimator.start();}
NoticeinListing18-40thattheObjectAnimatorisusingthemethodofObject()asopposedtoofFloat()orofInt().AlsonoticethatthestartingvalueandendingvaluefortheanimationarecompositevaluesrepresentedbytheclassPointF.ThegoaloftheobjectanimatorisnowtocomeupwithanintermediatevalueforPointFandthenpassittothemethodsetPoint(PointF)onthecustomclassMyAnimatableView.TheclassMyAnimatableViewcanaccordinglysettherespectiveindividualpropertiesonthecontainedtextview.ThisanimationinListing18-40usingtheTypeEvaluatorisinvokedbybutton7.Whenthisanimationruns,theviewwillmigratefrombottomrighttotopleft.
UnderstandingKeyFrames
Keyframesareusefulplacesduringananimationcycletoputkeytimemarkers(significantinstancesintime).Akeyframespecifiesaparticularvalueforapropertyatagivenmomentintime.Thekeymarker’stimeisbetween0(beginningofanimation)and1(endofanimation).Onceyougatherthesekey-framevalues,yousetthemagainstaparticularpropertysuchasalpha,x,ory.ThisassociationofkeyframestotheirrespectivepropertiesisdonethroughthePropertyValuesHolderclass.YouthentelltheObjectAnimatortoanimatetheresultingPropertyValuesHolder.Listing18-41demonstrateskey-frameanimation.
Listing18-41.AnimatingaViewUsingKeyFrames
//file:TestPropertyAnimationActivity.java(ProAndroid5_ch18_TestPropertyAnimation.zip)publicvoidtestKeyFrames(Viewv){
floath=m_tv.getHeight();floatw=m_tv.getWidth();floatx=m_tv.getX();floaty=m_tv.getY();
//Startframe:0.2,alpha:0.8Keyframekf0=Keyframe.ofFloat(0.2f,0.8f);
//Middleframe:0.5,alpha:0.2Keyframekf1=Keyframe.ofFloat(.5f,0.2f);
//endframe:0.8,alpha:0.8Keyframekf2=Keyframe.ofFloat(0.8f,0.8f);
PropertyValuesHolderpvhAlpha=PropertyValuesHolder.ofKeyframe("alpha",kf0,kf1,kf2);PropertyValuesHolderpvhX=PropertyValuesHolder.ofFloat("x",w,x);
//endframeObjectAnimatoranim=ObjectAnimator.ofPropertyValuesHolder(m_tv,pvhAlpha,pvhX);anim.setDuration(5000);anim.start();}
TheanimationinListing18-41isinvokedbybutton8.Whenthisanimationruns,youwillseethetextmovefromrighttoleft.When20%ofthetimehaspassed,alphawillchangeto80%.Thealphavaluewillreach20%athalfwayandchangebackto80%atthe80thpercentileoftheanimationtime.
UnderstandingLayoutTransitionsThepropertyanimationAPIalsoprovideslayout-basedanimationsthroughtheLayoutTransitionclass.ThisclassiswelldocumentedaspartofthestandardAPI
JavadocatthefollowingURL.
http://developer.android.com/reference/android/animation/LayoutTransition.html
Wewillsummarizehereonlythekeypointsoflayouttransitions.Toenablelayouttransitionsonaviewgroup(mostlayoutsareviewgroups),youwillneedtousethecodeshowninListing18-42.
Listing18-42.SettingaLayoutTransition
viewgroup.setLayoutTransition(newLayoutTransition());
WiththecodeinListing18-42thelayoutcontainer(ViewGroup)willexhibitdefaulttransitionsasviewsareaddedandremoved.ALayoutTransitionobjecthasfourdifferentdefaultanimationsthatcovereachofthefollowingscenarios:
Addaview(animationfortheviewthatisappearingduetoanaddorashow)
Changeappearing(animationfortherestoftheitemsinthelayoutastheycouldchangetheirsizeorappearanceduetoanewitembeingadded)
Removeaview(animationfortheviewthatisdisappearingduetoaremoveorahide)
Changedisappearing(animationfortherestoftheitemsinthelayoutastheycouldtheirsizeorappearanceduetoanitembeingremoved)
Ifyouwantcustomanimatorsforeachofthesecases,youcansetthemontheLayoutTransitionobject.HereisanexampleinListing18-43.
Listing18-43.LayoutTransitionMethods
//HereishowyougetanewlayouttransitionLayoutTransitionlt=newLayoutTransition();
//YoucansetthislayouttransitiononalayoutsomeLayout.setLayoutTransition(lt);
//obtainadefaultanimatorifyouneedtorememberAnimatordefaultAppearAnimator=lt.getAnimator(APPEARING);
//createanewanimatorObjectAnimatorsomeNewObjectAnimator1,someOtherObjectAnimator2;
//setitasyourcustomanimatorfortheallowedsetofanimators
lt.setAnimator(APPEARING,someNewObjectAnimator1);lt.setAnimator(CHANGE_APPEARING,someNewObjectAnimator1);lt.setAnimator(DISAPPEARING,someNewObjectAnimator1);lt.setAnimator(CHANGE_DISAPPEARING,someOtherObjectAnimator2);
Becausetheanimatoryousupplytoalayouttransitionappliestoeachview,theanimatorsareinternallyclonedbeforebeingappliedtoeachview.
ResourcesHerearesomeusefullinkstowhenyouareworkingwiththeAndroidAnimationAPI:
http://www.androidbook.com/item/3901:AuthorresearchnotesonAndroidpropertyanimations.
http://android-developers.blogspot.com/2011/02/animation-in-honeycomb.html:Akeyblogonpropertyanimations.
http://android-developers.blogspot.com/2011/05/introducing-viewpropertyanimator.html:Ablogonviewpropertyanimations.
http://developer.android.com/guide/topics/graphics/prop-animation.html:PrimarydocumentationonpropertyanimationsfromtheAndroidSDK.
http://developer.android.com/guide/topics/graphics/animation.htmlAndroiddocumentationlinkstoallanimationtypes,includingpropertyanimationsandold-styleanimations.
http://developer.android.com/reference/android/view/animation/package-summary.html:JavadocAPIfortheolderanimationpackageandroid.view.animation.
http://developer.android.com/guide/topics/resources/animation-resource.html:XMLtagsforvariousanimationtypes.
http://www.androidbook.com/proandroid5/projects:Downloadabletestprojectsforthischapter.ThenamesofthezipfilesareProAndroid5_ch18_TestFrameAnimation.zip,ProAndroid5_ch18_TestLayoutAnimation.zip,ProAndroid5_ch18_TestViewAnimation.zip,andProAndroid5_ch18_TestPropertyAnimation.zip.
SummaryInthischapterwehavecoveredframe-by-frameanimation,layoutanimation,viewanimation,interpolators,transformationmatrices,Camera,andvariouswaysofusingthenewpropertyanimationAPI.Allconceptsarepresentedwithworkingcodesnippetsandsupportedbyworkingdownloadableprojects.
Chapter19
ExploringMapsandLocation-BasedServicesInthischapter,wearegoingtotalkaboutmapsandlocation-basedservices.Location-basedservicesformoneofthemoreexcitingpiecesoftheAndroidSDK.ThisportionoftheSDKprovidesAPIstoletapplicationdevelopersdisplayandmanipulatemaps,obtainreal-timedevice-locationinformation,andtakeadvantageofotherexcitingfeatures.WorkingwithmapschangedsignificantlywhenGoogleintroducedtheMapFragmentandversion2oftheGoogleMapsAPI.Thischapterwillgointodetailsofthenewwaysofcreatingmapsandmanipulatingthem.
Thelocation-basedservicesfacilityinAndroidsitsontwopillars:themappingandlocation-basedAPIs.ThemappingAPIsinAndroidprovidefacilitiesforyoutodisplayamapandmanipulateit.Forexample,youcanzoomandpan;youcanchangethemapmode(fromsatelliteviewtotrafficview,forexample);youcanaddmarkersandcustomdatatothemap;andsoon.TheotherendofthespectrumisGlobalPositioningSystem(GPS)dataandinformationaboutlocations,bothofwhicharehandledbythelocationpackage.
TheseAPIsoftenreachacrosstheInternettoinvokeservicesfromGoogleservers,viaGooglePlayServices(thelocaluberapplicationonthedevice).Therefore,youwillusuallyneedtohaveInternetconnectivityforthesetowork.Inaddition,GooglehasTermsofServicethatyoumustagreetobeforeyoucandevelopapplicationswiththeseGoogleservices.Readthetermscarefully;Googleplacessomerestrictionsonwhatyoucandowiththeservicedata.Forexample,youcanuselocationinformationforusers’personaluse,butcertaincommercialusesarerestricted,asareapplicationsinvolvingautomatedcontrolofvehicles.ThetermswillbepresentedtoyouwhenyousignupforaMapsAPIkey.
Inthischapter,we’llgothrougheachofthesepackages.We’llstartwiththemappingAPIsandshowyouhowtousemapswithyourapplications.Asyou’llsee,mappinginAndroidboilsdowntousingMapFragmentclassinadditiontothemappingAPIs,whichintegratewithGoogleMaps.Wewillalsoshowyouhowtoplacecustomdataontothemapsthatyoudisplayandhowtoshowthecurrentlocationofthedeviceonamap.Aftertalkingaboutmaps,we’lldelveintolocation-basedservices,whichextendthemappingconcepts.WewillshowyouhowtousetheAndroidGeocoderclassandtheLocationServicesservice.WewillalsotouchonthreadingissuesthatsurfacewhenyouusetheseAPIs.
UnderstandingtheMappingPackageAswementioned,themappingAPIsareoneofthecomponentsofAndroid’slocation-
basedservices.Themappingpackagecontainsalmosteverythingyou’llneedtodisplayamaponthescreen,handleuserinteractionwiththemap(suchaszooming),displaycustomdataontopofthemap,andsoon.IntheoldversionofAndroidMaps,yourapplicationwouldtalkdirectlytotheGoogleMapsservicesforeverythingmap-related.Inthenewversion,yourapplicationmusttalktoGooglePlayServices,whichisalocalapponthedevice,providedaspartoftheoperatingsystem.YourappstillalsomakescallsovertheInternetfordata,butifGooglePlayServicesisnotpresentlocallyonthedevice,yourmapswillnotwork.Ifyouneedmapsondevicesthatdon’thaveGooglePlayServicesyou’llneedtoexploreoneoftheothermapspackagesavailableforAndroid(e.g.,MapQuest).
InorderforyourapplicationtotalktoGooglePlayServices,youwillneedtoincludetheGooglePlayServiceslibraryintoyourapplication.AndroidStudiodoesthisdifferentlythanEclipsewithADT.SeetheReferencessectionbelowforalinktoonlineinstructionsforthelatestwaytodothis.BeforeyouincludetheGooglePlayServiceslibraryinyourapplication,youmustfirstdownloaditthroughtheSDKManager.You’llfinditunderExtras.
YoumayhavenoticedthatyourAndroidSDKManagershowsGoogleAPIpackagesinadditiontotheAndroidSDKplatforms.Previously,youhadtobaseyourapplicationonaGoogleAPIspackageinordertousemaps,butthatisnolongertrue.Instead,theMapsAPIintegratestoGooglePlayServices,soyourapplicationcanbebasedonaregularAndroidpackage.However,totestamaps-basedappintheemulator,youwouldneedtobaseyouremulator’sAndroidVirtualDevice(AVD)onaGoogleAPIspackage.Moreontestingappslater.
Thefirststeptoworkingwiththemapspackageistodisplayamap.Todothat,you’lluseMapFragment(orSupportMapFragmentifyouwantbackwardscompatibilitywithversionsofAndroidpriortoAPI12,a.k.a.Honeycomb3.1).Usingthisclass,however,requiressomepreparationwork.Specifically,beforeyoucanuseGoogleMapsservices,you’llneedtogetaMapsAPIkeyfromGoogle.TheMapsAPIkeyenablesAndroidtointeractwithGoogleMapsservicestoobtainmapdata.ThenextsectionexplainshowtoobtainaMapsAPIkey.
ObtainingaMapsAPIKeyfromGoogleGooglewantstobeabletoidentifytheapplicationthatisconnectingtothemapservices.Itusesacombinationoftheapplicationpackageandthecertificateusedtosigntheapplication,togenerateaMapsAPIkeythattheapplicationmustthenusetorequestservice.TheMapsAPIkeycanbeusedacrossanumberofpairsofpackagesandcertificates.ThismeansyoucanusethesameMapsAPIkeyfordevelopmentandproduction;thepackagewouldbethesamebutthecertificatesareprobablydifferent.Intheoryyoucouldusethesamekeyacrossmultipleapplicationsbutthispracticeisdiscouraged.Youdon’twanttodothisanywaysinceGoogleimposescertainlimitsontheMapsAPIusageandbysharingaMapsAPIkeywithmultipleapplicationsyoucouldmoreeasilyexceedthelimit.
ToobtainaMapsAPIkey,youneedthecertificatethatyou’llusetosignyourapplication
(inthecaseofadevelopmentversionofyourapp,thedebugcertificate).You’llgettheSHA-1fingerprintofyourcertificate,andthenyou’llenterit,alongwithyourapplication’spackage,onGoogle’swebsitetogenerateanassociatedMapsAPIkey.
First,youmustlocateyourdebugcertificate,whichisgeneratedandmaintainedbyEclipse.YoucanfindtheexactlocationusingtheEclipseIDE.Ifyou’reusinganIDEotherthanEclipse,youjustneedtolocatethekeystorefilewherethecertificatesareheld.FromEclipse’sPreferencesmenu,gotoAndroid Build.Thedebugcertificate’slocationwillbedisplayedintheDefaultDebugKeystorefield,asshowninFigure19-1.
Figure19-1.Thedebugcertificate’slocation
ToextracttheSHA-1fingerprint,youcanrunthekeytoolwiththe–listoption,asshownhere:
keytool-list-aliasandroiddebugkey-keystore"FULLPATHOFYOURdebug.keystoreFILE"-storepassandroid-keypassandroid
Notethatthealiasyouwantfromthedebugstoreisandroiddebugkey.Similarly,thekeystorepasswordisandroid,andtheprivatekeypasswordisalsoandroid.Whenyourunthiscommand,thekeytoolprovidesthefingerprint(seeFigure19-2).
Figure19-2.Thekeytooloutputforthelistoption
You’llnoticethatthefingerprintdisplayedbythekeytoolcommandisthesameasdisplayedinthePreferencesscreenasshowninFigure19-1soyoucouldhavejustgottenitfromthatscreen.ButnowyouknowbothwaystoextractouttheSHA-1fingerprintforyourapplication.WhenyouusekeytooltoextractouttheSHA-1fingerprintfortheproductioncertificate,you’llusethekeystorefile,alias,andpasswordthatyousetupforyourproductioncertificate.
ThenextstepistogotoGoogle’sDeveloperConsoletoaddyourapplication,andfollowingthatyouenabletheMapsAPI.TheresultwillbeyourMapsAPIkeytoincludeinyourapplication.TheDeveloperConsoleishere,andyouwillneedaGoogleaccountinordertogetthere:
https://console.developers.google.com
Youwillneedtocreateanewproject.Aspartofcreatingthenewproject,youneedtoprovideaProjectNameandaProjectID.TheProjectIDwillbepre-populatedwithsomethingstrange-looking.Youcanputanyvalueyouwanthereaslongasitisunique.However,theProjectIDisjustforusebytheGoogleDeveloperConsole;ithasnothingtodowiththesourcecodeofyourapplication.Rememberthatyou’recreatingasampleprojectbasedonthecodeofthischapter’ssampleproject,sothatyoucangetaMapsAPIkeytoseeitwork.
ReadthroughtheTermsofService.Ifyouagreetotheterms,clickCreatetocreateyournewproject.ThissetsupabasictemplateofaprojectwithGoogle.Nextyou’regoingtoenabletheAPIsyouwant.Foramapsapplication,you’llchooseGoogleMapsAndroidAPIv2.Forthechapter’ssampleapplication,youalsowanttoincludetheGeocodingAPI.Youmightgetapop-upwindowcalled‘ConfigureAndroidKeyfor<yourappname>’.Ifyoudon’tgetapop-up,youcannavigatetotheAPIs&auth CredentialssectionofyourprojectintheDevelopersConsoleandgenerateanAPIkeythere.ThisiswhereyouneedtocopyandpasteinboththeSHA-1fingerprintoftheapplicationsigningcertificate,andthepackagenameoftheapplication,separatedbyasemicolon.Thepackagenameistheonefromyoursourcecode.Notethatyoucancopyinmorethanoneline,soifyouhavetheSHA-1fingerprintfromtheproductionapplicationsigningcertificate(whichistypicallydifferentfromtheandroiddebugkeyusedindevelopment),youcouldaddasecondlinefortheproductionapplication.
OnceyoupresstheCreatebuttononthisscreenyouwillgetanAPIkey.ThisiswhatyouwillincludeintheAndroidManifest.xmlfileofyourapplication.TheAPIkeyisactiveimmediately,soyoucanstartusingittoobtainmapdatafromGoogle.
AddingtheMapsAPIKeytoYourApplication
ToseehowtheMapsAPIkeyisaddedtothemanifestfile,seethebottomofListing19-1.
Listing19-1.AndroidManifest.xmlforaSimpleMapsApplication
<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.maps.whereami"android:versionCode="1"android:versionName="1.0">
<uses-sdkandroid:minSdkVersion="10"android:targetSdkVersion="19"/><uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"/><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-featureandroid:glEsVersion="0x00020000"android:required="true"/>
<applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><activityandroid:name="com.androidbook.maps.whereami.MainActivity"android:label="@string/app_name"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity><meta-dataandroid:name="com.google.android.gms.version"android:value="@integer/google_play_services_version"/><meta-dataandroid:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyBDs1ZQgu9X2A4TG1a7fPl-Ge_MKlyviKM"/></application></manifest>
Asyounodoubtnoticed,thereareotherelementswithinthemanifestfilethatmustbepresentforamapsapplicationtowork.The<meta-data>tagabovetheMapsAPIkeyisrequired,asarethepermissionsnearthetop.Technically,theACCESS_FINE_LOCATIONpermissionisnotneededtoshowmaps;itistheresolocationfunctionality(e.g.,GPS)willwork.GPSiscommonlyusedinmapsapplications.ACCESS_NETWORK_STATEandINTERNETpermissionsaretheresothemapsapplicationcandownloadmaptiledata(i.e.,themapgraphics)andtoknowwhattypeandstateofnetworkconnectiontheapplicationhas.TheWRITE_EXTERNAL_STORAGEpermissionistheresothemapsapplicationcancreatealocalcacheofmaptilefilesonthedevice’slocalstoragespace.Withoutcaching,amapsapplicationwouldlikelyspendalotoftimedownloadingmaptilesoverandoveragain,whichisnotonlyinefficientforyourapp,butitplacesanunwantedburdenontheGoogleserversanditcouldconsumealargeportionoftheuser’sdataplan.Andfinally,theglEsVersionfeatureispresentbecauserenderingmapsonthescreenusesOpenGL,sobyrequiringthefeature,theapplicationavoidsgettinginstalledondevicesthatcouldnotdisplaymaps.
Now,let’sstartplayingwithmaps.
UnderstandingMapFragmentThefoundationalbuildingblockofamapapplicationistheMapFragment.ThiswasintroducedinHoneycomb(Android3.1)andreplacedMapViewandMapActivityfunctionality.NowyoucanembedaMapFragmentinsideofaregularAndroidactivity.IfyouwantyourapplicationtorunondevicesrunninganolderversionofAndroid,youcanuseSupportMapFragmentandembeditinsideaFragmentActivity.TheMapFragmentcontainsthemapviewtodisplaymaps,ithandlesusergesturestomanipulatethemap,anditmanagesthebackgroundthreadsthattalktotheGoogleservicestoretrievemapdata.
MapFragmentisaverynicebundleoffunctionality,butit’snotallthatyouneedonyourdevicetomakemapswork.Fortunately,theintegrationwithGooglePlayServicesisallhandledforyou;allyouneedtodoismakeaspecialentryintoyourapp’sAndroidManifest.xmlfile,whichyousawintheprevioussection.
Thefirstsampleapplicationforthischapterwillsimplyshowamaptotheuserandlettheuserexplorethemap.
NoteNoteWegiveyouaURLattheendofthechapterthatyoucanusetodownloadprojectsfromthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.AlsonotethatifyouwanttotestthesesampleswithanAndroidemulator,makesuretheAndroidVirtualDevice(AVD)isbuiltwiththeGoogleAPIs.
PleaserefertothesampleprojectcalledWhereAmI.TheapplicationismadeupofaverybasicFragmentActivity,averysimplelayout,andaSupportMapFragment.Thesampleisusingthecompatibilityclasses,whichmeansitwillrunonGingerbreaddevicesaswellasthelatestmodels.IfyourapponlyneedstorunondevicesnewerthanHoneycomb3.0,youcouldusearegularactivityandaMapFragmentinstead.
Listing19-2showstheactivity.Allthat’sneededistosetupthelayoutand,ifneeded,createtheMapFragmentandinsertitintothelayout’scontainer(aFrameLayout).
Listing19-2.ABasicFragmentActivityforDisplayingaMap
publicclassMainActivityextendsFragmentActivity{privatestaticfinalStringMAPFRAGTAG="MAPFRAGTAG";privateMyMapFragmentmyMapFrag;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
if((myMapFrag=(MyMapFragment)getSupportFragmentManager().findFragmentByTag(MAPFRAGTAG))==null){myMapFrag=MyMapFragment.newInstance();getSupportFragmentManager().beginTransaction().add(R.id.container,myMapFrag,MAPFRAGTAG).commit();}}}
Iftheactivityisre-createdduetoanorientationchangeforexample,themapfragmentwillstillbeavailableandbeautomaticallyattachedtothenewactivitybyAndroid.Ifthemapfragmentisnotfound,itmeansthisisthefirsttimein,orthemapfragmenthasbeendestroyed,socreateanewoneandattachit.Itdoesn’tgetanyeasierthanthat.ThelayoutsourceisshowninListing19-3.ItissimplyaFrameLayoutwithanidof“container”thatfillstheavailablescreenspace.
Listing19-3.LayoutforSimpleMapDisplay(activity_main.xml)
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.androidbook.maps.whereami.MainActivity"tools:ignore="MergeRootFrame"/>
Ifyouareincludingthemapfragmentwithotheritemsinyouruserinterface,youcansimplyusetheFrameLayoutwhereyouwantthemapfragmenttoappear,embeddedwithinotherlayouts.TheonlycoderemainingisthatoftheMapFragment,whichisshowninListing19-4.Figure19-3showswhattheusersees.
Listing19-4.CodefortheMapFragment
publicclassMyMapFragmentextendsSupportMapFragmentimplementsOnMapReadyCallback{privateGoogleMapmMap=null;
publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}
@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);setRetainInstance(true);}
@OverridepublicvoidonResume(){super.onResume();doWhenMapIsReady();}
@OverridepublicvoidonPause(){super.onPause();if(mMap!=null)mMap.setMyLocationEnabled(false);}
@OverridepublicvoidonMapReady(GoogleMaparg0){mMap=arg0;doWhenMapIsReady();}
/*Wehavearaceconditionwherethefragmentcouldresume*beforeorafterthemapisready.Soweputallourlogic*forinitializingthemapintoacommonmethodthatis*calledwhenthefragmentisresumedorresumingandthe*mapisready.*/voiddoWhenMapIsReady(){if(mMap!=null&&isResumed())mMap.setMyLocationEnabled(true);}}
Figure19-3.AbasicMapFragmentdisplayingyourlocation
Arecentdevelopment(Dec2014)totheMapsAPIistheuseofacallbacktolettheapplicationknowwhenthemapisreadytobeactedupon.ThecallbackissetupusinggetMapAsync(),andtheonMapReady()callbackiscalledwhenthemapcanbe
usedbytheapplication.InbetweencallinggetMapAsync()andinvokingtheonMapReady()callback,Androidissettingupcommunications,threads,etc.,forthemap.ThismeansthatthemapmayormaynotbereadywhenonResume()isinvoked,whichtellsthefragmentthattheUIisnowbeingshown.Therefore,theapplicationneedsaseparatemethodtoworkonthemapanditneedstobecalledbothbyonResume()andbyonMapReady().Forthissampleapplication,thedoWhenMapIsReady()methodfillsthatrole.Theapplicationwantstoshowtheuserthedevice’scurrentlocation,sothesetMyLocationEnabled()methodiscalledindoWhenMapIsReady().ButdoWhenMapIsReady()needstocheckthatthemapexistsandthatthefragmentisresumingorhasresumed.Wedon’tknowwhichwilloccurfirst,butbothmustbetruebeforeweenablelocationupdates.Thecurrentlocationupdatesaredisabledwhenthefragmentgoesoutofview(seeonPause()).TheonlyothercodelinetonoticeisthesetRetainInstance()methodcall.Sincethemapdoesnotneedtobedestroyedandre-createdforaconfigurationchangeoftheactivity,itmakessensetokeepthefragmentandreuseit,alongwiththethreadsandtilesandsoon.YoushouldrememberthataconfigurationchangewillcauseonPause()andonResume()tobeinvokedduringtheconfigchange.Thiswillcorrectlydisablelocationupdatesandre-enablethemduringonResume().
MapControls:MyLocation,Zoom,PanThereareacoupleofartifactsontheuserinterfacetonotice.FirstistheMyLocationbuttonintheupper-rightcorner.Whenyoufirststartthesampleappyouwillseeaveryhigh-levelviewoftheworld.Toshowthecurrentlocation,taptheMyLocationbutton.Thiswillrelocatethemaptothecurrentpositionandzoomin.Secondisthebluedot.Thebluedotrepresentswheretheappthinksyouare,andthecirclerepresentshowaccurateitthinksthislocationis.Thecirclemaygroworshrinkasthelocationinformationchanges.
Theusercanusepinchgestures(i.e.,squeezingtwofingersapartortogether)tozoominorout.Therearemoregesturesthattheusercandoonthemap.Byswiping,theusercanpanthemap;thatis,theycanmovethemaptoseenearbyareas.Usingtwofingersandarotationmove,theusercanrotatethemap.That’salotoffunctionalitythat’sautomaticfromsimplycreatingaMapFragment.
ThesemapcontrolsandmorearecontainedinanobjectoftheUiSettingsclass.Youcangetthemap’sUiSettingsbycallinggetUiSettings()ontheGoogleMapobject(i.e.,mMapinthesampleapp).Youcanthenmodifythesettingsprogrammatically.Forexample,youcouldenableacompasstobedisplayedonthemap,oryoucouldenable/disablethezoomplus/minuscontrolsoitisorisnotdisplayed.Thezoomplus/minuscontrolappearsinthelower-rightcornerandallowstheusertozoominoroutbytappingtheplusorminusbutton,respectively.
MapTypesThedefaultmaptypeisMAP_TYPE_NORMAL.ThisisthetypethatwasusedinFigure
19-3.Itshowstheroadswiththebasicfeaturesofthelandsuchaswherewateris,wheregreenspaceis,andsomeplacesandbuildings.MAP_TYPE_SATELLITEshowsaphotographicsatelliteviewoftheground,sotheuserisabletoseeactualbuildings,cars,andevenpeople.MAP_TYPE_HYBRIDisacombinationofthesetwo;MAP_TYPE_TERRAINislikeanormalmapbutwithtopographicalfeaturesaddedsuchasmountainsandcanyons.ToreallyseetheeffectofMAP_TYPE_TERRAIN,zoominonaplacelikeBoulder,ColoradowithamapsettoTerrain.
YouusethesetMapType()methodofaGoogleMaptochangethetype.
AddingaTrafficLayerInthepreviousversionofAndroidMaps,trafficwastreatedlikethesatelliteandnormalmodesofmaps.WithAPIv2,trafficisenabledseparatelyusingthesetTrafficEnabled()methodofaGoogleMap.
MapTilesIt’shelpfultounderstandwhat’sgoingonwhenyourappdisplaysamap.Googlehascreatedmillionsofbasemaptilestorepresenttheearth’ssurface.Atthelowestzoomlevel(i.e.,zero)thereisonetiletoshowtheentireworld.Atzoomlevel1,therearefourtilesina2x2configuration.Atzoomlevel2,thereare16tilesina4x4configuration.Andsoonuptozoomlevel21.Dependingonwhatpartoftheworldyouwanttodisplay,andwhatthezoomlevelis,theGoogleMapobjectwillfetchandcachetheappropriatetiles.Pantotheside,andanyadditionaltileswillbefetchedanddisplayed.Panbacktowhereyouwereandyourappcanretrievemaptilesfromthecacheinsteadofmakingmoreroundtripstotheserver.
It’sinterestingtonotethatbasemaptilesforthenormaltypeofmapsarenotimages.Googlehasfiguredoutacompressedwaytodescribetheshapesandcolorswithinthetilesinsteadofjustsendingdownimagesforeachtile.Asaresult,normalmaptilesareveryefficientintermsofcachespaceaswellasnetworkbandwidth.Satellitetilesontheotherhandarenotascompressed,sincetheyareimages.
Nowyoucanunderstandwhysometimesamapsapplicationwillshowagraygridpatternandseemtofunctionbutwon’tshowstreetsandotheritems.TheGoogleMapobjecthasbeeninstantiated,anditknowsazoomlevelandwhereitissupposedtobedisplayingamap,butitisunabletoretrieveandrendertilestotheuser.ThisismostoftenduetoaninvalidMapsAPIkey,ortheAPIkeyhasnotbeensetupproperly.ButitcouldalsomeandifficultyinreachingtheGoogleMapsservers.However,ifmaptileshavebeencached,thosetilescanberenderedtotheuserevenifthetileserversatGoogleareunreachable.Therearetwounfortunatethingsaboutmaptilecaching.ThefirstisthattherearenoAPIcallstomanagethemaptilecache,eithertoforcemaptilestobecached,ortochangethesizeofthecache,ortoevicttilesfromthecache.YoujusthavetotrustthatGooglewilldotherightthing.Thesecondisthatmaptilesarecachedperapplication.SojustbecausetheGoogleMapsapplicationmayhavecachedtiles,yourapplicationdoesnothaveaccesstothosetiles.Yourapplicationcanonlyseethecachedtilesthatithascached.
AddingMarkerstoMapsUsuallyyou’llwanttoidentifypointsofinterestonamap,andthisisdoneusingMarkers.Thepointscouldbestationaryobjectslikeaddresses,landmarks,oraparkingspot.Buttheycouldalsobemovingobjectslikecars,planes,people,pets,storms,etc.Yougettochoosewhatthemarkerlookslikeandwhereitispositionedonamap.Andyoucanhavelotsofmarkersallatthesametime.We’regoingtomodifythesampleprogramfromabovetoincludeacoupleofmarkers.You’llseehowtoplacethem,andthenhowtomanipulatetheviewtomakesuretheuserseesthemarkers.
NowusethesampleprogramcalledWhereAmIMarkers.YouwillneedtomodifytheAndroidManifest.xmlfileasbeforetouseyourMapsAPIkey.ThesourcecodeforMyMapFragment.javahasbeenmodifiedasshowninListing19-5.ThescreenwillappearsimilartoFigure19-4.
Listing19-5.CodefortheMapFragmentShowingMarkers
publicclassMyMapFragmentextendsSupportMapFragmentimplementsOnMapReadyCallback{
publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}
@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);setRetainInstance(true);}
@OverridepublicvoidonMapReady(GoogleMapmyMap){LatLngdisneyMagicKingdom=newLatLng(28.418971,-81.581436);LatLngdisneySevenLagoon=newLatLng(28.410067,-81.583699);
//AddamarkerMarkerOptionsmarkerOpt=newMarkerOptions().draggable(false)
.flat(false).position(disneyMagicKingdom).title("MagicKingdom").icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));myMap.addMarker(markerOpt);
markerOpt.position(disneySevenLagoon).title("SevenSeasLagoon");myMap.addMarker(markerOpt);
//DeriveaboundingboxaroundthemarkersLatLngBoundslatLngBox=LatLngBounds.builder().include(disneyMagicKingdom).include(disneySevenLagoon).build();
//MovethecameratozoominonourlocationsmyMap.moveCamera(CameraUpdateFactory.newLatLngBounds(latLngBox,200,200,0));}}
Figure19-4.Markersonamap
Onceagain,everythingstartsfromacquiringtheGoogleMapobjectfromtheMapFragment.Oncethemapisavailable,youcancreatemarkerswhichinthiscasearebasedonacoupleoffixedLatLngobjects.You’llnoticethoughthatyoudon’tdirectlyinstantiateaMarkerobject.Instead,youuseaMarkerOptionsobjecttospecifyhowthemarkershouldbecreated.ItiswithintheMarkerOptionsobjectthatyoudecidetheposition,title,markershape,color,etc.WhileyoucouldinstantiateaMarkerobjectandthencalleachsetterthatyouwant,MarkerOptionsmakesthingsmucheasier,especiallyifyouneedtocreatemultiplemarkersthatwillsharecommonfeatures.ThissampleonlyusessomeoftheMarkerOptionsfeatures;pleaseseethereferencedocumentationtolearnalloftheoptionsavailable.
Thenextthingyoulikelywanttodoisshowthemaptotheusersuchthatallthemarkersarevisibleatthesametime.Thisrequirestwothings:centeringthemapinthemiddleofthemarkers,andsettingthezoomlevelashighaspossiblewithoutbeingsoclosethatyoucan’tfitallthemarkersintotheview.Fortunately,ahelperclassisavailableforthispurpose.TheLatLngBoundsobjectiscreatedbypassingitalloftheLatLngpointsthatshouldbewithintheview,anditcalculatesthesmallestboxthatcontainsthemall.Inthissample,bothpointsarepassedinatonce.Youcouldalsousealooptopassinallthe
pointsandtheninvokethebuild()methodtoreturntheboundingbox.
Onceyouhaveaboundingbox,youneedtoadjustthemap’scamera.IntheoldversionofGoogleMaps,therewasonlyastraight-downviewofamap,asifyouwereabovethemaplookingstraightdown.WithMapsAPIversion2,thereistheconceptofacamerathatcanlookstraightdown,butcanalsolookatanangle.Ifyouusetwofingersatthesametimeandswipethescreenfromtoptobottom,youwillseetheviewinganglechange.Youhaveineffectpivotedthecamerasoyouarenolongerlookingstraightdown.Thecameracanalsolooktotheeast,southoranyotherdirectionwhenitisangled.Youcantwisttwofingerstorotatethemaptoo.
Allthesecameraangles,zoomlevelsandsoonarecontrolledusingthemap’sanimateCamera()ormoveCamera()methods.ThesemethodstakeaCameraUpdateobjectasinstructions,andtheCameraUpdateFactoryclassgeneratesthose.Inthesample,theboundingboxispassedtotheCameraUpdateFactoryanditreturnsanappropriateCameraUpdatesothatthecamerawillbepositionedinthebestplacetoseeallthemarkers.ThereareseveralothermethodstoCameraUpdateFactorytoaccommodateotherwaysofpositioningthecamera.YoudocansimplezoomIn()andzoomOut()forexample.YoucanalsocreateaCameraPositionobjectandusethat.
Allinall,you’llagreethatplacingmarkersonamapcouldn’tbeeasier.Orcouldit?Wedon’thaveadatabaseoflatitude/longitudepairs,butwe’reguessingthatwe’llneedtosomehowcreateoneormoreLatLngobjectsusingarealaddress.That’swhenyoucanusetheGeocoderclass,whichispartofthelocationpackagethatwe’lldiscussnext.
UnderstandingtheLocationPackageTheandroid.locationpackageprovidesfacilitiesforlocation-basedservices.Inthissection,wearegoingtodiscusstwoimportantpiecesofthispackage:theGeocoderclassandtheLocationManagerservice.We’llstartwithGeocoder.
GeocodingwithAndroidIfyouaregoingtodoanythingpracticalwithmaps,you’lllikelyhavetoconvertanaddress(orlocation)toalatitude/longitudepair.Thisconceptisknownasgeocoding,andtheandroid.location.Geocoderclassprovidesthisfacility.Infact,theGeocoderclassprovidesbothforwardandbackwardconversion—itcantakeanaddressandreturnalatitude/longitudepair,anditcantranslatealatitude/longitudepairintoalistofaddresses.Theclassprovidesthefollowingmethods:
List<Address>getFromLocation(doublelatitude,doublelongitude,intmaxResults)
List<Address>getFromLocationName(StringlocationName,intmaxResults,double
lowerLeftLatitude,doublelowerLeftLongitude,doubleupperRightLatitude,doubleupperRightLongitude)
List<Address>getFromLocationName(StringlocationName,intmaxResults)
Itturnsoutthatcomputinganaddressisnotanexactsciencebecauseofthevariouswaysalocationcanbedescribed.Forexample,thegetFromLocationName()methodscantakethenameofaplace,thephysicaladdress,anairportcode,orsimplyawell-knownnameforthelocation.Thus,themethodsreturnalistofaddressesandnotasingleaddress.Becausethemethodsreturnalist,whichcouldbequitelong(andtakealongtimetoreturn),youareencouragedtolimittheresultsetbyprovidingavalueformaxResultsthatrangesbetween1and5.Now,let’sconsideranexample.
Listing19-6showstheXMLlayoutandcorrespondingcodefortheactivityandmapfragmentshowninFigure19-5.Toruntheexample,you’llneedtoupdatethemanifestwithyourownMapsAPIkey.
Listing19-6.WorkingwiththeAndroidGeocoderClass
<!--Thisisactivity_main.xml--><LinearLayoutxmlns: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"android:orientation="vertical"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.androidbook.maps.whereami.MainActivity"tools:ignore="MergeRootFrame">
<EditTextandroid:id="@+id/locationName"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Enterlocationname"android:inputType="text"android:imeOptions="actionGo"/>
<FrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"/>
</LinearLayout>
/***ThisisfromMainActivity.java**/publicclassMainActivityextendsFragmentActivity{
privatestaticfinalStringMAPFRAGTAG="MAPFRAGTAG";MyMapFragmentmyMapFrag=null;privateGeocodergeocoder;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
if((myMapFrag=(MyMapFragment)getSupportFragmentManager().findFragmentByTag(MAPFRAGTAG))==null){myMapFrag=MyMapFragment.newInstance();getSupportFragmentManager().beginTransaction().add(R.id.container,myMapFrag,MAPFRAGTAG).commit();}if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD&&!Geocoder.isPresent()){Toast.makeText(this,"Geocoderisnotavailableonthisdevice",Toast.LENGTH_LONG).show();finish();}geocoder=newGeocoder(this);EditTextloc=(EditText)findViewById(R.id.locationName);loc.setOnEditorActionListener(newOnEditorActionListener(){@OverridepublicbooleanonEditorAction(TextViewv,intactionId,KeyEventevent){if(actionId==EditorInfo.IME_ACTION_GO){StringlocationName=v.getText().toString();
try{List<Address>addressList=
geocoder.getFromLocationName(locationName,5);
if(addressList!=null&&addressList.size()>0){//Log.v(TAG,"Address:"+addressList.get(0).toString());myMapFrag.gotoLocation(newLatLng(addressList.get(0).getLatitude(),addressList.get(0).getLongitude()),locationName);}}catch(IOExceptione){e.printStackTrace();}}returnfalse;}});}}
publicclassMyMapFragmentextendsSupportMapFragmentimplementsOnMapReadyCallback{privateGoogleMapmMap=null;
publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}
@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);setRetainInstance(true);}
publicvoidgotoLocation(LatLnglatlng,StringlocString){if(mMap==null)return;//Addamarkerforthegivenlocation
MarkerOptionsmarkerOpt=newMarkerOptions().draggable(false).flat(false).position(latlng).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)).title("Youchose:").snippet(locString);//SeetheonMarkerClickedcallbackforwhywedothismMap.addMarker(markerOpt);
//MovethecameratozoominonourlocationmMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latlng,15));}
@OverridepublicvoidonMapReady(GoogleMaparg0){mMap=arg0;}}
Figure19-5.Geocodingtoapointgiventhelocationname
TodemonstratetheusesofgeocodinginAndroid,typethenameoraddressofalocationintheEditTextfieldandthentaptheGobuttononthekeyboard.Tofindtheaddressofalocation,wecallthegetFromLocationName()methodofGeocoder.Thelocationcanbeanaddressorawell-knownnamesuchas“WhiteHouse.”Geocodingcanbeaprolongedoperation,sowerecommendthatyoulimittheresultstofive,astheAndroiddocumentationsuggests.
ThecalltogetFromLocationName()returnsalistofaddresses.Thesampleapplicationtakesthelistofaddressesandprocessesthefirstoneifanywerefound.Everyaddresshasalatitudeandlongitude,whichyouusetocreateaLatLng.YouthencallourgotoLocation()methodtonavigatetothepoint.Thisnewmethodinthemapfragmentcreatesanewmarker,addsittothemap,andmovesthecameratothemarkerwithazoomlevelof15.Thezoomlevelcanbesettoafloatbetween1and21,inclusive.Asyoumovefrom1toward21by1’s,thezoomlevelincreasesbyafactorof2.Wecouldhavepresentedadialogtodisplaymultiplefoundlocationsifwewantedto,butfornow,we’lljustdisplaythefirstlocationreturnedtous.
Inourexampleapplication,weonlyreadthelatitudeandlongitudeofourreturned
Address.Infact,therecanbeatonofdataaboutAddressesreturnedtous,includingthelocation’scommonname,street,city,state,postal/ZIPcode,country,andevenphonenumberandwebsiteURL.Youshouldunderstandafewpointswithrespecttogeocoding:
WhiletheGeocoderclassmayexist,theservicemaynotbeimplemented.IfthedeviceisGingerbreadorhigher,youshouldcheckwithGeocoder.isPresent()beforeattemptingtogeocodeinyourapplication.
Areturnedaddressisnotalwaysanexactaddress.Obviously,becausethereturnedlistofaddressesdependsontheaccuracyoftheinput,youneedtomakeeveryefforttoprovideanaccuratelocationnametotheGeocoder.
Wheneverpossible,setthemaxResultsparametertoavaluebetween1and5.
YoushouldseriouslyconsiderdoingthegeocodingoperationinadifferentthreadfromtheUIthread.Therearetworeasonsforthis.Thefirstisobvious:theoperationistime-consuming,andyoudon’twanttheUItohangwhileyoudothegeocoding,causingAndroidtokillyouractivity.Thesecondreasonisthat,withamobiledevice,youalwaysneedtoassumethatthenetworkconnectioncanbelostandthattheconnectionisweak.Therefore,youneedtohandleinput/output(I/O)exceptionsandtimeoutsappropriately.Onceyouhavecomputedtheaddresses,youcanposttheresultstotheUIthread.SeetheincludedsampleapplicationcalledWhereAmIGeocoder2forhowtodothis.
UnderstandingLocationServicesLocationServicesprovidetwomainfunctions:amechanismforyoutoobtainthedevice’sgeographicallocationandafacilityforyoutobenotified(viaanintent)whenthedeviceentersorexitsaspecifiedgeographicallocation.Thislatteroperationisknownasgeofencing.
Inthissection,youaregoingtolearnhowtofindthedevice’scurrentlocation.Tousetheservice,youmustfirstobtainareferencetoit.Listing19-7showsasimpleusageoftheFusedLocationProviderApiservice.ThesampleprojectforthisiscalledWhereAmILocationAPI.
Listing19-7.UsingtheLocationProviderAPI
publicclassMyMapFragmentextendsSupportMapFragmentimplementsGoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener,OnMapReadyCallback{
privateContextmContext=null;privateGoogleMapmMap=null;privateGoogleApiClientmClient=null;privateLatLngmLatLng=null;
publicstaticMyMapFragmentnewInstance(){MyMapFragmentmyMF=newMyMapFragment();returnmyMF;}
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);getMapAsync(this);}
@OverridepublicvoidonActivityCreated(BundlesavedInstanceState){super.onActivityCreated(savedInstanceState);if(mClient==null){//firsttimein,setupthisfragmentsetRetainInstance(true);
mContext=getActivity().getApplication();mClient=newGoogleApiClient.Builder(mContext,this,this).addApi(LocationServices.API).build();mClient.connect();}}
@OverridepublicvoidonConnectionFailed(ConnectionResultarg0){Toast.makeText(mContext,"Connectionfailed",Toast.LENGTH_LONG).show();}
@OverridepublicvoidonConnected(Bundlearg0){//Figureoutwhereweare(lat,long)asbestaswecan//basedontheuser'sselectionsforLocationSettingsFusedLocationProviderApilocator=LocationServices.FusedLocationApi;
LocationmyLocation=locator.getLastLocation(mClient);//iftheservicesarenotavailable,couldgetanulllocationif(myLocation==null)return;doublelat=myLocation.getLatitude();doublelng=myLocation.getLongitude();mLatLng=newLatLng(lat,lng);doWhenEverythingIsReady();}
@OverridepublicvoidonConnectionSuspended(intarg0){Toast.makeText(mContext,"Connectionsuspended",Toast.LENGTH_LONG).show();}
@OverridepublicvoidonMapReady(GoogleMaparg0){mMap=arg0;doWhenEverythingIsReady();}
privatevoiddoWhenEverythingIsReady(){if(mMap==null||mLatLng==null)return;//AddamarkerMarkerOptionsmarkerOpt=newMarkerOptions().draggable(false).flat(true).position(mLatLng).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));mMap.addMarker(markerOpt);
//MovethecameratozoominonourlocationmMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mLatLng,15));}}
Toacquirealocationservice,youfirstneedtocreateaGoogleAPIclientobject,whichmakesavailabletoyoutheservicesfromGooglePlayServices.Thisisrelativelyeasytodo,andonceyouhavetheclientobject,youneedtocallitsconnect()method.ThiswilllaterinvoketheonConnected()callbackasynchronouslytoletyourapplicationknowthattheclienthasbeenconnectedandisnowavailableforuse.OryourapplicationmaygettheonConnectionFailed()callback,inwhichcaseyoushouldtake
appropriateaction.ForthesamplewesimplyshowaToastmessagewhentheconnectionattempthasfailed.Lateronyou’llseehowtodealmorerobustlywithafailedconnection.
WhentheonConnected()callbackisinvoked,nowyoucanworkwiththelocationproviderAPI.Recallthatatthebeginningofthischapteryousetpermissionsinthemanifestfiletoaccesslocationinformation.FinelocationsuseGPSwhilecoarselocationsusecelltowersandWiFihotspots.UsingafusedlocationproviderAPImeansthatyourapplicationisn’tworryingaboutwhatisenabledorwhatpermissionsareset.TheAPIcallsarethesame.Youjustaskforlocationsandyouwillgetthebestlocationinformationthatisavailableatthetime.
Forthissample,wecallthegetLastLocation()method.Withluck,thelocationthatisreturnedisverycurrent;however,beawarethatthelastlocationmightbefromminutesorhoursago.TheLocationobjectcantellyou,viathegetTime()method,whenthislocationfixwasobtained.Youcouldchecktoseeifitisnewenoughforyourpurposesbeforedecidingtouseit.ItistechnicallypossiblethatgetLastLocation()willreturnnullsoyoushouldbepreparedforthatcaseaswell.ThiscanhappenifLocationServiceshavebeendisabledinSettings.
You’llseesoonhowtogetupdatestolocations.Fornow,thesampletakeswhateverthelastlocationwasandcreatesamapmarkeroutofitfordisplaytotheuser.Youshouldrecognizethecodetocreateamarkerfromtheprevioussectionofthischapter.
HowtoEnableLocationServicesYoumightthinkthere’sasimpleAPItoenableLocationServicesiftheyarenotturnedonwhenyourapplicationruns.Unfortunately,thisisnotthecase.TogetLocationServicesturnedon,theusermustdothatfromwithintheSettingsscreensoftheirdevice.YourapplicationcanmakethisalotsimplerfortheuserbylaunchingthatparticularSettingsscreen.Thelocationsettingssourcescreenisreallyjustanactivity,andthisactivityissetuptorespondtoanintent.
Inthesampleapplicationjustcovered,youwillseethecodefromListing19-8intheactivity’sonCreate()callback.
Listing19-8.CheckingtoSeeIfLocationServicesAreOn
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
if((myMapFrag=(MyMapFragment)getSupportFragmentManager().findFragmentByTag(MAPFRAGTAG))==null){myMapFrag=MyMapFragment.newInstance();getSupportFragmentManager().beginTransaction().add(R.id.container,myMapFrag,MAPFRAGTAG).commit();
}
if(!isLocationEnabled(this)){//nolocationserviceprovidersareenabledToast.makeText(context,"LocationServicesappeartobeturnedoff."+"Thisappcan'tworkwithoutthem.Pleaseturnthemon.",Toast.LENGTH_LONG).show();startActivityForResult(newIntent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS),0);}}
@SuppressWarnings("deprecation")publicbooleanisLocationEnabled(Contextcontext){intlocationMode=Settings.Secure.LOCATION_MODE_OFF;StringlocationProviders;
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){try{locationMode=Settings.Secure.getInt(context.getContentResolver(),Settings.Secure.LOCATION_MODE);}catch(SettingNotFoundExceptione){e.printStackTrace();}returnlocationMode!=Settings.Secure.LOCATION_MODE_OFF;}else{locationProviders=Settings.Secure.getString(context.getContentResolver(),Settings.Secure.LOCATION_PROVIDERS_ALLOWED);return!TextUtils.isEmpty(locationProviders);}}
AchangeoccurredinAndroid19(KitKat)wherenewsettingsvalueswereaddedtothestaticSettings.Secureclass.ThismadeiteasiertotellifLocationServiceswereturnedonornot,andwhichones,buttheuserstillneedstodotheworktoenabletheservices.Therearetwowaysinthiscodetocheckforservices:useoneofthenewvalues,ordoagetontheavailablelocationproviders.ThefirstpartofListing19-8checkstoseeiftheversionofAndroidisKitKatorhigher,andifsoitlooksforthevalueofthenewSettingforlocationmode.Thesecondpartofthecode(iftheversionofAndroidisolderthanKitKat)doesagetontheallowedlocationproviders.Iflocationmodeisnotoff,orifthereisatleastonelocationprovideravailable,thenLocationServicesarerunning.Ifnot,
thiscodelaunchesanintenttotheLocationSettingsscreen.Atthatpoint,thisactivitywouldbepausedwhiletheSettingsactivityruns.WhentheSettingsactivityisdone,ouractivitywillresume.
IfyouwanttohandlearesponsefromtheSettingsactivity(i.e.,benotifiedwhenthatactivityisdoneandpresumablyasettingchangehasbeenmade),youmustimplementtheonActivityResult()callbackinyouractivity.Andalsokeepinmindthatalthoughyouhopetheuserturnsonlocationservices,theymaynot.Youwillneedtocheckagaintoseeiftheuserhasenabledlocationservicesandtakeappropriateactionbasedontheresult.We’llshowyouhowtodoallofthisinalatersection.
LocationProvidersYou’veseentheFusedLocationApi,butyoushouldalsobeawareoftheolder,alternatelocationproviders.Thehardwareisrightthereonthedeviceforgettinglocationinformation,andthelocationproviderswillgiveittoyourapplication.You’llsoonseehowtheFusedLocationApihandlesyourlocationneedsatahigherlevelthantheseproviders.Butifyouneedtodigintothedetails,forexampletocheckthestatusoftheavailableGPSsatellites,you’llbehappytoknowtheseprovidersexist.GooglerecommendsthateveryoneswitchovertotheFusedLocationApi;butsinceitreliesonGooglePlayServices,thatmeansapplicationsthatuseFusedLocationApiwillnotrunonanon-GoogleAndroiddevice.
TheLocationManagerserviceisasystem-levelservice.System-levelservicesareservicesthatyouobtainfromthecontextusingtheservicename;youdon’tinstantiatethemdirectly.Theandroid.app.ActivityclassprovidesautilitymethodcalledgetSystemService()thatyoucanusetoobtainasystem-levelservice.YoucallgetSystemService()andpassinthenameoftheserviceyouwant,inthiscase,Context.LOCATION_SERVICE.You’llseethisshortlyinListing19-9.
TheLocationManagerserviceprovidesgeographicallocationdetailsbyusinglocationproviders.Currently,therearethreetypesoflocationproviders:
GPSprovidersuseaGlobalPositioningSystemtoobtainlocationinformation.
Networkprovidersusecell-phonetowersorWiFinetworkstoobtainlocationinformation.
Thepassiveproviderislikealocationupdatesniffer,anditpassestoyourapplicationlocationupdatesthatarerequestedbyotherapplications,withoutyourapplicationhavingtospecificallyrequestanylocationupdates.Ofcourse,ifnooneelseisrequestinglocationupdates,youwon’tgetanyeither.
SimilartotheFusedLocationApi,theLocationManagerclasscanprovidethedevice’slastknownlocation,thistimeviathegetLastKnownLocation()method.Locationinformationisobtainedfromaprovider,sothemethodtakesasaparameterthenameoftheprovideryouwanttouse.Validvaluesforprovidernamesare
LocationManager.GPS_PROVIDER,LocationManager.NETWORK_PROVIDER,andLocationManager.PASSIVE_PROVIDER.Notethatthereisnooptionforafusedprovider,sincethatisaseparatelocation-findingcapability.
Inorderforyourapplicationtosuccessfullygetlocationinformation,itmusthavetheappropriatepermissionsintheAndroidManifest.xmlfile.android.permission.ACCESS_FINE_LOCATIONisrequiredforGPSandforpassiveproviders,whereasandroid.permission.ACCESS_COARSE_LOCATIONorandroid.permission.ACCESS_FINE_LOCATIONcanbeusedfornetworkproviders,dependingonwhatyouneed.Forinstance,assumeyourapplicationwilluseGPSornetworkdataforlocationupdates.BecauseyouneedACCESS_FINE_LOCATIONforGPS,you’vealsosatisfiedpermissionsfornetworkaccess,soyoudonotneedtoalsospecifyACCESS_COARSE_LOCATION.Ifyou’reonlygoingtousethenetworkprovider,youcouldgetbywithonlyACCESS_COARSE_LOCATIONinthemanifestfile.
CallinggetLastKnownLocation()returnsanandroid.location.Locationinstance,ornullifnolocationisavailable.TheLocationclassprovidesthelocation’slatitudeandlongitude,thetimethelocationwascomputed,andpossiblythedevice’saltitude,speed,andbearing.ALocationobjectcanalsotellyouwhichprovideritcamefromusinggetProvider(),whichwillbeeitherGPS_PROVIDERorNETWORK_PROVIDER.Ifyou’regettinglocationupdatesviathePASSIVE_PROVIDER,rememberthatyou’reonlyreallysniffinglocationupdates,soallupdatesareultimatelyfromeitherGPSorthenetwork.
BecausetheLocationManageroperatesonproviders,theclassprovidesAPIstoobtainproviders.Forexample,youcangetalloftheknownprovidersbycallinggetAllProviders().YoucanobtainaspecificproviderbycallinggetProvider(),passingthenameoftheproviderasanargument(suchasLocationManager.GPS_PROVIDER).OnethingtowatchoutforisthatgetAllProviders()willreturnprovidersthatyoumaynothaveaccesstoorthatarecurrentlydisabled.Fortunately,youareabletodeterminethestatusofprovidersusingothermethods,suchasisProviderEnabled(StringproviderName)orgetProviders(booleanenabledOnly),whichyoucouldcallwithavalueoftruetogetonlyprovidersyouareabletouseimmediately.
There’sanotherwaytogetasuitableprovider,andthatistousethegetProviders(Criteriacriteria,booleanenabledOnly)methodofLocationManager.Byspecifyingcriteriaforlocationupdates,andbysettingenabledOnlytotruesoyougetprovidersthatareenabledandreadytogo,youcangetalistofprovidernamesreturnedtoyouwithouthavingtoknowthespecificsofwhichprovideryougot.Thiscouldbemoreportable,becauseadevicemayhaveacustomLocationProviderthatmeetsyourneedswithoutyouhavingtoknowaboutitinadvance.TheCriteriaobjectcanbesetwithparametersthatincludeaccuracylevelandtheneedforinformationaboutspeed,bearing,altitude,cost,andpowerrequirements.
Ifnoprovidersmeetyourcriteria,anulllistwillbereturned,allowingyoutoeitherbailoutorrelaxthecriteriaandtryagain.
SendingLocationUpdatestoYourApplicationWhendoingdevelopmenttesting,yourapplicationneedslocationinformation,andtheemulatordoesn’thaveaccesstoGPSorcelltowers.Inorderforyoutotestyourapplicationintheemulator,youcanmanuallysendlocationupdatesfromEclipse.Listing19-9showsasimpleexampletoillustratehowtodothis.We’regoingtostickwiththeLocationManagerapproachhere,andthenshowtheFusedLocationApiapproachlater.
Listing19-9.RegisteringforLocationUpdates
publicclassLocationUpdateDemoActivityextendsActivity{LocationManagerlocMgr=null;LocationListenerlocListener=null;
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);
locMgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE);
locListener=newLocationListener(){publicvoidonLocationChanged(Locationlocation){if(location!=null){Toast.makeText(getBaseContext(),"Newlocationlatitude["+location.getLatitude()+"]longitude["+location.getLongitude()+"]",Toast.LENGTH_SHORT).show();}}
publicvoidonProviderDisabled(Stringprovider){}
publicvoidonProviderEnabled(Stringprovider){}
publicvoidonStatusChanged(Stringprovider,intstatus,Bundleextras){}};}
@OverridepublicvoidonResume(){super.onResume();
locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,0,//minTimeinms0,//minDistanceinmeterslocListener);}
@OverridepublicvoidonPause(){super.onPause();locMgr.removeUpdates(locListener);}}
We’renotdisplayingauserinterfaceforthisexample,sothestandardinitiallayoutXMLfilewilldo,aswellasaregularactivity.
OneoftheprimaryusesoftheLocationManagerserviceistoreceivenotificationsofthedevice’slocation.Listing19-9demonstrateshowyoucanregisteralistenertoreceivelocation-updateevents.Toregisteralistener,youcalltherequestLocationUpdates()method,passingtheprovidertypeasoneoftheparameters.Whenthelocationchanges,theLocationManagercallstheonLocationChanged()methodofthelistenerwiththenewLocation.Itisveryimportantthatyouremoveanyregistrationsforlocationupdatesattheappropriatetime.Inourexample,wedoregistrationinonResume(),andweremovethatregistrationinonPause().Ifwearen’tgoingtobearoundtodoanythingwithlocationupdates,weshouldtelltheprovidernottosendthem.There’salsothepossibilitythatouractivitycouldbedestroyed(forexample,iftheuserrotatestheirdeviceandouractivityisrestarted),inwhichcaseouroldactivitycouldstillexist,bereceivingupdates,displayingthemwithToast,andtakingupmemory.
Inourexample,wesettheminTimeandminDistancetozero.ThistellstheLocationManagertosendusupdatesasoftenaspossible.Thesearenotdesiredsettingsforyourproductionapplication,oronrealdevices,butweusethemheretomakethedemonstrationsrunbetterintheemulators.(Inreallife,youwouldnotwantthehardwaretryingtofigureoutourcurrentpositionsooften,asthisdrainsthebattery.)Setthesevaluesappropriatelyforthesituation,tryingtominimizehowoftenyoutrulyneedto
benotifiedofachangeinposition.Googletypicallyrecommendsvaluesnosmallerthan20seconds.
TestingLocationApplicationswiththeEmulatorLet’stestthisintheemulator,usingtheDalvikDebugMonitorService(DDMS)perspectivethatshipswiththeADTplug-inforEclipse.TheDDMSUIprovidesascreenforyoutosendtheemulatoranewlocation(seeFigure19-6).
Figure19-6.UsingtheDDMSUIinEclipsetosendlocationdatatotheemulator
TogettotheDDMSinEclipse,useWindow OpenPerspective DDMS.TheEmulatorControlviewshouldalreadybethereforyou,butifnot,useWindow ShowViewOther Android EmulatorControltomakeitvisibleinthisperspective.Youmayneedtoscrolldownintheemulatorcontroltofindthelocationcontrols.AsshowninFigure19-6,theManualtabintheDDMSuserinterfaceallowsyoutosendanewGPSlocation(latitude/longitudepair)totheemulator.SendinganewlocationwillfiretheonLocationChanged()methodonthelistener,whichwillresultinamessagetotheuserconveyingthenewlocation.
Youcansendlocationdatatotheemulatorusingseveralothertechniques,asshownintheDDMSuserinterface(seeFigure19-6).Forexample,theDDMSinterfaceallowsyoutosubmitaGPSExchangeFormat(GPX)fileoraKeyholeMarkupLanguage(KML)file.
YoucanobtainsampleGPXfilesfromthesesites:
www.topografix.com/gpx_resources.asp
http://tramper.co.nz/?view=gpxFiles
www.gpsxchange.com/
Similarly,youcanusethefollowingKMLresourcestoobtainorcreateKMLfiles:
http://bbs.keyhole.com/
http://code.google.com/apis/kml/documentation/kml_tut.html
NoteSomesitesprovideKMZfiles.ThesearezippedKMLfiles,sosimplyunzipthemtogettotheKMLfile.SomeKMLfilesneedtohavetheirXMLnamespacevaluesalteredinordertoplayproperlyinDDMS.IfyouhavetroublewithaparticularKMLfile,makesureithasthis:
<kmlxmlns=”http://earth.google.com/kml/2.x“>.
YoucanuploadaGPXorKMLfiletotheemulatorandsetthespeedatwhichtheemulatorwillplaybackthefile(seeFigure19-7).Theemulatorwillthensendlocationupdatestoyourapplicationbasedontheconfiguredspeed.AsFigure19-7shows,aGPXfilecontainspoints,showninthetoppart,andpaths,showninthebottompart.Youcan’tplayapoint,butwhenyouclickapoint,itwillbesenttotheemulator.Youclickapath,andthenthePlaybuttonwillbeenabledsoyoucanplaythepoints.
Figure19-7.UploadingGPXandKMLfilestotheemulatorforplayback
NoteTherehavebeenreportsthatnotallGPXfilesareunderstandablebytheemulatorcontrol.IfyouattempttoloadaGPXfileandnothinghappens,tryadifferentfilefromadifferentsource.
Listing19-9includessomeadditionalmethodsforLocationListenerwehaven’tmentionedyet.TheyarethecallbacksonProviderDisabled(),onProviderEnabled(),andonStatusChanged().Foroursample,wedidnotdoanythingwiththese,butinyourapplication,youcouldbenotifiedwhenalocationprovider,suchasgps,isdisabledorenabledbytheuser,orwhenastatuschangeswithoneofthelocationproviders.StatusesincludeOUT_OF_SERVICE,TEMPORARILY_UNAVAILABLE,andAVAILABLE.Evenifaproviderisenabled,itdoesnotmeanthatitwillbesendinganylocationupdates,andyoucantellthatusingstatuses.NotethatonProviderDisabled()willbeinvokedimmediatelyifarequestLocationUpdates()iscalledforadisabledprovider.
SendingLocationUpdatesfromtheEmulatorConsoleEclipsehassomeeasy-touse-toolsforsendinglocationupdatestoyourapplication,butthere’sanotherwaytodoit.Youcouldlaunchtheemulatorconsole,usingthefollowing
commandfromatoolswindow:
telnetlocalhostemulator_port_number
whereemulator_port_numberisthenumberassociatedtotheinstanceoftheAVDthat’salreadyrunning,displayedinthetitlebaroftheemulatorwindow.Youmayneedtoinstalltelnetforyourworkstationifit’snotalreadyavailable.Onceyou’reconnected,youcanusethegeofixcommandtosendinlocationupdates.Tosendinlatitude/longitudecoordinateswithaltitude(altitudeisoptional),usethisformofthecommand:
geofixlonlat[altitude]
Forexample,thefollowingcommandwillsendthelocationofJacksonville,Floridatoyourapplicationwithanaltitudeof120meters.
geofix-81.562530.334954120
Pleasepaycarefulattentiontotheorderoftheargumentstothegeofixcommand.Longitudeisthefirstargument,andlatitudeisthesecond.
WhatCanYouDowithaLocation?Asmentionedbefore,Locationscantellyouthelatitudeandlongitude,whentheLocationwascomputed,theproviderthatcomputedthisLocation,andoptionallythealtitude,speed,bearing,andaccuracylevel.DependingontheproviderwheretheLocationcamefrom,therecouldbeextrainformationaswell.Forexample,iftheLocationcamefromaGPSprovider,thereisanextrasBundlethatwilltellyouhowmanysatelliteswereusedtocomputetheLocation.Theoptionalvaluesmayormaynotbepresent,dependingontheprovider.ToknowifaLocationhasoneofthesevalues,theLocationclassprovidesasetofhas…()methodsthatreturnabooleanvalue,forexamplehasAccuracy().BeforerelyingonthereturnvalueofgetAccuracy(),itwouldbewisetocallhasAccuracy()first.
TheLocationclasshassomeotherusefulmethods,includingastaticmethoddistanceBetween(),whichwillreturntheshortestdistancebetweentwoLocations.Anotherdistance-relatedmethodisdistanceTo(),whichwillreturntheshortestdistancebetweenthecurrentLocationobjectandtheLocationobjectpassedtothemethod.NotethatdistancesareinmetersandthatthedistancecalculationstakeintoaccountthecurvatureoftheEarth.Butalsobeawarethatthedistancesarenotprovidedintermsofthedistanceyouwouldhavetogobycar,forexample.
Ifyouwanttogetdrivingdirectionsordrivingdistances,youwillneedtohaveyourbeginningandendingLocations,buttodothecalculations,youwilllikelyneedtousetheGoogleDirectionsAPI.TheDirectionsAPIwouldallowyourapplicationtoshowhowtogetfromyourbeginningtoyourendinglocation.ThisisanotheroftheGoogleAPIclientAPIsthatyoucanenableforyourapplication.
SettingUpforGooglePlayServicesLocationUpdates
You’veseenhowtogetlocationupdateswithaLocationManager,butlet’sreturntotheFusedLocationProviderApitoseehowtogetlocationupdatesfromit.ThesampleprojectforthissectionisFusedLocationApiUpdates.ThisoneisabittrickierbecausewearedealingwithGooglePlayServices,anindependentservicerunningonthedevice.Therefore,youcan’talwaysbesurethatyouhaveavalidclientconnection,andyouneedtobecarefulwhenrequestinglocationupdates.Forthisreason,yourapplicationwillneedtoworryaboutstate.
Intheearliersampleprogram(WhereAmILocationAPI),youcheckedtoseeifLocationServiceswereturnedon,butthecodeassumedthatGooglePlayServiceswereavailableandready.Nowyou’regoingtoseehowtocheckfortheexistenceofGooglePlayServicesandhowtheGooglePlayServicesUtilclasscanhelpyou.Thebasicflowistocheckeachdependencyforlocationupdatestooccurand,ifthereisawaytocorrectaproblem,helptheuserfixit.Iftheuserdoesnot,orcannot,fixaproblem,theapplicationquits.Iftheuserkeepsfixingproblemsuntileverythingisworking,thenlocationupdatesgetrequested,andtheapplicationdisplayslocationupdatesviaToastmessages.
Listing19-10showsourmainmethodfortryingtoconnect.YouwillseeinsidethismethodthesameLocationServicescheckfromtheearlierWhereAmILocationAPIsampleapplication.ThetryToConnect()methodwillbecalledfromtheactivity’sonResume()callback,sothateverytimethisactivityisresumed,anewclientconnectionwillbeestablishedtoGooglePlayServices.Wedonotwanttoassumethatanoldclientisstillvalidandactive.
Listing19-10.CheckingfortheAbilitytoDoLocationUpdates
privatevoidtryToConnect(){//CheckthatGooglePlayservicesisavailableintresultCode=GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);//IfGooglePlayservicesisavailable,thenwe'regoodif(resultCode==ConnectionResult.SUCCESS){Log.d(TAG,"GooglePlayservicesisavailable.");if(!isLocationEnabled(this)){if(lastFix==FIX.LOCATION_SETTINGS){//Sincewe'recomingthroughagain,itmeans//recoverydidn'thappen.Timetobailout.Log.e(TAG,"Locationsettingsdidn'twork");finish();}else{//nolocationserviceprovidersareenabledToast.makeText(this,"LocationServicesareoff."+"Can'tworkwithoutthem.Pleaseturnthemon.",Toast.LENGTH_LONG).show();Log.i(TAG,"LocationServicesneedtobeon."
+"LaunchingtheSettingsscreen");startActivityForResult(newIntent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS),LOCATION_SETTINGS_REQUEST);lastFix=FIX.LOCATION_SETTINGS;}}else{client.connect();Log.v(TAG,"ConnectingtoGoogleApiClient…");}}//GooglePlayserviceswasnotavailableforsomereason//Seeiftheusercandosomethingaboutitelseif(GooglePlayServicesUtil.isUserRecoverableError(resultCode)){if(lastFix==FIX.PLAY_SERVICES){//Sincewe'recomingthroughagain,itmeans//recoverydidn'thappen.Timetobailout.Log.e(TAG,"Recoverydoesn'tseemtowork");finish();}else{Log.d(TAG,"GooglePlayservicesmaybeavailable."+"Askinguserforhelp");//Thisformofthedialogcallwillresultineithera//callbacktoonActivityResult,oradialogonCancel.GooglePlayServicesUtil.showErrorDialogFragment(resultCode,this,PLAY_SERVICES_RECOVERY_REQUEST,this);lastFix=FIX.PLAY_SERVICES;}}else{//Nohopeleft.Log.e(TAG,"GooglePlayServicesis/arenotavailable."+"Nopointincontinuing");finish();}}
TheGooglePlayServicesUtilclasshasseveralstaticmethodstohelpgeteverythingsetup
forlocationupdates.ThefirstmethodisisGooglePlayServicesAvailable(),whichrequiresacontext.TheresultisanintegervaluewhichiseitherSUCCESSoroneofseveralothervalueswhichcouldindicateforexamplethattheservicesaremissing,ortheversionisnotappropriate.Formostpurposes,youdon’treallyneedtocareabouttheothervaluesthatarereturned,asyou’llsee.
IfGooglePlayServicesareavailable,youwillcheckforLocationServices(asbefore)andiftheyareokay,youcaninvoketheconnect()methodontheGoogleApiClientclient.Theconnect()callisasynchronousandaseparatecallbackwillhandletheresultsoftheconnectcall.Asbefore,ifLocationServicesarenotturnedon,youwouldlaunchthelocationsettingsactivitysotheusercouldturnthemon.Inthissample,wejustuseaToastmessagetotelltheuserwhytheyarebeingredirectedtotheSettingsscreen.Inaproductionapplication,youwouldprobablywanttoshowanalertdialogwithanOKandCancelbuttonbeforeredirectingtotheSettingsscreen.
IfGooglePlayServicesarenotavailable,thenextcheckistoseeiftheusercouldresolvetheissue,usingtheisUserRecoverableError()method.Hereyoupassintheresultcodefromtheearliercheck,whichshouldbesomethingotherthanSUCCESS.Thisiswhyyoudon’tneedtocarewhatothervaluewasreturned.Thismethoddecidesforyouiftheusercandosomethingaboutitornot.Iftheusercan’tcorrectthesituation(i.e.,isUserRecoverableError()returnsfalse),thentherereallyisn’tanythingelseyoucandoandyouwillprobablywanttobailout.Inthissampleapplicationalogmessageiswrittenandtheactivityends.Youmightwanttobemoregracefulinyourexit.
IftheusercandosomethingabouttheproblemwithGooglePlayServices,theGooglePlayServicesUtilclasshasyetanotherstaticmethodyoucanuse:showErrorDialogFragment().Thiswillshowadialogtotheuserindicatingwhattheproblemisandwhattheycandoaboutit.Thereareafewvariationsonthiscall,andthesampleisusingtheonewhichpopsadialogfragmentwhilelisteningforadialogcancel.Thedialogfragmentcouldlaunchanotheractivity,whichwouldresultinouronActivityResult()beingcalled.Forthisreason,youwanttopassinarequestvalue(i.e.,PLAY_SERVICES_RECOVERY_REQUEST),whichwillbepassedtoonActivityResult()later.Thismethodisalsoasynchronous,andyourapplicationwillseeeitheronActivityResult()invokedlater,ortheonCancel()forthedialog.ThesecondargumenttoshowErrorDialogFragment()isthecontext,andthelastargumentisthelistenerforthedialog.Becausewepassed'this'asthelastargument,torepresentthisactivity,thesampleactivitymustimplementDialogInterface.OnCancelListenerandhaveanonCancel()callback.
You’llsoonseethecodeforonActivityResult(),butyoushouldknowthatwhenaresultispassedbacktoyouractivity,you’regoingtohavedothesechecksagain,bycallingtryToConnect().ThatiswhythismethodsetsalastFixvalue,tokeeptrackofwhichproblemisbeingworkedon.Ifthesameproblemexistsaftertheuserhashadachancetofixit,wecouldassumethattheuserisn’tinterestedinfixingtheproblem,orthesystemisunabletofixtheproblem.Wedonotwantsomesortofinfiniteloopthattheusercannotbreakoutof.Forthissampleactivity,iftryToConnect()hitsthesameproblemtwiceinarow,itbailsoutandtheactivityisfinished.Yourapplicationmight
wanttotakealternativeaction,givingtheusermoreoptionstocontinuetousetheapp.
TorecapwhathashappenedintryToConnect(),youcheckedfortheexistenceandreadinessofGooglePlayServices,aswellasLocationServices.Ifeverythinglookedgood,aconnectcallwasmadeontheGoogleClientApiclient.Iftheuserwasabletocorrectanything,asuitableintentwasfiredtolaunchanactivitytotakecareofit.Andifthesituationwashopeless,theactivityended.Nowlet’slookatthevariouscallbacksthatcouldresultfromtheseactions.
Iftheconnectionrequestwassuccessful,theonConnected()callbackwillfire.Listing19-11showswhatthislookslike.
Listing19-11.ClientIsConnectedSoRequestLocationUpdates
@OverridepublicvoidonConnected(Bundlearg0){//SetuplocationupdatesLog.v(TAG,"Connected!");lastFix=FIX.NO_FAIL;locator.requestLocationUpdates(client,locReq,this);Log.v(TAG,"Requestinglocationupdates(onConnected)...");}
Thisoneisprettystraightforward.IfwegotagoodconnectiontoGooglePlayServices,startaskingtheFusedLocationProviderApi(locator)forlocationupdates.You’llseemoreaboutlocReqlater,butfornowjustknowthatitisaLocationRequestobjectwithparametersthatdefinewhatkindsoflocationupdatesyourapplicationwants.Thismethodalsoresetsastatevariable(lastFix)whichwillmakemoresensesoon.
Iftheconnectionrequestwasnotsuccessful,theonConnectionFailed()callbackwillfire.Listing19-12showsthiscallback.
Listing19-12.HandlingaFailedConnectionAttempt
@OverridepublicvoidonConnectionFailed(ConnectionResultconnectionResult){/**GooglePlayservicescanresolvesomeerrorsitdetects.*Iftheerrorhasaresolution,trysendinganIntentto*startaGooglePlayservicesactivitythatcanresolve*theerror.*/if(connectionResult.hasResolution()){Log.i(TAG,"Connectionfailed,tryingtoresolveit…");
if(lastFix==FIX.CONNECTION){//Sincewe'recomingthroughagain,itmeans//recoverydidn'thappen.Timetobailout.Log.e(TAG,"Connectionretrydidn'twork");finish();}try{//StartanactivitythattriestoresolvetheerrorlastFix=FIX.CONNECTION;connectionResult.startResolutionForResult(this,CONNECTION_FAILURE_RESOLUTION_REQUEST);}catch(IntentSender.SendIntentExceptione){//LogtheerrorLog.e(TAG,"Couldnotresolveconnectionfailure");e.printStackTrace();finish();}}else{/**Ifnoresolutionisavailable,displayerrortothe*user.*/Log.e(TAG,"Connectionfailed,noresolutionsavailable,"+GooglePlayServicesUtil.getErrorString(connectionResult.getErrorCode()));Toast.makeText(this,"Connectionfailed.Cannotcontinue",Toast.LENGTH_LONG).show();finish();}}
Iftheconnectionrequesthasfailed,itisstillpossiblethatthesituationcanbecorrected.Onceagainthereisamethodthatcantellifthereisawaytoresolvetheproblem.TheConnectionResultobjectcontainsbothanindicatorifthereisaresolution,aswellastheintenttofiretotrytoresolvethesituation.Inthiscase,theapplicationcallsstartResolutionForResult().Similartobefore,anintentwillbefired,someactivitywillbelaunched,andyourapplicationwillgetaresultbackinonActivityResult().NoticethatheretherequesttagisCONNECTION_FAILURE_RESOLUTION_REQUEST.Ifnothingcanbedone,displayanerrorandbailout.
Therecouldhavebeenseveralintentslaunched,eachofwhichshouldcauseyour
onActivityResult()callbacktofire.Listing19-13showswhatthiscallbacklookslike.Rememberthattherecouldhavebeenthreeseparateintentsfiredtohandleproblems,sothiscallbackmustexpectanyofthethree.Alsokeepinmindthattheintentscausedanactivitytorun,meaningyouractivitygotpaused,anditwillresumerightaftertheonActivityResult()hasfired.ThisisamajorreasonwhythetryToConnect()method(showninListing19-10)isonlycalledfromtheactivity’sonResume()callback.Wheneverthisactivityisbeingresumed,ittriestomakeanewconnectiontoGooglePlayServicesandtosetuplocationupdates.Whenthisactivitypauses,itdisconnectsfromGooglePlayServices.Itiseasytoreconnectratherthantryingtohangontoaconnectionwhileitisnotneeded.
Listing19-13.GettingNewsBackfromtheLaunchedIntents
@OverrideprotectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){/*Decidewhattodobasedontheoriginalrequestcode.*Notethatouractivitygotpausedtolaunchtheother*activity,soafterthiscallbackruns,ouractivity's*onResume()willrun.*/switch(requestCode){casePLAY_SERVICES_RECOVERY_REQUEST:Log.v(TAG,"GotaresultforPlayServicesRecovery");break;caseLOCATION_SETTINGS_REQUEST:Log.v(TAG,"GotaresultforLocationSettings");break;caseCONNECTION_FAILURE_RESOLUTION_REQUEST:Log.v(TAG,"Gotaresultforconnectionfailure");break;}Log.v(TAG,"resultCodewas"+resultCode);Log.v(TAG,"EndofonActivityResult");}
SinceonActivityResult()couldbecalledbecauseofanumberofintents,theswitchstatementisusedtofigureoutwhichoneisbeingrespondedto.TheGooglePlayServicescorrectiveactionmightsayitwassuccessfulbysettingtheresultCodetoActivity.RESULT_OK.Thisdoesn’tnecessarilymeanthattheuserfixedtheproblem,butittellsyouthatnothingfailed.IftheresponsetotheGooglePlayServicescorrectiveactionisActivity.RESULT_CANCELED,itcouldmeantherewassomesortoffailure.Regardlessiftheuserfixedtheproblemornot,you’regoingtoreturnfromthiscallback,andthenonResume()willrun,inwhichtryToConnect()willbecalledagain.Soitreallydoesn’tmatterwhatresultCodeis.Inpractice,evenwhenasettinghasbeenproperlysetforlocationupdatestooccur,youcouldseeresultCodesetto
RESULT_CANCELED.Similarly,ifthere’saresponsetotheotherfixes,logitandcontinuesinceonResume()willrunnextanyway.
Finally,referbacktotheonConnected()callbackinListing19-11,whichcallslocator.requestLocationUpdates(client,locReq,this).ThisiswheretheFusedLocationProviderApiwillbeaskedtosendlocationupdatesbacktothisactivity.GooglePlayServicesisupandrunning,andLocationServicesaresetappropriately.
Oncelocationupdateshavebeenrequested,anynewlocationupdateswillgetsenttotheonLocationChanged()callback.Inthissampleapplication,allthathappensisthatthelocationinformationisdisplayedinaToastmessage.Thenextsectiongoesintomoredetailonhowtorequestlocationupdates.
Thereareafewothermethodsintheactivitythatsofarwerenotdescribed.TheonPause()callbackdisconnectstheclientafterstoppingthelocationupdates.Youshouldnoticethattheclientischeckedforconnectednessbeforecallingmethods.TheGoogleApiClientclasshasamethodcalledisConnected(),whichyouwillusetobesureyourequestorremovelocationupdatesonlywhenthere’saconnectedclient.Otherwise,youwillgetanIllegalStateException.Thetwomethodsforsettingupthemenuarebasicmenucallbacks.Themenuisusedtoallowtheusertoswitchbetweenthevariouspriorityvalues.Whentheuserselectsamenuitem,thelocationrequestobjectisupdatedandpassedbackintoalterthelocationupdateprocess.TheonCancel()callbackcanbecalledfromthepop-uperrordialogthatisshownintryToConnect(seeListing19-10).Iftheusersimplyclosestheerrorfragmentdialogbox,weinferthattheuserdoesn’twanttogetupdatesandtheapplicationexits.
LocationUpdateswithFusedLocationProviderApiWiththeLocationManager,youhadtodealwiththespecificlocationproviders(i.e.,GPSorcell/WiFi).WiththeFusedLocationProviderApi,yousubmitaLocationRequestandtheAPIwillmakechoicesforyouofwhichproviderwouldbethebest,notonlyinitiallybutovertimeaswell.Ingeneral,thetrade-offwhengettinglocationupdatesisbetweenpowerconsumptionandaccuracy.GPSisusuallymoreaccuratebutusesthemostpower.Ontheotherhand,whenindoors,GPSmaybelessaccuratethancell/WiFi,andyou’dwanttoautomaticallyswitchtobemoreaccuratewhileconsumingtheleastamountofpower.TheFusedLocationProviderApicouldalsotakeadvantageofon-boardsensorssuchasagyroscopeorcompass.ThisAPIhidesthecomplexitiesoflocationfixingfromyou.
Youshouldwriteyourcodesoyou’rerequestinglocationupdatesonlywhenitmakessensetodoso.Ifyouaredisplayingthecurrentlocationonamap,andthemapisnotvisible,youdonotneedtorequestupdates.Therearecaseswhenyoumightwanttokeepgettingupdatesevenwhennotdisplayingthecurrentposition,andwe’llcoverthatinthenextsection.Thepointisthatlocationupdatescanbeabigdrainonthebattery,soaskforthemonlywhenyoureallyneedthem.Youshouldnotassumethattheuserisgoingto“berightback”andthereforekeepgettingupdates.Iftheysettheirdevicedownandwon’tbe
lookingatitagainforsometime,you’dbetternotbedrainingthebatterydown.
Listing19-14showshowthesampleapplicationsetstheLocationRequestobjecttomakealocationupdatesrequestoftheFusedLocationProviderApi.ThisisdoneintheonCreate()callbackoftheactivity.
Listing19-14.SettingUpaLocationRequestObject
locReq=LocationRequest.create().setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY).setInterval(10000).setFastestInterval(5000);
Usethestaticcreate()method,thencalltheappropriatesetterstofillouttherequestobject.ThisobjectwillbepassedtotherequestLocationUpdates()methodoftheFusedLocationProviderApi.Abigdifferencefromdealingwiththeolderlocationprovidersisthatthisrequestobjectdoesnotmakeanyreferencetoaspecificlocationprovider.SimilartotheCriteriamethodoffindingaprovider,thisrequestobjectultimatelyselectsthefrequencyofupdatesandtheconsumptionofpower.
YoucanspecifythedesiredfrequencyoflocationupdatesusingsetInterval()andsetFastestInterval();bothtakealongargumentrepresentingthenumberofmilliseconds.Theformerissayingthatyouwanttogetalocationupdateonaregularbasis,everysomanymillisecondsapart.Thesystemwilltrytohonorthisifitisableto,buttherearenoguarantees.Youcouldgetupdatesmorefrequentlythandesired,evenmuchmorefrequently.Thatiswherethesecondmethodcomesin.Youcanspecifythefastestintervalforreceivinglocationupdates.Moreonthisinabit.
ThepowerportionoftherequestishandledbythesetPriority()setter.Therearecurrentlyfouroptionsfortheargument:
PRIORITY_NO_POWER
PRIORITY_LOW_POWER
PRIORITY_BALANCED_POWER_ACCURACY
PRIORITY_HIGH_ACCURACY
TheNO_POWERoptionisprettymuchsayingthatyourapplicationwillbeusingthepassiveproviderdescribedearlier.Theonlywaytonotconsumeanypoweristopiggybackoffofthelocationupdatesforanotherapplication.Therefore,theaccuracyofthelocationsmaynotbeveryaccurateorfrequent;italldependsonwhatotherapplicationsarerequesting.YoujustlearnedthatyoucanrequestafrequencyofupdatesusingsetInterval()andsetFastestInterval().Ifyouarepiggybackingoffofanotherapplication,andthatapplicationisreceivinglocationupdatesevery5seconds,butyoudon’twantupdatesfasterthanevery20seconds,youshouldusesetFastestInterval(20000)soyourapplicationisnotoverwhelmedwith
updates.AtthesametimeyoucouldusesetInterval(60000)torequestadesiredintervalofoneupdateeveryminute.Iftherearefewotherlocationupdateshappeningonthedevice,youwon’thavetoworryaboutreducingthefrequencyfrom5secondsto20secondsapart,butatthesametimeyouprobablywon’tgetupdateseveryminuteeither.Youneedtousebothofthesesetterstoindicatewhatyourapplicationwants,butthatdoesn’tmeanyouareguaranteedtogetwhatyouwant.
TheLOW_POWERpriorityingeneralmeansthatlocationupdateswillbederivedonlyviacelltowertriangulationandWiFihotspotlocationinformation.Thesearelow-powerwaysofdeterminingposition,withacorrespondingreductioninaccuracy.Youcouldeasilyfindthelocationstobeaccurateonlytowithin1,500metersorworse,butthenyoucouldgetalocationthat’saccurateto10meters.Alloftheprioritieswilltakeadvantageofthepassiveprovider,soifanaccuratelocationupdatehappenstoberequestedbysomeotherapplication,yourapplicationcouldpickitupevenwhenyourpriorityissettolowpower.
TheBALANCEDprioritywilltrytodoadecentjoboftradingoffaccuracyforlesspower.Itwillconsiderusingalloftheavailablemethodsofdetermininglocation,exceptforGPS.
TheHIGH_ACCURACYprioritywillpotentiallyuseallavailablesourcesoflocationinformation,includingGPS.BecauseoftheGPSradio,thisprioritycouldconsumealotofbattery.
Locationupdatesalsodependonthelocationmodeofthedevice.Asyousawearlier,theLocationSettingschangedinKitKattoallowtheusertospecifyamodeoflocationupdatesfortheirdevice.ReferringnowtotheSettings.Secureclass,thelocationmodesettingvaluesareasfollows:
LOCATION_MODE_OFF
LOCATION_MODE_BATTERY_SAVING
LOCATION_MODE_HIGH_ACCURACY
LOCATION_MODE_SENSORS_ONLY
andthecurrentvaluecanberetrievedusingthecodefromListing19-8.Themodeissetbytheuserfortheentiredevice,notbyapplication.However,yourapplicationhasanopportunitytorequestaprioritytocomplementthemodechoicemadebytheuser.IfthedevicehasamodeofHIGH_ACCURACYandyourapplicationchoosesapriorityofLOW_POWER,yourapplicationwillnotbetheonedrainingthebatterybutcouldstillgetdecentlocationupdates.
Themodecanworkagainstyouhowever.IftheuserchoosesamodeofSENSORS_ONLY,andthepriorityissettoNO_POWER,LOW_POWERorevenBALANCED,locationupdateswillberare,regardlessofwhatyousetinthelocationrequestwithsetInterval().ThepreferredmodeformostusefullocationupdatesisHIGH_ACCURACY,becausethismodewillcombineallpossiblesourcesoflocationinformationandprovidethemostaccurateresults.Yourapplicationwillbeabletogethighaccuracywhenneeded(hopefullythisisarareneed)andgoodaccuracytherestof
thetime.YourapplicationcanaltertheprioritytoHIGH_ACCURACYwhenneeded,butBALANCEDorLOW_POWERtheothertimes.
SomeotherinterestingoptionswithaLocationRequestincludesettingaspecificnumberoflocationupdatestoreceive,ortospecifyatimelimitwhenthelocationupdatesshouldstop.Youcanalsosetaminimumdistance(inmeters)withinwhichyourapplicationdoesnotwantupdates.Thisisageofenceofsorts,whereyoutellthelocationservicethatyouonlywantalocationupdateifthedevicemovesacertaindistancefromitscurrentlocation.Thatisineffectsettingupageofencecirclearoundthecurrentlocation.Moreongeofenceslater.
AlternateWaysofGettingLocationUpdatesYou’veseenhowtogetlocationupdatessenttoyouractivityusingtherequestLocationUpdates()methodoftheLocationManagerandtheFusedLocationProviderApi.Thereareactuallyseveraldifferentsignaturesofthismethod,includingonesthatuseaPendingIntent.Thisgivesyoutheabilitytodirectlocationupdatestoservicesorbroadcastreceivers.YoucanalsodirectlocationupdatestootherLooperthreadsinsteadofthemainthread,givingyoulotsofflexibilityforyourapplication,althoughsomeofthesemethodshavebeenavailableonlysinceAndroid2.3.
UsingProximityAlertsandGeofencingGeofencingisapopularrequirementforamobileapplication.Itmeansthatyourapplicationshouldalteritsbehaviordependingonwhereitislocated.Atypicalusecaseistopreventthedevicefromworkingwhenitisoutsideofaparticularlocation.Forexample,ahospitalapplicationcouldrestrictaccesstopatientdatawhenitisnotatthehospital.Oryourapplicationmightwanttosilencenotificationswhenthedeviceisattheworkplace.LocationManagerhasamechanismcalledproximityalerts,andthereisasimilarrecentAPIcalledGeofencingApiforthenewerLocationServices.We’llbrieflydiscussthefirst,thenaddressthesecondindetail.
WementionedearlierthattheLocationManagercannotifyyouwhenthedeviceentersaspecifiedgeographicallocation.ThemethodtosetthisupisaddProximityAlert()fromtheLocationManagerclass.Basically,youtelltheLocationManagerthatyouwantanIntenttobefiredwhenthelocationofthedevicegoesinto,orleaves,acircleofacertainradiuswithacenteratalatitude/longitudeposition.TheIntentcantriggeraBroadcastReceiveroraServicetobecalled,oranActivitytobestarted.Thereisalsoanoptionaltimelimitplacedonthealert,soitcouldtimeoutbeforetheIntentfires.
Internally,thecodeforthismethodregisterslistenersforboththeGPSandnetworkprovidersandsetsuplocationupdatesforoncepersecondandaminDistanceof1meter.Youdon’thaveanywaytooverridethisbehaviororsetparameters.Therefore,ifyouleavethisrunningforalongtime,youcouldendupdrainingthebatteryveryquickly.Ifthescreengoestosleep,proximityalertswillonlybecheckedonceeveryfourminutes,butagain,youhavenocontroloverthetimedurationhere.Forthesereasons,wehave
includedademonstrationapplicationcalledProximityAlertDemowiththesampleapplications,butwewillnotdiveintothedetails.Instead,wewillturnourattentiontotheLocationServicesapproach,withanothersampleapplicationcalledGeofencingApi.NotethattheGeofencingApisampleapplicationwilllooksimilartotheFusedLocationProviderApisampleapplicationsincebothsharetheGoogleClientApimechanismforactivation.
TheGeofencingApiAPIAtthetimeofthiswriting,ageofenceisacircularregionwithalatitude/longitudecenter,plussometimeparameters.Atsomepointinthefuture,theregionmightnotbecircularbutfornowitis.Onceageofencehasbeenbuilt,itcanbepassedtotheGeofenceApiformonitoring.Yourapplicationcanevengoawayandyourgeofencecanbeactive.Alongwithageofence,orsetofgeofences,yourapplicationwillpassaPendingIntentwithanIntenttobefiredwhensomethinginterestinghappensaroundageofence.Thethreecurrenteventsareenter,exitanddwell.Enterandexitaresimpletounderstand;theIntentwillbefiredifthedevicegoesinto,orleaves,thecircularregion.ThedwelleventfirestheIntentafterthedeviceremainsinsideofthecircularregionforaperiodoftime.Thisloiteringdelayisspecifiedinmilliseconds.Andthat’sallthereistoit.
SeethesampleapplicationcalledGeofencingApiDemo.Itsetsuptwogeofencescalledhomeandwork,connectstoLocationServices,andregistersaserviceintenttobefiredwhenthedeviceenters,exitsordwellsineitherofthesegeofences.Whentriggered,theservicegeneratesanotificationpereventtomakeiteasierforyoutoseetheresults.Geofencesareoftenusedinthebackground,soaservicemakesalotofsensehere.Thatis,anapplicationshouldn’tneedtobeintheforegroundtohavegeofences.Infact,thebasicideaofageofenceisthatyouwantyourapplicationtobewakenedupifthedeviceentersorleavesaspecificgeographicregion.
ThesetupcodeusedearliertomakesurethatGooglePlayServicesandLocationServicesareavailableandreadyhasbeenleftoutofthissampleapplicationtomakeiteasiertofollowalong,butyouwouldwanttoincludethatcodeinaproductionapplication.Listing19-15showstheonCreate()methodofthemainactivity,inwhichthegeofencesandthePendingIntentarecreated.
Listing19-15.SettingUpGeofences
privateGoogleApiClientmClient=null;privateList<Geofence>mGeofences=newArrayList<Geofence>();privatePendingIntentpIntent=null;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
finalfloatradius=0.5f*1609.0f;//halfmiletimes1609meterspermile
Geofence.Buildergb=newGeofence.Builder();//MakeahalfmilegeofencearoundyourhomeGeofencehome=gb.setCircularRegion(28.993818,-81.383816,radius).setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER|Geofence.GEOFENCE_TRANSITION_EXIT|Geofence.GEOFENCE_TRANSITION_DWELL).setExpirationDuration(12*60*60*1000)//12hours.setLoiteringDelay(300000)//5minutes.setRequestId("home").setNotificationResponsiveness(5000)//5secs.build();mGeofences.add(home);
//MakeanothergeofencearoundyourworkGeofencework=gb.setCircularRegion(28.36631,-81.52120,radius).setRequestId("work").build();mGeofences.add(work);Intentintent=newIntent(this,ReceiveTransitionsIntentService.class);
pIntent=PendingIntent.getService(getApplicationContext(),0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
mClient=newGoogleApiClient.Builder(this,this,this).addApi(LocationServices.API).build();
Log.v(TAG,"Activity,clientarecreated");}
Seehowthegeofenceiscreatedasacirclearoundalat/lon,withtheeventsofinterest(inthiscaseallofthem)andsometimeparameters.Inthissample,thegeofenceswillbeactivefor12hours,oruntiltheyareremoved(seeonDestroy()).It’salsopossibletosetgeofencestoneverexpire.Theloiteringdelayof5minutesmeansthatthedwelleventwillfireifthedevicestaysinsidethegeofenceforatleast5minutes.TherequestIDwillbepassedbacktoyourapplicationwiththeIntentsoyoucanidentifywhichgeofencetheIntentisfor.Thenotificationresponsivenessof5secondsmeansthattheGeofencingApiwilltrytosendtheIntentwithin5secondsofwhentheeventhappens.However,therearenoguaranteesthattheIntentwillbethatquick.Thelargerthisvalue,thebetteritison
batterylife,sincetheAPIcouldsleepmoreandcheckthingslessoften.Ontheotherhand,ifthisvalueisverylong,forexampleseveralminutes,itispossibleyoumightevenmissaneventifthedevicepassesthroughyourgeofencequickly.Thechoiceofnotificationresponsivenesswilldependonhowbigyourgeofencesareandhowyouwantyourapplicationtobehave.
Similartotheprevioussampleapplication,aconnectionisattemptedfromonResume(),andListing19-16showswhatrunswhentheconnectionissuccessful.
Listing19-16.RegisteringGeofenceswiththeAPI
@OverridepublicvoidonConnected(Bundlearg0){//SetupgeofencesLog.v(TAG,"Settingupgeofences(onConnected)...");PendingResult<Status>pResult=mFencer.addGeofences(mClient,mGeofences,pIntent);pResult.setResultCallback(this);//ResultCallback<Status>interface}
@OverridepublicvoidonResult(Statusstatus){Log.v(TAG,"GotaresultfromaddGeofences("+status.getStatusCode()+"):"+status.getStatus().getStatusMessage());}
TheGeofencingApigetspassedtheAPIclienthandle,thelistofgeofences,andthePendingIntent.ThereturnisaPendingResult.Ifyouwanttofindoutiftheresultisultimatelysuccessfulornot,youneedtosetacallbackreceiverusingsetResultCallback().ThisactivityhasimplementedtheResultCallback<Status>interface,sotheonResult()callbackwillbeinvokedwiththeresultsoftheaddGeofences()methodcall.Forthissample,theresultissimplylogged,butofcourseyouwouldwanttotakestepsiftheresultwasnotsuccessful.That’sallthattheactivitydoes.NextupistheservicethatreceivesanIntentwhenaninterestingeventoccurs.
Listing19-17showstheinterestingcallbacksandmethodsoftheReceiveTransitionsIntentService,anIntentServiceforthisapplication.Itbasicallyreportsouttheinformationreceived,whetherthatisanerrororageofenceevent.Eventsaredisplayedusingnotifications.Thisisforyoursafetysincetheexpectationisthatyouwillstartthisapplicationathomeanddrivetowork.Wedonotwantyouhavingtowatchthedevice’sscreenduringthetrip.Instead,youwillbeabletoreviewallofthenotificationsfromeacheventwhenyouaresafelystopped.
Listing19-17.ReceivingIntentsfromtheGeofencingApi
publicvoidonCreate(){super.onCreate();notificationMgr=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);}
@OverrideprotectedvoidonHandleIntent(Intentintent){GeofencingEventgfEvent=GeofencingEvent.fromIntent(intent);//Firstcheckforerrorsif(gfEvent.hasError()){//GettheerrorcodewithastaticmethodinterrorCode=gfEvent.getErrorCode();//LogtheerrorLog.e(TAG,"LocationServiceserror:"+Integer.toString(errorCode));/**Ifthere'snoerror,getthetransitiontypeandtheIDs*ofthegeofenceorgeofencesthattriggeredthetransition*/}else{//Getthetypeoftransition(entryorexit)inttransitionType=gfEvent.getGeofenceTransition();StringtranTypeStr="UNKNOWN("+transitionType+")";switch(transitionType){caseGeofence.GEOFENCE_TRANSITION_ENTER:tranTypeStr="ENTER";break;caseGeofence.GEOFENCE_TRANSITION_EXIT:tranTypeStr="EXIT";break;caseGeofence.GEOFENCE_TRANSITION_DWELL:tranTypeStr="DWELL";break;}Log.v(TAG,"transitionTypereported:"+tranTypeStr);LocationtriggerLoc=gfEvent.getTriggeringLocation();Log.v(TAG,"triggeringlocationis"+triggerLoc);
List<Geofence>triggerList=gfEvent.getTriggeringGeofences();
String[]triggerIds=newString[triggerList.size()];
for(inti=0;i<triggerIds.length;i++){//GrabtheIdofeachgeofencetriggerIds[i]=triggerList.get(i).getRequestId();Stringmsg=tranTypeStr+":"+triggerLoc.getLatitude()+","+triggerLoc.getLongitude();Stringtitle=triggerIds[i];displayNotificationMessage(title,msg);}}}
privatevoiddisplayNotificationMessage(Stringtitle,Stringmessage){intnotif_id=(int)(System.currentTimeMillis()&0xFFL);
Notificationnotification=newNotificationCompat.Builder(this).setContentTitle(title).setContentText(message).setSmallIcon(android.R.drawable.ic_menu_compass).setOngoing(false).build();
notificationMgr.notify(notif_id,notification);}
Whenyoureplacethelatitudeandlongitudeofhomeandworkinthisapplication,yourunitonarealdevice,andyouthenmovethedevice,youwillseenotificationssuchasthoseinFigure19-8.
Figure19-8.NotificationsfromGeofencingApievents
Thefirsteventoccurredat6:40pmandhappenedbecausethedevicewasalreadyinsidethehomeregionwhentheappwasstarted.Thesecondeventat6:45pmisadwelleventbecausethedeviceisstillwithinthehomeregionaftertheloiteringdelayof5minutes.Hadthedeviceleftthehomeregionbeforethescreenshotwascaptured,therewouldhavebeenanexiteventfromhome.Notethatthelatitudeandlongitudeinthenotificationaretheactuallocationofthedeviceandnotnecessarilythecenteroftheregion.
ReferencesHerearehelpfulreferencesyoumaywishtoexplorefurther.
www.androidbook.com/proandroid5/projects.Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforazipfilecalledProAndroid5_Ch19_Maps.zip.Thiszipfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoanIDEfromoneofthesezipfiles.Therearesomeextrasampleapplicationsinhere,includingWhereAmI4,whichcontainscustominfowindowsformarkers.
https://developer.android.com/guide/topics/location/index.htmlTheAndroiddeveloper’sguideforLocationandMaps.
https://developer.android.com/google/play-services/index.html.TheGooglePlayServicesdocumentationwhichincludestheFusedLocationProviderApi,GeofencingApiandGoogleMap.
https://developer.android.com/google/play-services/setup.html.InstructionsforincludingtheGoogle
PlayServiceslibraryintoyourapplication.Notethedrop-downmenutoallowchoosingbetweenAndroidStudioandEclipsewithADT.https://developers.google.com/maps/documentation/android/TheMapsAPIdocumentationwhichisseparatefromtherestoftheonlineAndroiddocumentation.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedaboutmapssofar:
HowtogetyourownMapsAPIkeyfromGoogle.
MapFragment,themaincomponentforallmaps.
ThemodificationsyouneedtomaketoyourAndroidManifest.xmlfiletogetamapsapplicationtowork.
Definingalayouttocontainamap,andhowtoinstantiateamap.
Zoominginandout,panningandshowingthecurrentlocation.
Includingdifferentmodessuchassatelliteandtraffic.
Howmaptilesareusedtorendermaps.
Addingmarkerstoyourmaps.
Mapcamerasandmethodstosetazoomlevelthataccommodatesaspecificsetofmarkers.
TheGeocoder,andhowitconvertsfromaddresstolatitude/longitude,orfromlatitude/longitudetoaddressesandplacesofinterest.
PuttingtheGeocoderintoabackgroundthreadtoavoidnastyApplicationNotResponding(ANR)pop-ups.
TheLocationServicesservice,whichusesGPSand/ornetworktowerstopinpointthelocationofthedevice.
Selectingalocationprovider,andwhattodoifthedesiredlocationserviceorproviderisnotenabled.
Usingtheemulator’sfeaturestosendlocationeventstoyourapplicationfortesting.Thisincludesusingspecialfilesthatrecordentireseriesoflocationevents.
UsingmethodsoftheLocationclassto,forexample,calculatedistancesbetweenpoints.
HowtodoallofthechecksandcorrectiveactionstosetupGoogle
PlayServicesforLocationUpdates.
Alertingonproximity—thatis,settingupaproximityandbeingalertedwhenthedeviceentersorleavesthatproximity.
Settingupgeofencestoactonenter,exit,anddwelleventsforoneormoreregionswhileconservingbatterylife.
Chapter20
UnderstandingtheMediaFrameworksNowwearegoingtoexploreaveryinterestingpartoftheAndroidSDK:themediaframeworks.Wewillshowyouhowtoplayaudioandvideofromavarietyofsources.We’llalsocoverintheonlinecompanionsectionhowtotakephotoswiththecameraandrecordaudioandvideo.
UsingtheMediaAPIsAndroidsupportsplayingaudioandvideocontentundertheandroid.mediapackage.Inthischapter,wearegoingtoexplorethemediaAPIsfromthispackage.
Attheheartoftheandroid.mediapackageistheandroid.media.MediaPlayerclass.TheMediaPlayerclassisresponsibleforplayingbothaudioandvideocontent.Thecontentforthisclasscancomefromthefollowingsources:
Web:YoucanplaycontentfromtheWebviaaURL.
.apkfile:Youcanplaycontentthatispackagedaspartofyour
.apkfile.Youcanpackagethemediacontentasaresourceorasanasset(withintheassetsfolder).
TheStorageAccessFramework,newtoAndroidKitKat4.4,whichprovidesaccesstomediafilesstoredacrossarangeofprovidersandinternetservices.
SDcard:Youcanplaycontentthatresidesonthedevice’sSDcardoremulatedlocalstorage.
TheMediaPlayeriscapableofdecodingquiteafewdifferentcontentformats,including3rdGenerationPartnershipProject(3GPP,.3gp),MP3(.mp3),MIDI(.midandothers),OggVorbis(.ogg),PCM/WAVE(.wav),andMPEG-4(.mp4).RTSP,HTTP/HTTPSlivestreaming,andM3Uplaylistsarealsosupported,althoughplayliststhatincludeURLsarenot,atleastasofthiswriting.Foracompletelistofsupportedmediaformats,gotohttp://developer.android.com/guide/appendix/media-formats.html.
WhitherSDCards?Beforewediveintotheheartofthemediaframeworks,weshouldquicklyaddressthetopicofremovablestorage,andSDCardsinparticular.RecenttrendsinAndroiddevices
haveseensomemanufacturersdropthemfromdevices,whileotherscontinuetoincludethem.Googleitselfhasblurredthelinesofwhatisandisn’tremovalstoragebyobfuscatingthelow-levelfilesystemsinAndroid.
Regardlessofyourpersonalpreferenceasadeveloper,someofyouruserswilllikelystillhavedevicesthatsupportSDCardsandwanttousethem.Manyoftheexampleswe’llcoverhereareequallyapplicabletosourcingmediafilesfromSDCards.However,tosavespace,andspareyouunneededrepetition,we’veplacedsomeextraexamplesthatgointoSDCarddetailsandsupportingmaterialonthebook’swebsite.Besuretocheckitoutatwww.androidbook.com.
PlayingMediaTogetstarted,we’llshowyouhowtobuildasimpleapplicationthatplaysanMP3filelocatedontheWeb(seeFigure20-1).Afterthat,wewilltalkaboutusingthesetDataSource()methodoftheMediaPlayerclasstoplaycontentfromthe.apkfile.MediaPlayerisn’ttheonlywaytoplayaudio,though,sowe’llalsocovertheSoundPoolclass,aswellasJetPlayer,AsyncPlayer,and,forthelowestlevelofworkingwithaudio,theAudioTrackclass.Afterthat,wewilldiscusssomeoftheshortfallsoftheMediaPlayerclass.Finally,we’llseehowtoplayvideocontent.
PlayingAudioContentFigure20-1showstheuserinterfaceforourfirstexample.ThisapplicationwilldemonstratesomeofthefundamentalusesoftheMediaPlayerclass,suchasstarting,pausing,restarting,andstoppingthemediafile.Lookatthelayoutfortheapplication’suserinterface.
Figure20-1.Theuserinterfaceforthemediaapplication
TheuserinterfaceconsistsofaRelativeLayoutwithfourbuttons:onetostarttheplayer,onetopausetheplayer,onetorestarttheplayer,andonetostoptheplayer.WecouldhavemadethiseasyandjustcoupledourexamplewithaMediaControllerwidgetthatdoesthesamething,butwewanttoshowyoutheinnerworkingsofcontrollingthingsyourself.ThecodeandlayoutfilefortheapplicationareshowninListing20-1.We’regoingtoassumeyou’rebuildingagainstAndroid2.2orlaterforthisexample,becausewe’reusingthegetExternalStoragePublicDirectory()methodoftheEnvironmentclass.IfyouwanttobuildthisagainstanolderversionofAndroid,simplyusegetExternalStorageDirectory()insteadandadjustwhereyouputthemediafilessoyourapplicationwillfindthem.
NoteSeethe“References”sectionattheendofthischapterfortheURLfromwhichyoucanimporttheseprojectsintoEclipsedirectly,insteadofcopyingandpastingcode.
Listing20-1.TheLayoutandCodefortheMediaApplication
<RelativeLayoutxmlns: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=".MainActivity"android:orientation="vertical">
<Buttonandroid:id="@+id/startPlayerBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="StartPlayingAudio"
android:onClick="doClick"/>
<Buttonandroid:id="@+id/pausePlayerBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="PausePlayer"
android:layout_below="@+id/startPlayerBtn"android:onClick="doClick"/>
<Buttonandroid:id="@+id/restartPlayerBtn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="RestartPlayer"
android:layout_below="@+id/pausePlayerBtn"android:onClick="doClick"/>
<Buttonandroid:id="@+id/stopPlayerBtn"android:layout_width="match_parent"
android:layout_height="wrap_content"android:text="StopPlayer"
android:layout_below="@+id/restartPlayerBtn"android:onClick="doClick"/>
</RelativeLayout>
//ThisfileisMainActivity.javaimportandroid.app.Activity;importandroid.content.res.AssetFileDescriptor;importandroid.media.AudioManager;importandroid.media.MediaPlayer;importandroid.media.MediaPlayer.OnPreparedListener;importandroid.os.Bundle;importandroid.os.Environment;importandroid.util.Log;importandroid.view.View;
publicclassMainActivityextendsActivityimplementsOnPreparedListener{staticfinalStringAUDIO_PATH=
"http://www.androidbook.com/akc/filestorage/android/documentfiles/3389/play.mp3
privateMediaPlayermediaPlayer;privateintplaybackPosition=0;
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}
publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.startPlayerBtn:try{//OnlyhaveoneoftheseplaymethodsuncommentedplayAudio(AUDIO_PATH);//playLocalAudio();//playLocalAudio_UsingDescriptor();}catch(Exceptione){e.printStackTrace();}break;
caseR.id.pausePlayerBtn:if(mediaPlayer!=null&&mediaPlayer.isPlaying()){playbackPosition=mediaPlayer.getCurrentPosition();mediaPlayer.pause();}break;caseR.id.restartPlayerBtn:if(mediaPlayer!=null&&!mediaPlayer.isPlaying()){mediaPlayer.seekTo(playbackPosition);mediaPlayer.start();}break;caseR.id.stopPlayerBtn:if(mediaPlayer!=null){mediaPlayer.stop();playbackPosition=0;}break;}}
privatevoidplayAudio(Stringurl)throwsException{killMediaPlayer();
mediaPlayer=newMediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(url);mediaPlayer.setOnPreparedListener(this);mediaPlayer.prepareAsync();}
privatevoidplayLocalAudio()throwsException{mediaPlayer=MediaPlayer.create(this,R.raw.music_file);mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//callingprepare()isnotrequiredinthiscasemediaPlayer.start();}
privatevoidplayLocalAudio_UsingDescriptor()throwsException{
AssetFileDescriptorfileDesc=getResources().openRawResourceFd(R.raw.music_file);if(fileDesc!=null){
mediaPlayer=newMediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(fileDesc.getFileDescriptor(),fileDesc.getStartOffset(),fileDesc.getLength());
fileDesc.close();
mediaPlayer.prepare();mediaPlayer.start();}}
//ThisiscalledwhentheMediaPlayerisreadytostartpublicvoidonPrepared(MediaPlayermp){mp.start();}
@OverrideprotectedvoidonDestroy(){super.onDestroy();killMediaPlayer();}
privatevoidkillMediaPlayer(){if(mediaPlayer!=null){try{mediaPlayer.release();}catch(Exceptione){e.printStackTrace();}}}}
Inthisfirstscenario,youareplayinganMP3filefromawebaddress.Therefore,youwillneedtoaddandroid.permission.INTERNETtoyourmanifestfile.Listing20-1showsthattheMainActivityclasscontainsthreemembers:afinalstringthatpointstotheURLoftheMP3file,aMediaPlayerinstance,andanintegermembercalledplaybackPosition.OuronCreate()methodjustsetsuptheuserinterfacefromourlayoutXMLfile.Inthebutton-clickhandler,whentheStartPlayingAudiobuttonis
pressed,theplayAudio()methodiscalled.IntheplayAudio()method,anewinstanceoftheMediaPlayeriscreated,andthedatasourceoftheplayerissettotheURLoftheMP3file.
TheprepareAsync()methodoftheplayeristhencalledtopreparetheMediaPlayerforplayback.We’reinthemainUIthreadofouractivity,sowedon’twanttotaketoolongtopreparetheMediaPlayer.Thereisaprepare()methodonMediaPlayer,butitblocksuntiltheprepareiscomplete.Ifthistakesalongtime,oriftheservertakesawhiletorespond,theusercouldthinktheapplicationisstuckor,worse,getanerrormessage.Thingslikeprogressdialogscanhelpyouruserunderstandwhatishappening.TheprepareAsync()methodreturnsimmediatelybutsetsupabackgroundthreadtohandletheprepare()methodoftheMediaPlayer.Whenthepreparationiscomplete,ouractivity’sonPrepared()callbackiscalled.ThisiswhereweultimatelystarttheMediaPlayerplaying.WehavetotelltheMediaPlayerwhothelistenerisfortheonPrepared()callback,whichiswhywecallsetOnPreparedListener()justbeforethecalltoprepareAsync().Youdon’thavetousethecurrentactivityasthelistener;wedoherebecauseit’ssimplerforthisdemonstration.
NowlookatthecodeforthePausePlayerandRestartPlayerbuttons.YoucanseethatwhenthePausePlayerbuttonisselected,yougetthecurrentpositionoftheplayerbycallinggetCurrentPosition().Youthenpausetheplayerbycallingpause().Whentheplayerhastoberestarted,youcallseekTo(),passinginthepositionobtainedearlierfromgetCurrentPosition(),andthencallstart().
TheMediaPlayerclassalsocontainsastop()method.Notethatifyoustoptheplayerbycallingstop(),youneedtopreparetheMediaPlayeragainbeforecallingstart()again.Conversely,ifyoucallpause(),youcancallstart()againwithouthavingtopreparetheplayer.Also,besuretocalltherelease()methodofthemediaplayeronceyouaredoneusingit.Inthisexample,youdothisaspartofthekillMediaPlayer()method.
ThereisasecondURLinthesampleapplicationsourcecodeforanaudiosource,butitisnotanMP3file,it’sastreamingaudiofeed(Radio-Mozart).ThisalsoworkswiththeMediaPlayerandshowsagainwhyyouneedtocallprepareAsync()insteadofprepare().Preparinganaudiostreamforplaybackcantakeawhile,dependingontheserver,networktraffic,andsoon.
Listing20-1showsyouhowtoplayanaudiofilelocatedontheWeb.TheMediaPlayerclassalsosupportsplayingmedialocaltoyour.apkfile.Listing20-2showshowtoreferenceandplaybackafilefromthe/res/rawfolderofyour.apkfile.Goaheadandaddtherawfolderunder/resifit’snotalreadythereintheEclipseproject.Then,copytheMP3fileofyourchoiceinto/res/rawwiththefilenamemusic_file.mp3.NotealsothecommentintheoriginalcodetouncommentthedesiredcalltoplayLocalAudio(),andcommentingoutplayAudio().
Listing20-2.UsingtheMediaPlayertoPlayBackaFileLocaltotheApplication
privatevoidplayLocalAudio()throwsException
{mediaPlayer=MediaPlayer.create(this,R.raw.music_file);mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);//callingprepare()isnotrequiredinthiscasemediaPlayer.start();}
Ifyouneedtoincludeanaudioorvideofilewithyourapplication,youshouldplacethefileinthe/res/rawfolder.YoucanthengetaMediaPlayerinstancefortheresourcebypassingintheresourceIDofthemediafile.Youdothisbycallingthestaticcreate()method,asshowninListing20-2.NotethattheMediaPlayerclassprovidesafewotherstaticcreate()methodsthatyoucanusetogetaMediaPlayerratherthaninstantiatingoneyourself.InListing20-2,thecreate()methodisequivalenttocallingtheconstructorMediaPlayer(Contextcontext,intresourceId)followedbyacalltoprepare().Youshouldusethecreate()methodonlywhenthemediasourceislocaltothedevice,becauseitalwaysusesprepare()andnotprepareAsync().
UnderstandingthesetDataSourceMethodInListing20-2,wecalledthecreate()methodtoloadtheaudiofilefromarawresource.Withthisapproach,youdon’tneedtocallsetDataSource().Alternatively,ifyouinstantiatetheMediaPlayeryourselfusingthedefaultconstructor,orifyourmediacontentisnotaccessiblethrougharesourceIDoraURI,you’llneedtocallsetDataSource().
ThesetDataSource()methodhasoverloadedversionsthatyoucanusetocustomizethedatasourceforyourspecificneeds.Forexample,Listing20-3showshowyoucanloadanaudiofilefromarawresourceusingaFileDescriptor.
Listing20-3.SettingtheMediaPlayer’sDataSourceusingaFileDescriptor
privatevoidplayLocalAudio_UsingDescriptor()throwsException{AssetFileDescriptorfileDesc=getResources().openRawResourceFd(R.raw.music_file);if(fileDesc!=null){
mediaPlayer=newMediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(fileDesc.getFileDescriptor(),fileDesc.getStartOffset(),fileDesc.getLength());
fileDesc.close();
mediaPlayer.prepare();mediaPlayer.start();}}
Listing20-3assumesthatit’swithinthecontextofanactivity.Asshown,youcallthegetResources()methodtogettheapplication’sresourcesandthenusetheopenRawResourceFd()methodtogetafiledescriptorforanaudiofilewithinthe/res/rawfolder.YouthencallthesetDataSource()methodusingtheAssetFileDescriptor,thestartingpositiontobeginplayback,andtheendingposition.YoucanalsousethisversionofsetDataSource()ifyouwanttoplaybackaspecificportionofanaudiofile.Ifyoualwayswanttoplaytheentirefile,youcancallthesimplerversionofsetDataSource(FileDescriptordesc),whichdoesnotrequiretheinitialoffsetandlength.
Inthiscase,wechosetouseprepare()followedbystart(),onlytoshowyouwhatitmightlooklike.Weshouldbeabletogetawaywithitbecausetheaudioresourceislocal,butitwon’thurttouseprepareAsync()asbefore.
Wehaveonemoresourceforaudiocontenttotalkabout:theSDcard.RefertotheonlinecompanionchapterforthebasicsondealingwiththeSDcardanditsfilesystemcontents.Inourexample,weusedsetDataSource()toaccesscontentontheInternetbypassinginaURLforanMP3file.Ifyou’vegotanaudiofileonyourSDcard,youcanusethesamesetDataSource()methodbutinsteadpassitthepathtoyouraudiofileontheSDcard.Forexample,afilecalledmusic_file.mp3intheMusicdirectorycanbeplayedwiththeAUDIO_PATHvariablesetlikeso:
staticfinalStringAUDIO_PATH=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)+"/music_file.mp3";
YoumayhavenoticedthatwedidnotimplementonResume()andonPause()inourexample.Thismeansthatwhenouractivitygoesintothebackground,itcontinuestoplayaudio—atleast,untiltheactivityiskilled,oruntilaccesstotheaudiosourceisturnedoff.Forexample,ifwedonotholdawakelock,theCPUcouldbeshutdown,thusendingtheplayingofmusic.Manypeoplechoosetomanagemediaplaybackinaservicetoaidinworkingaroundtheseissues.Inourcurrentexample,additionalissuesincludeifMediaPlayerisplayinganaudiostreamoverWi-Fi,andifouractivitydoesnotobtainalockonWi-Fi,Wi-Ficouldbeturnedoff,andwe’llloseourconnectiontothestream.MediaPlayerhasamethodcalledsetWakeMode()thatallowsustosetaPARTIAL_WAKE_LOCKtokeeptheCPUalivewhileplaying.However,inordertolockWi-Fi,weneedtodothatseparatelythroughWifiManagerandWifiManager.WifiLock.
Theotheraspectofcontinuingtoplayaudiointhebackgroundisthatweneedtoknowwhennottodoso,perhapsbecausethere’sanincomingphonecall,orbecauseanalarmis
goingoff.AndroidhasanAudioManagertohelpwiththis.ThemethodstocallincluderequestAudioFocus()andabandonAudioFocus(),andthere’sacallbackmethodcalledonAudioFocusChange()intheinterfaceAudioManager.OnAudioFocusChangeListener.Formoreinformation,seetheMediapageintheAndroidDeveloper’sGuide.
UsingSoundPoolforSimultaneousTrackPlayingTheMediaPlayerisanessentialtoolinourmediatoolbox,butitonlyhandlesoneaudioorvideofileatatime.Whatifwewanttoplaymorethanoneaudiotracksimultaneously?OnewayistocreatemultipleMediaPlayersandworkwiththematthesametime.Ifyouonlyhaveasmallamountofaudiotoplay,andyouwantsnappyperformance,AndroidhastheSoundPoolclasstohelpyou.Behindthescenes,SoundPoolusesMediaPlayer,butwedon’tgetaccesstotheMediaPlayerAPI,justtheSoundPoolAPI.
OneoftheotherdifferencesbetweenMediaPlayerandSoundPoolisthatSoundPoolisdesignedtoworkwithlocalmediafilesonly.Thatis,youcanloadaudiofromresourcefiles,fileselsewhereusingfiledescriptors,orfilesusingapathname.ThereareseveralothernicefeaturesthatSoundPoolprovides,suchastheabilitytoloopanaudiotrack,pauseandresumeindividualaudiotracks,orpauseandresumeallaudiotracks.
TherearesomedownsidestoSoundPool,though.ThereisanoverallaudiobuffersizeinmemoryforallthetracksthatSoundPoolwillmanageofonly1MB.ThismightseemlargewhenyoulookatMP3filesthatareonlyafewkilobytesinsize.ButSoundPoolexpandstheaudioinmemorytomaketheplaybackfastandeasy.Thesizeofanaudiofileinmemorydependsonthebitrate,numberofchannels(stereoversusmono),samplerate,andlengthoftheaudio.IfyouhavetroublegettingyoursoundsloadedintoSoundPool,youcouldtryplayingwiththeseparametersofyoursourceaudiofiletomaketheaudiosmallerinmemory.
Ourexampleapplicationwillloadandplayanimalsounds.Oneofthesoundsisofcricketsanditplaysconstantlyinthebackground.Theothersoundsplayatdifferentintervalsoftime.Sometimesallyouheararecrickets;othertimesyouwillhearseveralanimalsallatthesametime.We’llalsoputabuttonintheuserinterfacetoallowforpausingandresuming.Listing20-4showsourlayoutXMLfileandtheJavacodeofouractivity.Yourbestbetistodownloadthisfromourwebsite,inordertogetthesoundfilesaswellasthecode.Seethe“References”sectionattheendofthischapterforinformationonhowtolocatethedownloadablesourcecode.
Listing20-4.PlayingAudiowithSoundPool
<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"
><ToggleButtonandroid:id="@+id/button"android:textOn="Pause"android:textOff="Resume"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="doClick"android:checked="true"/></LinearLayout>
//ThisfileisMainActivity.javaimportjava.io.IOException;importandroid.app.Activity;importandroid.content.Context;importandroid.content.res.AssetFileDescriptor;importandroid.media.AudioManager;importandroid.media.SoundPool;importandroid.os.Bundle;importandroid.os.Handler;importandroid.util.Log;importandroid.view.View;importandroid.widget.ToggleButton;
publicclassMainActivityextendsActivityimplementsSoundPool.OnLoadCompleteListener{privatestaticfinalintSRC_QUALITY=0;privatestaticfinalintPRIORITY=1;privateSoundPoolsoundPool=null;privateAudioManageraMgr;
privateintsid_background;privateintsid_roar;privateintsid_bark;privateintsid_chimp;privateintsid_rooster;
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}
@OverrideprotectedvoidonResume(){soundPool=newSoundPool(5,AudioManager.STREAM_MUSIC,SRC_QUALITY);soundPool.setOnLoadCompleteListener(this);
aMgr=
(AudioManager)this.getSystemService(Context.AUDIO_SERVICE);
sid_background=soundPool.load(this,R.raw.crickets,PRIORITY);
sid_chimp=soundPool.load(this,R.raw.chimp,PRIORITY);sid_rooster=soundPool.load(this,R.raw.rooster,PRIORITY);sid_roar=soundPool.load(this,R.raw.roar,PRIORITY);
try{AssetFileDescriptorafd=this.getAssets().openFd("dogbark.mp3");sid_bark=soundPool.load(afd.getFileDescriptor(),0,afd.getLength(),PRIORITY);afd.close();}catch(IOExceptione){e.printStackTrace();}//sid_bark=soundPool.load("/mnt/sdcard/dogbark.mp3",PRIORITY);
super.onResume();}
publicvoiddoClick(Viewview){switch(view.getId()){caseR.id.button:if(((ToggleButton)view).isChecked()){soundPool.autoResume();}else{soundPool.autoPause();}break;}}
@OverrideprotectedvoidonPause(){soundPool.release();soundPool=null;super.onPause();}
@Override
publicvoidonLoadComplete(SoundPoolsPool,intsid,intstatus){Log.v("soundPool","sid"+sid+"loadedwithstatus"+status);
finalfloatcurrentVolume=((float)aMgr.getStreamVolume(AudioManager.STREAM_MUSIC))/((float)aMgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
if(status!=0)return;if(sid==sid_background){if(sPool.play(sid,currentVolume,currentVolume,PRIORITY,-1,1.0f)==0)Log.v("soundPool","Failedtostartsound");}elseif(sid==sid_chimp){queueSound(sid,5000,currentVolume);}elseif(sid==sid_rooster){queueSound(sid,6000,currentVolume);}elseif(sid==sid_roar){queueSound(sid,12000,currentVolume);}elseif(sid==sid_bark){queueSound(sid,7000,currentVolume);}}
privatevoidqueueSound(finalintsid,finallongdelay,finalfloatvolume){newHandler().postDelayed(newRunnable(){@Overridepublicvoidrun(){if(soundPool==null)return;if(soundPool.play(sid,volume,volume,PRIORITY,0,1.0f)==0)Log.v("soundPool","Failedtostartsound("+sid+")");queueSound(sid,delay,volume);}},delay);}}
Thestructureofthisexampleisfairlystraightforward.WehaveauserinterfacewithasingleToggleButtononit.We’llusethistopauseandresumetheactiveaudio
streams.Whenourappstarts,wecreateourSoundPoolandloaditupwithaudiosamples.Whenthesamplesareproperlyloaded,westartplayingthem.Thecricketssoundplaysinaneverendingloop;theothersamplesplayafteradelayandthensetthemselvesuptoplayagainafterthesamedelay.Bychoosingdifferentdelays,wegetasomewhatrandomeffectofsoundsontopofsounds.
CreatingaSoundPoolrequiresthreeparameters:
ThefirstisthemaximumnumberofsamplesthattheSoundPoolwillplaysimultaneously.ThisisnothowmanysamplestheSoundPoolcanhold.
Thesecondparameteriswhichaudiostreamthesampleswillplayon.ThetypicalvalueisAudioManager.STREAM_MUSIC,butSoundPoolcanbeusedforalarmsorringtones.SeetheAudioManagerreferencepageforthecompletelistofaudiostreams.
TheSRC_QUALITYvalueshouldjustbesetto0whencreatingtheSoundPool.
Thecodedemonstratesseveraldifferentload()methodsofSoundPool.Themostbasicistoloadanaudiofilefrom/res/rawasaresource.Weusethismethodforthefirstfouraudiofiles.Thenweshowhowyoucouldloadanaudiofilefromthe/assetsdirectoryoftheapplication.Thisload()methodalsotakesparametersthatspecifytheoffsetandthelengthoftheaudiotoload.Thiswouldallowustouseasinglefilewithmultipleaudiosamplesinit,pullingoutjustwhatwewanttouse.Finally,weshowincommentshowyoumightaccessanaudiofilefromtheSDcard.UpthroughAndroid4.0,thePRIORITYparametershouldjustbe1.
Forourexample,wechosetousesomeofthefeaturesintroducedinAndroid2.2,specificallytheonLoadCompleteListenerinterfaceforouractivity,andtheautoPause()andautoResume()methodsinourbuttoncallback.
WhenloadingsoundsamplesintoaSoundPool,wemustwaituntiltheyareproperlyloadedbeforewecanstartplayingthem.WithinouronLoadComplete()callback,wecheckthestatusoftheload,and,dependingonwhichsounditis,wethensetituptoplay.Ifthesoundisthecrickets,weplaywithloopingturnedon(avalueof-1forthefifthparameter).Fortheothers,wequeuethesounduptoplayafterashortperiodoftime.Thetimevaluesareinmilliseconds.Notethesettingofthevolume.AndroidprovidestheAudioManagertoletusknowthecurrentvolumesetting.WealsogetthemaximumvolumesettingfromAudioManagersowecancalculateavolumevalueforplay()thatisbetween0and1(asafloat).Theplay()methodactuallytakesaseparatevolumevaluefortheleftandrightchannels,butwejustsetbothtothecurrentvolume.Again,PRIORITYshouldjustbesetto1.Thelastparameterontheplay()methodisforsettingtheplaybackrate.Thisvalueshouldbebetween0.5and2.0,with1.0beingnormal.
OurqueueSound()methodusesaHandlertobasicallysetupaneventintothefuture.OurRunnablewillrunafterthedelayperiodhaselapsed.WechecktobesurewestillhaveaSoundPooltoplayfrom,thenweplaythesoundonceandschedulethesamesoundtoplayagainafterthesameintervalasbefore.BecausewecallqueueSound()withdifferentsoundIDsanddifferentdelays,theeffectisasomewhatrandomplayingofanimalsounds.
Whenyourunthisexample,you’llhearcrickets,achimp,arooster,adog,andaroar(abear,wethink).Thecricketsareconstantlychirpingwhiletheotheranimalscomeandgo.OnenicethingaboutSoundPoolisthatitletsusplaymultiplesoundsatthesametimewithnorealworkonourpart.Also,we’renottaxingthedevicetoobadly,becausethesoundsweredecodedatloadtime,andwesimplyneedtofeedthesoundbitstothehardware.
Ifyouclickthebutton,thecricketswillstop,aswillanyotheranimalsoundcurrentlybeingplayed.However,theautoPause()methoddoesnotpreventnewsoundsfrombeingplayed.You’llheartheanimalsoundsagainwithinseconds(exceptforthecrickets).Becausewe’vebeenqueuingupsoundsintothefuture,wewillstillhearthosesounds.Infact,SoundPooldoesnothaveawaytostopallsoundsnowandinthefuture.You’llneedtohandlestoppingonyourown.Thecricketswillonlycomebackifweclickthebuttonagaintoresumethesounds.Buteventhen,wemighthavelostthecricketsbecauseSoundPoolwillthrowouttheoldestsoundtomakeroomfornewersoundsifthemaximumnumberofsimultaneouslyplayingsamplesisreached.
PlayingSoundswithJetPlayerSoundPoolisnottoobadaplayer,butthememorylimitationscanmakeitdifficulttogetthejobdone.AnalternativewhenyouneedtoplaysimultaneoussoundsisJetPlayer.Tailoredforgames,JetPlayerisaveryflexibletoolforplayinglotsofsoundsandforcoordinatingthosesoundswithuseractions.ThesoundsaredefinedusingMusicalInstrumentDigitalInterface(MIDI).
JetPlayersoundsarecreatedusingaspecialJETCreatortool.ThistoolisprovidedundertheAndroidSDKtoolsdirectory,althoughyou’llalsoneedtoinstallPythoninordertouseit,anditislimitedtotheMacOSXandWindowsSDKpackages.TheresultingJETfilecanbereadintoyourapplication,andthesoundssetupforplayback.Thewholeprocessissomewhatinvolvedandbeyondthescopeofthisbook,sowe’lljustpointyoutomoreinformationinthe“References”sectionattheendofthischapter.
PlayingBackgroundSoundswithAsyncPlayerIfallyouwantissomeaudioplayed,andyoudon’twanttotieupthecurrentthread,theAsyncPlayermaybewhatyou’relookingfor.TheaudiosourceispassedasaURItothisclass,sotheaudiofilecouldbelocalorremoteoverthenetwork.Thisclassautomaticallycreatesabackgroundthreadtohandlegettingtheaudioandstartingtheplayback.Becauseitisasynchronous,youwon’tknowexactlywhentheaudiowillstart.Norwillyouknowwhenitends,orevenifit’sstillplaying.Youcan,however,call
stop()togettheaudiotostopplaying.Ifyoucallplay()againbeforethepreviousaudiohasfinishedplaying,thepreviousaudiowillimmediatelystopandthenewaudiowillbeginatsometimeinthefuturewheneverythinghasbeensetupandfetched.Thisisaverysimpleclassthatprovidesanautomaticbackgroundthread.Listing20-5showshowyourcodeshouldlooktoimplementthis.
Listing20-5.PlayingAudiowithAsyncPlayer
privatestaticfinalStringTAG="AsyncPlayerDemo";privateAsyncPlayermAsync=null;
[...]
mAsync=newAsyncPlayer(TAG);mAsync.play(this,Uri.parse("file://”+“/perry_ringtone.mp3"),false,AudioManager.STREAM_MUSIC);
[...]
@OverrideprotectedvoidonPause(){mAsync.stop();super.onPause();}
Low-LevelAudioPlaybackUsingAudioTrackSofar,we’vebeendealingwithaudiofromfiles,betheylocalfilesorremotefiles.Ifyouwanttogetdowntoalowerlevel,perhapsplayingaudiofromastream,youneedtoinvestigatetheAudioTrackclass.Besidestheusualmethodslikeplay()andpause(),AudioTrackprovidesmethodsforwritingbytestotheaudiohardware.Thisclassgivesyouthemostcontroloveraudioplayback,butitismuchmorecomplicatedthantheaudioclassesdiscussedsofarinthischapter.OneofouronlinecompanionsampleapplicationsusestheAudioRecordclass.TheAudioRecordclassisverymuchliketheAudioTrackclass,sotogetabetterunderstandingoftheAudioTrackclass,refertotheAudioRecordsamplelateron.
MoreAboutMediaPlayerIngeneral,theMediaPlayerisverysystematic,soyouneedtocalloperationsinaspecificordertoinitializeaMediaPlayerproperlyandprepareitforplayback.ThefollowinglistsummarizessomeoftheotherdetailsyoushouldknowforusingthemediaAPIs:
OnceyousetthedatasourceofaMediaPlayer,youcannoteasilychangeittoanotherone—you’llhavetocreateanewMediaPlayerorcallthereset()methodtoreinitializethestateoftheplayer.
Afteryoucallprepare(),youcancallgetCurrentPosition(),getDuration(),andisPlaying()togetthecurrentstateoftheplayer.YoucanalsocallthesetLooping()andsetVolume()methodsafterthecalltoprepare().IfyouusedprepareAsync(),youshouldwaituntilonPrepared()iscalledbeforeusinganyoftheseothermethods.
Afteryoucallstart(),youcancallpause(),stop(),andseekTo().
EveryMediaPlayeryoucreateusesalotofresources,sobesuretocalltherelease()methodwhenyouaredonewiththemediaplayer.TheVideoViewtakescareofthisinthecaseofvideoplayback,butyou’llhavetodoitmanuallyifyoudecidetouseMediaPlayerinsteadofVideoView.MoreaboutVideoViewinthenextsections.
MediaPlayerworkswithseverallistenersyoucanuseforadditionalcontrolovertheuserexperience,includingOnCompletionListener,OnErrorListener,andOnInfoListener.Forexample,ifyou’remanagingaplaylistofaudio,OnCompletionListenerwillbecalledwhenapieceisfinishedsoyoucanqueueupthenextpiece.
Thisconcludesourdiscussionaboutplayingaudiocontent.Nowwe’llturnourattentiontoplayingvideo.Asyouwillsee,referencingvideocontentissimilartoreferencingaudiocontent.
PlayingVideoContentInthissection,wearegoingtodiscussvideoplaybackusingtheAndroidSDK.Specifically,wewilldiscussplayingavideofromawebserverandplayingonefromanSDcard.Asyoucanimagine,videoplaybackisabitmoreinvolvedthanaudioplayback.Fortunately,theAndroidSDKprovidessomeadditionalabstractionsthatdomostoftheheavylifting.
NotePlayingbackvideointheemulatorisnotveryreliable.Ifitworks,great.Butifitdoesn’t,tryrunningonadeviceinstead.Becausetheemulatormustuseonlysoftwaretorunvideo,itcanhaveaveryhardtimekeepingupwithvideo,andyouwilllikelygetunexpectedresults.
Playingvideorequiresmoreeffortthanplayingaudio,becausethere’savisualcomponenttotakecareofinadditiontotheaudio.Totakesomeofthepainaway,Androidprovidesaspecializedviewcontrolcalledandroid.widget.VideoViewthatencapsulatescreatingandinitializingtheMediaPlayer.Toplayvideo,youcreateaVideoView
widgetinyouruserinterface.YouthensetthepathorURIofthevideoandfirethestart()method.Listing20-6demonstratesvideoplaybackinAndroid.
Listing20-6.PlayingVideoUsingtheMediaAPIs
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="fill_parent"xmlns:android="http://schemas.android.com/apk/res/android">
<VideoViewandroid:id="@+id/videoView"android:layout_width="200px"android:layout_height="200px"/>
</LinearLayout>
//ThisfileisMainActivity.javaimportandroid.app.Activity;importandroid.net.Uri;importandroid.os.Bundle;importandroid.widget.MediaController;importandroid.widget.VideoView;
publicclassMainActivityextendsActivity{/**Calledwhentheactivityisfirstcreated.*/@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);this.setContentView(R.layout.main);
VideoViewvideoView=(VideoView)this.findViewById(R.id.videoView);MediaControllermc=newMediaController(this);videoView.setMediaController(mc);videoView.setVideoURI(Uri.parse("http://www.androidbook.com/akc/filestorage/android/+"documentfiles/3389/movie.mp4"));/*videoView.setVideoPath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/movie.mp4");*/videoView.requestFocus();videoView.start();}
}
Listing20-6demonstratesvideoplaybackofafilelocatedontheWebatwww.androidbook.com/akc/filestorage/android/documentfiles/3389/movie.mp4whichmeanstheapplicationrunningthecodewillneedtorequesttheandroid.permission.INTERNETpermission.AlloftheplaybackfunctionalityishiddenbehindtheVideoViewclass.Infact,allyouhavetodoisfeedthevideocontenttothevideoplayer.TheuserinterfaceoftheapplicationisshowninFigure20-2.
Figure20-2.ThevideoplaybackUIwithmediacontrolsenabled
Whenthisapplicationruns,youwillseethebuttoncontrolsalongthebottomofthescreenforaboutthreeseconds,andthentheydisappear.Yougetthembackbyclickinganywherewithinthevideoframe.Whenweweredoingplaybackofaudiocontent,weneededtodisplaythebuttoncontrolsonlytostart,pause,andrestarttheaudio.Wedidnotneedaviewcomponentfortheaudioitself.Withvideo,ofcourse,weneedbuttoncontrolsaswellassomethingtoviewthevideoin.Forthisexample,we’reusingaVideoViewcomponenttodisplaythevideocontent.Butinsteadofcreatingourownbuttoncontrols(whichwecouldstilldoifwechoseto),wecreateaMediaControllerthatprovidesthebuttonsforus.AsshowninFigure20-2andListing20-6,yousettheVideoView’smediacontrollerbycallingsetMediaController()toenabletheplay,pause,andseek-tocontrols.Ifyouwanttomanipulatethevideoprogrammaticallywithyourownbuttons,youcancallthestart(),pause(),stopPlayback(),andseekTo()methods.
Keepinmindthatwe’restillusingaMediaPlayerinthisexample—wejustdon’tseeit.Youcaninfact“play”videosdirectlyinMediaPlayer.IfyougobacktotheexamplefromListing20-1,putamoviefileonyourSDcard,andpluginthemovie’sfilepathinAUDIO_PATH,youwillfindthatitplaystheaudioquitenicelyeventhoughyoucan’tseethevideo.
WhereasMediaPlayerhasasetDataSource()method,VideoViewdoesnot.VideoViewinsteadusesthesetVideoPath()orsetVideoURI()methods.AssumingyouputamoviefileontoyourSDcard,youchangethecodefromListing20-6tocommentoutthesetVideoURI()callanduncommentthesetVideoPath()call,adjustingthepathtothemoviefileasnecessary.Whenyouruntheapplicationagain,youwillnowhearandseethevideointheVideoView.Technically,wecouldhavecalledsetVideoURI()withthefollowingtogetthesameeffectassetVideoPath():
videoView.setVideoURI(Uri.parse("file://"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/movie.mp4"));
YoumighthavenoticedthatVideoViewdoesnothaveamethodtoreaddatafromafiledescriptorasMediaPlayerdid.YoumayalsohavenoticedthatMediaPlayerhasacoupleofmethodsforaddingaSurfaceHoldertoaMediaPlayer(aSurfaceHolderislikeaviewportforimagesorvideo).OneoftheMediaPlayermethodsiscreate(Contextcontext,Uriuri,SurfaceHolderholder),andtheotherissetDisplay(SurfaceHolderholder).
BonusOnlineChapteronRecordingandAdvancedMediaNowthatyouhavemasteredmanyoftheaspectsofmediaplayback,includingthevarietyofmethodstobuildyourownaudioandvideocapabilitiesintoyourapplication,thereareafewmoreareastoexploreonthetopicthatarealmostabook’sworthofcontentintheirownright.Sowehaveputthemtogetherintoanotherbonusonlinechapterthatexploresthefollowing:
AudiorecordingwithMediaRecorder,AudioRecord,andothertechniques
Videorecordingfromthegroundup
Cameraandcamcorderprofilesforvideorecording
UsingintentsandtheMediaStoreclasstohaveotherapplicationsdoallyourrecordingforyou!
TakealookattheonlinematerialfortheAudioandVideoRecordingbonuschapter.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Fortheprojectsinthischapter,lookforazipfilecalledProAndroid5_Ch20_Media.zip.Thiszipfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoEclipsefromoneofthesezipfiles.
http://developer.android.com/guide/topics/media/jet/jetcreator_manual.htmlTheusermanualfortheJETCreatortool.YoucanusethistocreateaJETsoundfiletobeplayedusingtheJetPlayer.JETCreatorisonlyavailableforWindowsandMacOS.ToseeJetPlayerinaction,loadtheJetBoysampleprojectfromtheAndroidSDKintoEclipse,buildit,andrunit.NotethattheFirebuttonisthecenterdirectionalpadkey.
SummaryHereisasummarythetopicscoveredinthismediachapteronaudioandvideo:
PlayingaudiothroughaMediaPlayer
SeveralwaystosourceaudioforMediaPlayer,fromlocalapplicationresources,tofiles,tostreamingoverthenetwork
StepstotakewithaMediaPlayertogettheaudiotocomeoutproperly
SoundPoolanditsabilitytoplayseveralsoundssimultaneously
SoundPool’slimitationsintermsoftheamountofaudioitcanhandle
AsyncPlayer,whichisusefulbecausesoundsgenerallyneedtobemanagedinthebackground
AudioTrack,whichprovideslow-levelaccesstoaudioPlayingvideousingVideoView
Chapter21
HomeScreenWidgetsHomescreenwidgetsinAndroidpresentfrequentlychanginginformationonthehomescreenofAndroid.Homescreenwidgetsaredisconnectedviewsdisplayedonthehomescreen.Datacontentoftheseviewsisupdatedatregularintervalsbybackgroundprocessesorjustkeptasastaticview.
Forexample,ane-mailhomescreenwidgetmightalertyoutothenumberofoutstandinge-mailstoberead.Thewidgetmayjustshowyouthenumberofe-mailsandnotthemessagesthemselves.Clickingthee-mailcountmaythentakeyoutotheactivitythatdisplaysactuale-mails.Thesecouldevenbeexternale-mailsourcessuchasYahoo,Gmail,andHotmail,aslongasthedevicehasawaytoaccessthecountsthroughHTTPorotherconnectivitymechanisms.
IntheAndroidSDKawidgetisdeclarativelydefined.Awidgetdefinitioncontainsthefollowing:
Aviewlayouttobedisplayedonthehomescreen,alongwithhowbigitshouldbetofitonahomepage.
Atimerthatspecifiesthefrequencyofupdates.
AbroadcastreceiverJavaclasscalledawidgetproviderthatcanrespondtotimerupdatesinordertoaltertheviewinsomefashionbypopulatingwithdata.
Anactivityclassthatisresponsibleforcollectingtheinputnecessarytofurtherconfigurethewidgettobedisplayed.
Thetimer,thereceiver,andtheconfigurationactivityareoptional.OnceawidgetisdefinedandtheJavaclassesareprovided,thewidgetwillbeavailablefortheusertodragontoahomepage.TheviewandthecorrespondingJavaclassesarearchitectedinsuchawaythattheyaredisconnectedfromeachother.Forexample,anyAndroidserviceoractivitycanretrievetheviewusingitslayoutID,populatethatviewwithdata(justlikepopulatingatemplate),andsendittothehomescreen.Oncetheviewissenttothehomescreen,itisdislodgedfromtheunderlyingJavacode.
Beforeweshowyouhowtoimplementawidget,we’llfirstgiveyouanoverviewofhowawidgetisusedbyanenduser.
UserExperiencewithHomeScreenWidgetsHomescreenwidgetfunctionalityinAndroidallowsyoutochooseapreprogrammed
widgettobeplacedonthehomescreen.Whenplaced,thewidgetwillallowyoutoconfigureitusinganactivity(definedaspartofthewidgetpackage),ifnecessary.Itisimportanttounderstandthisinteractionbeforeactuallygoingintothedetailsofhowawidgetisimplemented.
WearegoingtowalkyouthroughawidgetcalledBirthdayWidgetthatwehavecreatedforthischapter.Wewillpresentthesourcecodeforitlaterinthechapter.First,wearegoingtousethiswidgetasanexampleforourwalkthrough.Asaconsequenceofsourcecodecominglater,weneedyourconsiderationtoreadalongandfollowthepicturesandnotlookforthiswidgetonyourscreen.Ifyoufollowtheprovidedfiguresandexplanation,youwillknowthenatureandbehavioroftheBirthdayWidget,whichwillmakethingsclearwhenwecodeitsubsequently.
Let’sstartthistourbylocatingthewidgetwewantandcreatinganinstanceofitonthehomescreen.ThewayyouaccesstheavailablewidgetlistisdifferentdependingontheAndroidrelease.Usuallythough,thelistofwidgetsiskeptalongsidethelistofapplicationsavailableonyourdevice.HereisanexamplefromAPI16(orJellybeanversionofAndroid)inFigure21-1.
Figure21-1.Homescreenwidgetpicklist
InthelistofwidgetsinFigure21-1,theBirthdayWidgetisdesignedforthischapter.Ifyouchoosethiswidget,Androidallowsyoutodragittooneofpagesofyourhome
screen.AndroidwillcreateacorrespondingwidgetinstanceonthehomescreenthatlooksliketheexampleBirthdayWidgetshowninFigure21-2.
Figure21-2.AnexampleBirthdayWidget
BirthdayWidgetinFigure21-2willindicateinitsheaderthenameoftheperson,howmanydaysawaythisperson’sbirthdayis,whenthedateofbirthfallsthisyear,andalinktobuygifts.Youmaybewonderinghowthenameofthepersonanddateofbirthwereconfigured.Whatifyouwanttwoinstancesofthiswidget,eachwiththenameanddateofbirthforadifferentperson?Thisiswherethewidgetconfigurationactivitycomesintoplayandisthetopicwearecoveringnext.
UnderstandingWidgetConfigurationActivityAwidgetdefinitionoptionallyincludesaspecificationofanactivitycalledawidgetconfigurationactivity.Whenyouchooseawidgetfromthehomepagewidgetpicklisttocreatethewidgetinstance,Androidinvokesthecorrespondingwidgetconfigurationactivityifoneisdefinedforit.Thisactivityissomethingyouneedtocode.
IncaseofourBirthdayWidget,thisconfigurationactivitywillpromptyouforthenameofthepersonandtheupcomingbirthdate,asshowninFigure21-3.Itistheresponsibilityoftheconfigurationactivitytosavethisinformationinapersistentplaceso
thatwhenanupdateiscalledonthewidgetprovider,thewidgetproviderwillbeabletolocatethisinformationandupdatethenumberofdaysuntilthebirthday.
Figure21-3.BirthdayWidgetconfigurationactivity
NoteWhenauserchoosestocreatetwoBirthdayWidgetinstancesonthehomescreen,theconfigurationactivitywillbecalledtwice(onceforeachwidgetinstance).
Internally,AndroidkeepstrackofthewidgetinstancesbyallocatingthemuniqueIDs.ThisuniquewidgetinstanceIDispassedtotheJavacallbacksandtotheconfiguratorJavaclasssothatinitialconfigurationandupdatescanbedirectedtotherightinstanceofthewidgetonthehomepage.InFigure21-2,inthelaterpartofthestringsatya:3,the3isthewidgetinstanceID.
UnderstandingtheLifeCycleofaWidgetThelifecycleofawidgethasthefollowingphases:
1. Widgetdefinition
2. Widgetinstancecreation
3. onUpdate()(whenthetimeintervalexpires)
4. Responsestoclicks(onthewidgetviewonthehomescreen)
5. Widgetdeletion(fromthehomescreen)
6. Uninstallation
Wewillgothroughthesephasesindetailnow.
UnderstandingWidgetDefinitionPhaseWidgetdefinitionstartswiththedefinitionofthewidgetproviderclassintheAndroidmanifestfile.Listing21-1showsthedefinitionfortheAppWidgetProviderthatwehavedesignedforthischaptercalledBDayWidgetProviderinthemanifestfile.
Listing21-1.WidgetDefinitioninAndroidManifestFile
<!--filename:AndroidManifest.xml,project:ProAndroid5_ch21_TestWidgets.zip--><manifest..><application>....<receiverandroid:name=".BDayWidgetProvider">
<meta-dataandroid:name="android.appwidget.provider"
android:resource="@xml/bday_appwidget_provider"/>
<intent-filter>
<actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"
/>
</intent-filter></receiver>...<activity>.....</activity></application></manifest>
ThisdefinitionindicatesthatthereisabroadcastreceiverJavaclasscalledBDayWidgetProviderwhichreceivesapplicationwidgetbroadcastupdatemessages.ThewidgetclassdefinitioninListing21-1alsopointstoanXMLfile@xml/bday_appwidget_providerwhichis/res/xml/bday_appwidget_provider.xml.ThisXMLfileisinListing21-2.Thiswidgetdefinitionfilehasanumberofthingsaboutthiswidgetsuchasitslayoutresourcefile,updatefrequency,etc.
Listing21-2.WidgetViewDefinitioninWidgetProviderInformationXMLFile
<!--/res/xml/bday_appwidget_provider.xml(ProAndroid5_ch21_TestWidgets.zip)--><appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="150dp"android:minHeight="120dp"android:updatePeriodMillis="43200000"android:initialLayout="@layout/bday_widget"android:configure="com.androidbook.BDayWidget.ConfigureBDayWidgetActivity"android:resizeMode="horizontal|vertical"android:previewImage="@drawable/some_preview_image_icon"></appwidget-provider>
ThisXMLfileiscalledtheAppwidgetproviderinformationfile.Internally,thisgetstranslatedtotheAppWidgetProviderInfoJavaclass.Thisfileidentifiesthewidthandheightofthelayouttobe150dpand120dp,respectively.Thisdefinitionfilealsoindicatestheupdatefrequencytobe12hourstranslatedtomilliseconds.ThewidgetdefinitionalsopointstoalayoutfilethroughtheinitialLayoutattribute.Thislayoutfile(seefutureListing21-6)producesthewidgetlookthatisshowninFigure21-2.
UnderstandingresizeModeAttributeStartingwithSDK3.1,usershavetheabilitytoresizeawidgetthatisplacedononeoftheirimages.Theuserseesresizehandleswhentheylong-clickthewidgetandcanthenusethesehandlestoresize.Thisresizecanbehorizontal,vertical,ornone.Youcancombinehorizontalandverticaltoresizethewidgetinbothdimensions,asshowninListing21-2.However,totakeadvantageofthis,yourwidgetcontrolsshouldbelaidoutinsuchawaythattheycanexpandandcontractusingtheirlayoutparameters.Thereisnocallbacktotellyouwhatsizeyourwidgetis.
UnderstandingpreviewImageAttributeThepreviewimageattributeinListing21-2indicateswhatimageoriconisusedtoshowyourwidgetinthelistofavailablewidgets.Ifyouomitit,thedefaultbehavioristoshowthemainiconforyourapplicationpackage,whichisindicatedinthemanifestfile.
UnderstandingWidgetLayout:initialLayoutAttributeThelayoutforwidgetviewsisrestrictedtocontainonlycertaintypesofviewelements.TheviewsallowedinawidgetlayoutareexposedthroughaninterfacecalledRemoteViews,andonlycertainviewscanbecomposedintothislayout.SomeoftheallowedviewelementsareshowninListing21-3.Notethattheirsubclassesarenotsupported—onlythosethatareincludedinListing21-3.
Listing21-3.AllowedViewControlsinRemoteViews
FrameLayoutLinearLayoutRelativeLayoutGridLayout
AnalogClockButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipperListViewGridViewStackViewAdapterViewFlipper
Thislistmaygrowwitheachrelease.Theprimaryreasonforrestrictingwhatisallowedinaremoteviewisthattheseviewsaredisconnectedfromtheprocessesthatactuallycontrolthem.ThesewidgetviewsarehostedbyanapplicationliketheHomeapplication.Thecontrollersfortheseviewsarebackgroundprocessesthatgetinvokedbytimers.Forthisreason,theseviewsarecalledremoteviews.ThereisacorrespondingJavaclasscalledRemoteViewsthatallowsaccesstotheseviews.Inotherwords,programmersdonothavedirectaccesstotheseviewstocallmethodsonthem.YouhaveaccesstotheseviewsonlythroughtheRemoteViews(likeagatekeeper).
WewillcovertherelevantmethodsofaRemoteViewsclasswhenweexploretheexampleinthenextmainsection.Fornow,rememberthatonlyalimitedsetofviewsinListing21-3areallowedinthewidgetlayoutfile.
UnderstandingconfigureAttributeThewidgetdefinition(Listing21-2)usestheconfigureattributetospecifytheconfigurationactivitythatneedstobeinvokedwhentheusercreatesawidgetinstance.ThisconfigurationactivityspecifiedinListing21-2istheConfigureBDayWidgetActivity.Thisactivity(Figure21-3)islikeanyotherAndroidactivity.Formfieldsonthisactivityareusedtocollecttheinformationneededbyawidgetinstance.
UnderstandingWidgetInstanceCreationPhaseWhenauserchoosesawidgettocreateawidgetinstance,Androidinvokestheconfigurationactivity(Figure21-3)ifitisdefinedintheconfigurationXMLfileforthewidget.Ifthisconfigurationactivityisnotdefinedthenthisphaseskippedandthewidgetispresenteddirectlyonthehomepage.Wheninvokedthisconfigurationactivitydoesthefollowing:
1. ReceivethewidgetinstanceIDfromtheinvokingintentthatstartedtheconfigurationactivity.
2. Prompttheuserthroughformfieldstocollectthewidget-instance–specificinformation.
3. PersistthewidgetinstanceinformationsothatsubsequentcallstoAppWidgetProvider’sonUpdatemethodhaveaccesstothisinformation.
4. PreparetodisplaythewidgetviewforthefirsttimebyretrievingthewidgetviewlayoutandcreateaRemoteViewsobjectwithit.
5. CallmethodsontheRemoteViewsobjecttosetvaluesonindividualviewobjects,suchastextandimages.
6. AlsousetheRemoteViewsobjecttoregisteranyonClickeventsonanyofthesubviewsofthewidget.
7. TelltheAppWidgetManagertopainttheRemoteViewsonthehomescreenusingtheinstanceIDofthatwidget.
8. ReturnthewidgetID,andclose.
NoticethatthefirstpopulationofthewidgetinthiscaseisdonebytheconfigurationactivityandnotAppWidgetProvider’sonUpdate()method.
NoteTheconfigurationactivityisoptional.Iftheconfigurationactivityisnotspecified,thecallgoesdirectlytotheonUpdate()methodoftheAppWidgetProvider.ItisuptoonUpdate()toupdatetheview.
Androidwillundertakethisprocessforeachwidgetinstancethattheusercreates.Besidesinvokingtheconfigurationactivity,AndroidalsoinvokestheonEnabledcallbackoftheAppWidgetProvider.Let’sbrieflyconsiderthecallbacksonanAppWidgetProviderclassbytakingalookattheshellofourBDayWidgetProvider(seeListing21-4).WewillexaminethecompletelistingofthisfilelaterinListing21-10.
Listing21-4.AWidgetProviderShell
//filename:BDayWidgetProvider.java(ProAndroid5_ch21_TestWidgets.zip)publicclassBDayWidgetProviderextendsAppWidgetProvider{
publicvoidonUpdate(Contextcontext,AppWidgetManagerappWidgetManager,int[]appWidgetIds){}publicvoidonDeleted(Contextcontext,int[]appWidgetIds){}publicvoidonEnabled(Contextcontext){}publicvoidonDisabled(Contextcontext){}}
TheonEnabled()callbackmethodindicatesthatthereisatleastoneinstanceofthewidgetupandrunningonthehomescreen.Thismeansausermusthavedroppedthewidgetonthehomepageatleastonce.Inthiscall,youwillneedtoenablereceivingmessagesforthisbroadcastreceivercomponent(youwillseethisinListing21-10).The
SDKbaseclassAppWidgetProviderhasthefunctionalitytoenableordisablereceivingbroadcastmessages.
TheonDeleted()callbackmethodiscalledwhenauserdragsthewidgetinstanceviewtothetrashcan.Thisiswhereyouwillneedtodeleteanypersistentvaluesyouareholdingforthatwidgetinstance.
TheonDisabled()callbackmethodiscalledafterthelastwidgetinstanceisremovedfromthehomescreen.Thishappenswhenauserdragsthelastinstanceofawidgettothetrash.Youshouldusethismethodtounregisteryourinterestinreceivinganybroadcastmessagesintendedforthiscomponent(youwillseethisinListing21-9).
TheonUpdate()callbackmethodiscalledeverytimethetimerspecifiedinListing21-2expires.Thismethodisalsocalledtheveryfirsttimethewidgetinstanceiscreatedifthereisnoconfigurationactivity.Ifthereisaconfigurationactivity,thismethodisnotcalledatthecreationofawidgetinstance.Thismethodwillsubsequentlybecalledwhenthetimerexpiresatthefrequencyindicated.
UnderstandingonUpdatePhaseOncethewidgetinstanceisonthehomescreen,thenextsignificanteventistheexpirationofthetimer.AndroidwillcallonUpdate()inresponsetothattimer.BecauseonUpdate()iscalledisthroughabroadcastreceiver,thecorrespondingJavaprocesswillbeloadedandwillremainliveuntiltheendofthatcall.Oncethecallreturns,theprocesswillbereadytobetakendown.
OnceyouhavethenecessarydataavailabletoupdatethewidgetintheonUpdate()method,youcaninvoketheAppWidgetManagertopainttheremoteview.ThisgoestoshowthattheAppWidgetProviderclassisstatelessandmayevenbeincapableofmaintainingstaticvariablesbetweeninvocations.ThisisbecausetheJavaprocesscontainingthisbroadcastreceiverclasscouldbetakendownandreconstructedbetweentwoinvocations,resultinginre-initializationofstaticvariables.
Asaresult,youwillneedtocomeupwithaschemetorememberstateifthatisrequired.Youcansavethestateofthewidgetinstanceinapersistentstoresuchasafile,sharedpreferences,oraSQLitedatabase.Intheexamplesinthischapter,weusedsharedpreferencesasthepersistenceAPI.
CautionTosavepower,Googlerecommendsthatthedurationoftheupdatesbemorethananhour,sothedevicewon’twakeuptoooften.Startingwiththe2.0API,thereisarestrictionof30minutesormorefortheupdatetimeout.
Fordurationsthatareshorter,suchasonlyseconds,youneedtocallthisonUpdate()methodyourselfbyusingthefacilitiesintheAlarmManagerclass.WhenyouuseAlarmManager,youalsohavetheoptionnottocallonUpdate()but,instead,dotheworkofonUpdate()inalarmcallbacks.RefertoChapter17forworkingwiththealarmmanager.
ThisiswhatyoutypicallyneedtodoinanonUpdate()method:
1. Makesuretheconfiguratorhasfinisheditswork;otherwise,justreturn.Thisshouldnotbeprobleminreleases2.0andabove,wherethedurationisexpectedtobelonger.Otherwise,basedontheupdateinterval(whenitistoosmall)itispossiblethatonUpdate()willbecalledbeforetheuserhasfinishedconfiguringthewidgetintheconfigurator.
2. Retrievethepersisteddataforthatwidgetinstance.
3. Retrievethewidgetviewlayout,andcreateaRemoteViewsobjectwithit.
4. CallmethodsontheRemoteViewstosetvaluesonindividualviewobjectssuchastextandimages.
5. RegisteranyonClickeventsonanyoftheviewsbyusingpendingintents.
6. TelltheAppWidgetManagertopainttheupdatedRemoteViewsusingtheinstanceID.
Asyoucansee,thereisalotofoverlapbetweenwhataconfiguratordoesinitiallyandwhattheonUpdate()methoddoes.Youmaywanttoreusethisfunctionalitybetweenthetwoplaces.
UnderstandingWidgetViewMouseClickEventCallbacksAsstated,theonUpdate()methodkeepsthewidgetviewsuptodate.Thewidgetviewandsubelementsinthatviewcouldhavecallbacksregisteredforamouseclick.Typically,theonUpdate()methodusesapendingintenttoregisteranactionforaneventlikeamouseclick.Thisactioncouldthenstartaserviceorstartanactivitysuchasopeningupabrowser.
Thisinvokedserviceoractivitycanthencommunicatebackwiththeview,ifneeded,usingthewidgetinstanceIDandtheAppWidgetManager.Hence,itisimportantthatthependingintentcarrieswithitthewidgetinstanceID.
DeletingaWidgetInstanceAnotherdistincteventthatcanhappentoawidgetinstanceisthatitcangetdeleted.Todothis,auserhastolong-pressthewidgetonthehomescreen.Thiswillenablethetrashcantoshowonthehomescreen.Theusercanthendragthewidgetinstancetothetrashcantodeletethewidgetinstancefromthescreen.
DoingsocallstheonDelete()methodofthewidgetprovider.Ifyouhavesavedanystateinformationforthiswidgetinstance,youwillneedtodeletethatdatainthisonDeletemethod.
AndroidalsocallsonDisable()ifthewidgetinstancethathasjustbeendeletedisthelastofthewidgetinstancesofthistype.Youwillusethiscallbacktocleanupanypersistenceattributesthatarestoredforallwidgetinstancesandalsounregisterfor
callbacksfromthewidgetonUpdate()broadcasts.
UninstallingWidgetPackagesThereisaneedtocleanupthewidgetsifyouareplanningtouninstallandinstallanewreleaseofyour.apkfilecontainingthesewidgets.
Itisrecommendedthatyouremoveordeleteallwidgetinstancesbeforetryingtouninstallthepackage.Followthedirectionsinthe“DeletingaWidgetInstance”sectiontodeleteeachwidgetinstanceuntilnoneremain.
Then,youcanuninstallandinstallthenewrelease.ThisisespeciallyimportantifyouareusingtheEclipseADTtodevelopyourwidgets,becauseduringthedevelopmenttime,ADTtriestodothiseverytimeyouruntheapplication.So,betweenruns,makesureyouremovethewidgetinstances.
ImplementingASampleWidgetApplicationSofar,wehavecoveredthetheoryandapproachbehindwidgets.Let’screatethesamplewidgetwhosebehaviorhasbeenusedastheexampletoexplainwidgetarchitecture.Wewilldevelop,test,anddeploythisBirthdayWidget.
EachBirthdayWidgetinstancewillshowaname,thedateofthenextbirthday,andhowmanydaysfromtodayuntilthebirthday.ItwillalsocreateanonClickareawhereyoucanclicktobuygifts.Thisclickwillopenabrowserandtakeyoutowww.google.com.
ThelayoutofthefinishedwidgetshouldlooklikeFigure21-4.
Figure21-4.BirthdayWidgetlookandfeel
Theimplementationofthiswidgetconsistsofthefollowingwidget-relatedfiles.TheentireprojectisalsoavailablefordownloadattheURLmentionedinthe“References”sectionofthischapter.
Thebasicfilesare
AndroidManifest.xml:WheretheAppWidgetProviderisdefined(seeListing21-5)
res/xml/bday_appwidget_provider.xml:Widget
dimensionsandlayout(seeListing21-2)
res/layout/bday_widget.xml:Thewidgetlayout(seeListing21-6)
res/drawable/box1.xml:Providesboxesforsectionsofthewidgetlayout(seeListing21-7)
src/…/BdayWidgetProvider.java:ImplementationoftheAppWidgetProviderclass(seeListing21-10)
Thesefilesimplementthewidgetconfigurationactivity:
src/…/ConfigureBDayWidgetActivity.java:Configurationactivity(seeListing21-8)
layout/edit_bday_widget.xml:Layoutfortakingthenameandbirthday(seeListing21-9)
Thesefilesstore/retrievethestateofawidgetinstanceusingpreferences:
src/…/IWidgetModelSaveContract.java:Contractforsavingandretrievingawidget’sdata(Seeindownloadableproject)
src/…/APrefWidgetModel.java:Abstractpreference-basedwidgetmodelthatsaveswidgetdatainpreferences(seeindownloadableproject)
src/…/BDayWidgetModel.java:Widgetmodelholdingthedataforawidgetview(seeindownloadableproject)
src/…/Utils.java:Afewutilityclasses(seeindownloadableproject)
Wewillwalkthroughsomeofthekeyfilesandexplainanyadditionalconceptsthatbearfurtherconsideration.Youcangettherestofthefilesfromthedownloadableprojectforthischapter.
DefiningtheWidgetProviderFortheBirthdayWidgetprojectthemanifestfileisinListing21-5.IthasthedeclarationsforthewidgetproviderBDayAppWidgetProviderasabroadcastreceiverandalsothedefinitionfortheconfigurationactivityConfigureBDayWidgetActivity.NoticehowthewidgetproviderdefinitionalsopointstothewidgetdefinitionXMLfile@xml/bday_appwidget_provider.
Listing21-5.AndroidManifestFileforBDayWidgetSampleApplication
<?xmlversion="1.0"encoding="utf-8"?><!--file:
AndroidManifest.xml(ProAndroid5_ch21_TestWidgets.zip)--><manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.BDayWidget"android:versionCode="1"android:versionName="1.0.0"><applicationandroid:icon="@drawable/icon"android:label="BirthdayWidget"><!--***********************************************************************BirthdayWidgetProviderReceiver**********************************************************************--><receiverandroid:name=".BDayWidgetProvider"><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/bday_appwidget_provider"/><intent-filter><actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/></intent-filter></receiver><!--***********************************************************************BirthdayProviderConfigurationactivity**********************************************************************--><activityandroid:name=".ConfigureBDayWidgetActivity"android:label="ConfigureBirthdayWidget"><intent-filter><actionandroid:name="android.appwidget.action.APPWIDGET_CONFIGURE"/></intent-filter></activity>
</application><uses-sdkandroid:minSdkVersion="3"/></manifest>
Theapplicationlabelidentifiedby“BirthdayWidget”inthefollowingline
<applicationandroid:icon="@drawable/icon"android:label="BirthdayWidget">
iswhatshowsupinthewidgetpicklist(seeFigure21-2)ofthehomepage.YoucanalsoindicateinthewidgetdefinitionXMLfile(Listing21-2)analternateicontobeshownwhenthewidgetislisted(alsocalledapreview).Theconfigurationactivitydefinitionislikeanyothernormalactivity,exceptthatitneedstodeclareitselfascapableofrespondingtoandroid.appwidget.action.APPWIDGET_CONFIGUREactions.
Refertothewidgetdefinitionfile@xml/bday_appwidget_providerinListing21-2toseehowthewidgetsizeandapathtothelayoutfilearespecified.ThislayoutfileisjustlikeanyotherlayoutfileforaviewinAndroid.Listing21-6showsthelayoutfileweusedtoproducethewidgetlayoutshowninFigure21-4.
Listing21-6.WidgetViewLayoutDefinitionforBDayWidget
<?xmlversion="1.0"encoding="utf-8"?><!--res/layout/bday_widget.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="@drawable/box1"><TextViewandroid:id="@+id/bdw_w_name"android:layout_width="fill_parent"android:layout_height="40sp"android:text="Anonymous"android:background="@drawable/box1"android:gravity="center"android:layout_weight="0"/><LinearLayoutandroid:orientation="horizontal"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_weight="1"><TextViewandroid:id="@+id/bdw_w_days"android:layout_width="wrap_content"android:layout_height="fill_parent"android:gravity="center"android:layout_weight="50"android:text="0"android:textSize="30sp"/><TextViewandroid:id="@+id/bdw_w_button_buy"
android:layout_width="wrap_content"android:layout_height="fill_parent"
android:layout_weight="50"android:gravity="center"android:textSize="20sp"android:text="Buy"android:background="#FF6633"/></LinearLayout><TextViewandroid:id="@+id/bdw_w_date"android:layout_width="fill_parent"android:layout_height="40sp"android:gravity="center"android:layout_weight="0"android:text="1/1/2000"android:background="@drawable/box1"/></LinearLayout>
Someofthecontrolsalsouseashapedefinitionfilecalledbox1.xmltodefinethe
borders.ThecodefortheshapedefinitionfileisshowninListing21-7.
Listing21-7.ABoundaryBoxShapeDefinition
<!--res/drawable/box1.xml--><shapexmlns:android="http://schemas.android.com/apk/res/android"><strokeandroid:width="4dp"android:color="#888888"/><paddingandroid:left="2dp"android:top="2dp"android:right="2dp"android:bottom="2dp"/><cornersandroid:radius="4dp"/></shape>
ImplementingWidgetConfigurationActivityFortheBirthdayWidgetexample,theconfigurationofthewidgetresponsibilitiesareimplementedinConfigureBDayWidgetActivity.SourcecodeforthisclassisinListing21-8.
Listing21-8.ImplementingaConfigurationActivity
//file:ConfigureBDayWidgetActivity.java(ProAndroid5_ch21_TestWidgets.zip)publicclassConfigureBDayWidgetActivityextendsActivity{privatestaticStringtag="ConfigureBDayWidgetActivity";privateintmAppWidgetId=AppWidgetManager.INVALID_APPWIDGET_ID;
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.edit_bday_widget);setupButton();//setupthesavebutton
//Getthewidgetinstanceidfromtheintentextra
Intentintent=getIntent();Bundleextras=intent.getExtras();if(extras!=null){mAppWidgetId=extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}}privatevoidsetupButton(){Buttonb=(Button)this.findViewById(R.id.bdw_button_update_bday_widget);b.setOnClickListener(
newButton.OnClickListener(){publicvoidonClick(Viewv){saveConfiguration(v);}});}//Readnameanddate.
//CallupdateAppWidgetLocaltosavethevaluesforthisinstance//inthatmethodalsosendtheviewtothehomepage.//ReturntheresultoftheconfigurationactivitytotheSDK//finishtheactivity.privatevoidsaveConfiguration(Viewv){
Stringname=this.getName();Stringdate=this.getDate();if(Utils.validateDate(date)==false){this.setDate("wrongdate:"+date);return;}if(this.mAppWidgetId==AppWidgetManager.INVALID_APPWIDGET_ID){return;}updateAppWidgetLocal(name,date);IntentresultValue=newIntent();resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
mAppWidgetId);
setResult(RESULT_OK,resultValue);
finish();}privateStringgetName(){EditTextnameEdit=(EditText)this.findViewById(R.id.bdw_bday_name_id);Stringname=nameEdit.getText().toString();returnname;}privateStringgetDate(){EditTextdateEdit=(EditText)this.findViewById(R.id.bdw_bday_date_id);StringdateString=dateEdit.getText().toString();returndateString;}privatevoidsetDate(StringerrorDate){EditTextdateEdit=(EditText)this.findViewById(R.id.bdw_bday_date_id);dateEdit.setText("error");
dateEdit.requestFocus();}privatevoidupdateAppWidgetLocal(Stringname,Stringdob){
//Createanobjecttoholdthedata:widgetid,name,anddob
BDayWidgetModelm=newBDayWidgetModel(mAppWidgetId,name,dob);//Createtheviewandsendittothehomescreen
updateAppWidget(this,AppWidgetManager.getInstance(this),m);//Usethedatamodelobjecttosavetheid,name,anddobinprefs
m.savePreferences(this);}//AkeymethodwherealotofmagichappenspublicstaticvoidupdateAppWidget(Contextcontext,AppWidgetManagerappWidgetManager,BDayWidgetModelwidgetModel){//ConstructaRemoteViewsObjectfromthewidgetlayoutfileRemoteViewsviews=newRemoteViews(context.getPackageName(),R.layout.bday_widget);
//Usethecontrolidsinthelayouttosetvaluesonthem.
//Noticethatthesemethodsarelimitedandavailableonthe
//ontheRemoteViewsobject.Inotherwordswearenotusingthe
//TextViewdirectlytosetthesevalues.
views.setTextViewText(R.id.bdw_w_name,widgetModel.getName()+":"+widgetModel.iid);
views.setTextViewText(R.id.bdw_w_date,widgetModel.getBday());
//updatethenameviews.setTextViewText(R.id.bdw_w_days,
Long.toString(widgetModel.howManyDays()));
//Setintentstoinvokeotheractivitieswhenwidgetisclickedon
IntentdefineIntent=newIntent(Intent.ACTION_VIEW,Uri.parse("http://www.google.com"));PendingIntentpendingIntent=PendingIntent.getActivity(context,0/*norequestCode*/,defineIntent,0/*noflags*/);views.setOnClickPendingIntent(R.id.bdw_w_button_buy,pendingIntent);
//Tellthewidgetmanagertopainttheremoteview
appWidgetManager.updateAppWidget(widgetModel.iid,views);}}
Beforewecoverwhatthiscodedoes,thelayoutusedbythiswidgetconfigurationactivityisinListing21-9.Thislayoutisstraightforward.YoucanalsoseethisvisuallyinFigure21-3.
Listing21-9.LayoutDefinitionforConfigurationActivity
<?xmlversion="1.0"encoding="utf-8"?><!--res/layout/edit_bday_widget.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_layout_id"android:orientation="vertical"
android:layout_width="fill_parent"android:layout_height="fill_parent"><TextViewandroid:id="@+id/bdw_text1"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Name:"/><EditTextandroid:id="@+id/bdw_bday_name_id"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Anonymous"/><TextViewandroid:id="@+id/bdw_text2"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Birthday(9/1/2001):"/><EditTextandroid:id="@+id/bdw_bday_date_id"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="ex:10/1/2009"/><Buttonandroid:id="@+id/bdw_button_update_bday_widget"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="update"/></LinearLayout>
GoingbacktotheconfigurationactivitycodeinListing21-8,itaccomplishesthefollowingtasks:
ReadingthewidgetinstanceIDfrominvokingintent
Collectingthenameanddateofbirthusingformfields
ObtainingRemoteViewsbyloadingwidgetlayoutfile
SettingtextvaluesontheRemoteViews
RegisteringapendingintentthroughRemoteViews
InvokingtheAppWidgetManagertosendtheRemoteViewstothewidget
SavingthenameanddateofbirthinpreferencesagainstthiswidgetinstanceID.ThisisdonethroughtheclassBDayWidgetModel.Wewilltalkaboutthisshortly.
Returningattheendwitharesult.
NoteThestaticfunctionudpateAppWidgetcanbecalledfromanywhereaslongasyouknowthewidgetID.Thissuggeststhatyoucanupdateawidgetfromanywhereonyourdeviceandfromanyprocess,bothvisualandnonvisual.
NoticehowwearepassingthewidgetIDbacktotheinvokerofthisconfigurationactivity.ThisishowAppWidgetManagerknowsthattheconfigurationactivityiscompletedforthatwidgetinstance.
Let’stalkaboutsavingandretrievalofthewidgetinstancestatethroughBDayWidgetModelobjectinListing21-8.TheroleofBDayWidgetModelobjectistostoreandretrievethreevalues:ThewidgetinstanceID(primarykey),name,anddateofbirth.ThisclassusesthepreferencesAPItopersistandreadbackthesevalues.Alternatively,youcanuseanypersistencemechanismforthisneed.Wearenotincludingthesourcecodeforthisclassasitisquiteasimpleneedtoimplement.Inthedownloadableprojectforthischapterwehaveanimplementationforthisclassthatisabitmoreextensive,wherewecodedareusableframeworktostorevaluesforanyjavaobjectinthepreferences.Wehaveamplydocumentedthesourcecodesothatyoucanuseitasisforotherneedsortweakitfurtherandusereflectiontosimplifyfurther.Intheendyouwillhaveamodelframeworkthatisquiteextensible.Asthisisnottheprimarygoalofthischapterwehavenotgottenintothosedetailshere.Whatmattersforthischapteristhatthesethreevalues,theinstanceID,name,anddobbesavedandretrieved.YoucanfollowthenamesontheBDayWidgetModelasaguide.
ImplementingaWidgetProviderLet’sseenowhowwewillrespondtothelifecycleeventsofwidgetsbyexaminingthewidgetproviderclass.Listing21-10implementsthewidgetproviderclass.
Listing21-10.SourcecodeforSampleWidgetProvider:BDayWidgetProvider
//file:
BDayWidgetProvider.java(ProAndroid5_ch21_TestWidgets.zip)publicclassBDayWidgetProviderextendsAppWidgetProvider{
privatestaticfinalStringtag="BDayWidgetProvider";publicvoidonUpdate(Contextcontext,AppWidgetManager
appWidgetManager,
int[]appWidgetIds){finalintN=appWidgetIds.length;for(inti=0;i<N;i++){intappWidgetId=appWidgetIds[i];updateAppWidget(context,appWidgetManager,appWidgetId);}}publicvoidonDeleted(Contextcontext,int[]appWidgetIds){
finalintN=appWidgetIds.length;for(inti=0;i<N;i++){BDayWidgetModelbwm=BDayWidgetModel.retrieveModel(context,appWidgetIds[i]);bwm.removePrefs(context);}}publicvoidonEnabled(Contextcontext){
BDayWidgetModel.clearAllPreferences(context);PackageManagerpm=context.getPackageManager();pm.setComponentEnabledSetting(newComponentName("com.androidbook.BDayWidget",".BDayWidgetProvider"),PackageManager.COMPONENT_ENABLED_STATE_ENABLED,PackageManager.DONT_KILL_APP);}
publicvoidonDisabled(Contextcontext){
BDayWidgetModel.clearAllPreferences(context);PackageManagerpm=context.getPackageManager();pm.setComponentEnabledSetting(newComponentName("com.androidbook.BDayWidget",".BDayWidgetProvider"),PackageManager.COMPONENT_ENABLED_STATE_DISABLED,PackageManager.DONT_KILL_APP);}privatevoidupdateAppWidget(Contextcontext,AppWidgetManager
appWidgetManager,
intappWidgetId){BDayWidgetModelbwm=BDayWidgetModel.retrieveModel(context,appWidgetId);if(bwm==null){return;}ConfigureBDayWidgetActivity.updateAppWidget(context,appWidgetManager,bwm);
}}
Inthe“LifeCycleofaWidget”sectionwediscussedtheresponsibilitiesofthesemethods.FortheBirthdayWidget,allthesemethodsmakeuseoftheBDayWidgetModeltoretrievethedataassociatedwithawidgetinstanceforwhichthecallbacksarecalled.SomeofthesemethodsontheBDayWidgetModelareremovePrefs(),retrievePrefs(),andclearAllPreferences().
Theupdatecallbackmethodiscalledforallthewidgetinstancesofthiswidgettype.Thismethodmustupdateallthewidgetinstances.ThewidgetinstancesarepassedinasanarrayofIDs.Foreachid,theonUpdate()methodwilllocatethecorrespondingwidgetinstancemodelandcallthesamemethodthatisusedbytheconfigurationactivity(seeListing21-8)todisplaytheretrievedwidgetmodel.
IntheonDeleted()method,wehaveinstantiatedaBDayWidgetModelandthenaskedittoremoveitselffromthepreferencespersistencestore.
IntheonEnabled()method,becauseitiscalledonlyoncewhenthefirstinstancecomesintoplay,wehaveclearedallpersistenceofthewidgetmodelssothatwestartwithacleanslate.WedothesameintheonDisabled()methodsothatnomemoryofwidgetinstancesexists.
IntheonEnabled()method,weenablethewidgetprovidercomponentsothatitcanreceivebroadcastmessages.IntheonDisabled()method,wedisablethecomponentsothatitwon’tlookforanybroadcastmessages.
Collection-BasedWidgetsStartingwithSDK3.0,Androidhasexpandedthewidgetstoincludewidgetsbasedoncollections.Wedon’thaveroomintheprintcopyofthisbook.Wewillincludethechapterfromthepreviouseditiononcollectionwidgetsatouronlinesitefordownload.
ResourcesHerearehelpfulreferencestothetopicsthatarecoveredinthischapter:
http://developer.android.com/guide/topics/appwidgets/index.htmlOfficialAndroidSDKdocumentationonappwidgets.
http://developer.android.com/reference/android/content/SharedPreferences.htmlSharedPreferencesAPIformanagingstate.
http://developer.android.com/reference/android/content/SharedPreferences.Editor.htmlTheSharedPreferences.EditorAPI,whichisrelatedtosharedpreferences.
http://developer.android.com/guide/practices/ui_guidelines/widget_design.html
Designpleasingwidgetlayouts.
http://developer.android.com/reference/android/widget/RemoteViews.htmlRemoteViewsAPI,usedtopaintandmanipulatewidgetviews.
http://developer.android.com/reference/android/appwidget/AppWidgetManager.htmlWidgetsthemselvesaremanagedbyawidgetmanagerclass.
http://www.androidbook.com/item/3938:Researchnotesusedwhilewritingthischapter,includingasummary,researchlogs,codesnippets,andusefulURLs.
http://www.androidbook.com/free-android-chapters:YoucanusethisURLtodownloadadetailedchapteronlistwidgets.
http://www.androidbook.com/proandroid5/projects:Downloadabletestprojectsforthischapter.ThenameoftheZIPfileforthischapterisProAndroid5_ch21_TestWidgets.zip.
SummaryWidgetsareoftenusedalongsideyourapplicationsinAndroid.Thischapterhascoveredtheessentialsyouneedtocreateandconfigurewidgets.Asupplementalchapteronlistwidgetsisprovidedonline.
Chapter22
TouchScreensManyAndroiddevicesincorporatetouchscreens.Whenadevicedoesnothaveaphysicalkeyboard,muchoftheuserinputmustcomethroughthetouchscreen.Thereforeyourapplicationswilloftenneedtobeabletodealwithtouchinputfromtheuser.You’vemostlikelyalreadyseenthevirtualkeyboardthatdisplaysonthescreenwhentextinputisrequiredfromtheuser.WeusedtouchwithmappingapplicationsinChapter19.Theimplementationsofthetouchscreeninterfacehavebeenhiddenfromyousofar,butnowwe’llshowyouhowtotakeadvantageofthetouchscreen.
Thischapterismadeupofthreemajorparts.ThefirstsectionwilldealwithMotionEventobjects,whichishowAndroidtellsanapplicationthattheuseristouchingatouchscreen.We’llalsocovertheVelocityTracker.Thesecondsectionwilldealwithmultitouch,whereausercanhavemorethanonefingeratatimeonthetouchscreen.Finally,wewillincludeasectionongestures,aspecializedtypeofcapabilityinwhichtouchsequencescanbeinterpretedascommands.
UnderstandingMotionEventsInthissection,we’regoingtocoverhowAndroidtellsapplicationsabouttoucheventsfromtheuser.Fornow,wewillonlybeconcernedwithtouchingthescreenonefingeratatime(we’llcovermultitouchinalatersection).
Atthehardwarelevel,atouchscreenismadeupofspecialmaterialsthatcanpickuppressureandconvertthattoscreencoordinates.Theinformationaboutthetouchisturnedintodata,andthatdataispassedtothesoftwaretodealwithit.
TheMotionEventObjectWhenausertouchesthetouchscreenofanAndroiddevice,aMotionEventobjectiscreated.TheMotionEventcontainsinformationaboutwhereandwhenthetouchtookplace,aswellasotherdetailsofthetouchevent.TheMotionEventobjectgetspassedtoanappropriatemethodinyourapplication.ThiscouldbetheonTouchEvent()methodofaViewobject.RememberthattheViewclassistheparentofquiteafewclassesinAndroid,includingLayouts,Buttons,Lists,Clocks,andmore.ThismeanswecaninteractwithallofthesedifferenttypesofViewobjectsusingtouchevents.Whenthemethodiscalled,itcaninspecttheMotionEventobjecttodecidewhattodo.Forexample,aGoogleMapcouldusetoucheventstomovethemapsidewaystoallowtheusertopanthemaptootherpointsofinterest.Avirtualkeyboardobjectcouldreceivetoucheventstoactivatethevirtualkeystoprovidetextinputtosomeotherpartoftheuserinterface(UI).
ReceivingMotionEventObjectsAMotionEventobjectisoneofasequenceofeventsrelatedtoatouchbytheuser.Thesequencestartswhentheuserfirsttouchesthetouchscreen,continuesthroughanymovementsofthefingeracrossthesurfaceofthetouchscreen,andendswhenthefingerisliftedfromthetouchscreen.Theinitialtouch(anACTION_DOWNaction),themovementssideways(ACTION_MOVEactions),andtheupevent(anACTION_UPaction)ofthefingerallcreateMotionEventobjects.YoucouldreceivequiteafewACTION_MOVEeventsasthefingermovesacrossthesurfacebeforeyoureceivethefinalACTION_UPevent.EachMotionEventobjectcontainsinformationaboutwhatactionisbeingperformed,wherethetouchistakingplace,howmuchpressurewasapplied,howbigthetouchwas,whentheactionoccurred,andwhentheinitialACTION_DOWNoccurred.Thereisafourthpossibleaction,whichisACTION_CANCEL.Thisactionisusedtoindicatethatatouchsequenceisendingwithoutactuallydoinganything.Finally,thereisACTION_OUTSIDE,whichissetinaspecialcasewhereatouchoccursoutsideofourwindowbutwestillgettofindoutaboutit.
Thereisanotherwaytoreceivetouchevents,andthatistoregisteracallbackhandlerfortoucheventsonaViewobject.TheclasstoreceivetheeventsmustimplementtheView.OnTouchListenerinterface,andtheViewobject’ssetOnTouchListener()methodmustbecalledtosetupthehandlerforthatView.TheimplementingclassoftheView.OnTouchListenermustimplementtheonTouch()method.WhereastheonTouchEvent()methodtakesjustaMotionEventobjectasaparameter,onTouch()takesbothaViewandaMotionEventobjectasparameters.ThisisbecausetheOnTouchListenercouldreceiveMotionEventobjectsformultipleviews.Thiswillbecomeclearerwithournextexampleapplication.
IfaMotionEventhandler(eitherthroughtheonTouchEvent()oronTouch()method)consumestheeventandnooneelseneedstoknowaboutit,themethodshouldreturntrue.ThistellsAndroidthattheeventdoesnotneedtobepassedtoanyotherviews.IftheViewobjectisnotinterestedinthiseventoranyfutureeventsrelatedtothistouchsequence,itreturnsfalse.TheonTouchEvent()methodofthebaseclassViewdoesn’tdoanythingandreturnsfalse.SubclassesofViewmayormaynotdothesame.Forexample,aButtonobjectwillconsumeatouchevent,becauseatouchisequivalenttoaclick,andthereforereturnstruefromtheonTouchEvent()method.UponreceivinganACTION_DOWNevent,theButtonwillchangeitscolortoindicatethatitisintheprocessofbeingclicked.TheButtonalsowantstoreceivetheACTION_UPeventtoknowwhentheuserhasletgo,soitcaninitiatethelogicofclickingthebutton.IfaButtonobjectreturnedfalsefromonTouchEvent(),itwouldnotreceiveanymoreMotionEventobjectstotellitwhentheuserliftedafingerfromthetouchscreen.
WhenwewanttoucheventstodosomethingnewwithaparticularViewobject,wecanextendtheclass,overridetheonTouchEvent()method,andputourlogicthere.We
canalsoimplementtheView.OnTouchListenerinterfaceandsetupacallbackhandlerontheViewobject.BysettingupacallbackhandlerwithonTouch(),MotionEventswillbedeliveredtherefirstbeforetheygototheView’sonTouchEvent()method.OnlyiftheonTouch()methodreturnedfalsewouldourView’sonTouchEvent()methodgetcalled.Let’sgettoourexampleapplicationwherethisshouldbeeasiertosee.
NoteWewillgiveyouaURLattheendofthechapterwhichyoucanusetodownloadprojectsofthischapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.
SettingUpanExampleApplicationListing22-1showstheXMLofalayoutfile.CreateanewAndroidprojectstartingwiththislayout.
Listing22-1.XMLLayoutFileforTouchDemo1
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/main.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">
<RelativeLayoutandroid:id="@+id/layout1"android:tag="trueLayoutTop"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1">
<com.androidbook.touch.demo1.TrueButtonandroid:text="ReturnsTrue"android:id="@+id/trueBtn1"android:tag="trueBtnTop"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<com.androidbook.touch.demo1.FalseButtonandroid:text="ReturnsFalse"android:id="@+id/falseBtn1"android:tag="falseBtnTop"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/trueBtn1"/>
</RelativeLayout><RelativeLayoutandroid:id="@+id/layout2"
android:tag="falseLayoutBottom"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="#FF00FF">
<com.androidbook.touch.demo1.TrueButtonandroid:text="ReturnsTrue"android:id="@+id/trueBtn2"android:tag="trueBtnBottom"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
<com.androidbook.touch.demo1.FalseButtonandroid:text="ReturnsFalse"android:id="@+id/falseBtn2"android:tag="falseBtnBottom"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/trueBtn2"/>
</RelativeLayout></LinearLayout>
Thereareacoupleofthingstopointoutaboutthislayout.We’veincorporatedtagsonourUIobjects,andwe’llbeabletorefertothesetagsinourcodeaseventsoccuronthem.We’veusedcustomobjects(TrueButtonandFalseButton).You’llseeintheJavacodethattheseareclassesextendedfromtheButtonclass.BecausetheseareButtons,wecanuseallofthesameXMLattributeswewoulduseonotherbuttons.Figure22-1showswhatthislayoutlookslike,andListing22-2showsourbuttonJavacode.
Figure22-1.TheUIofourTouchDemo1application
Listing22-2.JavaCodefortheButtonClassesforTouchDemo1
//ThisfileisBooleanButton.javapublicabstractclassBooleanButtonextendsButton{protectedbooleanmyValue(){returnfalse;}
publicBooleanButton(Contextcontext,AttributeSetattrs){super(context,attrs);}
@OverridepublicbooleanonTouchEvent(MotionEventevent){StringmyTag=this.getTag().toString();Log.v(myTag,"-----------------------------------");Log.v(myTag,MainActivity.describeEvent(this,event));Log.v(myTag,"superonTouchEvent()returns"+
super.onTouchEvent(event));Log.v(myTag,"andI'mreturning"+myValue());return(myValue());}}
//ThisfileisTrueButton.javapublicclassTrueButtonextendsBooleanButton{protectedbooleanmyValue(){returntrue;}
publicTrueButton(Contextcontext,AttributeSetattrs){super(context,attrs);}}
//ThisfileisFalseButton.javapublicclassFalseButtonextendsBooleanButton{
publicFalseButton(Contextcontext,AttributeSetattrs){super(context,attrs);}}
TheBooleanButtonclasswasbuiltsowecanreusetheonTouchEvent()method,whichwe’vecustomizedbyaddingthelogging.Then,wecreatedTrueButtonandFalseButton,whichwillresponddifferentlytotheMotionEventspassedtothem.Thiswillbemadeclearerwhenyoulookatthemainactivitycode,whichisshowninListing22-3.
Listing22-3.JavaCodeforOurMainActivity
//ThisfileisMainActivity.javaimportandroid.view.MotionEvent;importandroid.view.View.OnTouchListener;publicclassMainActivityextendsActivityimplementsOnTouchListener{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
RelativeLayoutlayout1=(RelativeLayout)findViewById(R.id.layout1);layout1.setOnTouchListener(this);ButtontrueBtn1=(Button)findViewById(R.id.trueBtn1);
trueBtn1.setOnTouchListener(this);ButtonfalseBtn1=(Button)findViewById(R.id.falseBtn1);falseBtn1.setOnTouchListener(this);
RelativeLayoutlayout2=(RelativeLayout)findViewById(R.id.layout2);layout2.setOnTouchListener(this);ButtontrueBtn2=(Button)findViewById(R.id.trueBtn2);trueBtn2.setOnTouchListener(this);ButtonfalseBtn2=(Button)findViewById(R.id.falseBtn2);falseBtn2.setOnTouchListener(this);}
@OverridepublicbooleanonTouch(Viewv,MotionEventevent){StringmyTag=v.getTag().toString();Log.v(myTag,"-----------------------------");Log.v(myTag,"Gotview"+myTag+"inonTouch");Log.v(myTag,describeEvent(v,event));if("true".equals(myTag.substring(0,4))){/*Log.v(myTag,"***callingmyonTouchEvent()method***");v.onTouchEvent(event);Log.v(myTag,"***backfromonTouchEvent()method***");*/Log.v(myTag,"andI'mreturningtrue");returntrue;}else{Log.v(myTag,"andI'mreturningfalse");returnfalse;}}
protectedstaticStringdescribeEvent(Viewview,MotionEventevent){StringBuilderresult=newStringBuilder(300);result.append("Action:").append(event.getAction()).append("\n");result.append("Location:").append(event.getX()).append("x").append(event.getY()).append("\n");if(event.getX()<0||event.getX()>view.getWidth()||event.getY()<0||event.getY()>
view.getHeight()){result.append(">>>Touchhaslefttheview<<<\n");}result.append("Edgeflags:").append(event.getEdgeFlags());result.append("\n");result.append("Pressure:").append(event.getPressure());result.append("").append("Size:").append(event.getSize());result.append("\n").append("Downtime:");result.append(event.getDownTime()).append("ms\n");result.append("Eventtime:").append(event.getEventTime());result.append("ms").append("Elapsed:");result.append(event.getEventTime()-event.getDownTime());result.append("ms\n");returnresult.toString();}}
Ourmainactivitycodesetsupcallbacksonourbuttonsandthelayoutssowecanprocessthetouchevents(theMotionEventobjects)foreverythinginourUI.We’veaddedlotsoflogging,soyou’llbeabletotellexactlywhat’sgoingonastoucheventsoccur.OneothergoodideaistoaddthefollowingtagtoyourmanifestfilesoGooglePlayStorewillknowyourapplicationrequiresatouchscreentowork:<uses-configurationandroid:reqTouchScreen=“finger”/>.Forexample,GoogleTVsdon’thavetouchscreens,soitwouldn’tmakesensetotrytorunthisappthere.Whenyoucompileandrunthisapplication,youshouldseeascreenthatlookslikeFigure22-1.
RunningtheExampleApplicationTogetthemostoutofthisapplication,youneedtoopenLogCatinyourIDE(EclipseorAndroidStudio)towatchthemessagesflybyasyoutouchthetouchscreen.Thisworksintheemulatoraswellasonarealdevice.WealsoadviseyoutomaximizetheLogCatwindow,soyoucanmoreeasilyscrollupanddowntoseeallofthegeneratedeventsfromthisapplication.Tomaximizethewindow,justdouble-clicktheLogCattab.Now,gototheapplicationUI,andtouchandreleaseonthetopmostbuttonmarkedReturnsTrue(ifyou’reusingtheemulator,useyourmousetoclickandreleasethebutton).YoushouldseeatleasttwoeventsloggedinLogCat.ThemessagesaretaggedascomingfromtrueBtnTopandwereloggedfromtheonTouch()methodinMainActivity.SeeMainActivity.javafortheonTouch()method’scode.AsyouviewtheLogCatoutput,seewhichmethodcallsareproducingthevalues.Forexample,thevaluedisplayedafterActioncomesfromthegetAction()method.Listing22-4showsasampleof
whatyoumightseeinLogCatfromthesampleapplication.
Listing22-4.SampleLogCatMessagesfromTouchDemo1
trueBtnTop-----------------------------trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:0trueBtnTopLocation:42.8374x25.293747trueBtnTopEdgeflags:0trueBtnTopPressure:0.05490196Size:0.2trueBtnTopDowntime:24959412mstrueBtnTopEventtime:24959412msElapsed:0mstrueBtnTopandI'mreturningtruetrueBtnTop-----------------------------trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:2trueBtnTopLocation:42.8374x25.293747trueBtnTopEdgeflags:0trueBtnTopPressure:0.05490196Size:0.2trueBtnTopDowntime:24959412mstrueBtnTopEventtime:24959530msElapsed:118mstrueBtnTopandI'mreturningtruetrueBtnTop-----------------------------trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:1trueBtnTopLocation:42.8374x25.293747trueBtnTopEdgeflags:0trueBtnTopPressure:0.05490196Size:0.2trueBtnTopDowntime:24959412mstrueBtnTopEventtime:24959567msElapsed:155mstrueBtnTopandI'mreturningtrue
UnderstandingMotionEventContentsThefirsteventhasanactionof0,whichisACTION_DOWN.Thelasteventhasanactionof1,whichisACTION_UP.Ifyouusedarealdevice,youmightseemorethantwoevents.AnyeventsinbetweenACTION_DOWNandACTION_UPwillmostlikelyhaveanactionof2,whichisACTION_MOVE.Theotherpossibilitiesareanactionof3,whichisACTION_CANCEL,or4,whichisACTION_OUTSIDE.Whenusingrealfingersonarealtouchscreen,youcan’talwaystouchandreleasewithoutaslightmovementonthesurface,soexpectsomeACTION_MOVEevents.
NoticetheLocationvalues.ThelocationforaMotionEventhasanxandycomponent,wherexrepresentsthedistancefromtheleft-handsideoftheViewobjecttothepointtouchedandyrepresentsthedistancefromthetopoftheViewobjecttothepointtouched.
Intheemulator,pressureislikely1.0andsizeislikely0.0.Forarealdevice,thepressure
representshowhardthefingerpresseddown,andsizerepresentshowlargethetouchis.Ifyoutouchlightlywiththetipofyourpinkyfinger,thevaluesforpressureandsizewillbesmall.Ifyoupresshardwithyourthumb,bothpressureandsizewillbelarger.Pressinglightlywithyourthumbshouldresultinasmallvalueforpressurebutalargevalueforsize.Thedocumentationsaysthatthevaluesofpressureandsizewillbebetween0and1.However,duetodifferencesinhardware,itmaybeverydifficulttouseanyabsolutenumbersinyourapplicationformakingdecisionsaboutpressureandsize.ItwouldbefinetocomparepressureandsizebetweenMotionEventsastheyoccurinyourapplication,butyoumayrunintotroubleifyoudecidethatpressuremustexceedavaluesuchas0.8tobeconsideredahardpress.Onthatparticulardevice,youmightnevergetavalueabove0.8.Youmightnotevengetavalueabove0.2.
Thedowntimeandeventtimevaluesoperateinthesamewaybetweentheemulatorandarealdevice,theonlydifferencebeingthattherealdevicehasmuchlargervalues.Theelapsedtimesworkthesame.
Theedgeflagsarefordetectingwhenatouchhasreachedtheedgeofthephysicalscreen.TheAndroidSDKdocumentationsaysthattheflagsaresettoindicatethatatouchhasintersectedwithanedgeofthedisplay(top,bottom,left,orright).However,thegetEdgeFlags()methodmayalwaysreturnzero,dependingonwhatdeviceoremulatoritisusedon.Withsomehardware,itistoodifficulttoactuallydetectatouchattheedgeofthedisplay,soAndroidissupposedtopinthelocationtotheedgeandsettheappropriateedgeflagforyou.Thisdoesn’talwayshappen,soyoushouldnotrelyontheedgeflagsbeingsetproperly.TheMotionEventclassprovidesasetEdgeFlags()methodsoyoucansettheflagsyourselfifyouwantto.
ThelastthingtonoticeisthatouronTouch()methodreturnstrue,becauseourTrueButtoniscodedtoreturntrue.ReturningtruetellsAndroidthattheMotionEventobjecthasbeenconsumedandthereisnoreasontogiveittosomeoneelse.ItalsotellsAndroidtokeepsendingtoucheventsfromthistouchsequencetothismethod.That’swhywegottheACTION_UPevent,aswellastheACTION_MOVEeventinthecaseoftherealdevice.
NowtouchtheReturnsFalsebuttonnearthetopofthescreen.Listing22-5showsasampleLogCatoutputforyourReturnsFalsetouch.
Listing22-5.SampleLogCatfromTouchingtheTopReturnsFalseButton
falseBtnTop-----------------------------falseBtnTopGotviewfalseBtnTopinonTouchfalseBtnTopAction:0falseBtnTopLocation:61.309372x44.281494falseBtnTopEdgeflags:0falseBtnTopPressure:0.0627451Size:0.26666668falseBtnTopDowntime:28612178msfalseBtnTopEventtime:28612178msElapsed:0msfalseBtnTopandI'mreturningfalsefalseBtnTop-----------------------------------falseBtnTopAction:0
falseBtnTopLocation:61.309372x44.281494falseBtnTopEdgeflags:0falseBtnTopPressure:0.0627451Size:0.26666668falseBtnTopDowntime:28612178msfalseBtnTopEventtime:28612178msElapsed:0msfalseBtnTopsuperonTouchEvent()returnstruefalseBtnTopandI'mreturningfalsetrueLayoutTop-----------------------------trueLayoutTopGotviewtrueLayoutTopinonTouchtrueLayoutTopAction:0trueLayoutTopLocation:61.309372x116.281494trueLayoutTopEdgeflags:0trueLayoutTopPressure:0.0627451Size:0.26666668trueLayoutTopDowntime:28612178mstrueLayoutTopEventtime:28612178msElapsed:0mstrueLayoutTopandI'mreturningtruetrueLayoutTop-----------------------------trueLayoutTopGotviewtrueLayoutTopinonTouchtrueLayoutTopAction:2trueLayoutTopLocation:61.309372x111.90039trueLayoutTopEdgeflags:0trueLayoutTopPressure:0.0627451Size:0.26666668trueLayoutTopDowntime:28612178mstrueLayoutTopEventtime:28612217msElapsed:39mstrueLayoutTopandI'mreturningtruetrueLayoutTop-----------------------------trueLayoutTopGotviewtrueLayoutTopinonTouchtrueLayoutTopAction:1trueLayoutTopLocation:55.08958x115.30792trueLayoutTopEdgeflags:0trueLayoutTopPressure:0.0627451Size:0.26666668trueLayoutTopDowntime:28612178mstrueLayoutTopEventtime:28612361msElapsed:183mstrueLayoutTopandI'mreturningtrue
Nowyou’reseeingverydifferentbehavior,sowe’llexplainwhathappened.AndroidreceivestheACTION_DOWNeventinaMotionEventobjectandpassesittoouronTouch()methodintheMainActivityclass.OuronTouch()methodrecordstheinformationinLogCatandreturnsfalse.ThistellsAndroidthatouronTouch()methoddidnotconsumetheevent,soAndroidlookstothenextmethodtocall,whichinourcaseistheoverriddenonTouchEvent()methodofourFalseButtonclass.BecauseFalseButtonisanextensionoftheBooleanButtonclass,refertotheonTouchEvent()methodinBooleanButton.javatoseethecode.IntheonTouchEvent()method,weagainwriteinformationtoLogCat,wecalltheparentclass’sonTouchEvent()method,andthenwealsoreturnfalse.NoticethatthelocationinformationinLogCatisexactlythesameasbefore.Thisshouldbeexpected
becausewe’restillinthesameViewobject,theFalseButton.WeseethatourparentclasswantstoreturntruefromonTouchEvent(),andwecanseewhy.IfyoulookatthebuttonintheUI,itshouldbeadifferentcolorfromtheReturnsTruebutton.OurReturnsFalsebuttonnowlookslikeit’spartwaythroughbeingpressed.Thatis,itlookslikeabuttonlookswhenithasbeenpressedbuthasnotbeenreleased.Ourcustommethodreturnedfalseinsteadoftrue.BecauseweagaintoldAndroidthatwedidnotconsumethisevent,byreturningfalse,AndroidneversendstheACTION_UPeventtoourbutton,soourbuttondoesn’tknowthatthefingereverliftedfromthetouchscreen.Therefore,ourbuttonisstillinthepressedstate.Ifwehadreturnedtruelikeourparentwantedto,wewouldeventuallyhavereceivedtheACTION_UPevent,sowecouldchangethecolorbacktothenormalbuttoncolor.Torecap,everytimewereturnfalsefromaUIobjectforareceivedMotionEventobject,AndroidstopssendingMotionEventobjectstothatUIobject,andAndroidkeepslookingforanotherUIobjecttoconsumeourMotionEventobject.
YoumighthaverealizedthatwhenwetouchedourReturnsTruebutton,wedidn’tgetacolorchangeinthebutton.Whyisthat?Well,ouronTouch()methodwascalledbeforeanyactualbuttonmethodsgotcalled,andonTouch()returnedtrue,soAndroidneverbotheredtocalltheReturnsTruebutton’sonTouchEvent()method.Ifyouaddav.onTouchEvent(event);linetotheonTouch()methodjustbeforereturningtrue,youwillseethebuttonchangecolor.YouwillalsoseemoreloglinesinLogCat,becauseouronTouchEvent()methodisalsowritinginformationtoLogCat.
Let’skeepgoingthroughtheLogCatoutput.NowthatAndroidhastriedtwicetofindaconsumerfortheACTION_DOWNeventandfailed,itgoestothenextViewintheapplicationthatcouldpossiblyreceivetheevent,whichinourcaseisthelayoutunderneaththebutton.WecalledourtoplayouttrueLayoutTop,andwecanseethatitreceivedtheACTION_DOWNevent.
NoticethatouronTouch()methodgotcalledagain,althoughnowwiththelayoutviewandnotthebuttonview.EverythingabouttheMotionEventobjectpassedtoonTouch()fortrueLayoutTopisthesameasbefore,includingthetimes,exceptfortheycoordinateofthelocation.Theycoordinatechangedfrom44.281494forthebuttonto116.281494forthelayout.ThismakessensebecausetheReturnsFalsebuttonisnotintheupper-leftcornerofthelayout,it’sbelowtheReturnsTruebutton.Thereforetheycoordinateofthetouchrelativetothelayoutislargerthantheycoordinateofthesametouchrelativetothebutton;thetouchisfurtherawayfromthetopedgeofthelayoutthanitisfromthetopedgeofthebutton.BecauseonTouch()forthetrueLayoutTopreturnstrue,Androidsendstherestofthetoucheventstothelayout,andweseethelogrecordscorrespondingtotheACTION_MOVEandtheACTION_UPevents.GoaheadandtouchthetopReturnsFalsebuttonagain,andnoticethatthesamesetoflogrecordsoccurs.Thatis,onTouch()iscalledforfalseBtnTop,onTouchEvent()iscalledforfalseBtnTop,andthenonTouch()iscalledfortrueLayoutTopfortherestoftheevents.Androidonlystopssendingtheeventstothebuttonforonetouchsequenceatatime.Foranewsequenceoftouchevents,Androidwillsendtothebuttonunlessitgets
anotherreturnoffalsefromthecalledmethod,whichitstilldoesinoursampleapplication.
Nowtouchyourfingeronthetoplayoutbutnotoneitherbutton,andthendragyourfingeraroundabitandliftitoffthetouchscreen(ifyou’reusingtheemulator,justuseyourmousetomakeasimilarmotion).NoticeastreamoflogmessagesinLogCat,wherethefirstrecordhasanactionofACTION_DOWN,andthenmanyACTION_MOVEeventsarefollowedbyanACTION_UPevent.
Now,touchthetopReturnsTruebutton,andbeforeliftingyourfingerfromthebutton,dragyourfingeraroundthescreenandthenliftitoff.Listing22-6showssomenewinformationinLogCat.
Listing22-6.LogCatRecordsShowingaTouchOutsideofOurView
[...logmessagesofanACTION_DOWNeventfollowedbysomeACTION_MOVEevents…]
trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:2trueBtnTopLocation:150.41768x22.628128trueBtnTop>>>Touchhaslefttheview<<<trueBtnTopEdgeflags:0trueBtnTopPressure:0.047058824Size:0.13333334trueBtnTopDowntime:31690859mstrueBtnTopEventtime:31691344msElapsed:485mstrueBtnTopandI'mreturningtrue
[...moreACTION_MOVEeventslogged…]
trueBtnTopGotviewtrueBtnTopinonTouchtrueBtnTopAction:1trueBtnTopLocation:291.5864x223.43854trueBtnTop>>>Touchhaslefttheview<<<trueBtnTopEdgeflags:0trueBtnTopPressure:0.047058824Size:0.13333334trueBtnTopDowntime:31690859mstrueBtnTopEventtime:31692493msElapsed:1634mstrueBtnTopandI'mreturningtrue
Evenafteryourfingerdragsitselfoffofthebutton,wecontinuetogetnotifiedoftoucheventsrelatedtothebutton.ThefirstrecordinListing22-6showsaneventrecordwherewe’renolongeronthebutton.Inthiscase,thexcoordinateofthetoucheventistotherightoftheedgeofourbuttonobject.However,wekeepgettingcalledwithMotionEventobjectsuntilwegetanACTION_UPevent,becausewecontinuetoreturntruefromtheonTouch()method.Evenwhenyoufinallyliftyourfingeroffofthetouchscreen,andevenifyourfingerisn’tonthebutton,ouronTouch()methodstillgetscalledtogiveustheACTION_UPeventbecausewekeepreturningtrue.Thisis
somethingtokeepinmindwhendealingwithMotionEvents.Whenthefingerhasmovedoffoftheview,wecoulddecidetocancelwhateveroperationmighthavebeenperformedandreturnfalsefromtheonTouch()method,sowedon’tgetnotifiedoffurtherevents.Orwecouldchoosetocontinuetoreceiveevents(byreturningtruefromtheonTouch()method)andonlyperformthelogicifthefingerreturnstoourviewbeforeliftingoff.
ThetouchsequenceofeventsgotassociatedtoourtopReturnsTruebuttonwhenwereturnedtruefromonTouch().ThistoldAndroidthatitcouldstoplookingforanobjecttoreceivetheMotionEventobjectsandjustsendallfutureMotionEventobjectsforthistouchsequencetous.Evenifweencounteranotherviewwhendraggingourfinger,we’restilltiedtotheoriginalviewforthissequence.
ExercisingtheBottomHalfoftheExampleApplicationLet’sseewhathappenswiththelowerhalfofourapplication.GoaheadandtouchtheReturnsTruebuttoninthebottomhalf.WeseethesamethingashappenedwiththetopReturnsTruebutton.BecauseonTouch()returnstrue,Androidsendsustherestoftheeventsinthetouchsequenceuntilthefingerisliftedfromthetouchscreen.Now,touchthebottomReturnsFalsebutton.Onceagain,theonTouch()methodandonTouchEvent()methodsreturnfalse(bothassociatedwiththefalseBtnBottomviewobject).Butthistime,thenextviewtoreceivetheMotionEventobjectisthefalseLayoutBottomobject,anditalsoreturnsfalse.Now,we’refinished.
BecausetheonTouchEvent()methodcalledthesuper’sonTouchEvent()method,thebuttonhaschangedcolortoindicateit’shalfwaythroughbeingpressed.Again,thebuttonwillstaythisway,becausewenevergettheACTION_UPeventinthistouchsequence,becauseourmethodsreturnfalseallthetime.Unlikebefore,eventhelayoutisnotinterestedinthisevent.IfyouweretotouchthebottomReturnsFalsebuttonandholditdownandthendragyourfingeraroundthedisplay,youwouldnotseeanyextrarecordsinLogCat,becausenomoreMotionEventobjectsaresenttous.Wereturnedfalse,soAndroidwon’tbotheruswithanymoreeventsforthistouchsequence.Again,ifwestartanewtouchsequence,wecanseenewLogCatrecordsshowingup.Ifyouinitiateatouchsequenceinthebottomlayoutandnotonabutton,youwillseeasingleeventinLogCatforfalseLayoutBottomthatreturnsfalseandthennothingafterthat(untilyoustartanewtouchsequence).
Sofar,we’veusedbuttonstoshowyoutheeffectsofMotionEventeventsfromtouchscreens.It’sworthpointingoutthat,normally,youwouldimplementlogiconbuttonsusingtheonClick()method.Weusedbuttonsforthissampleapplication,becausethey’reeasytocreateandtheyaresubclassesofViewthatcanthereforereceivetoucheventsjustlikeanyotherview.RememberthatthesetechniquesapplytoanyViewobjectinyourapplication,beitastandardorcustomizedviewclass.
RecyclingMotionEvents
Youmayhavenoticedtherecycle()methodoftheMotionEventclassintheAndroidreferencedocumentation.ItistemptingtowanttorecycletheMotionEventsthatyoureceiveinonTouch()oronTouchEvent(),butdon’tdoit.IfyourcallbackmethodisnotconsumingtheMotionEventobjectandyou’rereturningfalse,theMotionEventobjectislikelytobehandedtosomeothermethodorvieworouractivity,soyoudon’twantAndroidrecyclingityet.Evenifyouconsumedtheeventandreturnedtrue,theeventobjectdoesn’tbelongtoyou,soyoushouldnotrecycleit.
IfyoulookatMotionEventdocumentation,youwillseeafewvariationsofamethodcalledobtain().ThisiseithercreatingacopyofaMotionEventorabrandnewMotionEvent.Yourcopy,oryourbrand-neweventobject,istheeventobjectthatyoushouldrecyclewhenyouaredonewithit.Forexample,ifyouwanttohangontoaneventobjectthatispassedtoyouviaacallback,youshoulduseobtain()tomakeacopy,becauseonceyoureturnfromthecallback,thateventobjectwillberecycledbyAndroid,andyoumaygetstrangeresultsifyoucontinuetouseit.Whenyouarefinishedusingyourcopy,youinvokerecycle()onit.
UsingVelocityTrackerAndroidprovidesaclasstohelphandletouchscreensequences,andthatclassisVelocityTracker.Whenafingerisinmotiononatouchscreen,itmightbenicetoknowhowfastitismovingacrossthesurface.Forexample,iftheuserisdragginganobjectacrossthescreenandletsgo,yourapplicationprobablywantstoshowthatobjectflyingacrossthescreenaccordingly.AndroidprovidesVelocityTrackertohelpwiththemathinvolved.
TouseVelocityTracker,youfirstgetaninstanceofaVelocityTrackerbycallingthestaticmethodVelocityTracker.obtain().YoucanthenaddMotionEventobjectstoitwiththeaddMovement(MotionEventev)method.YouwouldcallthismethodinyourhandlerthatreceivesMotionEventobjects,fromahandlermethodsuchasonTouch(),orfromaview’sonTouchEvent().TheVelocityTrackerusestheMotionEventobjectstofigureoutwhatisgoingonwiththeuser’stouchsequence.OnceVelocityTrackerhasatleasttwoMotionEventobjectsinit,wecanusetheothermethodstofindoutwhat’shappening.
ThetwoVelocityTrackermethods—getXVelocity()andgetYVelocity()—returnthecorrespondingvelocityofthefingerinthexandydirections,respectively.Thevaluereturnedfromthesetwomethodswillrepresentpixelspertimeperiod.Thiscouldbepixelspermillisecondorpersecondorreallyanythingyouwant.TotelltheVelocityTrackerwhattimeperiodtouse,andbeforeyoucancallthesetwogettermethods,youneedtoinvoketheVelocityTracker’scomputeCurrentVelocity(intunits)method.Thevalueofunitsrepresentshowmanymillisecondsareinthetimeperiodformeasuringthevelocity.Ifyouwantpixelspermillisecond,useaunitsvalueof1;ifyouwantpixelspersecond,useaunitsvalueof1000.ThevaluereturnedbythegetXVelocity()and
getYVelocity()methodswillbepositiveifthevelocityistowardtheright(forx)ordown(fory).Thevaluereturnedwillbenegativeifthevelocityistowardtheleft(forx)orup(fory).
WhenyouarefinishedwiththeVelocityTrackerobjectyougotwiththeobtain()method,calltheVelocityTrackerobject’srecycle()method.Listing22-7showsasampleonTouchEvent()handlerforanactivity.ItturnsoutthatanactivityhasanonTouchEvent()callback,whichiscalledwhenevernoviewshavehandledthetouchevent.Becausewe’reusingastock,emptylayout,wehavenoviewsconsumingourtouchevents.
Listing22-7.SampleActivityThatUsesVelocityTracker
importandroid.view.MotionEvent;importandroid.view.VelocityTracker;
publicclassMainActivityextendsActivity{privatestaticfinalStringTAG="VelocityTracker";
/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);}
privateVelocityTrackervTracker=null;
publicbooleanonTouchEvent(MotionEventevent){intaction=event.getAction();switch(action){caseMotionEvent.ACTION_DOWN:if(vTracker==null){vTracker=VelocityTracker.obtain();}else{vTracker.clear();}vTracker.addMovement(event);break;caseMotionEvent.ACTION_MOVE:vTracker.addMovement(event);vTracker.computeCurrentVelocity(1000);Log.v(TAG,"Xvelocityis"+vTracker.getXVelocity()+"pixelspersecond");Log.v(TAG,"Yvelocityis"+vTracker.getYVelocity()+
"pixelspersecond");break;caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:Log.v(TAG,"FinalXvelocityis"+vTracker.getXVelocity()+"pixelspersecond");Log.v(TAG,"FinalYvelocityis"+vTracker.getYVelocity()+"pixelspersecond");vTracker.recycle();vTracker=null;break;}returntrue;}}
Obviously,whenyou’veonlyaddedoneMotionEventtoaVelocityTracker(theACTION_DOWNevent),thevelocitiescannotbecomputedasanythingotherthanzero.ButweneedtoaddthestartingpointsothatthesubsequentACTION_MOVEeventscancalculatevelocitiesthen.
VelocityTrackerissomewhatcostlyintermsofperformance,souseitsparingly.Also,makesurethatyourecycleitassoonasyouaredonewithit.TherecanbemorethanoneVelocityTrackerinuseinAndroid,buttheycantakeupalotofmemory,sogiveyoursbackifyou’renotgoingtocontinuetouseit.InListing22-7,wealsousetheclear()methodifwe’restartinganewtouchsequence(thatis,ifwegetanACTION_DOWNeventandourVelocityTrackerobjectalreadyexists)insteadofrecyclingthisoneandobtaininganewone.
MultitouchNowthatyou’veseensingletouchesinaction,let’smoveontomultitouch.MultitouchhasgainedalotofinteresteversincetheTEDconferencein2006atwhichJeffHandemonstratedamultitouchsurfaceforacomputeruserinterface.Usingmultiplefingersonascreenopensupalotofpossibilitiesformanipulatingwhat’sonthescreen.Forexample,puttingtwofingersonanimageandmovingthemapartcouldzoominontheimage.Byplacingmultiplefingersonanimageandturningclockwise,youcouldrotatetheimageonthescreen.ThesearestandardtouchoperationsinGoogleMaps,forinstance.
Ifyouthinkaboutit,though,thereisnomagictothis.Ifthescreenhardwarecandetectmultipletouchesastheyinitiateonthescreen,notifyyourapplicationasthosetouchesmoveintimeacrossthesurfaceofthescreen,andnotifyyouwhenthosetouchesliftoffofthescreen,yourapplicationcanfigureoutwhattheuseristryingtodowiththosetouches.Althoughit’snotmagic,itisn’teasyeither.We’regoingtohelpyouunderstand
multitouchinthissection.
TheBasicsofMultitouchThebasicsofmultitouchareexactlythesameasforsingletouches.MotionEventobjectsgetcreatedfortouches,andtheseMotionEventobjectsarepassedtoyourmethodsjustlikebefore.Yourcodecanreadthedataaboutthetouchesanddecidewhattodo.Atabasiclevel,themethodsofMotionEventarethesame;thatis,wecallgetAction(),getDownTime(),getX(),andsoon.However,whenmorethanonefingeristouchingthescreen,theMotionEventobjectmustincludeinformationfromallfingers,withsomecaveats.TheactionvaluefromgetAction()isforonefinger,notall.Thedowntimevalueisfortheveryfirstfingerdownandmeasuresthetimeaslongasatleastonefingerisdown.ThelocationvaluesgetX()andgetY(),aswellasgetPressure()andgetSize(),cantakeanargumentforthefinger;therefore,youneedtouseapointerindexvaluetorequesttheinformationforthefingeryou’reinterestedin.Therearemethodcallsthatweusedpreviouslythatdidnottakeanyargumenttospecifyafinger(forexample,getX(),getY()),sowhichfingerwouldthevaluesbeforifweusedthosemethods?Youcanfigureitout,butittakessomework.Therefore,ifyoudon’ttakeintoaccountmultiplefingersallofthetime,youmightendupwithsomestrangeresults.Let’sdigintothistofigureoutwhattodo.
ThefirstmethodofMotionEventyouneedtoknowaboutformultitouchisgetPointerCount().ThistellsyouhowmanyfingersarerepresentedintheMotionEventobjectbutdoesn’tnecessarilytellyouhowmanyfingersareactuallytouchingthescreen;thatdependsonthehardwareandontheimplementationofAndroidonthathardware.Youmayfindthat,oncertaindevices,getPointerCount()doesnotreportallfingersthataretouching,justsome.Butlet’spresson.Assoonasyou’vegotmorethanonefingerbeingreportedinMotionEventobjects,youneedtostartdealingwiththepointerindexesandthepointerIDs.
TheMotionEventobjectcontainsinformationforpointersstartingatindex0andgoinguptothenumberoffingersbeingreportedinthatobject.Thepointerindexalwaysstartsat0;ifthreefingersarebeingreported,pointerindexeswillbe0,1,and2.CallstomethodssuchasgetX()mustincludethepointerindexforthefingeryouwantinformationabout.PointerIDsareintegervaluesrepresentingwhichfingerisbeingtracked.PointerIDsstartat0forthefirstfingerdownbutdon’talwaysstartat0oncefingersarecomingandgoingonthescreen.ThinkofapointerIDasthenameofthatfingerwhileitisbeingtrackedbyAndroid.Forexample,imagineapairoftouchsequencesfortwofingers,startingwithfinger1down,andfollowedbyfinger2down,finger1up,andfinger2up.ThefirstfingerdownwillgetpointerID0.ThesecondfingerdownwillgetpointerID1.Oncethefirstfingergoesup,thesecondfingerwillstillbepointerID1.Atthatpoint,thepointerindexforthesecondfingerbecomes0,becausethepointerindexalwaysstartsat0.Inthisexample,thesecondfinger(pointerID1)startsaspointerindex1whenitfirsttouchesdownandthenshiftstopointerindex0oncethefirstfingerleavesthescreen.Evenwhenthesecondfingeristheonlyfingeronthescreen,itremainsaspointerID1.YourapplicationswillusepointerIDstolinktogethertheevents
associatedtoaparticularfingerevenasotherfingersareinvolved.Let’slookatanexample.
Listing22-8showsournewXMLlayoutplusourJavacodeforamultitouchapplication.ThisistheapplicationcalledMultiTouchDemo1.Figure22-2showswhatitshouldlooklike.
Listing22-8.XMLLayoutandJavaforaMultitouchDemonstration
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileis/res/layout/main.xml--><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/androidandroid:id="@+id/layout1"android:tag="trueLayout"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1">
<TextViewandroid:text="TouchfingersonthescreenandlookatLogCat"android:id="@+id/message"android:tag="trueText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"/>
</RelativeLayout>
//ThisfileisMainActivity.javaimportandroid.view.MotionEvent;importandroid.view.View.OnTouchListener;
publicclassMainActivityextendsActivityimplementsOnTouchListener{/**Calledwhentheactivityisfirstcreated.*/@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
RelativeLayoutlayout1=(RelativeLayout)findViewById(R.id.layout1);layout1.setOnTouchListener(this);}
publicbooleanonTouch(Viewv,MotionEventevent){StringmyTag=v.getTag().toString();Log.v(myTag,"-----------------------------");
Log.v(myTag,"Gotview"+myTag+"inonTouch");Log.v(myTag,describeEvent(event));logAction(event);if("true".equals(myTag.substring(0,4))){returntrue;}else{returnfalse;}}
protectedstaticStringdescribeEvent(MotionEventevent){StringBuilderresult=newStringBuilder(500);result.append("Action:").append(event.getAction()).append("\n");intnumPointers=event.getPointerCount();result.append("Numberofpointers:");result.append(numPointers).append("\n");intptrIdx=0;while(ptrIdx<numPointers){intptrId=event.getPointerId(ptrIdx);result.append("PointerIndex:").append(ptrIdx);result.append(",PointerId:").append(ptrId).append("\n");result.append("Location:").append(event.getX(ptrIdx));result.append("x").append(event.getY(ptrIdx)).append("\n");result.append("Pressure:");result.append(event.getPressure(ptrIdx));result.append("Size:").append(event.getSize(ptrIdx));result.append("\n");
ptrIdx++;}result.append("Downtime:").append(event.getDownTime());result.append("ms\n").append("Eventtime:");result.append(event.getEventTime()).append("ms");result.append("Elapsed:");result.append(event.getEventTime()-event.getDownTime());result.append("ms\n");returnresult.toString();
}
privatevoidlogAction(MotionEventevent){intaction=event.getActionMasked();intptrIndex=event.getActionIndex();intptrId=event.getPointerId(ptrIndex);
if(action==5||action==6)action=action-5;
Log.v("Action","Pointerindex:"+ptrIndex);Log.v("Action","PointerId:"+ptrId);Log.v("Action","Trueactionvalue:"+action);}}
Figure22-2.Ourmultitouchdemonstrationapplication
Ifyouonlyhavetheemulator,thisapplicationwillstillwork,butyouwon’tbeabletogetmultiplefingerssimultaneouslyonthescreen.You’llseeoutputsimilartowhatwesawin
thepreviousapplication.Listing22-9showssampleLogCatmessagesforatouchsequencelikewedescribedearlier.Thatis,thefirstfingerpressesonthescreen,andthenthesecondfingerpresses,thefirstfingerleavesthescreen,andthesecondfingerleavesthescreen.Listing22-9.SampleLogCatOutputforaMultitouchApplication
trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:0trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:114.88211x499.77502trueLayoutPressure:0.047058824Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33733650msElapsed:0msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:0trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:2trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:114.88211x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33733740msElapsed:90msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:2trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:261trueLayoutNumberofpointers:2trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:114.88211x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutPointerIndex:1,PointerId:1trueLayoutLocation:320.30692x189.67395trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33733962msElapsed:312msActionPointerindex:1ActionPointerId:1ActionTrueActionvalue:0trueLayout-----------------------------
trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:2trueLayoutNumberofpointers:2trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:111.474594x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutPointerIndex:1,PointerId:1trueLayoutLocation:320.30692x189.67395trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734189msElapsed:539msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:2trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:6trueLayoutNumberofpointers:2trueLayoutPointerIndex:0,PointerId:0trueLayoutLocation:111.474594x499.77502trueLayoutPressure:0.05882353Size:0.13333334trueLayoutPointerIndex:1,PointerId:1trueLayoutLocation:320.30692x189.67395trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734228msElapsed:578msActionPointerindex:0ActionPointerId:0ActionTrueActionvalue:1trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:2trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:1trueLayoutLocation:318.84656x191.45105trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734240msElapsed:590msActionPointerindex:0ActionPointerId:1ActionTrueActionvalue:2trueLayout-----------------------------trueLayoutGotviewtrueLayoutinonTouchtrueLayoutAction:1trueLayoutNumberofpointers:1trueLayoutPointerIndex:0,PointerId:1
trueLayoutLocation:314.95224x190.5625trueLayoutPressure:0.050980393Size:0.13333334trueLayoutDowntime:33733650mstrueLayoutEventtime:33734549msElapsed:899msActionPointerindex:0ActionPointerId:1ActionTrueActionvalue:1
UnderstandingMultitouchContentsWe’llnowdiscusswhatisgoingonwiththisapplication.ThefirsteventweseeistheACTION_DOWN(actionvalueof0)ofthefirstfinger.WelearnaboutthisusingthegetAction()method.PleaserefertothedescribeEvent()methodinMainActivity.javatofollowalongwithwhichmethodsproducewhichoutput.Wegetonepointerwithindex0andpointerID0.Afterthat,you’llprobablyseeseveralACTION_MOVEevents(actionvalueof2)forthisfirstfinger,eventhoughwe’reonlyshowingoneoftheseinListing22-9.WestillonlyhaveonepointerandtheindexandIDarestillboth0.
Alittlelaterwegetthesecondfingertouchingthescreen.Theactionisnowadecimalvalueof261.Whatdoesthismean?Theactionvalueisactuallymadeupoftwoparts:anindicatorofwhichpointertheactionisforandwhatactionthatpointerisdoing.Convertingdecimal261tohexadecimal,weget0x00000105.Theactionisthesmallestbyte(5inthiscase),andthepointerindexisthenextbyteover(1inthiscase).NotethatthistellsusthepointerindexbutnotthepointerID.Ifyoupressedathirdfingerontothescreen,theactionwouldbe0x00000205(ordecimal517).Afourthfingerwouldbe0x00000305(ordecimal773)andsoon.Youhaven’tseenanactionvalueof5yet,butit’sknownasACTION_POINTER_DOWN.It’sjustlikeACTION_DOWNexceptthatit’susedinmultitouchsituations.
Now,lookatthenextpairofrecordsfromLogCatinListing22-9.ThefirstrecordisforanACTION_MOVEevent(actionvalueof2).Rememberthatitisdifficulttokeepfingersfrommovingonarealscreen.We’reonlyshowingoneACTION_MOVEevent,butyoumightseeseveralwhenyoutrythisforyourself.Whenthefirstfingerisliftedoffofthescreen,wegetanactionvalueof0x00000006(ordecimal6).Likebefore,wehavepointerindex0andanactionvaluethatisACTION_POINTER_UP(similartoACTION_UPbutformultitouchsituations).Ifthesecondfingerwasliftedinamultitouchsituation,wewouldgetanactionvalueof0x00000106(ordecimal262).NoticehowwestillhaveinformationfortwofingerswhenwegettheACTION_UPforoneofthem.
ThelastpairofrecordsinListing22-9showsonemoreACTION_MOVEeventforthesecondfinger,followedbyanACTION_UPforthesecondfinger.Thistime,weseeanactionvalueof1(ACTION_UP).Wedidn’tgetanactionvalueof262,butwe’llexplainthatnext.Also,noticethatoncethefirstfingerleftthescreen,thepointerindexforthesecondfingerhaschangedfrom1to0,butthepointerIDhasremainedas1.
ACTION_MOVEeventsdonottellyouwhichfingermoved.Youwillalwaysgetanaction
valueof2foramoveregardlessofhowmanyfingersaredownorwhichfingerisdoingthemoving.AlldownfingerpositionsareavailablewithintheMotionEventobject,soyouneedtoreadthepositionsandthenfigurethingsout.Ifthere’sonlyonefingerleftonthescreen,thepointerIDwilltellyouwhichfingeritisthat’sstillmovingbecauseit’stheonlyfingerleft.InListing22-9,whenthesecondfingerwastheonlyoneleftonthescreen,theACTION_MOVEeventhadapointerindexof0andapointerIDof1,soweknewitwasthesecondfingerthatwasmoving.
NotonlycanaMotionEventobjectcontainmoveeventsformorethanonefinger,butitcanalsocontainmultiplemoveeventsperfinger.Itdoesthisusinghistoricalvaluescontainedwithintheobject.AndroidshouldreportallhistorysincethelastMotionEventobject.SeegetHistoricalSize()andtheothergetHistorical…()methods.
GoingbacktothebeginningofListing22-9,thefirstfingerdownispointerindex0andpointerID0,sowhydon’tweget0x00000005(ordecimal5)fortheactionvaluewhenthefirstfingerispressedtothescreenbeforeanyotherfingers?Unfortunately,thisquestiondoesn’thaveahappyanswer.Wecangetanactionvalueof5inthefollowingscenario:pressthefirstfingertothescreenandthenthesecondfinger,resultinginactionvaluesof0and261(ignoringtheACTION_MOVEeventsforthemoment).Now,liftthefirstfinger(actionvalueof6),andpressitbackdownonthescreen.ThepointerIDofthesecondfingerremainedas1.Forthemomentwhenthefirstfingerwasintheair,ourapplicationknewaboutpointerID1only.Oncethefirstfingertouchedthescreenagain,AndroidreassignedpointerID0tothefirstfingerandgaveitpointerindex0aswell.Becausenowweknowtherearemultiplefingersinvolved,wegetanactionvalueof5(pointerindexof0andtheactionvalueof5).Theanswertothequestion,therefore,isbackwardcompatibility,butitisnotahappyanswer.Theactionvaluesof0and1arepre-multitouch.
Whenonlyonefingerremainsonthescreen,Androidtreatsitlikeasingle-touchcase.SowegettheoldACTION_UPvalueof1insteadofamultitouchACTION_UPvalueof6.Ourcodewillneedtoconsiderthesecasescarefully.Apointerindexof0couldresultinanACTION_DOWNvalueof0or5,dependingonwhichpointersareinplay.ThelastfingerupwillgetanACTION_UPvalueof1nomatterwhichpointerIDithas.
Thereisanotheractionwehaven’tmentionedsofar:ACTION_SCROLL(valueof8),introducedinAndroid3.1.Thiscomesfromaninputdevicelikeamouse,notatouchscreen.Infact,asyoucanseefromthemethodsinMotionEvent,theseobjectscanbeusedforlotsofthingsotherthantouchscreentouches.Wewon’tbecoveringtheseotherinputdevicesinthisbook.
GesturesGesturesareaspecialtypeofatouchscreenevent.ThetermgestureisusedforavarietyofthingsinAndroid,fromasimpletouchsequencelikeaflingorapinchtotheformalGestureclass.Flings,pinches,longpresses,andscrollshaveexpectedbehaviorswith
expectedtriggers.Thatis,itisprettycleartomostpeoplethataflingisagesturewhereafingertouchesthescreen,dragssomewhatquicklyoffinasingledirection,andthenliftsup.Forexample,whensomeoneusesaflingintheGalleryapplication(theonethatshowsimagesinaleft-to-rightchain),theimageswillmovesidewaystoshownewimagestotheuser.
Inthefollowingsections,youwilllearnhowtoimplementapinchgesture,fromwhichyoucaneasilyimplementtheothercommongestures.TheformalGestureclassreferstogesturesdrawnbyauseronatouchscreen,sothatanapplicationcanreacttothosegestures.Thetypicalexampleincludesdrawinglettersofthealphabetwhichtheapplicationcanunderstandasletters.TheformalGestureclassisnotcoveredinthisbook.Let'slearntopinch!
ThePinchGestureOneofthecoolapplicationsofmultitouchisthepinchgesture,whichisusedforzooming.Theideaisthatifyouplacetwofingersonthescreenandspreadthemapart,theapplicationshouldrespondbyzoomingin.Ifyourfingerscometogether,theapplicationshouldzoomout.Theapplicationisusuallyshowingimages,whichcouldbemaps.
Beforewegettothepinchgesture’snativesupport,wefirstneedtocoveraclassthat’sbeenaroundfromthebeginning—GestureDetector.
GestureDetectorandOnGestureListenersThefirstclasstohelpuswithgesturesisGestureDetector,whichhasbeenaroundfromtheverybeginningofAndroid.ItspurposeinlifeistoreceiveMotionEventobjectsandtelluswhenasequenceofeventslookslikeacommongesture.WepassallofoureventobjectstotheGestureDetectorfromourcallback,anditcallsothercallbackswhenitrecognizesagesture,suchasaflingorlongpress.WeneedtoregisteralistenerforthecallbacksfromtheGestureDetector,andthisiswhereweputourlogicthatsayswhattodoiftheuserhasperformedoneofthesecommongestures.Unfortunately,thisclassdoesnottellusifapinchgestureistakingplace;forthat,weneedtouseanewclass,whichwe’llgettoshortly.
Thereareafewwaystobuildthelistenerside.Yourfirstoptionistowriteanewclassthatimplementstheappropriategesturelistenerinterface:forexample,theGestureDetector.OnGestureListenerinterface.Thereareseveralabstractmethodsthatmustbeimplementedforeachofthepossiblecallbacks.
Yoursecondoptionistopickoneofthesimpleimplementationsofalistenerandoverridetheappropriatecallbackmethodsthatyoucareabout.Forexample,theGestureDetector.SimpleOnGestureListenerclasshasimplementedalloftheabstractmethodstodonothingandreturnfalse.Allyouhavetodoisextendthatclassandoverridethefewmethodsyouneedtoactonthosefewgesturesyoucareabout.Theothermethodshavetheirdefaultimplementations.It’smorefuture-prooftochoosethesecondoptionevenifyoudecidetooverrideallofthecallbackmethods,becauseifa
futureversionofAndroidaddsanotherabstractcallbackmethodtotheinterface,thesimpleimplementationwillprovideadefaultcallbackmethod,soyou’recovered.
We’regoingtoexploreScaleGestureDetector,plusthecorrespondinglistenerclass,toseehowtousethepinchgesturetoresizeanimage.Inthisexample,weextendthesimpleimplementation(ScaleGestureDetector.SimpleOnScaleGestureListener)forourlistener.Listing22-10hastheXMLlayoutandtheJavacodeforourMainActivity.
Listing22-10.LayoutandJavaCodeforthePinchGestureUsingScaleGestureDetector
<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/layout"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">
<TextViewandroid:text="Usethepinchgesturetochangetheimagesize"android:layout_width="match_parent"android:layout_height="wrap_content"/>
<ImageViewandroid:id="@+id/image"android:src="@drawable/icon"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="matrix"/>
</LinearLayout>
//ThisfileisMainActivity.javapublicclassMainActivityextendsActivity{privatestaticfinalStringTAG="ScaleDetector";privateImageViewimage;privateScaleGestureDetectormScaleDetector;privatefloatmScaleFactor=1f;privateMatrixmMatrix=newMatrix();@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
image=(ImageView)findViewById(R.id.image);mScaleDetector=newScaleGestureDetector(this,newScaleListener());}
@OverridepublicbooleanonTouchEvent(MotionEventev){Log.v(TAG,"inonTouchEvent");//GivealleventstoScaleGestureDetectormScaleDetector.onTouchEvent(ev);
returntrue;}
privateclassScaleListenerextendsScaleGestureDetector.SimpleOnScaleGestureListener{@OverridepublicbooleanonScale(ScaleGestureDetectordetector){mScaleFactor*=detector.getScaleFactor();
//Makesurewedon'tgettoosmallortoobigmScaleFactor=Math.max(0.1f,Math.min(mScaleFactor,5.0f));
Log.v(TAG,"inonScale,scalefactor="+mScaleFactor);mMatrix.setScale(mScaleFactor,mScaleFactor);
image.setImageMatrix(mMatrix);image.invalidate();returntrue;}}}
Ourlayoutisstraightforward.WehaveasimpleTextViewwithourmessagetousethepinchgesture,andwehaveourImageViewwiththestandardAndroidicon.We’regoingtoresizethisiconimageusingapinchgesture.Ofcourse,feelfreetosubstituteyourownimagefileinsteadoftheicon.Justcopyyourimagefileintoadrawablefolder,andbesuretochangetheandroid:srcattributeinthelayoutfile.Noticetheandroid:scaleTypeattributeintheXMLlayoutforourimage.ThistellsAndroidthatwe’llbeusingagraphicsmatrixtodoscalingoperationsontheimage.Althoughagraphicsmatrixcanalsodomovementofourimagewithinthelayout,we’reonlygoingtofocusonscalingfornow.AlsonoticethatwesettheImageViewsizetoasbigaspossible.Aswescaletheimage,wedon’twantitclippedbytheboundariesoftheImageView.
Thecodeisalsostraightforward.WithinonCreate(),wegetareferencetoourimageandcreateourScaleGestureDetector.WithinouronTouchEvent()callback,
allwedoispasseveryeventobjectwegettotheScaleGestureDetector’sonTouchEvent()methodandreturntruesowekeepgettingnewevents.ThisallowstheScaleGestureDetectortoseealleventsanddecidewhentonotifyusofgestures.
TheScaleListeneriswherethezoominghappens.Thereareactuallythreecallbackswithinthelistenerclass:onScaleBegin(),onScale(),andonScaleEnd().Wedon’tneedtodoanythingspecialwiththebeginandendmethods,sowedidn’timplementthemhere.
WithinonScale(),thedetectorpassedincanbeusedtofindoutlotsofinformationaboutthescalingoperation.Thescalefactorisavaluethathoversaround1.Thatis,asthefingerspinchclosertogether,thisvalueisslightlybelow1;asthefingersmoveapart,thisvalueisslightlylargerthan1.OurmScaleFactormemberstartsat1,soitgetsprogressivelysmallerorlargerthan1asthefingersmovetogetherorapart.IfmScaleFactorequals1,ourimagewillbenormalsize.Otherwise,ourimagewillbesmallerorlargerthannormalasmScaleFactormovesbeloworabove1.WesetsomeboundsonmScaleFactorwiththeelegantmin/maxfunctioncombination.Thispreventsourimagefromgettingtoosmallortoolarge.WethenusemScaleFactortoscalethegraphicsmatrix,andweapplythenewlyscaledmatrixtoourimage.Theinvalidate()callforcesaredrawoftheimageonthescreen.
ToworkwiththeOnGestureListenerinterface,you’ddosomethingverysimilartowhatwe’vedoneherewithourScaleListener,exceptthatthecallbackswillbefordifferentcommongesturessuchassingletap,doubletap,longpress,andfling.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther.
www.androidbook.com/proandroid5/projects:Downloadableprojectsrelatedtothisbook.Forthischapter,lookforazipfilecalledProAndroid5_Ch22_Touchscreens.zip.Thiszipfilecontainsallprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneofthesezipfiles.
www.ted.com/talks/jeff_han_demos_his_breakthrough_touchscreen.htmlJeffHandemonstrateshismultitouchcomputeruserinterfaceatTEDin2006—verycool.
http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html:AnAndroidblogpostaboutmultitouchoffersyetanotherwaytoimplementaGestureDetectorinside
anextensionofaview.
SummaryLet’sconcludethischapterbyquicklyenumeratingwhatyouhavelearnedabouttouchscreenssofar:
MotionEventasthefoundationonwhichtouchhandlingisdone
DifferentcallbacksthathandletoucheventsonaViewobjectandthroughanOnTouchListener
Differenttypesofeventsthatoccurduringatouchsequence
Howtoucheventstravelthroughanentireviewhierarchy,unlesshandledalongtheway
InformationthataMotionEventobjectcontainsabouttouches,includingformultiplefingers
WhentorecycleaMotionEventobjectandwhennotto
Determiningthespeedatwhichafingerdragsacrossascreen
Thewonderfulworldofmultitouch,andtheinternaldetailsofhowitworks
Implementingthepinchgesture,aswellasothercommongestures
Chapter23
ImplementingDragandDropInthelastchapter,wecoveredtouchscreens,theMotionEventclass,andgestures.Youlearnedhowtousetouchtomakethingshappeninyourapplication.Oneareathatwedidn’tcoverwasdraganddrop.Onthesurface,draganddropseemslikeitshouldbefairlysimple:touchanobjectonthescreen,dragitacrossthescreen(usuallyoversomeotherobject),andletgo,andtheapplicationshouldtaketheappropriateaction.Inmanycomputeroperatingsystems,thisisacommonwaytodeleteafilefromthedesktop;youjustdragthefile’sicontothetrash-binicon,andthefilegetsdeleted.InAndroid,youmayhaveseenhowtorearrangeiconsonthehomescreenbydraggingthemtonewlocationsortothetrash.
Thischapterisgoingtogoindepthintodraganddrop.PriortoAndroid3.0,developerswereontheirownwhenitcametodraganddrop.ButbecausetherearestillquiteafewphonesoutthererunningAndroid2.3,we’llshowyouhowtododraganddroponthem.We’llshowyoutheoldwayinthefirstsectionofthischapter,andthenwe'llshowyouthenewwayinthesecondpart.
ExploringDragandDropInthisnextexampleapplication,we’regoingtotakeawhitedotanddragittoanewlocationinouruserinterface.We’realsogoingtoplacethreecountersinouruserinterface,andiftheuserdragsthewhitedottooneofthecounters,thatcounterwillincrementandthedotwillreturnbacktoitsstartingplace.Ifthedotisdraggedsomewhereelseonthescreen,we’lljustleaveitthere.
NoteSeethe“References”sectionattheendofthischapterfortheURLfromwhichyoucanimporttheseprojectsintoyourIDEdirectly.We’llonlyshowcodeinthetexttoexplainconcepts.You'llneedtodownloadthecodetocreateaworkingexampleapplication.
ThefirstsampleapplicationforthischapteriscalledTouchDragDemo.Therearetwokeyfileswewanttotalkaboutinthissection:
/res/layout/main.xml
/src/com/androidbook/touch/dragdemo/Dot.java
Themain.xmlfilecontainsourlayoutforthedrag-and-dropdemo.ItisshowninListing23-1.SomeofthekeyconceptswewantyoutonoticearetheuseofaFrameLayoutasthetop-levellayout,insideofwhichisaLinearLayoutcontainingTextViewsandacustomViewclasscalledDot.BecausetheLinearLayoutand
DotcoexistwithintheFrameLayout,theirpositionsandsizesdon’treallyimpacteachother,buttheywillbesharingthescreenrealestate,oneontopoftheother.TheUIforthisapplicationisshowninFigure23-1.
Listing23-1.ExampleLayoutXMLforOurDragExample
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/main.xml--><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#0000ff">
<LinearLayoutandroid:id="@+id/counters"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">
<TextViewandroid:id="@+id/top"android:text="0"android:background="#111111"android:layout_height="wrap_content"android:layout_width="60dp"android:layout_gravity="right"android:layout_marginTop="30dp"android:layout_marginBottom="30dp"android:padding="10dp"/>
<TextViewandroid:id="@+id/middle"android:text="0"android:background="#111111"android:layout_height="wrap_content"android:layout_width="60dp"android:layout_gravity="right"android:layout_marginBottom="30dp"android:padding="10dp"/>
<TextViewandroid:id="@+id/bottom"android:text="0"android:background="#111111"android:layout_height="wrap_content"android:layout_width="60dp"android:layout_gravity="right"android:padding="10dp"/></LinearLayout>
<com.androidbook.touch.dragdemo.Dotandroid:id="@+id/dot"android:layout_width="match_parent"android:layout_height="match_parent"/>
</FrameLayout>
Figure23-1.UserinterfaceforTouchDragDemo
NotethatthepackagenameinthelayoutXMLfilefortheDotelementmustmatchthepackagenameyouuseforyourapplication.Asmentioned,thelayoutofDotisseparatedfromtheLinearLayout.Thisisbecausewewantthefreedomtomovethedotaroundthescreen,whichiswhywechosethelayout_widthandlayout_heightof“match_parent”.Whenwedrawthedotonthescreen,wewantittobevisible,andifweconstrictthesizeofourdot’sviewtothediameterofthedot,wewon’tbeabletoseeitwhenwedragitawayfromourstartingplace.
NoteTechnically,wecouldsetandroid:clipChildrentotrueintheFrameLayouttagandsetthelayoutwidthandheightofthedottowrap_content,butthatdoesn’tfeelasclean.
Foreachofthecounters,wesimplylaythemoutwithabackground,padding,margins,andgravitytogetthemtoshowupalongtheright-handsideofthescreen.Westartthemoffatzero,butasyou’llsoonsee,we'llbeincrementingthosevaluesasdotsaredraggedovertothem.AlthoughwechosetouseTextViewsinthisexample,youcouldusejustaboutanyViewobjectasadroptarget.NowwewilllookattheJavacodeforourDotclassinListing23-2.
Listing23-2.JavaCodeforOurDotClass
publicclassDotextendsView{privatestaticfinalStringTAG="TouchDrag";privatefloatleft=0;privatefloattop=0;privatefloatradius=20;privatefloatoffsetX;privatefloatoffsetY;
privatePaintmyPaint;privateContextmyContext;
publicDot(Contextcontext,AttributeSetattrs){super(context,attrs);
//Savethecontext(theactivity)myContext=context;
myPaint=newPaint();myPaint.setColor(Color.WHITE);myPaint.setAntiAlias(true);}
publicbooleanonTouchEvent(MotionEventevent){intaction=event.getAction();floateventX=event.getX();floateventY=event.getY();switch(action){caseMotionEvent.ACTION_DOWN://Firstmakesurethetouchisonourdot,//sincethesizeofthedot'sviewis//technicallythewholelayout.Ifthe//touchis*not*within,thenreturnfalse//indicatingwedon'twantanymoreevents.if(!(left-20<eventX&&eventX<left+radius*2+20&&top-20<eventY&&eventY<top+radius*2+20))returnfalse;
//Remembertheoffsetofthetouchascompared//toourleftandtopedges.offsetX=eventX-left;offsetY=eventY-top;break;caseMotionEvent.ACTION_MOVE:caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:left=eventX-offsetX;top=eventY-offsetY;if(action==MotionEvent.ACTION_UP){checkDrop(eventX,eventY);}break;}invalidate();returntrue;
}
privatevoidcheckDrop(floatx,floaty){//Seeifthex,yofourdroplocationisnearto//oneofourcounters.Ifso,incrementit,and//resetthedotbacktoitsstartingpositionLog.v(TAG,"checkingdroptargetfor"+x+","+y);
intviewCount=((MainActivity)myContext).counterLayout.getChildCount();
for(inti=0;i<viewCount;i++){Viewview=((MainActivity)myContext).counterLayout.getChildAt(i);if(view.getClass()==TextView.class){Log.v(TAG,"Isthedroptotherightof"+(view.getLeft()-20));Log.v(TAG,"andverticallybetween"+(view.getTop()-20)+"and"+(view.getBottom()+20)+"?");if(x>view.getLeft()-20&&view.getTop()-20<y&&y<view.getBottom()+20){Log.v(TAG,"Yes.Yesitis.");
//IncreasethecountvalueintheTextViewbyoneintcount=Integer.parseInt(((TextView)view).getText().toString());((TextView)view).setText(String.valueOf(++count));
//Resetthedotbacktostartingpositionleft=top=0;break;}}}}
publicvoiddraw(Canvascanvas){canvas.drawCircle(left+radius,top+radius,radius,myPaint);
}}
Whenyourunthisapplication,youwillseeawhitedotonabluebackground.Youcantouchthedotanddragitaroundthescreen.Whenyouliftyourfinger,thedotstayswhereitisuntilyoutouchitagainanddragitsomewhereelse.Thedraw()methodputsthedotatitscurrentlocationofleftandtop,adjustedbythedot’sradius.ByreceivingMotionEventobjectsintheonTouchEvent()method,wecanmodifytheleftandtopvaluesbythemovementofourtouch.
Becausetheuserwon’talwaystouchtheexactcenteroftheobject,thetouchcoordinateswillnotbethesameasthelocationcoordinatesoftheobject.Thatisthepurposeoftheoffsetvalues:togetusbacktotheleftandtopedgesofourdotfromthepositionofthetouch.Butevenbeforewestartadragoperation,wewanttobesurethattheuser’stouchisconsideredcloseenoughtothedottobevalid.Iftheusertouchesthescreenfarawayfromthedot,whichistechnicallywithintheviewlayoutofthedot,wedon’twantthattostartadragsequence.Thatiswhywelooktoseeifthetouchiswithinthewhitedotitself;ifitisnot,wesimplyreturnfalse,whichpreventsreceivinganymoretoucheventsinthattouchsequence.
Whenyourfingerstartsmovingacrossthescreen,weadjustthelocationoftheobjectbythedeltasinxandybasedontheMotionEventsthatweget.Whenyoustopmoving(ACTION_UP),wefinalizeourlocationusingthelastcoordinatesofyourtouch.Wedon’thavetoworryaboutscrollbarsinthisexample,whichcouldcomplicatethecalculationoftheobject’spositionofourobjectonthescreen.Butthebasicprincipleisstillthesame.ByknowingthestartinglocationoftheobjecttobemovedandkeepingtrackofthedeltavaluesofatouchfromACTION_DOWNthroughtoACTION_UP,wecanadjustthelocationoftheobjectonthescreen.
Droppinganobjectontoanotherobjectonthescreenhasmuchlesstodowithtouchthanitdoeswithknowingwherethingsareonthescreen.Aswedraganobjectaroundthescreen,weareawareofitspositionrelativetooneormorereferencepoints.Wecanalsointerrogateobjectsonthescreenfortheirlocationsandsizes.Wecanthendetermineifourdraggedobjectis“over”anotherobject.Thetypicalprocessoffiguringoutadroptargetforadraggedobjectistoiteratethroughtheavailableobjectsthatcanbedroppedonanddetermineifourcurrentpositionoverlapswiththatobject.Eachobject’ssizeandposition(andsometimesshape)canbeusedtomakethisdetermination.IfwegetanACTION_UPevent,meaningthattheuserhasletgoofourdraggedobject,andtheobjectisoversomethingwecandroponto,wecanfirethelogictoprocessthedropaction.
Weusedthisapproachinoursampleapplication.WhentheACTION_UPactionisdetected,wethenlookthroughthechildviewsoftheLinearLayout,andforeachTextViewthatisfound,wecomparethelocationofthetouchtotheedgesoftheTextView(plusalittlebitextra).IfthetouchiswithinthatTextView,wegrabthecurrentnumericvalueoftheTextView,incrementitbyone,andwriteitback.Ifthishappens,thepositionofthedotisresetbacktoitsstartingplace(left=0,top=0)forthenextdrag.
OurexampleshowsyouthebasicsofawaytododraganddropinAndroidpriorto3.0.Withthisyoucouldimplementdrag-and-dropfeaturesinyourapplication.Thismightbetheactionofdraggingsomethingtothetrashcan,wheretheobjectbeingdraggedshouldbedeleted,oritcouldbedraggingafiletoafolderforthepurposesofmovingorcopyingit.Toembellishyourapplication,youcouldpre-identifywhichviewsarepotentialdroptargetsandcausethemtovisuallychangeasadragstarts.Ifyouwantedthedraggedobjecttodisappearfromthescreenwhenitisdropped,youcouldalwaysprogrammaticallyremoveitfromthelayout(seethevariousremoveViewmethodsinViewGroup).
Nowthatyou’veseenthehardwaytododraganddrop,we’dliketoshowyouthedrag-and-dropsupportthatwasaddedinAndroid3.0.
BasicsofDragandDropin3.0+PriortoAndroid3.0,therewasnodirectsupportfordraganddrop.YoulearnedinthefirstsectionofthischapterhowtodragaViewaroundthescreen;youalsolearnedthatitwaspossibletousethecurrentlocationofthedraggedobjecttodetermineiftherewasadroptargetunderneath.WhentheMotionEventforthefinger-upeventwasreceived,yourcodecouldfigureoutifthatmeantadrophadoccurred.Althoughthiswasdoable,itcertainlywasn’taseasyashavingdirectsupportinAndroidforthedrag-and-dropoperation.Younowhavethatdirectsupport.
Atitsmostbasic,thedrag-and-dropoperationstartswithaviewdeclaringthatadraghasstarted;thenallinterestedpartieswatchthedragtakeplaceuntilthedropeventisfired.Ifaviewcatchesthedropeventandwantstoreceiveit,thenadraganddrophasjustoccurred.Ifthereisnoviewtoreceivethedrop,oriftheviewthatreceivesitdoesn’twantit,thennodroptakesplace.DraggingiscommunicatedthroughtheuseofaDragEventobject,whichispassedtoallofthedraglistenersavailable.
WithintheDragEventobjectaredescriptorsforlotsofinformation,dependingontheinitiatorofthedragsequence.Forexample,theDragEventcancontainobjectreferencestotheinitiatoritself,stateinformation,textualdata,URIs,orprettymuchwhateveryouwanttopassthroughthedragsequence.
Informationcouldbepassedthatresultsinview-to-viewdynamiccommunication;however,theoriginatordatainaDragEventobjectissetwhentheDragEventiscreated,anditstaysthesamethereafter.Inadditiontothisdata,theDragEventhasanactionvalueindicatingwhatisgoingonwiththedragsequence,andlocationinformationindicatingwherethedragisonthescreen.
ADragEventhassixpossibleactions:
ACTION_DRAG_STARTEDindicatesthatanewdragsequencehasbegun.
ACTION_DRAG_ENTEREDindicatesthatthedraggedobjecthasbeendraggedintotheboundariesofaspecificview.
ACTION_DRAG_LOCATIONindicatesthatthedraggedobjecthasbeendraggedonthescreentoanewlocation.
ACTION_DRAG_EXITEDindicatesthatthedraggedobjecthasbeendraggedoutsidetheboundariesofaspecificview.
ACTION_DROPindicatesthattheuserhasletgoofthedraggedobject.Itisuptothereceiverofthiseventtodeterminewhetherthistrulymeansadrophasoccurred.
ACTION_DRAG_ENDEDtellsalldraglistenersthatthepreviousdragsequencehasended.TheDragEvent.getResult()methodindicatesasuccessfuldroporfailure.
Youmightthinkthatyouneedtosetupadraglisteneroneachviewinthesystemthatcouldparticipateinadragsequence;but,infact,youcandefineadraglisteneronjustaboutanythinginyourapplication,anditwillreceiveallofthedrageventsforallviewsinthesystem.Thiscanmakethingsalittleconfusingbecausethedraglistenerdoesnotneedtobeassociatedwitheithertheobjectbeingdraggedorthedroptarget.Thelistenercanmanageallofthecoordinationofthedraganddrop.
Infact,ifyouinspectthedrag-and-dropexampleprojectthatcomeswiththeAndroidSDK,youwillseethatitsetsupalisteneronaTextViewthathasnothingtodowiththeactualdragginganddropping.Theupcomingexampleprojectusesdraglistenersthataretiedtospecificviews.ThesedraglistenerseachreceiveaDragEventobjectforthedrageventsthatoccurinthedragsequence.ThismeansaviewcouldreceiveaDragEventobjectthatcanbeignoredbecauseitisreallyaboutadifferentview.ThisalsomeansthedraglistenermustmakethatdeterminationincodeandthattheremustbeenoughinformationwithintheDragEventobjectforthedraglistenertofigureoutwhattodo.
IfadraglistenergotaDragEventobjectthatmerelysaidthere’sanunknownobjectbeingdraggedandit’satcoordinates(15,57),thereisn’tmuchthedraglistenercandowithit.ItismuchmorehelpfultogetaDragEventobjectthatsaysaparticularobjectisbeingdragged,it’satcoordinates(15,57),it’sacopyoperation,andthedataisaspecificURI.Whenthatdrops,there’senoughinformationtobeabletoinitiateacopyoperation.
We’reactuallyseeingtwodifferentkindsofdragginggoingon.Inourfirstexampleapplication,wedraggedaviewacrossaframelayout,andwecouldletgoandthatviewwouldstaywhereitwas.Weonlygotdrag-and-dropbehaviorwhenwedroppedourviewontopofsomethingelse.Thesupportedformofdraganddropworksdifferentlythanthis.Now,whenyoudragaviewaspartofadrag-and-dropsequence,thedraggedviewdoesn’tmoveatall.Wegetashadowimageofthedraggedviewwhichdoestravelacrossthescreen,butifweletgoofit,thatshadowviewgoesaway.WhatthismeansisthatyoumightstillhaveoccasiontousethetechniquefromthebeginningofthischapterinanAndroid3.0+application,tomoveimagesaroundonthescreenperhaps,withoutnecessarilydoingdraganddrop.
Drag-and-DropExampleApplicationForyournextexampleapplication,you’regoingtoemployastapleof3.0,thefragment.This,amongotherthings,willprovethatdragscancrossfragmentboundaries.You’llcreateapaletteofdotsontheleftandasquaretargetontheright.Whenadotisgrabbedusingalongclick,you’llchangethecolorofthatdotinthepaletteandAndroidwillshowashadowofthedotasyoudrag.Whenthedraggeddotreachesthesquaretarget,thetargetwillbegintoglow.Ifyoudropthedotonthesquaretarget,amessagewillindicatethatyou’vejustaddedonemoredroptothedropcount,theglowingwillstop,andtheoriginaldotwillgobacktoitsoriginalcolor.
ListofFilesThisapplicationbuildsuponconceptswe’vecoveredthroughoutthisbook.We’reonlygoingtoincludetheinterestingfilesinthetext.Fortheothers,justlookattheminyourIDEatyourleisure.Herearetheonesthatwe’veincludedinthetext:
palette.xmlisthefragmentlayoutforthedotsontheleftside(seeListing23-3).
dropzone.xmlisthefragmentlayoutforthesquaretargetontherightside,plusthedrop-countmessage(seeListing23-4).
DropZone.javainflatesthedropzone.xmlfragmentlayoutfileandthenimplementsthedraglistenerforthedroptarget(seeListing23-5).
Dot.javaisyourcustomviewclassfortheobjectsyou’regoingtodrag.Ithandlesbeginningthedragsequence,watchingdragevents,anddrawingthedots(seeListing23-6).
LayingOuttheExampleDrag-and-DropApplicationBeforewegetintothecode,Figure23-2showswhattheapplicationwilllooklike.
Figure23-2.DragDropFragsexampleapplicationuserinterface
Themainlayoutfilehasasimplehorizontallinearlayoutandtwofragmentspecifications.Thefirstfragmentwillbeforthepaletteofdotsandthesecondwillbeforthedropzone.
Thepalettefragmentlayoutfile(Listing23-3)getsabitmoreinteresting.Althoughthislayoutrepresentsafragment,youdon’tneedtoincludeafragmenttagwithinthislayout.Thislayoutwillbeinflatedtobecometheviewhierarchyforyourpalettefragment.Thedotsarespecifiedascustomdots,andtherearetwoofthemarrangedvertically.NoticethatthereareacoupleofcustomXMLattributesinthedefinitionofyourdots(dot:coloranddot:radius).Asyoucansee,theseattributesspecifythecolorandtheradiusofyourdots.Youmightalsohavenoticedthatthelayoutwidthandheightarewrap_content,notmatch_parentasintheearlierexampleapplicationinthischapter.Thenewdrag-and-dropsupportmakesthingsmucheasier.
Listing23-3.Thepalette.xmlLayoutFilefortheDots
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/palette.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:dot="http://schemas.android.com/apk/res/com.androidbook.drag.drop.demoandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">
<com.androidbook.drag.drop.demo.Dotandroid:id="@+id/dot1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="30dp"android:tag="Bluedot"dot:color="#ff1111ff"dot:radius="20dp"/>
<com.androidbook.drag.drop.demo.Dotandroid:id="@+id/dot2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="10dp"android:tag="Whitedot"dot:color="#ffffffff"dot:radius="40dp"/>
</LinearLayout>
ThedropzonefragmentlayoutfileinListing23-4isalsoeasytounderstand.There’sagreensquareandatextmessagearrangedhorizontally.Thiswillbethedropzoneforthedotsyou’llbedragging.Thetextmessagewillbeusedtodisplayarunningcountofthedrops.
Listing23-4.Thedropzone.xmlLayoutFile
<?xmlversion="1.0"encoding="utf-8"?><!--Thisfileisres/layout/dropzone.xml--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal">
<Viewandroid:id="@+id/droptarget"android:layout_width="75dp"android:layout_height="75dp"android:layout_gravity="center_vertical"android:background="#00ff00"/>
<TextViewandroid:id="@+id/dropmessage"android:text="0drops"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:paddingLeft="50dp"android:textSize="17sp"/>
</LinearLayout>
RespondingtoonDragintheDropzoneNowthatyouhavethemainapplicationlayoutset,let’sseehowthedroptargetneedstobeorganizedbyexaminingListing23-5.
Listing23-5.TheDropZone.javaFile
publicclassDropZoneextendsFragment{
privateViewdropTarget;privateTextViewdropMessage;
@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,Bundleicicle){Viewv=inflater.inflate(R.layout.dropzone,container,false);
dropMessage=(TextView)v.findViewById(R.id.dropmessage);
dropTarget=(View)v.findViewById(R.id.droptarget);dropTarget.setOnDragListener(newView.OnDragListener(){privatestaticfinalStringDROPTAG="DropTarget";privateintdropCount=0;privateObjectAnimatoranim;
publicbooleanonDrag(Viewv,DragEventevent){intaction=event.getAction();booleanresult=true;switch(action){caseDragEvent.ACTION_DRAG_STARTED:Log.v(DROPTAG,"dragstartedindropTarget");break;caseDragEvent.ACTION_DRAG_ENTERED:Log.v(DROPTAG,"dragentereddropTarget");anim=ObjectAnimator.ofFloat((Object)v,"alpha",1f,0.5f);anim.setInterpolator(newCycleInterpolator(40));anim.setDuration(30*1000);//30secondsanim.start();break;
caseDragEvent.ACTION_DRAG_EXITED:Log.v(DROPTAG,"dragexiteddropTarget");if(anim!=null){anim.end();anim=null;}break;caseDragEvent.ACTION_DRAG_LOCATION:Log.v(DROPTAG,"dragproceedingindropTarget:"+event.getX()+","+event.getY());break;caseDragEvent.ACTION_DROP:Log.v(DROPTAG,"dragdropindropTarget");if(anim!=null){anim.end();anim=null;}
ClipDatadata=event.getClipData();Log.v(DROPTAG,"Itemdatais"+data.getItemAt(0).getText());
dropCount++;Stringmessage=dropCount+"drop";if(dropCount>1)message+="s";dropMessage.setText(message);break;caseDragEvent.ACTION_DRAG_ENDED:Log.v(DROPTAG,"dragendedindropTarget");if(anim!=null){anim.end();anim=null;}break;default:Log.v(DROPTAG,"otheractionindropzone:"+action);result=false;}returnresult;}});returnv;
}}
Nowyou’restartingtogetintointerestingcode.Forthedropzone,youneedtocreatethetargetuponwhichyouwanttodragthedots.Asyousawearlier,thelayoutspecifiesagreensquareonthescreenwithatextmessagenexttoit.Becausethedropzoneisalsoafragment,you’reoverridingtheonCreateView()methodofDropZone.Thefirstthingtodoisinflatethedropzonelayoutandthenextractouttheviewreferenceforthesquaretarget(dropTarget)andforthetextmessage(dropMessage).Thenyouneedtosetupadraglisteneronthetargetsoitwillknowwhenadragisunderway.
Thedrop-targetdraglistenerhasasinglecallbackmethodinit:onDrag().ThiscallbackwillreceiveaviewreferenceaswellasaDragEventobject.TheviewreferencerelatestotheviewthattheDragEventisrelatedto.Asmentioned,thedraglistenerisnotnecessarilyconnectedtotheviewthatwillbeinteractingwiththedragevent,sothiscallbackmustidentifytheviewforwhichthedrageventistakingplace.
OneofthefirstthingsyoulikelywanttodoinanyonDrag()callbackisreadtheactionfromtheDragEventobject.Thiswilltellyouwhat’sgoingon.Forthemostpart,theonlythingyouwanttodointhiscallbackislogthefactthatadrageventistakingplace.Youdon’tneedtoactuallydoanythingforACTION_DRAG_LOCATION,forexample.Butyoudowanttohavesomespeciallogicforwhentheobjectisdraggedwithinyourboundaries(ACTION_DRAG_ENTERED)thatwillbeturnedoffeitherwhentheobjectisdraggedoutsideofyourboundaries(ACTION_DRAG_EXITED)orwhentheobjectisdropped(ACTION_DROP).
You’reusingtheObjectAnimatorclassthatwasintroducedinChapter18,onlyhereyou’reusingitincodetospecifyacyclicinterpolatorthatmodifiesthetarget’salpha.Thiswillhavetheeffectofpulsingthetransparencyofthegreentargetsquare,whichwillbethevisualindicationthatthetargetiswillingtoacceptadropoftheobjectontoit.Becauseyouturnontheanimation,youmustmakesuretoalsoturnitoffwhentheobjectleavesorisdropped,orthedraganddropisended.Intheory,youshouldn’tneedtostoptheanimationonACTION_DRAG_ENDED,butit’swisetodoitanyway.
Forthisparticulardraglistener,you’regoingtogetACTION_DRAG_ENTEREDandACTION_DRAG_EXITEDonlyifthedraggedobjectinteractswiththeviewwithwhichyou’reassociated.Andasyou’llsee,theACTION_DRAG_LOCATIONeventshappenonlyifthedraggedobjectisinsideyourtargetview.
TheonlyotherinterestingconditionistheACTION_DROPitself(noticethatDRAG_isnotpartofthenameofthisaction).Ifadrophasoccurredonyourview,itmeanstheuserhasletgoofthedotoverthegreensquare.Becauseyou’reexpectingthisobjecttobedroppedonthegreensquare,youcanjustgoaheadandreadthedatafromthefirstitemandthenlogittoLogCat.Inaproductionapplication,youmightpaycloserattentiontotheClipDataobjectthatiscontainedinthedrageventitself.Byinspectingitsproperties,youcoulddecideifyouevenwanttoacceptthedropornot.
ThisisagoodtimetopointouttheresultbooleaninthisonDrag()callbackmethod.
Dependingonhowthingsgo,youwanttoletAndroidknoweitherthatyoutookcareofthedragevent(byreturningtrue)orthatyoudidn’t(byreturningfalse).Ifyoudon’tseewhatyouwanttoseeinsideofthedrageventobject,youcouldcertainlyreturnfalsefromthiscallback,whichwouldtellAndroidthatthisdropwasnothandled.
OnceyoulogtheinformationfromthedrageventinLogCat,youincrementthecountofthedropsreceived;thisisupdatedintheuserinterface,andthat’saboutitforDropZone.
Ifyoulookthisclassover,it’sreallyrathersimple.Youdon’tactuallyhaveanycodeinherethatdealswithMotionEvents,nordoyouevenneedtomakeyourowndeterminationofwhetherthereisadraggoingon.Youjustgetappropriatecallbackcallsasadragsequenceunfolds.
SettingUptheDragSourceViewsLet’snowconsiderhowviewscorrespondingtoadragsourceareorganized,startingbylookingatListing23-6.
Listing23-6.TheJavafortheCustomView:Dot
publicclassDotextendsViewimplementsView.OnDragListener{privatestaticfinalintDEFAULT_RADIUS=20;privatestaticfinalintDEFAULT_COLOR=Color.WHITE;privatestaticfinalintSELECTED_COLOR=Color.MAGENTA;protectedstaticfinalStringDOTTAG="DragDot";privatePaintmNormalPaint;privatePaintmDraggingPaint;privateintmColor=DEFAULT_COLOR;privateintmRadius=DEFAULT_RADIUS;privatebooleaninDrag;
publicDot(Contextcontext,AttributeSetattrs){super(context,attrs);
//Applyattributesettingsfromthelayoutfile.//Note:thesecouldchangeonareconfiguration//suchasascreenrotation.TypedArraymyAttrs=context.obtainStyledAttributes(attrs,R.styleable.Dot);
finalintnumAttrs=myAttrs.getIndexCount();for(inti=0;i<numAttrs;i++){intattr=myAttrs.getIndex(i);switch(attr){caseR.styleable.Dot_radius:
mRadius=myAttrs.getDimensionPixelSize(attr,DEFAULT_RADIUS);break;caseR.styleable.Dot_color:mColor=myAttrs.getColor(attr,DEFAULT_COLOR);break;}}myAttrs.recycle();
//SetuppaintcolorsmNormalPaint=newPaint();mNormalPaint.setColor(mColor);mNormalPaint.setAntiAlias(true);
mDraggingPaint=newPaint();mDraggingPaint.setColor(SELECTED_COLOR);mDraggingPaint.setAntiAlias(true);
//StartadragonalongclickonthedotsetOnLongClickListener(lcListener);setOnDragListener(this);}
privatestaticView.OnLongClickListenerlcListener=newView.OnLongClickListener(){privatebooleanmDragInProgress;
publicbooleanonLongClick(Viewv){ClipDatadata=ClipData.newPlainText("DragData",(String)v.getTag());
mDragInProgress=v.startDrag(data,newView.DragShadowBuilder(v),(Object)v,0);
Log.v((String)v.getTag(),"startingdrag?"+mDragInProgress);
returntrue;}};
@OverrideprotectedvoidonMeasure(intwidthSpec,intheightSpec){intsize=2*mRadius+getPaddingLeft()+getPaddingRight();
setMeasuredDimension(size,size);}
//ThedraggingfunctionalitypublicbooleanonDrag(Viewv,DragEventevent){StringdotTAG=(String)getTag();//Onlyworryaboutdrageventsifthisisusbeingdraggedif(event.getLocalState()!=this){Log.v(dotTAG,"Thisdrageventisnotforus");returnfalse;}booleanresult=true;
//geteventvaluestoworkwithintaction=event.getAction();floatx=event.getX();floaty=event.getY();
switch(action){caseDragEvent.ACTION_DRAG_STARTED:Log.v(dotTAG,"dragstarted.X:"+x+",Y:"+y);inDrag=true;//usedindraw()belowtochangecolorbreak;caseDragEvent.ACTION_DRAG_LOCATION:Log.v(dotTAG,"dragproceeding…At:"+x+","+y);break;caseDragEvent.ACTION_DRAG_ENTERED:Log.v(dotTAG,"dragentered.At:"+x+","+y);break;caseDragEvent.ACTION_DRAG_EXITED:Log.v(dotTAG,"dragexited.At:"+x+","+y);break;caseDragEvent.ACTION_DROP:Log.v(dotTAG,"dragdropped.At:"+x+","+y);//Returnfalsebecausewedon'tacceptthedropinDot.result=false;break;caseDragEvent.ACTION_DRAG_ENDED:Log.v(dotTAG,"dragended.Success?"+event.getResult());
inDrag=false;//changecoloroforiginaldotbackbreak;default:Log.v(dotTAG,"someotherdragaction:"+action);result=false;break;}returnresult;}
//Hereiswhereyoudrawourdot,andwhereyouchangethecolorif//you'reintheprocessofbeingdragged.Note:thecolorchange//affectstheoriginaldotonly,nottheshadow.publicvoiddraw(Canvascanvas){floatcx=this.getWidth()/2+getLeftPaddingOffset();floatcy=this.getHeight()/2+getTopPaddingOffset();Paintpaint=mNormalPaint;if(inDrag)paint=mDraggingPaint;canvas.drawCircle(cx,cy,mRadius,paint);invalidate();}}
TheDotcodelookssomewhatsimilartothecodeforDropZone.Thisisinpartbecauseyou’realsoreceivingdrageventsinthisclass.TheconstructorforaDotfiguresouttheattributesinordertosetthecorrectradiusandcolor,andthenitsetsupthetwolisteners:oneforlongclicksandanotherforthedragevents.
Thetwopaintsaregoingtobeusedtodrawyourcircle.Youusethenormalpaintwhenthedotisjustsittingthere.Butwhenthedotisbeingdragged,youwanttoindicatethatbychangingthecoloroftheoriginaltomagenta.
Thelong-clicklisteneriswhereyouinitiateadragsequence.Theonlywayyoulettheuserstartdraggingadotisiftheuserclicksandholdsonadot.Whenthelong-clicklistenerisfiring,youcreateanewClipDataobjectusingastringandthedot’stag.YouhappentoknowthatthetagisthenameofthedotasspecifiedintheXMLlayoutfile.ThereareseveralotherwaystospecifydataintoaClipDataobject,sofeelfreetoreadthereferencedocumentationonotherwaystostoredatainaClipDataobject.
Thenextstatementisthecriticalone:startDrag().ThisiswhereAndroidwilltakeoverandstarttheprocessofdragging.NotethatthefirstargumentistheClipDataobjectfrombefore;thenit’sthedrag-shadowobject,thenalocal-stateobject,andfinallythenumberzero.
Thedrag-shadowobjectistheimagethatwillbedisplayedasthedraggingistakingplace.Inyourcase,thisdoesnotreplacetheoriginaldotimageonthescreenbutshowsashadowofadotasthedraggingistakingplace,inadditiontotheoriginaldotonthescreen.ThedefaultDragShadowBuilderbehavioristocreateashadowthatlooksverymuchliketheoriginal,soforyourpurposes,youmerelycallitandpassinyourview.Youcangetfancyhereandcreatewhateversortofshadowviewyouwant,butifyoudooverridethisclass,you’llneedtoimplementafewmethodstomakeitwork.
TheonMeasure()methodisheretosupplydimensioninformationtoAndroidforthecustomviewyou’reusinghere.YouhavetotellAndroidhowbigyourviewissoitknowshowtolayitoutwitheverythingelse.Thisisstandardpracticeforacustomview.
Finally,there’stheonDrag()callback.Asmentioned,eachdraglistenercanreceivedragevents.TheyallgetACTION_DRAG_STARTEDandACTION_DRAG_ENDED,forexample.So,wheneventshappen,youmustbecarefulwhatyoudowiththeinformation.Becausetherearetwodotsinplayinthisexampleapplication,wheneveryoudosomethingwiththedots,youmustbecarefulthatyou’reaffectingthecorrectone.
WhenbothdotsreceivetheACTION_DRAG_STARTEDaction,onlyoneshouldsetthecolorofitselftomagenta.Tofigureoutwhichoneiscorrect,comparethelocalstateobjectpassedinwithyourself.Ifyoulookbackwhereyousetthelocal-stateobject,youpassedthecurrentviewin.Sonow,whenyou’vereceivedthelocal-stateobjectout,youcompareittoyourselftoseeifyou’retheviewthatinitiatedthedragsequence.
Ifyouaren’tthesameview,youwritealogmessagetoLogCatsayingthisisnotforyou,andyoureturnfalsetosayyou’renothandlingthismessage.
Ifyouaretheviewthatshouldbereceivingthisdragevent,youcollectsomevaluesfromthedragevent,thenyoumostlyjustlogtheeventtoLogCat.ThefirstexceptiontothisisACTION_DRAG_STARTED.Ifyougotthisactionandit’sforyou,youthenknowthatyourdothasbegunadragsequence.Therefore,yousettheinDragbooleansothedraw()methodlateronwilldotherightthinganddisplayadifferent-coloreddot.ThisdifferentcoloronlylastsuntilACTION_DRAG_ENDEDisreceived,atwhichtimeyourestoretheoriginalcolorofthedot.
IfadotgetstheACTION_DROPaction,thismeanstheusertriedtodropadotonadot—maybeeventheoriginaldot.Thisshouldn’tdoanything,soyoujustreturnfalsefromthiscallbackinthiscase.
Finally,thedraw()methodofyourcustomviewfiguresoutthelocationofthecenterpointofyourcircle(dot)andthendrawsitwiththeappropriatepaint.Theinvalidate()methodistheretotellAndroidthatyou’vemodifiedtheviewandthatAndroidshouldredrawtheuserinterface.Bycallinginvalidate(),youensurethattheuserinterfacewillbeupdatedveryshortlywithwhateverisnew.
Younowhaveallthefilesandthebackgroundnecessarytocompileanddeploythisexampledrag-and-dropapplication.
TestingtheExampleDrag-and-DropApplicationFollowingissomeexampleoutputfromLogCatwhenweranthisexampleapplication.NoticehowthelogmessageusedBluedottoindicatemessagesfromthebluedot,Whitedotformessagesfromthewhitedot,andDropTargetfortheviewwherethedropsareallowedtogo.
Whitedot:startingdrag?trueBluedot:ThisdrageventisnotforusWhitedot:dragstarted.X:53.0,Y:206.0DropTarget:dragstartedindropTargetDropTarget:dragentereddropTargetDropTarget:dragproceedingindropTarget:29.0,36.0DropTarget:dragproceedingindropTarget:48.0,39.0DropTarget:dragproceedingindropTarget:45.0,39.0DropTarget:dragproceedingindropTarget:41.0,39.0DropTarget:dragproceedingindropTarget:40.0,39.0DropTarget:dragdropindropTargetDropTarget:ItemdataisWhitedotViewRoot:Reportingdropresult:trueWhitedot:dragended.Success?trueBluedot:ThisdrageventisnotforusDropTarget:dragendedindropTarget
Inthisparticularcase,thedragwasstartedwiththewhitedot.Oncethelongclickhastriggeredthebeginningofthedragsequence,wegetthestartingdragmessage.
NoticehowthenextthreelinesallindicatethatanACTION_DRAG_STARTEDactionwasreceivedinthreedifferentviews.Bluedotdeterminedthatthecallbackwasnotforit.ItwasalsonotforDropTarget.
Next,noticehowthedrag-proceedingmessagesshowthedraghappeningthroughDropTarget,beginningwiththeACTION_DRAG_ENTEREDaction.Thismeansthedotwasbeingdraggedontopofthegreensquare.Thexandycoordinatesreportedinthedrageventobjectarethecoordinatesofthedragpointrelativetotheupper-leftcorneroftheview.So,intheexampleapp,thefirstrecordofthedraginthedroptargetisat(x,y)=(29,36),andthedropoccurredat(40,39).Seehowthedroptargetwasabletoextractthetagnameofthewhitedotfromtheevent’sClipDatatowriteittoLogCat.
Alsoseehowonceagain,alldraglistenersreceivedtheACTION_DRAG_ENDEDaction.OnlyWhitedotdeterminedthatit’sokaytodisplaytheresultsusinggetResult().
Feelfreetoexperimentwiththisexampleapplication.Dragadottotheotherdot,oreventoitself.Goaheadandaddanotherdottopalette.xml.Noticehowwhenthedragged
dotleavesthegreensquare,there’samessagesayingthatthedragexited.Notealsothatifyoudropadotsomewhereotherthanthegreensquare,thedropisconsideredfailed.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforazipfilecalledProAndroid5_Ch23_DragnDrop.zip.Thiszipfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoyourIDEfromoneofthesezipfiles.
http://developer.android.com/guide/topics/ui/drag-drop.html:TheAndroiddeveloper’sguidetodraganddrop.
SummaryLet’ssummarizethetopicscoveredinthischapter:
Drag-and-dropsupportinAndroid3.0,andimplementingitpriorto3.0usingothermethods
Iteratingthroughpossibledroptargetstoseeifadrop(thatis,fingerleavingthescreenafterdragging)occurred
Thedifficultyofdoingthemathtokeeptrackofwhereadraggedobjectisandwhetherit’soveradroptarget
Drag-and-dropsupportinAndroid3.0+,whichismuchnicerbecauseiteliminatesalotofguesswork
Draglisteners,whichcanbeanyobjectsanddonotneedtobedraggablesordrop-targetviews
Thefactthatadragcanoccuracrossfragments
TheDragEventobject,whichcancontainlotsofgreatinformationaboutwhatisbeingdraggedandwhy
HowAndroidtakescareofthemathtodeterminewhetheradropisoccurringontopofaview
Chapter24
UsingSensorsAndroiddevicesoftencomewithhardwaresensorsbuiltin,andAndroidprovidesaframeworkforworkingwiththosesensors.Sensorscanbefun.Measuringtheoutsideworldandusingthatinsoftwareinadeviceisprettycool.Itisthekindofprogrammingexperienceyoujustdon’tgetonaregularcomputerthatsitsonadeskorinaserverroom.Thepossibilitiesfornewapplicationsthatusesensorsarehuge,andwehopeyouareinspiredtorealizethem.
Inthischapter,we’llexploretheAndroidsensorframework.We’llexplainwhatsensorsareandhowwegetsensordata,andthendiscusssomespecificsofthekindsofdatawecangetfromsensorsandwhatwecandowithit.WhileAndroidhasdefinedseveralsensortypesalready,therearenodoubtmoresensorsinAndroid’sfuture,andweexpectthatfuturesensorswillgetincorporatedintothesensorframework.
WhatIsaSensor?InAndroid,asensorisasourceofdataeventsfromthephysicalworld.Thisistypicallyapieceofhardwarethathasbeenwiredintothedevice,butAndroidalsoprovidessomelogicalsensorsthatcombinedatafrommultiplephysicalsensors.Applicationsinturnusethesensordatatoinformtheuseraboutthephysicalworld,tocontrolgameplay,todoaugmentedreality,ortoprovideusefultoolsforworkingintherealworld.Sensorsoperateinonedirectiononly;they’reread-only.Thatmakesusingthemfairlystraightforward.Yousetupalistenertoreceivesensordata,andthenyouprocessthedataasitcomesin.GPShardwareislikethesensorswecoverinthischapter.InChapter19,wesetuplistenersforGPSlocationupdates,andweprocessedthoselocationupdatesastheycamein.ButalthoughGPSissimilartoasensor,itisnotpartofthesensorframeworkthatisprovidedbyAndroid.
SomeofthesensortypesthatcanappearinanAndroiddeviceinclude
Lightsensor
Proximitysensor
Temperaturesensor
Pressuresensor
Gyroscopesensor
Accelerometer
Magneticfieldsensor
Gravitysensor
Linearaccelerationsensor
Rotationvectorsensor
Relativehumiditysensor
DetectingSensorsPleasedon’tassume,however,thatallAndroiddeviceshaveallofthesesensors.Infact,manydeviceshavejustsomeofthesesensors.TheAndroidemulator,forexample,hasonlyanaccelerometer.Sohowdoyouknowwhichsensorsareavailableonadevice?Therearetwoways,onedirectandoneindirect.
ThefirstwayisthatyouasktheSensorManagerforalistoftheavailablesensors.Itwillrespondwithalistofsensorobjectsthatyoucanthensetuplistenersforandgetdatafrom.We’llshowyouhowabitlaterinthischapter.Thismethodassumesthattheuserhasalreadyinstalledyourapplicationontoadevice,butwhatifthedevicedoesn’thaveasensorthatyourapplicationneeds?
That’swherethesecondmethodcomesin.WithintheAndroidManifest.xmlfile,youcanspecifythefeaturesadevicemusthaveinordertoproperlysupportyourapplication.Ifyourapplicationneedsaproximitysensor,youspecifythatinyourmanifestfilewithalinesuchasthefollowing:
<uses-featureandroid:name="android.hardware.sensor.proximity"/>
TheGooglePlayStorewillonlyinstallyourapponadevicethathasaproximitysensor,soyouknowit’stherewhenyourapplicationruns.ThesamecannotbesaidforallotherAndroidappstores.Thatis,someAndroidappstoresdonotperformthatkindofchecktomakesureyourappcanonlybeinstalledontoadevicethatsupportsthesensorsyouspecify.
WhatCanWeKnowAboutaSensor?Whileusingtheuses-featuretagsinthemanifestfileletsyouknowthatasensoryourapplicationrequiresexistsonadevice,itdoesn’ttellyoueverythingyoumaywanttoknowabouttheactualsensor.Let’sbuildasimpleapplicationthatqueriesthedeviceforsensorinformation.Listing24-1showstheJavacodeofourMainActivity.
Note
Youcandownloadthischapter’sprojects.WewillgiveyoutheURLattheendofthechapter.ThiswillallowyoutoimporttheseprojectsintoyourIDEdirectly.
Listing24-1.JavaforaSensorListApp
publicclassMainActivityextendsActivity{@Override
publicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
TextViewtext=(TextView)findViewById(R.id.text);
SensorManagermgr=(SensorManager)this.getSystemService(SENSOR_SERVICE);
List<Sensor>sensors=mgr.getSensorList(Sensor.TYPE_ALL);
StringBuildermessage=newStringBuilder(2048);message.append("Thesensorsonthisdeviceare:\n");
for(Sensorsensor:sensors){message.append(sensor.getName()+"\n");message.append("Type:"+sensorTypes.get(sensor.getType())+"\n");message.append("Vendor:"+sensor.getVendor()+"\n");message.append("Version:"+sensor.getVersion()+"\n");try{message.append("MinDelay:"+sensor.getMinDelay()+"\n");}catch(NoSuchMethodErrore){}//ignoreifnotfoundtry{message.append("FIFOMaxEventCount:"+sensor.getFifoMaxEventCount()+"\n");}catch(NoSuchMethodErrore){}//ignoreifnotfoundmessage.append("Resolution:"+sensor.getResolution()+"\n");message.append("MaxRange:"+sensor.getMaximumRange()+"\n");message.append("Power:"+sensor.getPower()+"mA\n");}text.setText(message);}
privateHashMap<Integer,String>sensorTypes=newHashMap<Integer,String>();
{sensorTypes.put(Sensor.TYPE_ACCELEROMETER,"TYPE_ACCELEROMETER");sensorTypes.put(Sensor.TYPE_AMBIENT_TEMPERATURE,"TYPE_AMBIENT_TEMPERATURE");/*...therestisomittedtosavespace…*/}}
WithinouronCreate()method,westartbygettingareferencetotheSensorManager.Therecanbeonlyoneofthese,soweretrieveitasasystemservice.WethencallitsgetSensorList()methodtogetalistofsensors.Foreachsensor,wewriteoutinformationaboutit.TheoutputwilllooksomethinglikeFigure24-1.
Figure24-1.Outputfromoursensorlistapp
Thereareafewthingstoknowaboutthissensorinformation.Thetypevaluetellsyouthebasictypeofthesensorwithoutgettingspecific.Alightsensorisalightsensor,butyoucouldgetvariationsinlightsensorsfromonedevicetoanother.Forexample,theresolutionofalightsensorononedevicecouldbedifferentfromthatonanotherdevice.
Whenyouspecifythatyourappneedsalightsensorina<uses-feature>tag,youdon’tknowinadvanceexactlywhattypeoflightsensoryou’regoingtoget.Ifitmatterstoyourapplication,you’llneedtoquerythedevicetofindoutandadjustyourcodeaccordingly.Thevaluesyougetforresolutionandmaximumrangewillbeintheappropriateunitsforthatsensor.Thepowermeasurementisinmilliamperes(mA)andrepresentstheelectricalcurrentthatthesensordrawsfromthedevice’sbattery;smallerisbetter.
Nowthatweknowwhatsensorswehaveavailabletous,howdowegoaboutgettingdatafromthem?Asweexplainedearlier,wesetupalistenerinordertogetsensordatasenttous.Let’sexplorethatnow.
GettingSensorEventsSensorsprovidedatatoourapplicationonceweregisteralistenertoreceivethedata.Whenourlistenerisnotlistening,thesensorcanbeturnedoff,conservingbatterylife,somakesureyouonlylistenwhenyoureallyneedto.Settingupasensorlisteneriseasytodo.Let’ssaythatwewanttomeasurethelightlevelsfromthelightsensor.Listing24-2showstheJavacodeforasampleappthatdoesthis.
Listing24-2.JavaCodeforaLightSensorMonitorApp
publicclassMainActivityextendsActivityimplementsSensorEventListener{privateSensorManagermgr;privateSensorlight;privateTextViewtext;privateStringBuildermsg=newStringBuilder(2048);
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
mgr=(SensorManager)this.getSystemService(SENSOR_SERVICE);light=mgr.getDefaultSensor(Sensor.TYPE_LIGHT);text=(TextView)findViewById(R.id.text);}
@OverrideprotectedvoidonResume(){mgr.registerListener(this,light,SensorManager.SENSOR_DELAY_NORMAL);super.onResume();}
@OverrideprotectedvoidonPause(){mgr.unregisterListener(this,light);super.onPause();}
publicvoidonAccuracyChanged(Sensorsensor,intaccuracy){msg.insert(0,sensor.getName()+"accuracychanged:"+accuracy+(accuracy==1?"(LOW)":(accuracy==2?"(MED)":"(HIGH)"))+"\n");text.setText(msg);text.invalidate();}
publicvoidonSensorChanged(SensorEventevent){msg.insert(0,"Gotasensorevent:"+event.values[0]+"SIluxunits\n");text.setText(msg);text.invalidate();}}
Inthissampleapp,weagaingetareferencetotheSensorManager,butinsteadofgettingalistofsensors,wequeryspecificallyforthelightsensor.WethensetupalistenerintheonResume()methodofouractivity,andweunregisterthelistenerintheonPause()method.Wedon’twanttobeworryingaboutthelightlevelswhenourapplicationisnotintheforeground.
FortheregisterListener()method,wepassinavaluerepresentinghowoftenwewanttobenotifiedofsensorvaluechanges.Thisparametercouldbe
SENSOR_DELAY_NORMAL(represents200,000microseconddelay)
SENSOR_DELAY_UI(represents60,000microseconddelay)
SENSOR_DELAY_GAME(represents20,000microseconddelay)
SENSOR_DELAY_FASTEST(representsasfastaspossible)
YoucanalsospecifyaspecificmicroseconddelayusingoneoftheotherregisterListener
methods,aslongasit’slargerthan3microseconds;howeveranythinglessthan20,000isnotlikelytobehonored.Itisimportanttoselectanappropriatevalueforthisparameter.Somesensorsareverysensitiveandwillgeneratealotofeventsinashortamountoftime.IfyouchooseSENSOR_DELAY_FASTEST,youmightevenoverrunyourapplication’sabilitytokeepup.Dependingonwhatyourapplicationdoeswitheachsensorevent,itispossiblethatyouwillbecreatinganddestroyingsomanyobjectsinmemorythatgarbagecollectionwillcausenoticeableslowdownsandhiccupsonthedevice.Ontheotherhand,certainsensorsprettymuchdemandtobereadasoftenaspossible;thisistrueoftherotationvectorsensorinparticular.Also,don’trelyonthisparametertogenerateeventswithprecisetiming.Theeventscouldcomealittlefasterorslower.
BecauseouractivityimplementstheSensorEventListenerinterface,wehavetwocallbacksforsensorevents:onAccuracyChanged()andonSensorChanged().Thefirstmethodwillletusknowiftheaccuracychangesonoursensor(orsensors,sinceitcouldbecalledformorethanone).ThevalueoftheaccuracyparameterwillbeSENSOR_STATUS_UNRELIABLE,SENSOR_STATUS_ACCURACY_LOW,SENSOR_STATUS_ACCURACY_MEDIUM,orSENSOR_STATUS_ACCURACY_HIGH.Unreliableaccuracydoesnotmeanthatthedeviceisbroken;itnormallymeansthatthesensorneedstobecalibrated.Thesecondcallbackmethodtellsuswhenthelightlevelhaschanged,andwegetaSensorEventobjecttotellusthedetailsofthenewvalueorvaluesfromthesensor.
ASensorEventobjecthasseveralmembers,oneofthembeinganarrayoffloatvalues.Foralightsensorevent,onlythefirstfloatvaluehasmeaning,whichistheSIluxvalueofthelightthatwasdetectedbythesensor.Foroursampleapp,webuildupamessagestringbyinsertingthenewmessagesontopoftheoldermessages,andthenwedisplaythebatchofmessagesinaTextView.Ournewestsensorvalueswillalwaysbedisplayedatthetopofthescreen.
Whenyourunthisapplication(onarealdevice,ofcourse,sincetheemulatordoesnothavealightsensor),youmaynoticethatnothingisdisplayedatfirst.Justchangethelightthatisshiningontheupper-leftcornerofyourdevice.Thisismostlikelywhereyourlightsensoris.Ifyoulookverycarefully,youmightseethedotbehindthescreenthatisthelightsensor.Ifyoucoverthisdotwithyourfinger,thelightlevelwillprobablychangetoaverysmallvalue(althoughitmaynotreachzero).Themessagesshoulddisplayonthescreen,tellingyouaboutthechanginglightlevels.
Note
Youmightalsonoticethatwhenthelightsensoriscovered,yourbuttonslightup(ifyouhaveadevicewithlightedbuttons).ThisisbecauseAndroidhasdetectedthedarknessandlightsupthebuttonstomakethedeviceeasiertouse“inthedark.”
IssueswithGettingSensorDataTheAndroidsensorframeworkhasproblemsthatyouneedtobeawareof.Thisisthepartthat’snotfun.Insomecases,wehavewaysofworkingaroundtheproblem;inotherswe
don’t,orit’sverydifficult.
NoDirectAccesstoSensorValuesYoumayhavenoticedthatthereisnodirectwaytoquerythesensor’scurrentvalue.Theonlywaytogetdatafromasensoristhroughalistener.Therearetwokindsofsensors:thosethatarestreamingandthosethatarenot.Streamingsensorswillsendvaluesonaregularbasis,suchastheaccelerometer.ThemethodcallgetMinDelay()willreturnanonzerovalueforstreamingsensors,totellyoutheminimumnumberofmicrosecondsthatasensorwillusetosensetheenvironment.Fornon-streamingsensorsthereturnvalueiszero,soevenonceyou’vesetupthelistener,therearenoguaranteesthatyou’llgetanewdatumwithinasetperiodoftime.Atleastthecallbackisasynchronoussoyouwon’tblocktheUIthreadwaitingforapieceofdatafromasensor.However,yourapplicationhastoaccommodatethefactthatsensordatamaynotbeavailableattheexactmomentthatyouwantit.RevisitingFigure24-1,you’llnoticethatthelightsensorisnon-streaming.Therefore,yourappwillgetaneventonlyifthelightlevelchanges.Fortheothersensorsshown,thedelaybetweeneventswillbeaminimumof20milliseconds,butcouldbemore.
ItispossibletodirectlyaccesssensorsusingnativecodeandtheJNIfeatureofAndroid.You’llneedtoknowthelow-levelnativeAPIcallsforthesensordriveryou’reinterestedin,plusbeabletosetuptheinterfacebacktoAndroid.Soitcanbedone,butit’snoteasy.
SensorValuesNotSentFastEnoughEvenatSENSOR_DELAY_FASTEST,youprobablywon’tgetnewvaluesmoreoftenthanevery20ms(itdependsonthedeviceandthesensor).IfyouneedmorerapidsensordatathanyoucangetwitharatesettingofSENSOR_DELAY_FASTEST,itispossibletousenativecodeandJNItogettothesensordatafaster,butsimilartotheprevioussituation,itisnoteasy.
SensorsTurnOffwiththeScreenTherehavebeenproblemsinAndroid2.xwithsensorupdatesthatgetturnedoffwhenthescreenisturnedoff.Apparentlysomeonethoughtitwasagoodideatonotsendsensorupdatesifthescreenisoff,evenifyourapplication(mostlikelyusingaservice)hasawakelock.Basically,yourlistenergetsunregisteredwhenthescreenturnsoff.
Thereareseveralworkaroundstothisproblem.Formoreinformationonthisissueandpossibleresolutionsandworkarounds,pleaserefertoAndroidIssue11028:
http://code.google.com/p/android/issues/detail?id=11028
Nowthatyouknowhowtogetdatafromsensors,whatcanyoudowiththedata?Aswesaidearlier,dependingonwhichsensoryou’regettingdatafrom,thevaluesreturnedinthevaluesarraymeandifferentthings.Thenextsectionwillexploreeachofthesensortypesandwhattheirvaluesmean.
InterpretingSensorDataNowthatyouunderstandhowtogetdatafromasensor,you’llwanttodosomethingmeaningfulwiththedata.Thedatayouget,however,willdependonwhichsensoryou’regettingthedatafrom.Somesensorsaresimplerthanothers.Inthesectionsthatfollow,wewilldescribethedatathatyou’llgetfromthesensorswecurrentlyknowabout.Asnewdevicescomeintobeing,newsensorswillundoubtedlybeintroducedaswell.Thesensorframeworkisverylikelytoremainthesame,sothetechniquesweshowhereshouldapplyequallywelltothenewsensors.
LightSensorsThelightsensorisoneofthesimplestsensorsonadevice,andoneyou’veusedinthefirstsampleapplicationsofthischapter.Thesensorgivesareadingofthelightleveldetectedbythelightsensorofthedevice.Asthelightlevelchanges,thesensorreadingschange.TheunitsofthedataareinSIluxunits.Tolearnmoreaboutwhatthismeans,pleaseseethe“References”sectionattheendofthischapterforlinkstomoreinformation.
ForthevaluesarrayintheSensorEventobject,alightsensorusesjustthefirstelement,values[0].Thisvalueisafloatandrangestechnicallyfrom0tothemaximumvaluefortheparticularsensor.Wesaytechnicallybecausethesensormayonlysendverysmallvalueswhenthere’snolight,andneveractuallysendavalueof0.
Rememberalsothatthesensorcantellusthemaximumvaluethatitcanreturnandthatdifferentsensorscanhavedifferentmaximums.Forthisreason,itmaynotbeusefultoconsiderthelight-relatedconstantsintheSensorManagerclass.Forexample,SensorManagerhasaconstantcalledLIGHT_SUNLIGHT_MAX,whichisafloatvalueof120,000;however,whenwequeriedourdeviceearlier,themaximumvaluereturnedwas10,240,clearlymuchlessthanthisconstantvalue.There’sanotheronecalledLIGHT_SHADEat20,000,whichisalsoabovethemaximumofthedevicewetested.Sokeepthisinmindwhenwritingcodethatuseslightsensordata.
ProximitySensorsTheproximitysensoreithermeasuresthedistancethatsomeobjectisfromthedevice(incentimeters)orrepresentsaflagtosaywhetheranobjectiscloseorfar.Someproximitysensorswillgiveavaluerangingfrom0.0tothemaximuminincrements,whileothersreturneither0.0orthemaximumvalueonly.Ifthemaximumrangeoftheproximitysensorisequaltothesensor’sresolution,thenyouknowit’soneofthosethatonlyreturns0.0,orthemaximum.Therearedeviceswithamaximumof1.0andotherswhereit’s6.0.Unfortunately,there’snowaytotellbeforetheapplicationisinstalledandrunwhichproximitysensoryou’regoingtoget.Evenifyouputa<uses-feature>taginyourAndroidManifest.xmlfilefortheproximitysensor,youcouldgeteitherkind.Unlessyouabsolutelyneedtohavethemoregranularproximitysensor,yourapplicationshouldaccommodatebothtypesgracefully.
Here’saninterestingfactaboutproximitysensors:theproximitysensorissometimesthesamehardwareasthelightsensor.Androidstilltreatsthemaslogicallyseparatesensors,though,soifyouneeddatafrombothyouwillneedtosetupalistenerforeachone.Here’sanotherinterestingfact:theproximitysensorisoftenusedinthephoneapplicationtodetectthepresenceofaperson’sheadnexttothedevice.Iftheheadisthatclosetothetouchscreen,thetouchscreenisdisabledsonokeyswillbeaccidentlypressedbytheearorcheekwhilethepersonistalkingonthephone.
Thesourcecodeprojectsforthischapterincludeasimpleproximitysensormonitorapplication,whichisbasicallythelightsensormonitorapplicationmodifiedtousetheproximitysensorinsteadofthelightsensor.Wewon’tincludethecodeinthischapter,butfeelfreetoexperimentwithitonyourown.
TemperatureSensorsTheolddeprecatedtemperaturesensor(TYPE_TEMPERATURE)providedatemperaturereadingandalsoreturnedjustasinglevalueinvalues[0].Thissensorusuallyreadaninternaltemperature,suchasatthebattery.ThereisanewtemperaturesensorcalledTYPE_AMBIENT_TEMPERATURE.ThenewvaluerepresentsthetemperatureoutsidethedeviceindegreesCelsius.
Theplacementofthetemperaturesensorisdevice-dependent,anditispossiblethatthetemperaturereadingscouldbeimpactedbytheheatgeneratedbythedeviceitself.TheprojectsforthischapterincludeoneforthetemperaturesensorcalledTemperatureSensor.IttakescareofcallingthecorrecttemperaturesensorbasedonwhichversionofAndroidisrunning.
PressureSensorsThissensormeasuresbarometricpressure,whichcoulddetectaltitudeforexampleorbeusedforweatherpredictions.ThissensorshouldnotbeconfusedwiththeabilityofatouchscreentogenerateaMotionEventwithapressurevalue(thepressureofthetouch).WecoveredthistouchtypeofpressuresensinginChapter22.Touchscreenpressuresensingdoesn’tusetheAndroidsensorframework.
TheunitofmeasurementforapressuresensorisatmosphericpressureinhPa(millibar),andthismeasurementisdeliveredinvalues[0].
GyroscopeSensorsGyroscopesareverycoolcomponentsthatcanmeasurethetwistofadeviceaboutareferenceframe.Saidanotherway,gyroscopesmeasuretherateofrotationaboutanaxis.Whenthedeviceisnotrotating,thesensorvalueswillbezeros.Whenthereisrotationinanydirection,you’llgetnonzerovaluesfromthegyroscope.Gyroscopesareoftenusedfornavigation.Butbyitself,agyroscopecan’ttellyoueverythingyouneedtoknowtonavigate.Andunfortunately,errorscreepinovertime.Butcoupledwithaccelerometers,youcandeterminethepathofmovementofthedevice.
Kalmanfilterscanbeusedtolinkdatafromthetwosensorstogether.Accelerometersarenotterriblyaccurateintheshortterm,andgyroscopesarenotveryaccurateinthelongterm,socombinedtheycanbereasonablyaccurateallthetime.WhileKalmanfiltersareverycomplex,thereisanalternativecalledcomplementaryfiltersthatareeasiertoimplementincodeandproduceresultsthatareprettygood.Theseconceptsarebeyondthescopeofthisbook.
Thegyroscopesensorreturnsthreevaluesinthevaluesarrayforthex,y,andzaxes.Theunitsareradianspersecond,andthevaluesrepresenttherateofrotationaroundeachofthoseaxes.Onewaytoworkwiththesevaluesistointegratethemovertimetocalculateananglechange.Thisisasimilarcalculationtointegratinglinearspeedovertimetocalculatedistance.
AccelerometersAccelerometersareprobablythemostutilizedofthesensorsonadevice.Usingthesesensors,yourapplicationcandeterminethephysicalorientationofthedeviceinspacerelativetogravity’spullstraightdown,plusbeawareofforcesactingonthedevice.Providingthisinformationallowsanapplicationtodoallsortsofinterestingthings,fromgameplaytoaugmentedreality.Andofcourse,theaccelerometerstellAndroidwhentoswitchtheorientationoftheuserinterfacefromportraittolandscapeandbackagain.
Theaccelerometercoordinatesystemworkslikethis:theaccelerometer’sxaxisoriginatesinthebottom-leftcornerofthedeviceandgoesacrossthebottomtotheright.Theyaxisalsooriginatesinthebottom-leftcornerandgoesupalongtheleftofthedisplay.Thezaxisoriginatesinthebottom-leftcornerandgoesupinspaceawayfromthedevice.Figure24-2showswhatthismeans.
Figure24-2.Accelerometercoordinatesystem
Thiscoordinatesystemisdifferentthantheoneusedinlayoutsand2Dgraphics.Inthatcoordinatesystem,theorigin(0,0)isatthetop-leftcorner,andyispositiveinthedirectiondownthescreenfromthere.Itiseasytogetconfusedwhendealingwithcoordinatesystemsindifferentframesofreference,sobecareful.
Wehaven’tyetsaidwhattheaccelerometervaluesmean,sowhatdotheymean?Accelerationismeasuredinmeterspersecondsquared(m/s2).NormalEarthgravityis9.81m/s2,pullingdowntowardthecenteroftheEarth.Fromtheaccelerometer’spointofview,themeasurementofgravityis–9.81.Ifyourdeviceiscompletelyatrest(notmoving)andisonaperfectlyflatsurface,thexandyreadingswillbe0andthezreadingwillbe+9.81.Actually,thevalueswon’tbeexactlythesebecauseofthesensitivityandaccuracyoftheaccelerometer,buttheywillbeclose.Gravityistheonlyforceactingonthedevicewhenthedeviceisatrest,andbecausegravitypullsstraightdown,ifourdeviceisperfectlyflat,itseffectonthexandyaxesiszero.Onthezaxis,theaccelerometerismeasuringtheforceonthedeviceminusgravity.Therefore,0minus–9.81is+9.81,andthat’swhatthezvaluewillbe(a.k.a.values[2]intheSensorEventobject).
Thevaluessenttoyourapplicationbytheaccelerometeralwaysrepresentthesumoftheforcesonthedeviceminusgravity.Ifyouweretotakeyourperfectlyflatdeviceandliftitstraightup,thezvaluewouldincreaseatfirst,becauseyouincreasedtheforceintheup(z)direction.Assoonasyourliftingforcestopped,theoverallforcewouldreturntobeingjustgravity.Ifthedeviceweretobedropped(hypothetically—pleasedon’tdothis),itwouldbeacceleratingtowardtheground,whichwouldzerooutgravitysotheaccelerometerwouldread0force.
Let’stakethedevicefromFigure24-2androtateitupsoitisinportraitmodeandvertical.Thexaxisisthesame,pointinglefttoright.Ouryaxisisnowstraightupanddown,andthezaxisispointingoutofthescreenstraightatus.Theyvaluewillbe+9.81,andbothxandzwillbe0.
Whathappenswhenyourotatethedevicetolandscapemodeandcontinuetoholditvertically,sothescreenisrightinfrontofyourface?Ifyouguessedthatyandzarenow0andxis+9.81,you’dbecorrect.Figure24-3showswhatitmightlooklike.
Figure24-3.Accelerometervaluesinlandscapevertical
Whenthedeviceisnotmoving,orismovingwithaconstantvelocity,theaccelerometersareonlymeasuringgravity.Andineachaxis,thevaluefromtheaccelerometerisgravity’scomponentinthataxis.Therefore,usingsometrigonometry,youcouldfigureouttheanglesandknowhowthedeviceisorientedrelativetogravity’spull.Thatis,youcouldtellifthedevicewereinportraitmodeorinlandscapemodeorinsometiltedmode.Infact,thisisexactlywhatAndroiddoestofigureoutwhichdisplaymodetouse(portraitorlandscape).Note,however,thattheaccelerometersdonotsayhowthedeviceisoriented
withrespecttomagneticnorth.Sowhileyoucouldknowthatthedeviceisbeingheldinlandscapemodevertically,youwouldn’tknowifyouwerefacingeastorwestoranywhereinbetween.That’swherethemagneticfieldsensorwillcomein,whichwewillcoverinalatersection.
AccelerometersandDisplayOrientationAccelerometersinadevicearehardware,andthey’refirmlyattached,andassuchhaveaspecificorientationrelativetothedevicethatdoesnotchangeasthedeviceisturnedthiswayorthat.ThevaluesthattheaccelerometerssendintoAndroidwillchangeofcourseasadeviceismoved,butthecoordinatesystemoftheaccelerometerswillstaythesamerelativetothephysicaldevice.Thecoordinatesystemofthedisplay,however,changesastheusergoesfromportraittolandscapeandbackagain.Infactdependingonwhichwaythescreenisturned,portraitcouldberight-sideup,or180degreesupside-down.Similarly,landscapecouldbeinoneoftwodifferentrotations180degreesapart.
Whenyourapplicationisreadingaccelerometerdataandwantingtoaffecttheuserinterfacecorrectly,yourapplicationmustknowhowmuchrotationofthedisplayhasoccurredtoproperlycompensate.Asyourscreenisreorientedfromportraittolandscape,thescreen’scoordinatesystemhasrotatedwithrespecttothecoordinatesystemoftheaccelerometers.Tohandlethis,yourapplicationmustusethemethodDisplay.getRotation().Thereturnvalueisasimpleintegerbutnottheactualnumberofdegreesofrotation.ThevaluewillbeoneofSurface.ROTATION_0,Surface.ROTATION_90,Surface.ROTATION_180,orSurface.ROTATION_270.Theseareconstantswithvaluesof0,1,2,and3,respectively.Thisreturnvaluetellsyouhowmuchthedisplayhasrotatedfromthe“normal”orientationofthedevice.BecausenotallAndroiddevicesarenormallyinportraitmode,youcannotassumethatportraitisatROTATION_0.
AccelerometersandGravitySofar,we’veonlybrieflytouchedonwhathappenstotheaccelerometervalueswhenthedeviceismoved.Let’sexplorethatfurther.Allforcesactingonthedevicewillbedetectedbytheaccelerometers.Ifyouliftthedevice,theinitialliftingforceispositiveinthezdirection,andyougetazvaluegreaterthan+9.81.Ifyoupushthedeviceonitsleftside,you’llgetaninitialnegativereadinginthexdirection.
Whatyou’dliketobeabletodoisseparateouttheforceofgravityfromtheotherforcesactingonthedevice.There’safairlyeasywaytodothis,andit’scalledalow-passfilter.Forcesotherthangravityactingonthedevicewilldosoinawaythatistypicallynotgradual.Inotherwords,iftheuserisshakingthedevice,theshakingforcesarereflectedintheaccelerometervaluesquickly.Alow-passfilterwillineffectstripouttheshakingforcesandleaveonlythesteadyforce,whichisgravity.Let’suseasampleapplicationtoillustratethisconcept.It’scalledGravityDemo.Listing24-3showstheJavacode.
Listing24-3.MeasuringGravityfromtheAccelerometers
//ThisfileisMainActivity.java
publicclassMainActivityextendsActivityimplementsSensorEventListener{privateSensorManagermgr;privateSensoraccelerometer;privateTextViewtext;privatefloat[]gravity=newfloat[3];privatefloat[]motion=newfloat[3];privatedoubleratio;privatedoublemAngle;privateintcounter=0;
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);
mgr=(SensorManager)this.getSystemService(SENSOR_SERVICE);accelerometer=mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);text=(TextView)findViewById(R.id.text);}
@OverrideprotectedvoidonResume(){mgr.registerListener(this,accelerometer,SensorManager.SENSOR_DELAY_UI);super.onResume();}
@OverrideprotectedvoidonPause(){mgr.unregisterListener(this,accelerometer);super.onPause();}
publicvoidonAccuracyChanged(Sensorsensor,intaccuracy){//ignore}
publicvoidonSensorChanged(SensorEventevent){//Usealow-passfiltertogetgravity.//Motioniswhat'sleftoverfor(inti=0;i<3;i++){gravity[i]=(float)(0.1*event.values[i]+0.9*gravity[i]);
motion[i]=event.values[i]-gravity[i];}
//ratioisgravityontheYaxiscomparedtofullgravity//shouldbenomorethan1,nolessthan-1ratio=gravity[1]/SensorManager.GRAVITY_EARTH;if(ratio>1.0)ratio=1.0;if(ratio<-1.0)ratio=-1.0;
//convertradianstodegrees,makenegativeiffacingupmAngle=Math.toDegrees(Math.acos(ratio));if(gravity[2]<0){mAngle=-mAngle;}
//Displayevery10thvalueif(counter++%10==0){Stringmsg=String.format("Rawvalues\nX:%8.4f\nY:%8.4f\nZ:%8.4f\n"+"Gravity\nX:%8.4f\nY:%8.4f\nZ:%8.4f\n"+"Motion\nX:%8.4f\nY:%8.4f\nZ:%8.4f\nAngle:%8.1f",event.values[0],event.values[1],event.values[2],gravity[0],gravity[1],gravity[2],motion[0],motion[1],motion[2],mAngle);text.setText(msg);text.invalidate();counter=1;}}}
TheresultofrunningthisapplicationisadisplaythatlookslikeFigure24-4.Thisscreenshotwastakenasthedevicelayflatonatable.
Figure24-4.Gravity,motion,andanglevalues
MostofthissampleapplicationisthesameastheAccelSensorapplicationfrombefore.ThedifferencesareintheonSensorChanged()method.Insteadofsimplydisplayingthevaluesfromtheeventarray,weattempttokeeptrackofgravityandmotion.Yougetgravitybyusingonlyasmallportionofthenewvaluefromtheeventarray,andalargeportionofthepreviousvalueofthegravityarray.Thetwoportionsusedmustaddupto1.0.Weused0.9and0.1.Youcouldtryothervalues,too,suchas0.8and0.2.Ourgravityarraycannotpossiblychangeasfastastheactualsensorvaluesarechanging.Butthisisclosertoreality.Andthisiswhatalow-passfilterdoes.Theeventarrayvalueswouldonlybechangingifforceswerecausingthedevicetomove,andyoudon’twanttomeasurethoseforcesaspartofgravity.Youonlywanttorecordintoyourgravityarraytheforceofgravityitself.Themathheredoesnotmeanyou’remagicallyrecordingonlygravity,butthevaluesyou’recalculatingaregoingtobealotcloserthantherawvaluesfromtheeventarray.
Noticealsothemotionarrayinthecode.Bytrackingthedifferencebetweentheraweventarrayvaluesandthecalculatedgravityvalues,youarebasicallymeasuringtheactive,non-gravity,forcesonthedeviceinthemotionarray.Ifthevaluesinthemotionarrayarezeroorveryclosetozero,itmeansthedeviceisprobablynotmoving.Thisisusefulinformation.Technically,adevicemovinginaconstantspeedwouldalsohavevaluesinthemotionarrayclosetozero,buttherealityisthatifauserismovingthedevice,themotionvalueswillbesomewhatlargerthanzero.Userscan’tpossiblymoveadeviceataperfectconstantspeed.
Lastly,pleasenoticethatthisexampledoesnotproducenewobjectsthatneedtobegarbagecollected.Itisveryimportantwhendealingwithsensoreventstonotcreatenew
objects;otherwiseyourapplicationwillspendtoomuchtimepausedforgarbagecollectioncycles.
UsingAccelerometerstoMeasuretheDevice’sAngleWewantedtoshowyouonemorethingabouttheaccelerometersbeforewemoveon.Ifwegobacktoourtrigonometrylessons,werememberthatthecosineofanangleistheratioofthenearsideandthehypotenuse.Ifweconsidertheanglebetweentheyaxisandgravityitself,wecouldmeasuretheforceofgravityontheyaxisandtakethearccosinetodeterminetheangle.We’vedonethatinthiscodeaswell,althoughherewehavetodealyetagainwithsomeofthemessinessofsensorsinAndroid.ThereareconstantsinSensorManagerfordifferentgravityconstants,includingEarth’s.Butyouractualmeasuredvaluescouldpossiblyexceedthedefinedconstants.Wewillexplainwhatwemeanbythisnext.
Intheory,yourdeviceatrestwouldmeasureavalueforgravityequaltotheconstantvalue,butthisisrarelythecase.Atrest,theaccelerometersensorisverylikelytogiveusavalueforgravitythatislargerorsmallerthantheconstant.Therefore,ourratiocouldendupgreaterthanone,orlessthannegativeone.Thiswouldmaketheacos()methodcomplain,sowefixtheratiovaluetobenomorethan1andnolessthan–1.Thecorrespondinganglesindegreesrangefrom0to180.That’sfineexceptthatwedon’tgetnegativeanglesfrom0to–180thisway.Togetthenegativeangles,weuseanothervaluefromourgravityarray,whichisthezvalue.Ifthezvalueofgravityisnegative,itmeansthedevice’sfaceisorienteddownward.Forallthosevalueswherethedevicefaceispointeddown,wemakeouranglenegativeaswell,withtheresultbeingthatouranglegoesfrom–180to+180,justaswewouldexpect.
Goaheadandexperimentwiththissampleapplication.Noticethatthevalueoftheangleis90whenthedeviceislaidflat,andit’szero(orclosetoit)whenthedeviceisheldstraightupanddowninfrontofus.Ifwekeeprotatingdownpastflat,wewillseethevalueoftheangleexceed90.Ifwetiltthedeviceupmorefromthe0position,thevalueofanglegoesnegativeuntilwe’reholdingthedeviceaboveourheadsandthevalueoftheangleis–90.Finally,youmayhavenoticedourcounterthatcontrolshowoftenthedisplayisupdated.Becausethesensoreventscancomeratherfrequently,wedecidedtoonlydisplayeverytenthtimewegetvalues.
MagneticFieldSensorsThemagneticfieldsensormeasurestheambientmagneticfieldinthex,y,andzaxes.Thiscoordinatesystemisalignedjustliketheaccelerometers,sox,y,andzareasshowninFigure24-2.Theunitsofthemagneticfieldsensoraremicroteslas(uT).ThissensorcandetecttheEarth’smagneticfieldandthereforetelluswherenorthis.Thissensorisalsoreferredtoasthecompass,andinfactthe<uses-feature>tagusesandroid.hardware.sensor.compassasthenameofthissensor.Becausethissensorissotinyandsensitive,itcanbeaffectedbymagneticfieldsgeneratedbythingsnearthedevice,andeventosomeextenttocomponentswithinthedevice.Thereforetheaccuracyofthemagneticfieldsensormayattimesbesuspect.
We’veincludedasimpleCompassSensorapplicationinthedownloadsectionofthewebsite,sofeelfreetoimportthatandplaywithit.Ifyoubringmetalobjectsclosetothedevicewhilethisapplicationisrunning,youmightnoticethevalueschanginginresponse.Certainlyifyoubringamagnetclosetothedeviceyouwillseethevalueschange.Infact,theGoogleCardboard“device”usesamagnetunderaphysicalbuttonwhichisthendetectedbythephoneasachangeinthemagneticfieldwhenthebuttonispressed.
Youmightbeasking,canIusethecompasssensorasacompasstodetectwherenorthis?Andtheansweris:notbyitself.Whilethecompasssensorcandetectmagneticfieldsaroundthedevice,ifthedeviceisnotbeingheldperfectlyflatinrelationtotheEarth’ssurface,you’dhavenowayofcorrectlyinterpretingthecompasssensorvalues.ButyouhaveaccelerometersthatcantellyoutheorientationofthedevicerelativetotheEarth’ssurface!Therefore,youcancreateacompassfromthecompasssensor,butyou’llneedhelpfromtheaccelerometerstoo.Solet’sseehowtodothat.
UsingAccelerometersandMagneticFieldSensorsTogetherTheSensorManagerprovidessomemethodsthatallowustocombinethecompasssensorandtheaccelerometerstofigureoutorientation.Aswejustdiscussed,youcan’tusejustthecompasssensoralonetodothejob.SoSensorManagerprovidesamethodcalledgetRotationMatrix(),whichtakesthevaluesfromtheaccelerometersandfromthecompassandreturnsamatrixthatcanbeusedtodetermineorientation.
AnotherSensorManagermethod,getOrientation(),takestherotationmatrixfromthepreviousstepandgivesanorientationmatrix.Thevaluesfromtheorientationmatrixtellyouyourdevice’srotationrelativetotheEarth’smagneticnorth,aswellasthedevice’spitchandrollrelativetotheground.
MagneticDeclinationandGeomagneticFieldThere’sanothertopicwewanttocoverwithregardtoorientationanddevices.Thecompasssensorwilltellyouwheremagneticnorthis,butitwon’ttellyouwheretruenorthis(a.k.a.,geographicnorth).Imagineyouarestandingatthemidpointbetweenthemagneticnorthpoleandthegeographicnorthpole.They’dbe180degreesapart.Thefurtherawayyougetfromthetwonorthpoles,thesmallerthisangledifferencebecomes.Theangledifferencebetweenmagneticnorthandtruenorthiscalledmagneticdeclination.Andthevaluecanonlybecomputedrelativetoapointontheplanet’ssurface.Thatis,youhavetoknowwhereyou’restandingtoknowwheregeographicnorthisinrelationtomagneticnorth.Fortunately,Androidhasawaytohelpyouout,andit’stheGeomagneticFieldclass.
InordertoinstantiateanobjectoftheGeomagneticFieldclass,youneedtopassinalatitudeandlongitude.Therefore,inordertogetamagneticdeclinationangle,youneedtoknowwherethepointofreferenceis.Youalsoneedtoknowthetimeatwhichyouwantthevalue.Magneticnorthdriftsovertime.Onceinstantiated,yousimplycallthismethod
togetthedeclinationangle(indegrees):
floatdeclinationAngle=geoMagField.getDeclination();
ThevalueofdeclinationAnglewillbepositiveifmagneticnorthistotheeastofgeographicnorth.
GravitySensorsThissensorisn’taseparatepieceofhardware.It’savirtualsensorbasedontheaccelerometers.Infact,thissensoruseslogicsimilartowhatwedescribedearlierforaccelerometerstoproducethegravitycomponentoftheforcesactingonadevice.Wecannotaccessthislogic,however,sowhateverfactorsandlogicareusedinsidethegravitysensorclassarewhatwemustaccept.It’spossible,though,thatthevirtualsensorwilltakeadvantageofotherhardwaresuchasagyroscopetohelpitcalculategravitymoreaccurately.Thevaluesarrayforthissensorreportsgravityjustliketheaccelerometersensorreportsitsvalues.
LinearAccelerationSensorsSimilartothegravitysensor,thelinearaccelerationsensorisavirtualsensorthatrepresentstheaccelerometerforcesminusgravity.Again,wedidourowncalculationsearlierontheaccelerometersensorvaluestostripoutgravitytogetjusttheselinearaccelerationforcevalues.Thissensormakesthatmoreconvenientforyou.Anditcouldtakeadvantageofotherhardware,suchasagyroscope,tohelpitcalculatelinearaccelerationmoreaccurately.Thevaluesarrayreportslinearaccelerationjustliketheaccelerometersensorreportsitsvalues.
RotationVectorSensorsTherotationvectorsensorrepresentstheorientationofthedeviceinspace,withanglesrelativetotheframeofreferenceofthehardwareaccelerometer(seeFigure24-2).Thissensorreturnsasetofvaluesthatrepresentsthelastthreecomponentsofaunitquaternion.Quaternionsareasubjectthatcouldfillabook,sowewon’tbegoingintothemhere.
Thankfully,GooglehasprovidedafewmethodswithinSensorManagertohelpwiththissensor.ThegetQuaternionFromVector()methodconvertsarotationvectorsensoroutputtoanormalizedquaternion.ThegetRotationMatrixFromVector()methodconvertsarotationvectorsensoroutputtoarotationmatrix,andthatcanbeusedwithgetOrientation().Whenconvertingrotationvectorsensoroutputtoanorientationvector,though,youneedtorealizethatitgoesfrom–180degreesto+180degrees.
TheZIPfileofsampleappsforthischapterincludesaversionofVirtualJaxthatshowstherotationvectorinuse.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
www.androidbook.com/proandroid5/projects:Alistofdownloadableprojectsrelatedtothisbook.Forthischapter,lookforaZIPfilecalledProAndroid5_Ch24_Sensors.zip.Thisfilecontainsalltheprojectsfromthischapter,listedinseparaterootdirectories.ThereisalsoaREADME.TXTfilethatdescribesexactlyhowtoimportprojectsintoanIDEfromoneoftheseZIPfiles.
http://en.wikipedia.org/wiki/Lux:TheWikipediaentryforlux,theunitoflightmeasurement.
www.ngdc.noaa.gov/geomag/faqgeom.shtml:InformationaboutgeomagnetismfromNOAA.
www.youtube.com/watch?v=C7JQ7Rpwn2k:AGoogleTechTalkfromDavidSachsonaccelerometers,gyroscopes,compasses,andAndroiddevelopment.
http://stackoverflow.com/questions/1586658/combine-gyroscope-and-accelerometer-data:Anicepostingonstackoverflow.comthattalksaboutcombininggyroscopeandaccelerometersensordataforuseinapplications.
http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotationTheWikipediapageonquaternionsandhowtheycanbeusedinrepresentingspatialrotation,suchasanAndroiddevice.
SummaryInthischapter,wecoveredthefollowingtopics:
WhatsensorsareinAndroid.
Findingoutwhatsensorsareonadevice.
SpecifyingthesensorsthatarerequiredforanapplicationbeforeitwillbeloadableontoanAndroiddevice.
Determiningthepropertiesofasensoronadevice.
Howtogetsensorevents.
Thefactthateventscomewheneverthesensorvaluechanges,soitisimportanttounderstandtherecouldbealagbeforeyougetyourfirstvalue.
Thedifferentspeedsofupdatesfromasensorandwhentouseeach
one.
ThedetailsofaSensorEventandhowthesecanbeusedforthevarioussensortypes.
Virtualsensors,madeupofdatafromothersensors.TheROTATION_VECTORsensorisoneofthese.
Determiningtheangleofthedeviceusingsensors,andtellingwhichdirectionthedeviceisfacing.
Chapter25
ExploringAndroidPersistenceandContentProvidersThereareanumberofwaysofsavingstateintheAndroidSDK.Someoftheseare1)sharedpreferences,2)internalfiles,3)externalfiles,4)SQLite,5)contentproviders,6)O/Rmappingtools,and7)networkstorageinthecloud.Wewillbrieflyintroduceeachofthesestate-savingoptionsfirstandthencoverindetailmanagingapplicationstateusingSQLiteandcontentproviders.
SavingStateUsingSharedPreferencesWehavecoveredsharedpreferencesinChapter11.Sharedpreferencesarekey/value-basedXMLfilesownedbyyourapplication.Androidhasaframeworkontopofthisgeneralpersistencemechanismtodisplay/update/retrievepreferenceswithoutwritingalotofcode.ThislatteraspectisthemaintopicofChapter11.
Chapter11alsotouchedbrieflyonhowanapplicationcanstoreanytypeofdatausingthesharedpreferenceAPIinXMLfiles.Inthisapproachdataisconvertedtoastringrepresentationfirstandthenstoredinthepreferenceskey/valuestore.Thisapproachcanbeusedtostoreanyarbitrarystateofyourapplicationaslongasitissmalltomediuminsize.
ThesharedpreferenceXMLfilesareinternaltotheapplicationonyourdevice.Thisdataisnotdirectlyavailabletootherapplications.EndusercannotdirectlymanipulatethisdatabymountingontoaUSBport.Thisdataisremovedautomaticallywhentheapplicationisremoved.
Fromsimpletomoderateapplicationpersistenceneeds,youcantakeadvantageofsharedpreferencesbystoringvarioustreesofJavaobjectsdirectlyinasharedpreferencefile.InagivenpreferencefileyoucanhaveakeypointtoaserializedJavaobjecttree.YoucanalsousemultiplepreferencefilesformultipleJavaobjecttrees.WehaveusedJSON/GSONlibraryfromgoogletodothisconversionfromJavaobjectstotheirequivalentJSONstringvaluesquiteeffectively.InthisapproachaJavaobjecttreeisstreamedasaJSONstringusingthegoogleGSONlibrary.Thistreeisthenstoredasavalueinakey/valuepairofapreferencefile.KeepinmindthatGSONandJSONconversionofaJavaobjectmayhavesomelimitations.ReadtheGSON/JSONdocumentationtoseehowcomplexaJavaobjectcangettomakethisapproachwork.Wearefairlyconfidentthatformostdata-basedJavaobjectsthiswillwork.
Listing25-1hassomesamplecodeforhowtosaveaJavatreeusingGSON/JSONandsharedpreferences.
Listing25-1.SavingaJavaObjectTreeUsingJSONinSharedPreferencesXMLFiles
//ImplementationofstoreJSONforstoringanyobjectpublicvoidstoreJSON(Contextcontext,ObjectanyObject){
//GetaGSONinstanceGsongson=newGson();
//ConvertJavaobjecttoaJSONstringStringjsonString=gson.toJson(anyObject);
//SeeChapter11formoredetailsonhowtogetasharedpreferencesreferenceStringfilename="somefilename.xml";intmode=Context.MODE_PRIVATE;SharedPreferencessp=context.getSharedPreferences(filename,mode);
//SavetheJSONstringinthesharedpreferencesSharedPreferences.Editorspe=sp.edit();spe.putString("json",jsonString);spe.commit();}//Thiscodecanthenbeusedbyaclientlikethis://Createanydataobjectwithreasonablecomplexity//Ex:MainObjectmo=MainObject.createTestMainObject();//YoucanthencallstoreJSON(some-activity,mo)below
Listing25-2showssomesamplecodeforhowtoretrieveaJavatreeusingGSON/JSONandsharedpreferences.
Listing25-2.ReadingaJavaObjectTreeUsingJSONfromSharedPreferencesXMLFiles
publicObjectretrieveJSON(Contextcontext,Stringfilename,ClassclassRef){intmode=Context.MODE_PRIVATE;SharedPreferencessp=context.getSharedPreferences(filename,mode);StringjsonString=sp.getString("json",null);if(jsonString==null){thrownewRuntimeException("Notabletoreadthepreference");}Gsongson=newGson();returngson.fromJson(jsonString,classRef);}
//YoucanthendothisintheclientcodeMainObjectmo
=(MainObject)retrieveJSON(context,"somefilename.xml",MainObject.class);StringcompareResult=MainObject.checkTestMainObject(mo);if(compareResult!=null){thrownewRuntimeException("Somethingiswrong.Objectsdon'tmatch");}
ThiscoderequiresthatyouhavetheGSONJavalibraryaddedtoyourproject.ThisGSON-basedapproachiscoveredindetailinourcompanionbook,ExpertAndroidfromApress.Thisisalsobrieflydocumentedonlineathttp://androidbook.com/item/4438.
SavingStateUsingInternalFilesInAndroid,youcanalsouseinternalfilestostorethestateofyourapplication.Theseinternalfilesareinternaltotheapplicationonyourdevice.Thisdataisnotdirectlyavailabletootherapplications.EndusercannotdirectlymanipulatethisdatabymountingontoaUSBport.Thisdataisremovedautomaticallywhentheapplicationisremoved.
Listing25-3showssamplecodeforhowtosaveaJavatreeusingGSON/JSONandinternalfiles.
Listing25-3.Reading/WritingJSONStringsfrom/toanAndroidInternalFile
privateObjectreadFromInternalFile(ContextappContext,Stringfilename,ClassclassRef)throwsException{FileInputStreamfis=null;try{fis=appContext.openFileInput(filename);//ReadthefollowingstringfromthefilestreamfisStringjsonString;
Gsongson=newGson();returngson.fromJson(jsonString,classRef);}finally{//writecodetocloseStreamSilently(fis);}}privatevoidsaveToInternalFile(ContextappContext,Stringfilename,ObjectanyObject){Gsongson=newGson();StringjsonString=gson.toJson(anyObject);
FileOutputStreamfos=null;
try{fos=appContext.openFileOutput(filename,Context.MODE_PRIVATE);fos.write(jsonString.getBytes());}finally{//closeStreamSilently(fos);}}
ThisapproachbasedoninternalfilesandGSONiscoveredindetailinourcompanionbook,ExpertAndroidfromApress(http://www.apress.com/9781430249504).Thisisalsobrieflydocumentedonlineathttp://androidbook.com/item/4439.
SavingStateUsingExternalFilesInAndroid,externalfilesarestoredeitherontheSDcardoronthedevice.Thesebecomepublicfilesthatotherappsincludingtheusercouldseeandreadoutsidethecontextofyourapplication.Formanyappsthatwanttomanagetheirinternalstatetheseexternalfileswillunnecessarilypollutethepublicspace.
BecausethedatayouwouldrepresentasJSONistypicallyveryspecifictoyourapplication,itdoesn’tmakesensetomakethisavailableasexternalstorage,whichistypicallyusedformusicfiles,videofiles,orfilesthatarecommonlyinaformatthatisunderstandablebyotherapplications.
BecauseexternalstoragesuchasanSDcardcanbeinvariousstates(available,notmounted,full,etc.),itishardertoprogramthisforsimpleappswhenthedataissmallenough.Sowecouldnotmakeagoodcasefornowthattheapplicationstatebemaintainedonexternalstorage.
AhybridapproachmaybemeaningfuliftheapplicationrequiresmusicandphotosandthosecangoontheexternalstoragewhilekeepingthecorestatedatainJSONandinternal.
Theandroid.os.Environmentclassandtheandroid.content.Contextclasshaveanumberofmethodstoreadandwritetoexternalfilesanddirectories.Wehavenotincludedcodeexamplesbecausetheapproachisverysimilartointernalfilesonceyougetaccesstothesefilesthroughtheandorid.content.Context.
SavingStateUsingSQLiteAndroidappscanuseSQLitedatabasestostoretheirstate.SQLiteiswellintegratedintothefabricofAndroid.Ifyouwanttostoretheinternalstateofyourapplicationrobustlythenthisisprobablythebestapproach.However,workingwithanyrelationaldatabaseincludingSQLitehasalotofnuances.WewillcovertheessentialsandnuancesofusingSQLiteonAndroidalittlelaterinthechapter.
SavingStateUsingO/RMappingLibrariesO/RmappingstandsforObject-to-RelationalMapping.AkeydifficultywithstoringstateinarelationaldatabasefromaprogramminglanguagelikeJavaisthemismatchbetweenJavaobjectstructuresandrelationalstructuresofthedatabase.Oneneedstomapbetweenthenames,types,andrelationshipsoffieldsastheyareintheJavaspaceandtheirequivalentsinthedatabasespace.Thismappingiserrorprone.YouwillseethiswhenwecovertheSQLiteindetaillater.
ThereisaneedforsimplifyingthismappingofdatabetweenJavaandSQL.ThisspaceintheindustryiscalledO/Rmapping.AfewtoolsarenowavailabletosolvethisinAndroid.ItisbeyondthescopeofthisbooktocovertheessentialsoftheseO/Rmappingtools.Butwewillnameacoupleofthesetoolsandgivetheironlinereferencesnow.
TwokeytoolsinthisspaceareGreenDAO(http://greendao-orm.com/)andOrmLite(http://ormlite.com/).Therearemoreappearingeveryyear.Socheckoftentoseeifthenewonesarefasteroreasier.GreenDAOusesacodegenerationapproachbasedonschemadefinitions.ItissaidtobethreetofourtimesfasterthanOrmLite.OrmLitefusestheschemadefinitionwithJavaclassesthroughannotations.Thelatterapproachiseasierprogrammatically.AlsoOrmLiteworksthesameonanyJavaplatform.However,possiblyduetoreflectionusedatruntime,itcanbeslower,butIsuspectisfastenoughformostapplications.
WepredictthatusingoneoftheseO/Rmappinglibrariesisakeyneedtogetyourappsfastertothemarket.WerecommendthatyouisolatethepersistenceservicesandstartwithOrmLiteandthenmovetoGreenDAOifyourappgainsenoughtractionorformovingtoproductionfromyourprototype.
SavingStateUsingContentProvidersAndroidprovidesahigher-levelabstractionontopofdatastoresbasedonURIs.UsingthisapproachanyapplicationcanreadorstoredatausingRESTlikeURIs.ThisabstractionalsoallowsapplicationstosharetheirdatathroughAPIsbasedonURIstrings.InthisapproachsubmittingaURIwillgivebackacollectionofrowsandcolumnsinadatabasecursor.AURIcanalsotakeasetofkey/valuepairsandpersisttheminatargetdatabaseifpermissionsaregranted.Thisisageneral-purposemechanismforinteroperabilityofdatabetweenAndroidapplications.Wewillcoverthisingreaterdetaillaterinthechapter.Thisisapreferredmechanismifyourapplicationhasdatathatisvaluabletobeshared,created,ormanipulatedbyotherapplications.Forexamplemanyapplicationsthatdealwithnotes,documents,audio,orvideoimplementtheirdataascontentproviders.ThisisalsothecasewithmostofAndroid’scoredata-relatedservices.
SavingStateUsingNetworkStorageNetworkstoragecomesintoplaywhenthedatacreatedorusedbyanapplicationneedstobesharedviaanetworkbyotherusersoneitherthesameplatformordifferentplatformslikeinacollaborativeapplication.Thisback-endservicefacilityutilizedbymobile
applicationisbeingcalledMBaaS(MobileBack-endAsAService).Parse.comisanexampleofaMBAASthatprovidesback-endservicessuchasusermanagement,userlogins,security,social,commonnetworkstorage,serversidebusinesslogic,andnotifications.
Androidalsonativelyusesaconceptcalledsyncadapterstotransferdatabetweenthedeviceandnetworkservers.Youcanreadmoreonsyncadaptersathttp://developer.android.com/training/sync-adapters/index.html.Thisisaframeworkthatusesasynchronouscallbackstooptimizetransferofarbitraryamountsofdataefficientlybyschedulingandexecutingitatthemostopportunemoment.Theframeworksweatsthedetailanddevelopersjustprovidethetransfercode.
ThatconcludestheoverviewofvariouswaystosavestateforAndroidmobileapplications.Wewillcovernowtwoofthoseapproachesindetail:SQLiteandcontentproviders.WewillstartwiththeAndroidSQLiteAPI.
StoringDataDirectlyUsingSQLiteInthissectionwewillexploreindetailhowtouseSQLiteeffectivelytomanageAndroidapplicationstate.YouwillunderstandtheextentofSQLitesupportinAndroid.Wewillshowyoutheessentialcodesnippets.WewillshowyoubestpracticesforusingSQLiteonAndroid.WewillshowyouhowbesttoloadDDLstocreateyourdatabase.Wewillshowyouacleanerarchitecturalpatterntoabstractpersistenceservices.Wewillshowhowtoapplytransactionsthroughdynamicproxies.ThissectionisarobusttreatmentofusingSQLiteonAndroid.Wealsohaveasampleprogramthatyoucandownloadtoseethecompleteworkingimplementation.Let’sstartwithaquickoverviewofSQLitepackagesandclassesinAndroid.
SummarizingKeySQLitePackagesandClassesAndroidsupportsSQLitethroughitsJavapackageandroid.database.sqlite.SomeofthekeyclassesyouwillneedtounderstandforeffectivelyusingtheAndroidSQLiteAPIarelistedinListing25-4.Notethatsomeoftheclassesareoutsidetheandroid.database.sqlitepackage.
Listing25-4.KeySQLiteJavaClassesintheAndroidSDK
android.database.sqlite.SQLiteDatabaseandroid.database.sqlite.SQLiteCursorandroid.database.sqlite.SQLiteQueryBuilderandroid.content.ContentValuesandroid.database.Cursorandroid.database.SQLExceptionandroid.database.sqlite.SQLiteOpenHelper
Let’stalkabouteachofthesepackagesandclassesbriefly.
SQLiteDatabase:SQLiteDatabaseisaJavaclassthatrepresentsthedatabaseusuallyreferringtoa“.db”fileonthefilesystem.Usingthisobjectyoucanquery,insert,update,ordeleteforagiventableinthatdatabase.YoucanalsoexecuteasinglearbitrarySQLstatement.Youcanapplytransactions.YoucanalsousethisobjecttodefinetablesthroughDDLs(DataDefinitionLanguage).DDLsarestatementsthatletyoucreatedatabaseentitiessuchastables,views,indexes,etc.Typicallythereisasingleinstanceofthisobjectinyourapplicationrepresentingyourdatabase.
SQLiteCursor:ThisJavaclassrepresentsacollectionofrowsthatarereturnedfromanSQLiteDatabase.Italsoimplementstheandroid.database.Cursorinterface.Thisobjecthasmethodstonavigatetherowsoneatatimelikeaforwarddatabasecursorandretrievingtherowsonlyasneeded.Thisobjectcanalsojumpforwardorbackwardifneededlikearandomcursorbyimplementingwindowingqualities.Thisisalsotheobjectyouwillusetoreadthecolumnvaluesforanycurrentrow.
SQLiteQueryBuilder:ThisisahelperJavaclasstoconstructanSQLitequerystringbyincrementallyspecifyingtablenames,columnnames,whereclause,etc.,asseparatefields.ThisclasshasanumberofsetmethodstograduallybuildupthequeryasopposedtospecifyingtheentireSQLQueryasastring.YoucanalsousethequerymethodsdirectlyontheSQLiteDatabaseclassifyourqueryissimple.
ContentValues:AJavaobjectofthisclassholdsasetofkey/valuepairsthatareusedbyanumberofSQLiteclassestoinsertorupdatearowofdata.
SQLException:MostAndroidSQLitedatabaseAPIsthrowthisexceptionwhenthereareerrors.
SQLiteOpenHelper:ThishelperJavaobjectprovidesaccesstoanSQLiteDatabasebyexaminingafewthings:givenafilenameofthedatabase,thisobjectcheckstoseeifthatdatabaseisalreadyinstalledandavailable.Ifitisavailableitcheckstoseeiftheversionisthesame.IftheversionisalsothesameitprovidesareferencetotheSQLiteDatabaserepresentingthatdatabase.Iftheversionisdifferentitprovidesacallbacktomigratethedatabasepriortoprovidingavalidreferencetothedatabase.Ifthedatabasefiledoesn’texistthenitprovidesacallbacktocreateandpopulatethedatabase.Youwillextendthisbaseclassandprovideimplementationstothesevariouscallbacks.Youwillseethisshortlyintheprovidedcodesnippets.
ThatisaquicksummaryofthekeyclassesyouusetosavestateofyourapplicationinanSQLitedatabase.LetusnowturntokeyconceptsinusingSQLiteformanagingapplicationstate.Let’sstartwithcreatingadatabase.
CreatinganSQLiteDatabaseCreationofadatabaseinAndroidiscontrolledthroughtheSQLiteOpenHelperclass.ForeachdatabaseinyourapplicationyouwillhaveaJavadatabaseobjectthatisaninstanceofthisclass.ThisSQLiteOpenHelperobjecthasapairofgetmethodstogetareferencetotheread-optimized(configuredfor)orwrite-optimized(configuredfor)SQLiteDatabaseobject.CreatingorgettingaccesstoyourSQLitedatabaseobject
involvesthefollowing:
1. ExtendingSQLiteOpenHelperandsupplyingthedatabasenameandversiontotheconstructorofthisderivedclasssothatthosevaluescanbepassedtothebaseclass
2. OverridingtheonCreate(),onUpgrade(),andonDowngrade()methodsfromSQLiteOpenHelper.YougetacalltoonCreate()ifthisdatabaseisnotthere.YougetacalltoonUpgrade()iftheversionofthedatabaseisnewer,andacalltoonDowngrade()iftheversionofthedatabaseisolderfromtheonethatisonthedevice.YouwilluseexecuteDDLstatementsinthesemethodstocreateoradjustyourdatabase.Ifyourdatabaseisnotneworhasthesameversion,thenneitherofthesecallbackswillbeinvoked.
3. Haveasinglestaticreferencetothisderivedobject.Callgetmethodsonthisobjecttogetareferencetoacopyofreadableorwritabledatabase.UsethesedatabasereferencestoperformCRUDoperationsandtransactions.
Listing25-5isacodesnippetthatdemonstrateshowthesestepsareimplementedincreatingadatabasecalled“booksqlite.db”,adatabasetoholdasingletableofbooksandtheirdetail.
Listing25-5.UsingSQLiteOpenHelper
//Filereferenceinproject:DirectAccessBookDBHelper.Java/***AcompleteexampleofSQLiteOpenHelperdemonstrating*1.Howtocreateadatabases*2.Howtomigrateadatabase*3.Howtoholdastaticreference*4.Howtogiveoutreadandwritedatabasereferences**ThisclassalsocanactasaDatabaseContext.IFactorytoproducereadandwrite*databasereferences.Thisaspectisnotcriticaltounderstandingbutincluded*foradvancedreadersandforsomemateriallaterinthechapter.*/publicclassDirectAccessBookDBHelperextendsSQLiteOpenHelper
implementsDatabaseContext.IFactory{//thereisoneandonlyoneofthesedatabasehelpers//forthisdatabaseforthisentireapplicationpublicstaticDirectAccessBookDBHelperm_self=new
DirectAccessBookDBHelper(MyApplication.m_appContext);
//NameofthedatabaseonthedeviceprivatestaticfinalStringDATABASE_NAME="bookSQLite.db";
//NameoftheDDLfileyouwanttoloadwhilecreatingadatabaseprivatestaticfinalStringCREATE_DATABASE_FILENAME="create-book-db.sql";
//CurrentversionnumberofthedatabasefortheApptoworkprivatestaticfinalintDATABASE_VERSION=1;
//JustaloggingtagprivatestaticfinalStringTAG="DirectAccessBookDBHelper";
//Passthedatabasenameandversiontothebaseclass//Thisisanonpublicconstructor//Clientscanjustusem_selfandnotconstructthisobjectatalldirectlyDirectAccessBookDBHelper(Contextcontext){
super(context,DATABASE_NAME,null,DATABASE_VERSION);//Initializeanythingelseinyoursystemthatmayneeda//referencetothisobject.//Example:DatabaseContext.initialize(this);}@OverridepublicvoidonCreate(SQLiteDatabasedb){
try{//Nodatabaseexists.LoadDDLfromafileintheassetsdirectoryloadSQLFrom(this.CREATE_DATABASE_FILENAME,db);}catch(Throwablet){//ProblemcreatingdatabasethrownewRuntimeException(t);}}//AfunctiontoloadoneSQLstatementatatimeusingexecSQLmethodprivatevoidloadSQLFrom(StringassetFilename,SQLiteDatabasedb){List<String>statements
=getDDLStatementsFrom(assetFilename);for(Stringstmt:statements){Log.d(TAG,"ExecutingStatement:"+stmt);db.execSQL(stmt);
}}//Optimizethisfunctionforrobustness.//Fornowitassumestherearenocommentsinthefile//thestatementsareseparatedbyasemicolonprivateList<String>getDDLStatementsFrom(StringassetFilename){ArrayList<String>l=newArrayList<String>();Strings=getStringFromAssetFile(assetFilename);for(Stringstmt:s.split(";")){//Addthestmtifitisavalidstatementif(isValid(stmt)){l.add(stmt);}}returnl;}privatebooleanisValid(Strings){//writelogicheretoseeifitisnull,emptyetc.returntrue;//fornow}@OverridepublicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,int
newVersion){
//UseoldandnewversionnumberstorunDDLstatements//toupgradethedatabase}//Usingyourspecificapplicationobjecttoremembertheapplicationcontext//ThenusingthatapplicationcontexttoreadassetsprivateStringgetStringFromAssetFile(Stringfilename){Contextctx=MyApplication.m_appContext;if(ctx==null){thrownewRuntimeException("Sorryyourappcontextisnull");}try{AssetManageram=ctx.getAssets();InputStreamis=am.open(filename);Strings=convertStreamToString(is);is.close();
returns;}catch(IOExceptionx){thrownewRuntimeException("Sorrynotabletoreadfilename:"+filename,x);}}//Optimizelater.ThismaynotbeanefficientreadprivateStringconvertStreamToString(InputStreamis)throwsIOException{ByteArrayOutputStreambaos=newByteArrayOutputStream();inti=is.read();while(i!=-1){baos.write(i);i=is.read();}returnbaos.toString();}//Herearesomeexamplesofhowtogetaccesstoreadableand//writabledatabases.Thesemethodswillmakesenseoncewegetthroughthe//thetransactionsappliedthroughdynamicproxies/*publicReadDatabaseContextcreateReadableDatabase(){returnnewReadDatabaseContext(this.getReadableDatabase());}publicWriteDatabaseContextcreateWritableDatabase(){returnnewWriteDatabaseContext(this.getWritableDatabase());}*/}//eof-classDatabaseHelper//HereisthecodeforMyApplicationtorememberthecontextpublicclassMyApplicationextendsApplication{
publicfinalstaticStringtag="MyApplication";publicstaticvolatileContextm_appContext=null;
@OverridepublicvoidonCreate(){super.onCreate();MyApplication.m_appContext=this.getApplicationContext();}
}//assets/create-book-db.sql
CREATETABLEt_books(idINTEGERPRIMARYKEY,nameTEXT,isbnTEXT,authorTEXT,created_onINTEGER,created_byTEXT,last_updated_onINTEGER,last_updated_byTEXT);
DefiningaDatabaseThroughDDLsInListing25-5,theDirectAccessBookDBHelperisaderivedclassofSQLiteOpenHelperthatallowsustoexamineanexistingdatabaseandseeifitneedstobecreatedorjustmigratedbasedonitsversion.
ThemethodonCreate()iscalledonlyifthisdatabasedoesnotexistonthedevice.WithouttheSQLiteOpenHelperwewouldhavehadtoexaminethephysicallocationofthisfileandseeifitexists.InotherwordsSQLiteOpenHelperisreallyathinwrapperthatissavingusanumberof“if-else”clausestoexaminethedatabaseanddothenecessaryinitialization:beitcreatingitormigratingit.
AnumberofexamplesforcreatinganAndroiddatabaseontheInternetuseembeddedDDLstringsinJavacodetocreatethetablesneeded.AsDDLstatements,stringsinJavacodearedifficulttoreadanderrorprone.Abetterapproachistoputthesedatabasecreationscriptsinatextfileintheassetsdirectory.SamplecodeinListing25-5demonstrateshowtoreadatextfilefromanassetsdirectoryofyourapplicationandusetheexecSQL()functionavailableontheSQLiteDatabasetoinitializethedatabase.
AlimitationofexecSQL()isitcanexecuteonlyoneSQLstatementatatime.ThatiswhythecodeinListing25-5readsthescriptfileandparsesitintoaseriesofstatementsusingasimplesyntax.YoumaywanttoscourtheInternettoseebetterparsingutilitiestoallowabetterscriptfilesupport.Anotheralternative,ifitworksforyourcase,istohaveaschemaclasswhosesolepurposeiscontainstaticpublicstringsforyourDDLasitalleviatestheneedforparsingfiles.WehavelinkstosomeoftheseJava-basedlibrariesintheonlinereferencesprovidedattheendofthischapter.Especially,Java-basedtoolsusingANTLRhavealotofpromiseforcomplexdatabasesetups.
TheonCreate()functionalsowrapsitsexecutioninatransactionsothattheexecuteddatabaseisconsistent.
Ifyouhavealotofscriptsitisalsopossibletocreatethedatabaseentirelyandkeepitintheassetsfolder.Duringdeploymentifthedatabasedoesn’texistyoucanjustcopythefiletoitstargetlocation.
MigratingaDatabaseAsstatedtheSQLiteOpenHelperrecognizesversionnumbersandappropriatelycallstheonUpgrade()methodtoupgradethedatabase.Herealsoyoumaywanttokeepseriesofscriptsintheassetsfolderthatcanalterthedatabaseappropriatelydependingon
thedifferencesintheversionnumbers.Keepinmindthattheversionnumberonthedevicemaybesmallerorlargerthanyourtargetversion.Soyoumayneedasetofscriptsthatareuniquetoeachconversionsequence:GoingfromV1toV3orfromV2toV3orV3toV1.Goingbackwardsmayrequireeitherwarningsordynamicdownloadingofserver-sideconversionstoanolderversion,asthesourcecodeofanolderversionoftheappisunlikelytohavetheneededutilitiestostepdownfromafutureversion.
InsertingRowsAtitscore,insertingarowwithitscolumnvaluesintoSQLiteDatabaseismerelycallingtheinsert-relatedmethodsontheSQLiteDatabaseobject.PseudocodethatexplainsthisisshowninListing25-6.
Listing25-6.BasicsofInsertingaRowUsingSQLiteDatabase
//Getareferencetothedatabaseobject//DependingontheframeworkyouhavetherecouldbemanywaysofdoingthisSQLiteDatatabasedb=DirectAccessBookDBHelper.m_self.getReadableDatabase();Stringtablename;//whichtableyouwanttoinserttherowinto
//populateastructurewiththeneededcolumnsandtheirvaluesContentValuescv=newContentValues();cv.put(columnName1,column1Value);//etc.
//Acolumnthatcouldbenullif'cv'isemptyifanemptyrowisneeded//ProvidenullifthatbehaviorisnotneededStringnullColumnName=null;
//InserttherowlongrowId=db.insertOrThrow(tablename,nullColumnName,cv);
Thiscodeisreallysimple.InsertinganyJavaobjectusingthiscodeismerelyreadingitsattributesandputtingthosevaluesintotheContentValuesdatasetandjustinsert.AsfarasAndroid’sSQLiteinsertcapabilitiesareconcerned,thatisallyouneedtoknow.
HowbesttostructuretogettoyourJavaobjectsandhowtoconvertthosevaluesintothecontentvaluesdependsonyourframework.Thisisatediousprocesstodocorrectly.Butagain,thisdetailisnotessentialforthebasicunderstandingofinsert.Youwillneedthislevelofrigorformostofyourapplications.Youcanskipthisifyouthinkthisiscomplicated,butweareincludingithereaswefeelyouwillneedthislevelofrigorformostofyourapplications.
Sogettingtherightcolumnnamesandvaluesforinsertingrowsrequiressomeworkand
youtypicallyneedthefollowing(irrespectiveoftheframeworkyouuse):
1. AJavaobjectthattypicallyrepresentstherowinadatabase,forexample,aBookobject
2. Atablenametoholdasetofbooks
3. StringnamesforthecolumnsavailableintheBookstable
4. Finally,callingtheinsertmethodtopersisttheBookobjectasarowintheBookstable
Wewillgivecodesnippets(someinpseudocodefashion)foreachoftheseneeds.Foractualcodeyoucandownloadtheprojectforthischapter.HereareacoupleofclassesinListing25-7thatrepresentaBookobjectinJavacode.
Listing25-7.EnsuringMinimalDependencyBetweenDomainObjectsandPersistence
//Filereferenceinproject:BaseEntity.JavapublicclassBaseEntity{
privateintid;//databaseidentifier
privateStringownedAccount=null;//Multi-tenantifneededprivateStringcreatedBy;privateDatecreatedOn;privateStringlastUpdatedBy;privateDatelastUpdatedOn;
publicBaseEntity(StringownedAccount,StringcreatedBy,DatecreatedOn,StringlastUpdatedBy,DatelastUpdatedOn,intid){super();this.ownedAccount=ownedAccount;this.createdBy=createdBy;this.createdOn=createdOn;this.lastUpdatedBy=lastUpdatedBy;this.lastUpdatedOn=lastUpdatedOn;this.id=id;}//ForpersistencepublicBaseEntity(){}
//Usualgeneratedget/setmethods//eliminatedhereforspace.Seethedownloads}//Filereferenceinproject:Book.JavapublicclassBookextendsBaseEntity
{//Keydatafields
//*************************************privateStringname;privateStringauthor;privateStringisbn;//*************************************
publicBook(StringownedAccount,StringcreatedBy,DatecreatedOn,StringlastUpdatedBy,DatelastUpdatedOn,Stringname,Stringauthor,Stringisbn){super(ownedAccount,createdBy,createdOn,lastUpdatedBy,lastUpdatedOn,-1);this.name=name;this.author=author;this.isbn=isbn;}//TohelpwithpersistencepublicBook(){}//Generatedmethodsgetandsetmethods…//....//Thefollowingmethodisherefortestingpurposes//andalsotoseehowabookobjectistypicallycreatedpublicstaticBookcreateAMockBook(){
StringownedAccount="Account1";StringcreatedBy="satya";DatecreatedOn=Calendar.getInstance().getTime();StringlastUpdatedBy="satya";DatelastUpdatedOn=Calendar.getInstance().getTime();
//SeehowmanybooksIhaveandincrementitbyone//Thefollowingmethodreturnsacollectionofbooksinthedatabase//Thisisnotessentialforyourunderstandinghere//YouwillseethisclarifiedwhenyoureadthesectionoftransactionsList<Book>books=Services.PersistenceServices.bookps.getAllBooks();inti=books.size();Stringname=String.format("Book%s",i);Stringauthor="satya";Stringisbn="isbn-12344-"+i;
returnnewBook(ownedAccount,createdBy,createdOn,lastUpdatedBy,lastUpdatedOn,name,author,isbn);}
}
ThislistinghastwoJavaclasses:aBaseEntityandaBookthatextendstheBaseEntity.ObjectsthatlooklikeaBookinListing25-7arecalleddomainobjects.ThesearepureJavaobjectsthatcanmovearoundintheJavaspaceofyourprogramwithoutbeingburdenedbytheirbehaviorrelatingtopersistence.However,whocreatedtheseobjects,whentheywerecreated,andsuchattributesareencapsulatedintheBaseEntitysothatalldomainobjectshavethisbasicinformation.
BecausetheSQLitedatabasemethodsrequireexplicitcolumnnamesfortheseobjectsthataspectisdefinedinaseparatesetofclassesthatdescribethemetadatafortheseobjects.ThesesupportingclassesaregiveninListing25-8.
Listing25-8.DefiningMetadataforDomainObjects
//Filereferenceinproject:BaseEntitySQLiteSQLiteMetaData.JavapublicclassBaseEntitySQLiteSQLiteMetaData{
staticpublicfinalStringOWNED_ACCOUNT_COLNAME="owned_account";staticpublicfinalStringCREATED_BY_COLNAME="created_by";staticpublicfinalStringCREATED_ON_COLNAME="created_on";staticpublicfinalStringLAST_UPDATED_ON="last_updated_on";staticpublicfinalStringLAST_UPDATED_BY="last_updated_by";staticpublicfinalStringID_COLNAME="id";}//Filereferenceinproject:BookSQLiteSQLiteMetaData.JavapublicclassBookSQLiteSQLiteMetaDataextends
BaseEntitySQLiteSQLiteMetaData{
staticpublicfinalStringTABLE_NAME="t_books";staticpublicfinalStringNAME="name";staticpublicfinalStringAUTHOR="author";staticpublicfinalStringISBN="isbn";}
ThesetwoclassesparalleltheirrespectiveBaseEntityandBookobjectclasses.Youhavetopayattentionthatthecolumnnamesmatchtothoseinthedatabase.Sothisneedisfundamentallyerrorprone.UnlessyouuseanO/Rmappinglibraryandcraftoneofyourown,thisissuewillremainandyouhavetotestwell.ItisdefiningtheseclassesexplicitlybytheprogrammerthatiseliminatedintheO/Rmappingtoolsthatwediscussedearlier.
NowthatwehaveaJavaclasstorepresentabookanditsmetadatadefinition,thattellsusthetablenameandthefieldswecanproceedtowritetheJavacodetosaveabookobjectinthedatabase,asshowninListing25-9(notethatthisisstillpseudocodeandusethedownloadtoseeanymissingdetails).
Listing25-9.UsingAndroidSQLiteAPIstoInsertaRow
//Filereferenceinproject:BookPSSQLite.JavaprivatelongcreateBook(Bookbook){//Getaccesstoareaddatabase
SQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getWritableDatabase();
//Fillfieldsfromthebookobjectintothecontentvalues
ContentValuesbcv=newContentValues();//....fillotherfieldsexamplebcv.put(BookSQLiteSQLiteMetaData.NAME,book.getName());bcv.put(BookSQLiteSQLiteMetaData.ISBN,book.getIsbn());bcv.put(BookSQLiteSQLiteMetaData.AUTHOR,book.getAuthor());//....fillotherfields
//ifbcvisanemptyset,thenanemptyrowcanpossiblybeinserted.//Itisnotthecaseforourbooktable.Ifitwerethough,theemptybcv//willresultinaninsertstatementwithnocolumnnamesinit.//AtleastonecolumnnameisneededbySQLinsertsyntax.//Itisoneofthesecolumnnamesthatgoesbelow.ForusthisisnotcasesoanullStringnullColumnName=null;
longrowId=db.insertOrThrow(BookSQLiteSQLiteMetaData.TABLE_NAME,
nullColumnName,
bcv);
returnrowId;}
ThelogicinListing25-9isquitesimple.GetareferencetoaBookobjectwewanttosave.CopythefieldvaluesfromthebookintoaContentValueskey/valuepairobject.Usemetadataclassestodefinethefieldnamescorrectly.Usethefilled-inContentValuesobjectandcalltheinsertmethod.Ifwedon’tdoanything,theinsertisencapsulatedinanautocommit.Wewilltalkabouthowtodotransactionsshortly,asthetheoryofitisabitinvolvedalthoughthecodeisquitesimpletowrite.TheinsertmethodreturnsthenewlyinsertedprimarykeyIDforthistable.ThisconventionofreturningtheprimarykeyofthetablecomesfromtheunderlyingSQLiteproductdocumentationandisnotAndroidspecific.
ThenullColumnNameisrelatedtothesyntaxoftheSQLinsertstatement.Iftherowhastencolumnsbutonlytwocolumnsandtheirnon-nullvaluesareindicated,thenanewrowisinsertedwiththosetwocolumns,anditisexpectedthattheremainingeightcolumnswillallownulls.Ifyouwantarowwitheverycolumnasnullitispossibleto
issueaninsertstatementwithnocolumnnamesatall,matchingtheemptycontentvaluesset.However,aninsertstatementwithnocolumnnamesisnotallowed.SothisparameternullColumnNamecancontainoneofthecolumnnamesthatcouldbenullsothattheinsertstatementsyntaxrequirementissatisfied.Therestofthecolumnswillbemadenullbythedatabaseinternallywhenthisrowisinserted.Usuallythiscolumnnameispassedinasnullbecauseitisrarethatwewanttoinsertarowwhereeverycolumnisemptyornull.
UpdatingRowsListing25-10isasamplepseudocodesnippet(forfullcodeseethedownloadproject)toshowhowtoupdatearowinthedatabase.NoticehowBookobjectandBookSQLiteMetaDataclassesareusedtominimizeerrorsinspecifyingtablenamesandcolumnnames.Theapproachissimilartotheinsertmethod.
Listing25-10.AndroidSQLiteAPItoUpdateaRecord
//Filereferenceinproject:BookPSSQLite.JavapublicvoidupdateBook(Bookbook){
if(book.getId()<0){thrownewSQLException("Bookidislessthan0");}//GetaccesstoareaddatabaseSQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getWritableDatabase();
//FillfieldsfromthebookobjectintothecontentvaluesContentValuesbcv=newContentValues();//....fillotherfieldsbcv.put(BookSQLiteSQLiteMetaData.NAME,book.getName());bcv.put(BookSQLiteSQLiteMetaData.ISBN,book.getIsbn());bcv.put(BookSQLiteSQLiteMetaData.AUTHOR,book.getAuthor());//....fillotherfields
//YoucandothisStringwhereClause=String.format("%s=%s",BookSQLiteSQLiteMetaData.ID_COLNAME,book.getId());StringwhereClauseArgs=null;//Orthenext4lines(thisispreferred)StringwhereClause2=BookSQLiteSQLiteMetaData.ID_COLNAME+"=?";String[]whereClause2Args=newString[1];whereClause2Args[1]=Integer.toString(book.getId());
intcount=db.update(BookSQLiteSQLiteMetaData.TABLE_NAME,bcv,whereClause2,whereClause2Args);if(count==0){
thrownewSQLException(String.format("Failedtoupdatebookforbookid:%s",book.getId()));}}
DeletingRowsListing25-11isanexampleofhowtodeletearowfromthedatabase.
Listing25-11.AndroidSQLiteAPItoDeleteaRecord
//Filereferenceinproject:BookPSSQLite.JavapublicvoiddeleteBook(intbookid){
//GetaccesstoawritabledatabaseSQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getWritableDatabase();
Stringtname=BookSQLiteSQLiteMetaData.TABLE_NAME;StringwhereClause=String.format("%s=%s;",BookSQLiteSQLiteMetaData.ID_COLNAME,bookid);String[]whereClauseargs=null;inti=db.delete(tname,whereClause,whereClauseargs);if(i!=1){thrownewRuntimeException("Thenumberofdeletedbooksisnot1but:"+i);}}
ReadingRowsListing25-12showspseudocodesnippet(forfullcodeseethedownloadproject)toreadfromSQLiteusingtheSQLiteDatabase.query()method.ThismethodreturnsaCursorobject,whichyoucanusetoretrieveeachrow.
Listing25-12.AndroidSQLiteAPItoReadRecords
//Filereferenceinproject:BookPSSQLite.JavapublicList<Book>getAllBooks(){
//GetaccesstoareaddatabaseSQLiteDatabasedb=DirectAccessBookDBHelper.m_self.getReadableDatabase();
Stringtname=BookSQLiteSQLiteMetaData.TABLE_NAME;//Getcolumnnamearrayfromthemetadataclass//(Seethedownloadhowthecolumnnamesaregathered)
//(attheendofthedayitisjustasetofcolumnnamesString[]colnames=BookSQLiteSQLiteMetaData.s_self.getColumnNames();
//SelectionStringselection=null;//allrows.Usuallyawhereclause.excludewherepartString[]selectionArgs=null;//use?sifyouneedit
StringgroupBy=null;//sqlgroupbyclause:excludegroupbypartStringhaving=null;//similarStringorderby=null;StringlimitClause=null;//maxnumberofrows//db.query(tname,colnames)Cursorc=null;
try{c=db.query(tname,colnames,selection,selectionArgs,groupBy,having,orderby,limitClause);//Thismaynotbetheoptimalwaytoreaddatathroughalist//DirectlypassthecursorbackifyourintentistoreadtheseonerowatatimeList<Book>bookList=newArrayList<Book>();for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){Log.d(tag,"Therearebooks");Bookb=newBook();
//..fillbasefieldsthesameway
b.setName(c.getString(c.getColumnIndex(BookSQLiteMetaData.NAME)));
b.setAuthor(c.getString(c.getColumnIndex(BookSQLiteMetaData.AUTHOR)));
b.setIsbn(c.getString(c.getColumnIndex(BookSQLiteMetaData.ISBN)));//..fillotherfields
//OryoucoulddelegatethisworktotheBookSQLiteMetaDataobject//aswehavedoneinthesampledownloadableproject//Ex:BookSQLiteSQLiteMetaData.s_self.fillFields(c,b);
bookList.add(b);}returnbookList;}
finally{if(c!=null)c.close();}}
HereareafewfactsaboutanAndroidcursorobject:
Acursorisacollectionofrows.
YouneedtousemoveToFirst()beforereadinganydatabecausethecursorstartsoffpositionedbeforethefirstrow.
Youneedtoknowthecolumnnames.
Youneedtoknowthecolumntypes.
Allfield-accessmethodsarebasedoncolumnnumber,soyoumustconvertthecolumnnametoacolumnnumberfirst.Notethatthislookupcanbeoptimized.It’smoreefficienttopopulatethecolumnnamearrayinorderifyouwishtofetchthevaluesandthenuseexplicitconstantindicesonthecursor.
Thecursorisrandom(youcanmoveforwardandbackward,andyoucanjump).
Becausethecursorisrandom,youcanaskitforarowcount.
ApplyingTransactionsSQLitelibrariesonAndroidsupporttransactions.ThetransactionmethodsareavailableontheSQLiteDatabaseclass.Thesemethodsareshowninpseudocodesnippet(forfullcodeseethedownloadproject)inListing25-13.
Listing25-13.SQLiteAPIforTransactions
//Filereferenceinproject:DBServicesProxyHandler.JavapublicvoiddoSomeUpdates(){SQLiteDatabasedb;//Getareferencetothisdatabasethroughhelperdb.beginTransaction();
try{//...callanumberofdatabasemethodsdb.setTransactionSuccessful();
}finally{db.endTransaction();
}}
SummarizingSQLite
IfyouareaJavaprogrammerwithafewyearsofexperience,whatwehavecoveredsofarissufficienttounderstandtheSQLiteAPIinAndroid.Withthematerialcoveredsofar,youknowhowtocheckforadatabase,createadatabasethroughDDL,insertrows,updaterows,deleterows,orreadusingdatabasecursors.WehavealsoshowedyouthebasicAPIfortransactions.However,ifyouarenotanexperiencedhandatJava,databasetransactionsaretrickytoimplementcorrectlyandefficiently.ThenextsectionwilltellyouanAPI-basedpatternusingJavadynamicproxies.
DoingTransactionsThroughDynamicProxiesYoucanvisualizeyourmobileapplicationasacollectionoftwobricks:AnAPIbrickandaUIbrick.TheAPIbrickwillhaveaseriesofstatelessmethodsthatprovidelogicanddatatotheUIbrick.InthiscontextthemethodinListing25-13doSomeUpdates()isconsideredareusableAPIbymanypartsoftheUIorbyotherAPIs.BecauseitisareusableAPItheclientdecideswhethersomethingshouldbecommittedornotcommittedinthattransaction.ThismeanstheAPIshouldnotbedealingwithtransactionsmostofthetime.Itisverymuchlikeastoredprocedureinarelationaldatabase.Astoredprocedurerarelydoestransactionsdirectly.Thecontainerofthestoredproceduredecidestocommitornotcommitexternaltothestoredprocedure.Thelogicisthis:ifthestoredprocedureisinvokedbyitselfthenitsoutputiscommittedatthestoredprocedurelevel.Ifthestoredprocedureiscalledbyanotherstoredprocedurethecommitwaitsuntilthemaininvokingstoredprocedureiscomplete.
ItisbettertousethesamestrategyfortheseAPIsinyourapplicationtoreducethecomplexityinimplementingtheAPIs.ThisisdonebyinterceptingcallstoalltheAPIstomakeadeterminationifthisisadirectcallorbeingcalledbyanotherAPIthatisalreadybeingmonitoredforatransaction.ThereareanumberofwaystointercepttheAPIcallsthatneedtobeintercepted.ThisisalsosometimescalledAspect-OrientedProgrammingorAOP.AOPneedssophisticatedtoolingtodo.Javaprovidesalesssophisticatedbutstraightforwardwaytodothisthroughdynamicproxies.AdynamicproxyisafacilityinJava,basedonJavareflection,thatallowsyoutointerceptcallstoanunderlyingobjectwithouttheobjectbeingawareofit.Whenaclientcallstheobjectthroughthisproxy,theclientthinksitistalkingtotheobjectdirectly.However,theproxycanchoosetoapplyotheraspects(likesecurity,logging,transactions,etc.)beforesendingthecalltotherealobject.Theincludedprojectforthischapterprovidesafullimplementationofadynamicproxythatautomaticallyappliesthetransactionalaspects.
WewillshowyoufirstwhatyourAPIimplementationslooklikeonceadynamicproxyisinplace.Thiswillgiveyouanideaofthesimplicityofthisapproachtotransactionsfirst.Thenyoucanseeifyouwanttotakethisrouteandusedynamicproxies.Aswepresentthecodebelownotethatwewillbeincludingonlysnippetsorsamplesandnottheentirecode.Usethedownloadableprojectforfulldetails.Wehaveannotatedthedownloadprojectwithalotofcommentstohelpyourunderstanding.WiththatcaveatconsidertheAPItoworkwithBook-basedobjects.
Listing25-14.API-BasedInterfacesforWorkingwiththeBookDomainObject
//Filereferenceinproject:IBookPS.Java
publicinterfaceIBookPS{publicintsaveBook(Bookbook);publicBookgetBook(intbookid);publicvoidupdateBook(Bookbook);publicvoiddeleteBook(intbookid);publicList<Book>getAllBooks();}
ThisinterfacedefinesoperationsusingJava-basedobjects.Theletters“PS”attheendofIBookPSserviceindicatesthatthisisapersistenceserviceAPIforabook.Listing25-15showsanSQLiteimplementationfortheIBookPS
Listing25-15.ImplementingtheBookAPIsUsingSQLite
//Filereferenceinproject:BookPSSQLite.Java//Themissingclassesinthiscodeareinthedownloadandnotessentialfor//exploringtheidea.//ASQLitePSisaclassthatcontainsreusablecommonmethodslikegettingaccess//tothereadandwritedatabasesusingthesingletondatabasehelper.publicclassBookPSSQLiteextendsASQLitePSimplementsIBookPS{
privatestaticStringtag="BookPSSQLite";@OverridepublicintsaveBook(Bookbook){
//getthedatabase//case:iddoesnotexistinthebookobjectif(book.getId()==-1){//idofthebookdoesn'texistsocreateitreturn(int)createBook(book);}//case:idexistsinbookobjectupdateBook(book);returnbook.getId();}@OverridepublicvoiddeleteBook(intbookid){
SQLiteDatabasedb=getWriteDb();Stringtname=BookSQLiteSQLiteMetaData.TABLE_NAME;StringwhereClause=String.format("%s=%s;",BookSQLiteSQLiteMetaData.ID_COLNAME,bookid);String[]whereClauseargs=null;inti=db.delete(tname,whereClause,whereClauseargs);if(i!=1){thrownewRuntimeException("Thenumberofdeletedbooksisnot1but:"+i);}
}privatelongcreateBook(Bookbook){
//bookdoesn'texist//createitSQLiteDatabasedb=getWriteDb();
ContentValuesbcv=this.getBookAsContentValuesForCreate(book);
//Idon'tneedtoinsertanemptyrow//usuallyanynullablecolumnnamegoeshereifIwanttoinsertanemptyrow.StringnullColumnNameHack=null;//ConstructvaluesfromtheBookobject.SQLExceptionisaruntimeexceptionlongrowId=db.insertOrThrow(BookSQLiteMetaData.TABLE_NAME,nullColumnNameHack,bcv);returnrowId;}@OverridepublicvoidupdateBook(Bookbook){
if(book.getId()<0){thrownewSQLException("Bookidislessthan0");}SQLiteDatabasedb=getWriteDb();ContentValuesbcv=this.getBookAsContentValuesForUpdate(book);StringwhereClause=String.format("%s=%s",BookSQLiteMetaData.ID_COLNAME,book.getId());whereArgs[0]=BookSQLiteMetaData.ID_COLNAME;whereArgs[1]=Integer.toString(book.getId());
intcount=db.update(BookSQLiteMetaData.TABLE_NAME,bcv,whereClause,null);if(count==0){thrownewSQLException(String.format("Failedtoupdatebookforbookid:%s",book.getId()));}}privateContentValuesgetBookAsContentValuesForUpdate(Bookbook){
ContentValuescv=newContentValues();//Followingcodeloadscolumnvaluesfrombookobjecttothecv//SeethedownloadableprojectforthemechanicsofitBookSQLiteMetaData.s_self.fillUpdatableColumnValues(cv,book);
returncv;}privateContentValuesgetBookAsContentValuesForCreate(Bookbook){
ContentValuescv=newContentValues();BookSQLiteMetaData.s_self.fillAllColumnValues(cv,book);returncv;}@OverridepublicList<Book>getAllBooks(){
SQLiteDatabasedb=getReadDb();Stringtname=BookSQLiteMetaData.TABLE_NAME;String[]colnames=BookSQLiteMetaData.s_self.getColumnNames();
//SelectionStringselection=null;//allrows.Usuallyawhereclause.excludewherepartString[]selectionArgs=null;//use?sifyouneedit
StringgroupBy=null;//sqlgroupbyclause:excludegroupbypartStringhaving=null;//similarStringorderby=null;StringlimitClause=null;//maxnumberofrows//db.query(tname,colnames)Cursorc=null;
try{c=db.query(tname,colnames,selection,selectionArgs,groupBy,having,orderby,limitClause);//Thismaynotbetheoptimalwaytoreaddatathroughalist//DirectlypassthecursorbackifyourintentistoreadtheseonerowatatimeList<Book>bookList=newArrayList<Book>();for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){Log.d(tag,"Therearebooks");Bookb=newBook();BookSQLiteMetaData.s_self.fillFields(c,b);bookList.add(b);}returnbookList;}finally{if(c!=null)c.close();}
}@OverridepublicBookgetBook(intbookid){
SQLiteDatabasedb=getReadDb();Stringtname=BookSQLiteMetaData.TABLE_NAME;String[]colnames=BookSQLiteMetaData.s_self.getColumnNames();
//SelectionStringselection=String.format("%s=%s",BookSQLiteMetaData.ID_COLNAME,bookid);//allrows.Usuallyawhereclause.excludewherepartString[]selectionArgs=null;//use?sifyouneedit
StringgroupBy=null;//sqlgroupbyclause:excludegroupbypartStringhaving=null;//similarStringorderby=null;StringlimitClause=null;//maxnumberofrows//db.query(tname,colnames)Cursorc=db.query(tname,colnames,selection,selectionArgs,groupBy,having,orderby,limitClause);try{if(c.isAfterLast()){Log.d(tag,"Norowsforid"+bookid);returnnull;}Bookb=newBook();BookSQLiteMetaData.s_self.fillFields(c,b);returnb;}finally{c.close();}}}//eof-class
NoticehowtheimplementationoftheBookpersistenceAPIdoesnotdirectlydealwiththetransactionalaspectsofthesemethods.Instead,thetransactionsarehandledbyJavadynamicproxy,whichwewillshowshortly.Listing25-16showshowaclientcanseetheseAPIsandinvokethesepersistenceAPIsindirectly(again,pleaserefertothedownloadprojectsforclassesthatarereferencedinthiscodebutnotlistedhereastheyarenotessentialforunderstanding).
Listing25-16.ClientAccesstoAPI-BasedServices
//Filereferenceinproject:SQLitePersistenceTester.Java
//BaseTesterisjustahelperclasstoprovidercommonfunctionality//itimplementssomeloggingandreportbackmethodstotheUIactivitypublicclassSQLitePersistenceTesterextendsBaseTester{
privatestaticStringtag="SQLitePersistenceTester";//Servicesisastaticclassthatprovidesaccesstopersistenceservices//ServicesclassprovidesvisibilitytotheimplementeroftheIBookPS//Itdemonstrateshowaclientgetsaccesstothenamespaceofservices//Youwillshortlyseewhatthisclassis.Understandtheintentfirst.privateIBookPSbookPersistenceService
=Services.PersistenceServices.bookps;
//IReportBackisalogginginterfacetoreportloggableeventsbacktotheUI//UIwillthenchoosetologthoseeventsandalsoshowontheactivityscreen.SQLitePersistenceTester(Contextctx,IReportBacktarget){super(ctx,target,tag);}
//Addabookwhoseidisonelargerthanthebooks//inthedatabasepublicvoidaddBook(){
Bookbook=Book.createAMockBook();intbookid=bookPersistenceService.saveBook(book);
reportString(String.format("Insertedabook%swhosegeneratedidnowis%s",book.getName(),bookid));}//DeletethelastbookpublicvoidremoveBook(){List<Book>bookList=bookPersistenceService.getAllBooks();if(bookList.size()<=0){reportString("Therearenobooksthatcanbedeleted");return;}reportString(String.format("Thereare%sbooks.Firstonewillbedeleted",bookList.size()));
Bookb=bookList.get(0);bookPersistenceService.deleteBook(b.getId());reportString(String.format("Bookwithid:%ssuccessfullydeleted",b.getId()));}
//writethelistofbookssofartothescreenpublicvoidshowBooks(){
List<Book>bookList=bookPersistenceService.getAllBooks();
reportString(String.format("Numberofbooks:%s",bookList.size()));for(Bookb:bookList){reportString(String.format("id:%sname:%sauthor:%sisbn:%s",b.getId(),b.getName(),b.getAuthor(),b.getIsbn()));}}
//CountthenumberofbooksinthedatabaseprivateintgetCount(){
List<Book>bookList=bookPersistenceService.getAllBooks();
returnbookList.size();}}
InListing25-16,noticehowsimpleitistoaccesstheAPIsthroughthestaticclassServices.Ofcoursewehaven’tshownyoutheimplementationofServicesandalsothedynamicproxyheldbythestaticclassServices.Listing25-17showsthesourcecodeforthestaticServicesclassinordertogiveyouanideaofhowthisschemeworks.Thegoalofmany,ifnotall,ofthelistingsinthischapteristoaidyourunderstanding.Forcompletecompilablesourcecodewekindlyrequestthatyourefertothedownloadableprojectsforthischapter.
Listing25-17.ExposingAPIstoClientsThroughaServicesNameSpace
//Filereferenceinproject:Services.Java/***Allowanamespaceforclientstodiscovervariousservices*Usage:Services.persistenceServices.bookps.addBook();etc.
*Dynamicproxywilltakecareoftransactions.*Dynamicproxywilltakecareofmockdata.*DynamicProxywillallowmorethanoneinterface*toapplytheaboveaspects.*/
publicclassServices{
publicstaticStringtag="Services";publicstaticclassPersistenceServices{
////sethispointerduringinitializationpublicstaticIBookPSbookps=null;
static{Services.init();}}//Althoughthismethodisempty,callingit//willtriggerallstaticinitializationcodeforthisclasspublicstaticvoidinit(){}privatestaticObjectmainProxy;static{//Autilityclasstocompilealldatabase-relatedinitializationssofar//Getsthedatabasehelpergoing.//SeethedownloadprojecthowitusestheconceptspresentedsofartodothisDatabase.initialize();
//setupbookpsClassLoadercl=IBookPS.class.getClassLoader();//AddmoreinterfacesasavailableClass[]variousServiceInterfaces=newClass[]{IBookPS.class};
//Createabigobjectthatcanproxyalltherelatedinterfaces//forwhichsimilarcommonaspectsareapplied//InthiscasesitisandroidSQLitetransactionsmainProxy=Proxy.newProxyInstance(cl,
variousServiceInterfaces,newDBServicesProxyHandler());
//PresetthenamespaceforeasydiscoveryPersistenceServices.bookps=(IBookPS)mainProxy;}}
NoticehowDBServicesProxyHandlerisaproxyfortheimplementationofIBookPS.Whencalledbyclients,theDBServicesProxyHandlerthencallstheactualimplementationforIBookPS.TheactualimplementationofIBookPSisshowninListing25-15.Let’sturntotheimplementationofthedynamicproxyinListing25-18.SomeofthecodeandclassesreferencedinListing25-18areonlyavailableinthedownloadableprojects.However,thatshouldnothinderthegeneralunderstandingofthearchitectureofthedynamicproxy.
Listing25-18.JavaDynamicProxytoWraptheSQLiteAPIImplementations
//Filereferenceinproject:DBServicesProxyHandler.Java/***DBServicesProxyHandler:AclasstoexternalizeSQLiteTransactions.*Itisadynamicproxy.SeeServices.Javatoseehowareferencetothisisused.**Thisproxyiscapableofhostingmultiplepersistenceinterfaces.*Eachinterfacemayrepresentpersistenceaspectsofaparticularentityoradomainobject*likeaBook.Ortheinterfacecanbeacompositeinterfacedealingwithmultipleentities.**ItalsousesThreadLocalstopasstheDatabaseContext*DatabaseContextholdsareferencetothedatabasethatisonthisthread*Italsoknowshowtoapplytransactionstothatdatabase*Italsoknowsifthecurrentthreadalsohasarunningtransaction*@SeeDatabaseContext**DatabaseContextprovidestheSQLiteDatabasereferenceto*theimplementationclasses.**Relatedclasses******************Services.Java:Clientaccesstointerfaces*IBookPS:ClientinterfacetodealwithpersistingaBook*BookPSSQLite:SQLiteImplementationofIBookPS**DBServicesProxyHandler:Thisclassthatisadynamicproxy*DatabaseContext:HoldsadbreferenceforBookPSSQLiteimplementation*DirectAccessBookDBHelper:AndroidDBHelpertoconstructthedatabase**/publicclassDBServicesProxyHandlerimplementsInvocationHandler{
privateBookPSSQLitebookServiceImpl=newBookPSSQLite();privatestaticStringtag="DBServicesProxyHandler";DBServicesProxyHandler(){}publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
throwsThrowable{logMethodSignature(method);Stringmname=method.getName();if(mname.startsWith("get")){returnthis.invokeForReads(method,args);}else{returnthis.invokeForWrites(method,args);}}privatevoidlogMethodSignature(Methodmethod){
StringinterfaceName=method.getDeclaringClass().getName();Stringmname=method.getName();Log.d(tag,String.format("%s:%s",interfaceName,mname));}privateObjectcallDelegatedMethod(Methodmethod,Object[]args)
throwsThrowable{returnmethod.invoke(bookServiceImpl,args);}privateObjectinvokeForReads(Methodmethod,Object[]args)throws
Throwable{
//SeecommentsaboveaboutDatabaseContextif(DatabaseContext.isItAlreadyInsideATransaction()==true){//ItisalreadyboundreturninvokeForReadsWithoutATransactionalWrap(method,args);}else{//AnewtransactionreturninvokeForReadsWithATransactionalWrap(method,args);}
}privateObjectinvokeForReadsWithATransactionalWrap(Methodmethod,
Object[]args)
throwsThrowable{try{DatabaseContext.setReadableDatabaseContext();returncallDelegatedMethod(method,args);}finally{DatabaseContext.reset();}
}privateObjectinvokeForReadsWithoutATransactionalWrap(Methodmethod,
Object[]args)
throwsThrowable{returncallDelegatedMethod(method,args);}privateObjectinvokeForWrites(Methodmethod,Object[]args)throws
Throwable{
if(DatabaseContext.isItAlreadyInsideATransaction()==true){//ItisalreadyboundreturninvokeForWritesWithoutATransactionalWrap(method,args);}else{//AnewtransactionreturninvokeForWritesWithATransactionalWrap(method,args);}}privateObjectinvokeForWritesWithATransactionalWrap(Methodmethod,
Object[]args)
throwsThrowable{try{DatabaseContext.setWritableDatabaseContext();DatabaseContext.beginTransaction();ObjectrtnObject=callDelegatedMethod(method,args);DatabaseContext.setTransactionSuccessful();returnrtnObject;}finally{try{DatabaseContext.endTransaction();}finally{DatabaseContext.reset();}}}privateObjectinvokeForWritesWithoutATransactionalWrap(Methodmethod,
Object[]args)
throwsThrowable{returncallDelegatedMethod(method,args);}}//eof-class
ThiscodeinListing25-18isthedynamicproxyimplementation.Wehavenotincludedallthedetailsbutsufficientdetailisheretounderstandhowthisdynamicproxyperforms
transactionsinanautomatedaspect-orientedway.Itexaminesthecalledmethodnamethroughreflectiontoseeifthemethodnamestartswith“get,”andifsothenitassumesthemethoddoesn’tneedatransactionalcontext.Otherwiseitmarksthecurrentthreadasatransactionalcontext.Atthereturnofthemethoditcompletesthetransactionassuccessful.Ifthereareothermethodscalledinbetween,thedynamicproxyknowsfromthethreadthatthereisatransactioninplaceandhenceignoresthatmethodfromatransactionalaspectperspective.
Nowbasedonyourneedyoumaywanttoalterthisprotocolbasedonannotationsorsomeotheraspectofyourinterfaces,butyougettheidea.ThisapproachofseparatingAPIsfromyourUIisgooddesignandyoucanuseanynumberofpersistentstoreswithoutchangingyourclientUIcode.Westronglyrecommendthatyouadaptthisapproachirrespectiveofthepersistencemechanismyouuse,includingtheO/Rmappingtools.
ExploringDatabasesontheEmulatorandAvailableDevicesAsyouuseSQLiteasyourpersistencemechanismeitherdirectlyorthroughcontentproviders(nextsection),youmaywanttoexaminetheresultingdatabasefilesonthedevicefordebuggingpurposes.
ThedatabasefilescreatedbySQLiteAPIarekeptinthefollowingdirectory:
/data/data/<fully-qualified-package-name>/databases
YoucanuseEclipseAndroidfileexplorertolocatethedirectoryandcopythefilestoyourlocaldriveandusenativeSQLitetoolsprovidedbySQLitedirectlytoseeandmanipulatethatdatabase.
YoucanalsousetoolsprovidedbothbyAndroidandbySQLitetoexaminethesedatabases.Manyofthesetoolsresideinthe\<android-sdk-install-directory>\toolssubdirectory;othersarein\<android-sdk-install-directory>\platform-tools.
Someusefulcommandsfromthesedirectoriesare
androidlistavd:ToseealistofAVDsoremulatorsemulator.exe@avdname:Tostartanemulatorwithagivennameadb.exedevices:Toseethedevicesoremulatorsadbshell:Toopenashellontheemulatorordevice
Youcanusethefollowingcommandsfroman“adbshell.”Thesewillworkontheemulatorbutonarealdeviceyouwillneedrootaccess.
ls/system/bin:Toseeavailablecommandsls-l/:Rootleveldirectoriesls/data/data/com.android.providers.contacts/databases:anexample
ls-R/data/data/*/databases:Toseealldatabasesonthedeviceoremulator
IftherewereafindcommandintheincludedAndroidUnixshell,youcouldlookatallthe*.dbfiles.Butthereisnogoodwaytodothiswithlsalone.Thenearestthingyoucandoisthis:
ls-R/data/data/*/databases
Withthiscommand,youwillnoticethattheAndroiddistributionhasthedatabasesshowninListing25-19(dependingonyourrelease,thislistmayvary):
Listing25-19.AFewSampleDatabases
alarms.dbcontacts.dbdownloads.dbinternal.dbsettings.dbmmssms.dbtelephony.db
Youcaninvokesqlite3ononeofthesedatabasesinsidetheadbshellbytypingthis:
sqlite3/data/data/com.android.providers.contacts/databases/contacts.db
Youcanexitsqlite3bytypingthis:
sqlite>.exit
Noticethatthepromptforadbis#andthepromptforsqlite3issqlite>.Thesepromptscouldbedifferentdependingonthedevice.Youcanreadaboutthevarioussqlite3commandsbyvisitingwww.sqlite.org/sqlite.html.However,wewilllistafewimportantcommandsheresoyoudon’thavetomakeatriptotheWeb.Youcanseealistoftablesbytyping
sqlite>.tables
Thiscommandisashortcutforqueryingonthesqlite_mastertableasshowninListing25-20(formatandstructureoftheresultingoutputmayvary).
Listing25-20.UsingSQLitesqlite_masterTable
SELECTnameFROMsqlite_masterWHEREtypeIN('table','view')ANDnameNOTLIKE'sqlite_%'UNIONALLSELECTnameFROMsqlite_temp_masterWHEREtypeIN('table','view')ORDERBY1
Thetablesqlite_masterisamastertablethatkeepstrackoftablesandviewsintheSQLitedatabase.Thefollowingcommandlinedisplaysacreatestatementforatablecalledpeopleincontacts.db(assumingthisdatabaseexistsonyourdevice):
.schemapeople
ThisisonewaytogetatthecolumnnamesofatableinSQLite.Thiswillalsoshowthecolumndatatypes.Whileworkingwithcontentproviders,youshouldnotethesecolumntypesbecauseaccessmethodsdependonthem.Alsonotethatthismaynotbeapracticalwaytoseethesedatabasesasyoumaynothaveaccesstothemonrealdevices.Inthatcaseyouhavetorelyonthedocumentationprovidedbythecontentprovider.
YoucanissuethefollowingcommandfromyourOScommandprompttopulldownthecontacts.dbfiletothelocalfilesystem:
adbpull/data/data/com.android.providers.contacts/databases/contacts.dbÉc:/somelocaldir/contacts.db
ThesampleSQLstatementsinListing25-21couldhelpyounavigatethroughanSQLitedatabasequickly(alternativelyyoucanuseanythird-partySQLitebrowsertool):
Listing25-21.SampleSQLCodeforSQLite
--Setthecolumnheaderstoshowinthetoolsqlite>.headerson
--selectallrowsfromatableselect*fromtable1;
--countthenumberofrowsinatableselectcount(*)fromtable1;
--selectaspecificsetofcolumnsselectcol1,col2fromtable1;
--Selectdistinctvaluesinacolumnselectdistinctcol1fromtable1;
--countingthedistinctvaluesselectcount(col1)from(selectdistinctcol1fromtable1);
--groupbyselectcount(*),col1fromtable1groupbycol1;
--regularinnerjoinselect*fromtable1t1,table2t2wheret1.col1=t2.col1;
--leftouterjoin--Givemeeverythingint1eventhoughtherearenorowsint2select*fromtablet1leftouterjointable2t2ont1.col1=t2.col1where….
ExploringContentProvidersEarlierinthechapterwetoucheduponcontentproviderstosharedatabetweenapplications.Contentprovidersasstatedarewrappersaroundadatastore.Thedatastorescouldbelocalorremote.ThedatastoresareusuallySQLitedatabasesonthelocaldevice.
Toretrievedatafromacontentproviderorsavedataintoacontentprovider,youwilluseasetofREST-likeURIs.Forexample,ifyouweretoretrieveasetofbooksfromacontentproviderthatisanencapsulationofabookdatabase,youmightneedtouseaURIlikethis:
content://com.android.book.BookProvider/books
Toretrieveaspecificbookfromthebookdatabase(likesaybook23),youmightuseaURIlikethis:
content://com.android.book.BookProvider/books/23
YouwillseeinthischapterhowtheseURIstranslatetounderlyingdatabase-accessmechanisms.AnyapplicationwiththeappropriateaccesspermissionsonthedevicecanmakeuseoftheseURIstoaccessandmanipulatedata.
ExploringAndroid’sBuilt-inProvidersAndroidcomeswithanumberofbuilt-incontentproviders,whicharedocumentedintheSDK’sandroid.providerJavapackage.Youcanviewthelistoftheseprovidershere:
http://developer.android.com/reference/android/provider/package-summary.html
Theprovidersinclude,forexample,ContactsandMediaStore.TheseSQLitedatabasestypicallyhaveanextensionof.dbandareaccessibleonlyfromtheimplementationpackage.Anyaccessoutsidethatpackagemustgothroughthecontent-providerinterface.Youcanusetheprevioussection“ExploringDatabasesontheEmulatorandAvailableDevices”toexplorethedatabasefilescreatedbybuilt-inprovidersontheemulator.Onrealdevicesthisisnotfeasibleunlessofcourseyouhaverootaccessonthedevice.
UnderstandingtheStructureofContentProvider
URIsEachcontentprovideronadeviceisregisteredintheAndroidmanifestfilelikeawebsitewithastringidentifiercalledanauthority(akintoadomainname).Listing25-22hastwoexamplesofthisregistration:
Listing25-22.ExampleofRegisteringaProvider
<!--Filereferenceinproject:AndroidManifest.xml--><providerandroid:name="SomeProviderJavaClass"android:authorities="com.your-company.SomeProvider"/>
<providerandroid:name="BookProvider"
android:authorities="com.androidbook.provider.BookProvider"/>
TheuniqueauthoritystringformsthebasisofasetofURIsthatthiscontentprovideroffers.AnAndroidcontentURIhasthefollowingstructure:
content://<authority-name>/<path-segment1>/<path-segment2>/etc…
Here’sanexampleURIthatidentifiesabooknumbered23inadatabaseofbooks:
content://com.androidbook.provider.BookProvider/books/23
Aftercontent:,theURIcontainstheauthority,whichisusedtolocatetheproviderintheproviderregistry.Intheprecedingexample,com.androidbook.provider.BookProvideristheauthorityportionoftheURI.
/books/23isthepathsectionoftheURIthatisspecifictoeachprovider.Thebooksand23portionsofthepathsectionarecalledpathsegments.ItistheresponsibilityoftheprovidertodocumentandinterpretthepathsectionandpathsegmentsoftheURIs.HencecontentprovidersprovidetheseREST-likeURLstoretrieveormanipulatedata.Fortheprecedingregistration,theURItoidentifyadirectoryoracollectionofbooksinthebooksdatabaseis
content://com.androidbook.provider.BookProvider/books
TheURItoidentifyaspecificnoteis
content://com.androidbook.provider.BookProvider/books/#
where#istheidofaparticularnote.Listing25-23showsadditionalexamplesofURIsthatsomedataprovidersonAndroidaccept:
Listing25-23.FewSampleAndroidContentURLs
content://media/internal/images
content://media/external/imagescontent://contacts/people/content://contacts/people/23
Noticehowtheseproviders’media(content://media)andcontacts(content://contacts)don’thaveafullyqualifiedauthorityname.ThisisbecauseprovidersofferedbyAndroidmaynotcarryafullyqualifiedauthorityname.
GiventhesecontentURIs,aproviderisexpectedtoretrieverowsthattheURIsrepresent.TheproviderisalsoexpectedtoaltercontentatthisURIusinganyofthestate-changemethods:insert,update,ordelete.
ImplementingContentProvidersLet’sfullyunderstandcontentprovidersbyimplementingandusingone.Towriteacontentprovider,youhavetoextendandroid.content.ContentProviderandimplementthefollowingkeymethods:query(),insert(),update(),delete(),andgetType().
You’llneedtosetupanumberofthingsforimplementingthesemethods.Implementingacontentproviderneedsthefollowingsteps:
1. Planyourdatabase,URIs,columnnames,andsoon,andcreateametadataclassthatdefinesconstantsforallofthesemetadataelements.
2. ExtendtheabstractclassContentProvider.
3. Implementthesemethods:query,insert,update,delete,andgetType.
4. Registertheproviderinthemanifestfile.
5. Usethecontentprovider.
PlanningaDatabaseToexplorethistopic,we’llcreateadatabasesimilartotheonethatwehaveusedforthebookcollectionthatwasusedtoillustratethestoringofdatainSQLitedirectly.Notethattokeepthedatabasesfromconflictingwitheachothersomeofthenamesmaybedifferent.
Thebookdatabasecontainsonlyonetablecalledbooks,anditscolumnsarename,isbn,andauthor.Thesecolumnnamesfallundermetadata.You’lldefinethissortofrelevantmetadatainaJavaclass.Thismetadata-bearingJavaclassBookProviderMetaDataisshowninListing25-24.
Listing25-24.DefiningMetadataforYourDatabase
//Filereferenceinproject:BookProviderMetaData.Java
publicclassBookProviderMetaData{
publicstaticfinalStringAUTHORITY="com.androidbook.provider.BookProvider";
publicstaticfinalStringDATABASE_NAME="book.db";publicstaticfinalintDATABASE_VERSION=1;publicstaticfinalStringBOOKS_TABLE_NAME="books";
privateBookProviderMetaData(){}
//innerclassdescribingBookTablepublicstaticfinalclassBookTableMetaDataimplementsBaseColumns{privateBookTableMetaData(){}publicstaticfinalStringTABLE_NAME="books";
//uriandMIMEtypedefinitionspublicstaticfinalUriCONTENT_URI=Uri.parse("content://"+AUTHORITY+"/books");publicstaticfinalStringCONTENT_TYPE=
"vnd.android.cursor.dir/vnd.androidbook.book";publicstaticfinalStringCONTENT_ITEM_TYPE="vnd.android.cursor.item/vnd.androidbook.book";
publicstaticfinalStringDEFAULT_SORT_ORDER="modifiedDESC";
//AdditionalColumnsstarthere.//stringtypepublicstaticfinalStringBOOK_NAME="name";//stringtypepublicstaticfinalStringBOOK_ISBN="isbn";//stringtypepublicstaticfinalStringBOOK_AUTHOR="author";//IntegerfromSystem.currentTimeMillis()publicstaticfinalStringCREATED_DATE="created";//IntegerfromSystem.currentTimeMillis()publicstaticfinalStringMODIFIED_DATE="modified";}}
ThisBookProviderMetaDataclassstartsbydefiningitsauthoritytobecom.androidbook.provider.BookProvider.
Thisclassthenproceedstodefineitsonetable(books)asaninnerBookTableMetaDataclass.TheBookTableMetaDataclassthendefinesaURIfor
identifyingacollectionofbooks.Giventheauthorityinthepreviousparagraph,theURIforacollectionofbookswilllooklikethis:
content://com.androidbook.provider.BookProvider/books
ThisURIisindicatedbytheconstant
BookProviderMetaData.BookTableMetaData.CONTENT_URI
TheBookTableMetaDataclassthenproceedstodefinetheMIMEtypesforacollectionofbooksandasinglebook.TheproviderimplementationwillusetheseconstantstoreturntheMIMEtypesfortheincomingURIs.MIMEtypesaresimilartotheMIMEtypesdefinedbyHTTP.AsaguidelinetheprimaryMIMEtypeforacollectionofitemsreturnedthroughanAndroidcursorshouldalwaysbevnd.android.cursor.dir,andtheprimaryMIMEtypeofasingleitemretrievedthroughanAndroidcursorshouldbevnd.android.cursor.item.Youhavemorewiggleroomwhenitcomestothesubtype,asinvnd.androidbook.bookinListing25-24.
BookTableMetaDatathendefinesthesetofcolumnsforthebooktable:name,isbn,author,created(creationdate),andmodified(last-updateddate).
ThemetadataclassBookTableMetaDataalsoinheritsfromtheBaseColumnsclassthatprovidesthestandard_idfield,whichrepresentstherowID.Withthesemetadatadefinitionsinhand,we’rereadytotackletheproviderimplementation.
ExtendingContentProviderImplementingtheBookProviderinvolvesextendingtheContentProviderclassandoverridingonCreate()tocreatethedatabaseandthenimplementthequery,insert,update,delete,andgetTypemethods.
Aquerymethodrequiresthesetofcolumnsitneedstoreturn.Thisissimilartoaselectclausethatrequirescolumnnamesalongwiththeirascounterparts(sometimescalledsynonyms).AsaconventionAndroidSDKusesamapobjectthatitcallsaprojectionmaptorepresentthesecolumnnamesandtheirsynonyms.Wewillneedtosetupthismapsowecanuseitlaterinthequery-methodimplementation.Inthecodefortheproviderimplementation(seeListing25-26),youwillseethisdoneupfrontaspartofprojectionmapsetup.
Mostofthemethodswe’llbeimplementingforthecontentprovidercontracttakeaURIasaninput.Listing25-25showsbookproviderURIexamples:
Listing25-25.ExamplesofBookProviderContentURIs
Uri1:content://com.androidbook.provider.BookProvider/booksUri2:content://com.androidbook.provider.BookProvider/books/12
ThebookproviderneedstodistinguisheachoftheseURIs.BookProviderisasimplecase.Ifourbookproviderhadbeenhousingmoreobjectsinadditiontojustbooks,thentherewouldbemoreURIstoidentifythoseadditionalobjects.
TheproviderimplementationneedsamechanismtodistinguishoneURIfromtheother;AndroidusesaclasscalledUriMatcherforthispurpose.SoweneedtosetupthisobjectwithallourURIvariations.YouwillseethiscodeinListing25-26rightafterwedefinetheprojectionmap.We’llfurtherexplaintheUriMatcherclassinthesection“UsingUriMatchertoFigureOuttheURIs.”
ThecodeinListing25-26thenoverridestheonCreate()methodtofacilitatethedatabasecreation.ThedatabasecreationisidenticaltothedatabasecreationwehavecoveredaspartofusingSQLitedirectlyforinternalpersistenceneeds.
ThesourcecodeinListing25-26thenimplementstheinsert(),query(),update(),getType(),anddelete()methods.ThecodeforallofthisispresentedtogetherinListing25-26,butwewillexplaineachaspectinaseparatesubsection.
Listing25-26.ImplementingtheBookProviderContentProvider
//Filereferenceinproject:BookProvider.JavapublicclassBookProviderextendsContentProvider{//Logginghelpertag.Nosignificancetoproviders.privatestaticfinalStringTAG="BookProvider";
//SetupprojectionMap//Projectionmapsaresimilarto"as"(columnalias)construct//inansqlstatementwherebyyoucanrenamethe//columns.privatestaticHashMap<String,String>sBooksProjectionMap;
static{sBooksProjectionMap=newHashMap<String,String>();sBooksProjectionMap.put(BookTableMetaData._ID,BookTableMetaData._ID);
//name,isbn,authorsBooksProjectionMap.put(BookTableMetaData.BOOK_NAME,BookTableMetaData.BOOK_NAME);sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN,BookTableMetaData.BOOK_ISBN);sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR,BookTableMetaData.BOOK_AUTHOR);
//createddate,modifieddatesBooksProjectionMap.put(BookTableMetaData.CREATED_DATE,BookTableMetaData.CREATED_DATE);
sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE,BookTableMetaData.MODIFIED_DATE);}
//Provideamechanismtoidentifyalltheincominguripatterns.
privatestaticfinalUriMatchersUriMatcher;privatestaticfinalintINCOMING_BOOK_COLLECTION_URI_INDICATOR=1;privatestaticfinalintINCOMING_SINGLE_BOOK_URI_INDICATOR=2;static{sUriMatcher=newUriMatcher(UriMatcher.NO_MATCH);sUriMatcher.addURI(BookProviderMetaData.AUTHORITY,"books",INCOMING_BOOK_COLLECTION_URI_INDICATOR);sUriMatcher.addURI(BookProviderMetaData.AUTHORITY,"books/#",INCOMING_SINGLE_BOOK_URI_INDICATOR);
}//Setup/CreateDatabasetousefortheimplementationprivatestaticclassDatabaseHelperextendsSQLiteOpenHelper{
DatabaseHelper(Contextcontext){super(context,BookProviderMetaData.DATABASE_NAME,null,BookProviderMetaData.DATABASE_VERSION);}@OverridepublicvoidonCreate(SQLiteDatabasedb){Log.d(TAG,"inneroncreatecalled");db.execSQL("CREATETABLE"+BookTableMetaData.TABLE_NAME+"("+BookTableMetaData._ID+"INTEGERPRIMARYKEY,"+BookTableMetaData.BOOK_NAME+"TEXT,"+BookTableMetaData.BOOK_ISBN+"TEXT,"+BookTableMetaData.BOOK_AUTHOR+"TEXT,"+BookTableMetaData.CREATED_DATE+"INTEGER,"+BookTableMetaData.MODIFIED_DATE+"INTEGER"+");");}@OverridepublicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
Log.d(TAG,"inneronupgradecalled");Log.w(TAG,"Upgradingdatabasefromversion"+oldVersion+"to"+newVersion+",whichwilldestroyallolddata");db.execSQL("DROPTABLEIFEXISTS"+BookTableMetaData.TABLE_NAME);onCreate(db);}}//eof-innerDatabaseHelperclass//ThisisinitializedintheonCreate()methodprivateDatabaseHelpermOpenHelper;
//Componentcreationcallback@OverridepublicbooleanonCreate(){
Log.d(TAG,"mainonCreatecalled");mOpenHelper=newDatabaseHelper(getContext());returntrue;}
@OverridepublicCursorquery(Uriuri,String[]projection,Stringselection,
String[]selectionArgs,StringsortOrder){SQLiteQueryBuilderqb=newSQLiteQueryBuilder();
switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:qb.setTables(BookTableMetaData.TABLE_NAME);qb.setProjectionMap(sBooksProjectionMap);break;
caseINCOMING_SINGLE_BOOK_URI_INDICATOR:qb.setTables(BookTableMetaData.TABLE_NAME);qb.setProjectionMap(sBooksProjectionMap);qb.appendWhere(BookTableMetaData._ID+"="+uri.getPathSegments().get(1));break;
default:thrownewIllegalArgumentException("UnknownURI"+uri);}
//IfnosortorderisspecifiedusethedefaultStringorderBy;if(TextUtils.isEmpty(sortOrder)){orderBy=BookTableMetaData.DEFAULT_SORT_ORDER;
}else{orderBy=sortOrder;}
//GetthedatabaseandrunthequerySQLiteDatabasedb=mOpenHelper.getReadableDatabase();Cursorc=qb.query(db,projection,selection,selectionArgs,null,null,orderBy);
//exampleofgettingacountinti=c.getCount();
//Tellthecursorwhaturitowatch,//soitknowswhenitssourcedatachangesc.setNotificationUri(getContext().getContentResolver(),uri);returnc;}@OverridepublicStringgetType(Uriuri){
switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:returnBookTableMetaData.CONTENT_TYPE;caseINCOMING_SINGLE_BOOK_URI_INDICATOR:returnBookTableMetaData.CONTENT_ITEM_TYPE;default:thrownewIllegalArgumentException("UnknownURI"+uri);}}@OverridepublicUriinsert(Uriuri,ContentValuesinitialValues){
//Validatetherequesteduriif(sUriMatcher.match(uri)!=INCOMING_BOOK_COLLECTION_URI_INDICATOR){thrownewIllegalArgumentException("UnknownURI"+uri);}ContentValuesvalues;if(initialValues!=null){values=newContentValues(initialValues);}else{values=newContentValues();}Longnow=Long.valueOf(System.currentTimeMillis());//Makesurethatthefieldsareallsetif(values.containsKey(BookTableMetaData.CREATED_DATE)
==false){values.put(BookTableMetaData.CREATED_DATE,now);}if(values.containsKey(BookTableMetaData.MODIFIED_DATE)==false){values.put(BookTableMetaData.MODIFIED_DATE,now);}if(values.containsKey(BookTableMetaData.BOOK_NAME)==false){thrownewSQLException("FailedtoinsertrowbecauseBookNameisneeded"+uri);}if(values.containsKey(BookTableMetaData.BOOK_ISBN)==false){values.put(BookTableMetaData.BOOK_ISBN,"UnknownISBN");}if(values.containsKey(BookTableMetaData.BOOK_AUTHOR)==false){values.put(BookTableMetaData.BOOK_ISBN,"UnknownAuthor");}
SQLiteDatabasedb=mOpenHelper.getWritableDatabase();longrowId=db.insert(BookTableMetaData.TABLE_NAME,BookTableMetaData.BOOK_NAME,values);if(rowId>0){UriinsertedBookUri=ContentUris.withAppendedId(BookTableMetaData.CONTENT_URI,rowId);getContext().getContentResolver().notifyChange(insertedBookUri,null);
returninsertedBookUri;}thrownewSQLException("Failedtoinsertrowinto"+uri);}@Overridepublicintdelete(Uriuri,Stringwhere,String[]whereArgs){
SQLiteDatabasedb=mOpenHelper.getWritableDatabase();intcount;switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:
count=db.delete(BookTableMetaData.TABLE_NAME,where,whereArgs);break;caseINCOMING_SINGLE_BOOK_URI_INDICATOR:StringrowId=uri.getPathSegments().get(1);count=db.delete(BookTableMetaData.TABLE_NAME,BookTableMetaData._ID+"="+rowId+(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);break;default:thrownewIllegalArgumentException("UnknownURI"+uri);}
getContext().getContentResolver().notifyChange(uri,null);returncount;}@Overridepublicintupdate(Uriuri,ContentValuesvalues,
Stringwhere,String[]whereArgs){SQLiteDatabasedb=mOpenHelper.getWritableDatabase();intcount;switch(sUriMatcher.match(uri)){caseINCOMING_BOOK_COLLECTION_URI_INDICATOR:count=db.update(BookTableMetaData.TABLE_NAME,values,where,whereArgs);break;
caseINCOMING_SINGLE_BOOK_URI_INDICATOR:StringrowId=uri.getPathSegments().get(1);count=db.update(BookTableMetaData.TABLE_NAME,values,BookTableMetaData._ID+"="+rowId+(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);break;
default:thrownewIllegalArgumentException("UnknownURI"+uri);}
getContext().getContentResolver().notifyChange(uri,null);
returncount;}}
Now,let’sanalyzethiscodesectionbysection.
UsingUriMatchertoFigureOuttheURIsWe’vementionedtheUriMatcherclassseveraltimesnow;let’slookintoit.AlmostallmethodsinacontentproviderareoverloadedwithrespecttotheURI.Forexample,thesamequery()methodiscalledwhetheryouwanttoretrieveasinglebookoralistofbooks.ItisuptothemethodtoknowwhichtypeofURIisbeingrequested.Android’sUriMatcherutilityclasshelpsyouidentifytheURItypes.
Here’showitworks.YoutellaninstanceofUriMatcherwhatkindofURIpatternstoexpectduringitsinitialization.Youwillalsoassociateauniquenumberwitheachpattern.Oncethesepatternsareregistered,youcanthenaskUriMatcheriftheincomingURImatchesacertainpattern.
Aswe’vementioned,ourBookProvidercontentproviderhastwoURIpatterns:oneforacollectionofbooksandoneforasinglebook.ThecodeinListing25-26registersbothofthesepatternsusingUriMatcher.Itallocates1foracollectionofbooksand2forasinglebook(theURIpatternsthemselvesaredefinedinthemetadataforthebookstable).YoucanseethisinthestaticinitializationofthevariablesUriMatcherinListing25-26.YoucanthenseehowUriMatcherplaysapartinthequery()methodimplementationindistinguishingtheURIsusingtheconstantsforeachtypeofURI.
UsingProjectionMapsAcontentprovideractslikeanintermediarybetweenanabstractsetofcolumnsandarealsetofcolumnsinadatabase,yetthesecolumnsetscoulddiffer.Whileconstructingqueries,youmustmapbetweenthewhereclausecolumnsthataclientspecifiesandtherealdatabasecolumns.YousetupthisprojectionmapwiththehelpoftheSQLiteQueryBuilderclass.YoucanseehowthisprojectionmapvariablesBooksProjectionMapissetfortheBookProviderinListing25-26.YoucanalsoseeinthatlistinghowthisvariablesBooksProjectionMapisthenusedbytheSQLiteQueryBuilderobject.
FulfillingMIME-TypeContractsLet’sstartwiththegetType()methodinListing25-26.ThismethodreturnsaMIMEtypeforagivenURI.Thismethod,likemanyothermethodsofacontentprovider,issensitivetotheincomingURI.Asaresult,thefirstresponsibilityofthegetType()methodistodistinguishthetypeoftheURI.Isitacollectionofbooksorasinglebook?ThecodeusedtheUriMatchertodecipherthisURItype.DependingonthisURI,the
BookTableMetaDataclasshasdefinedtheMIME-typeconstantstoreturnforeachURI.
ImplementingtheQueryMethodLiketheothermethods,thequerymethodusesUriMatchertoidentifytheURItype.IftheURItypeisasingle-itemtype,themethodretrievesthebookIDfromtheincomingURIbylookingatthefirstsegmentreturnedbygetPathSegments().
ThequerymethodthenusestheprojectionsthatwecreatedupfrontinListing25-26toidentifythereturncolumns.Intheend,queryreturnsthecursortothecaller.Throughoutthisprocess,thequerymethodusestheSQLiteQueryBuilderobjecttoformulateandexecutethequery.
WhilereadingthedataonecanconstraintherowsreturnedeitherusingtheURIorthroughexplicitwhereclauseargumentspassedtothequerymethodasinputs.IntheBookProviderimplementationofListing25-26weusedtheapproachofusingtheURIsegmentstoretrievethebookIDtoreturnthevaluesforjustthatbook.
InsteadyoucanusetheselectionparameterandtheselectionArgsparameterofthequery()methodtoexplicitlypassthewhereclausearguments.TheseargumentsworkjustliketheSQLiteDatabase.query()argumentsinListing25-12,where“?”areusedasplaceholdersforthevaluespassedintheselectionArgsarray.
ImplementingtheInsertMethodTheinsertmethodinacontentproviderisresponsibleforinsertingarecordintotheunderlyingdatabaseandthenreturningaURIthatpointtothenewlycreatedrecord.
Liketheothermethods,insertusesUriMatchertoidentifytheURItype.ThecodefirstcheckswhethertheURIindicatesthepropercollection-typeURI.Ifnot,thecodethrowsanexception.
Thecodethenvalidatestheoptionalandmandatorycolumnparameters.Thecodecansubstitutedefaultvaluesforsomecolumnsiftheyaremissing.
Next,thecodeusesanSQLiteDatabaseobjecttoinsertthenewrecordandreturnsthenewlyinsertedID.Intheend,thecodeconstructsthenewURIusingthereturnedIDfromthedatabase.
ImplementingtheUpdateMethodTheupdatemethodinacontentproviderisresponsibleforupdatingarecord(orrecords)basedonthecolumnvaluespassedin,aswellasthewhereclausethatispassedin.Theupdatemethodthenreturnsthenumberofrowsupdatedintheprocess.
Liketheothermethods,updateusesUriMatchertoidentifytheURItype.IftheURItypeisacollection,thewhereclauseispassedthroughsoitcanaffectasmanyrecordsas
possible.IftheURItypeisasingle-recordtype,thenthebookIDisextractedfromtheURIandspecifiedasanadditionalwhereclause.Intheend,thecodereturnsthenumberofrecordsupdated.AlsonoticehowthisnotifyChangemethodenablesyoutoannouncetotheworldthatthedataatthatURIhaschanged.Potentially,youcandothesameintheinsertmethodbysayingthatthecollectionofbooksdataatURI“…/books”haschangedwhenarecordisinserted.
ImplementingtheDeleteMethodThedeletemethodinacontentproviderisresponsiblefordeletingarecord(orrecords)basedonthewhereclausethatispassedin.Thedeletemethodthenreturnsthenumberofrowsdeletedintheprocess.
Liketheothermethods,deleteusesUriMatchertoidentifytheURItype.IftheURItypeisacollectiontype,thewhereclauseispassedthroughsoyoucandeleteasmanyrecordsaspossible.Ifthewhereclauseisnull,allrecordswillbedeleted.IftheURItypeisasingle-recordtype,thebookIDisextractedfromtheURIandspecifiedasanadditionalwhereclause.Intheend,thecodereturnsthenumberofrecordsdeleted.
RegisteringtheProviderFinally,youmustregisterthecontentproviderintheAndroid.Manifest.xmlfileusingthetagstructureinListing25-27.Aproviderisacomponentandhenceasiblingoftheothercomponentssuchasanactivityandareceiver.SoitisasiblingnodetootheractivitiesintheAndroidmanifestfile.
Listing25-27.RegisteringaProvider
<providerandroid:name=".BookProvider"
android:authorities="com.androidbook.provider.BookProvider"/>
ExercisingtheBookProviderNowthatwehaveabookprovider,wearegoingtoshowyousamplecodetoexercisethatprovider.Thesamplecodeincludesaddingabook,removingabook,gettingacountofthebooks,andfinallydisplayingallthebooks.
Keepinmindthatthesearecodeextractsfromthesampleprojectandwillnotcompile,becausetheyrequireadditionaldependencyfiles.However,wefeelthissamplecodeissufficientindemonstratingtheconceptswehaveexplored.
Attheendofthischapter,wehaveincludedalinktothedownloadablesampleproject,whichyoucanuseinyourEclipseenvironmenttocompileandtest.
AddingaBook
ThecodeinListing25-28insertsanewbookintothebookdatabase.
Listing25-28.ExercisingaProviderInsert
//Filereferenceinproject:ProviderTester.JavapublicvoidaddBook(Contextcontext){Stringtag="ExerciseBookProvider";Log.d(tag,"Addingabook");ContentValuescv=newContentValues();cv.put(BookProviderMetaData.BookTableMetaData.BOOK_NAME,"book1");cv.put(BookProviderMetaData.BookTableMetaData.BOOK_ISBN,"isbn-1");cv.put(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR,"author-1");
ContentResolvercr=context.getContentResolver();Uriuri=BookProviderMetaData.BookTableMetaData.CONTENT_URI;Log.d(tag,"bookinserturi:"+uri);UriinsertedUri=cr.insert(uri,cv);Log.d(tag,"inserteduri:"+insertedUri);}
RemovingaBookThecodeinListing25-29deletesthelastrecordfromthebookdatabase.
Listing25-29.ExercisingaProviderDelete
//Filereferenceinproject:ProviderTester.JavapublicvoidremoveBook(){intfirstBookId=this.getFirstBookId();if(firstBookId==-1)thrownewSQLException("Bookidislessthan0");ContentResolvercr=this.mContext.getContentResolver();Uriuri=BookProviderMetaData.BookTableMetaData.CONTENT_URI;UridelUri=Uri.withAppendedPath(uri,Integer.toString(firstBookId));reportString("DelUri:"+delUri);cr.delete(delUri,null,null);this.reportString("NumberofBooksafterthedelete:"+getCount());}
privateintgetFirstBookId(){Uriuri
=BookProviderMetaData.BookTableMetaData.CONTENT_URI;Activitya=(Activity)this.mContext;Cursorc=null;try{c=a.getContentResolver().query(uri,null,//projectionnull,//selectionstringnull,//selectionargsarrayofstringsnull);//sortorderintnumberOfRecords=c.getCount();if(numberOfRecords==0){return-1;}c.moveToFirst();intid=c.getInt(1);//idcolumnreturnid;}finally{if(c!=null)c.close();}}
DisplayingtheListofBooksThecodeinListing25-30retrievesalltherecordsinthebookdatabase.
Listing25-30.DisplayingaListofBooks
//Filereferenceinproject:ProviderTester.JavapublicvoidshowBooks(){Uriuri=BookProviderMetaData.BookTableMetaData.CONTENT_URI;Activitya=(Activity)this.mContext;Cursorc=null;try{c=a.getContentResolver().query(uri,null,//projectionnull,//selectionstringnull,//selectionargsarrayofstringsnull);//sortorderintiid=c.getColumnIndex(BookProviderMetaData.BookTableMetaData._ID);intiname=c.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_NAME);intiisbn=c.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_ISBN);intiauthor
=c.getColumnIndex(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR);
//ReportyourindexesLog.d(tag,"name,isbn,author:"+iname+iisbn+iauthor);
//walkthroughtherowsbasedonindexesfor(c.moveToFirst();!c.isAfterLast();c.moveToNext()){//GathervaluesStringid=c.getString(iid);Stringname=c.getString(iname);Stringisbn=c.getString(iisbn);Stringauthor=c.getString(iauthor);
//ReportorlogtherowStringBuffercbuf=newStringBuffer(id);cbuf.append(",").append(name);cbuf.append(",").append(isbn);cbuf.append(",").append(author);Log.d(tag,cbuf.toString());}
//ReporthowmanyrowshavebeenreadintnumberOfRecords=c.getCount();Log.d(tag,"NumofRecords:"+numberOfRecords);}finally{if(c!=null)c.close();}}
NoticethatthemethodofretrievingthebooksfromacontentproviderisverysimilartoretrievingdatafromanSQLitedatabase.InListing25-30wehaveusedthequery()methodfromaContentResolverobject.Afterusingthecursorobjectwehaveclosedthecursor.
InsteadifyouwerepassingthiscursorobjecttoaUIcomponentthatisintheActivitythenthiscursorobjectneedstobemanagedastheactivityfollowsitslifecycle.PriortoHoneycomb,therewasamethodcalledmanagedQuery()ontheActivitytodothisautomatically,whichhassincebeendeprecatedinfavorofCursorLoader.
WhenaqueryisthusmanagedthroughmanagedQuery(),theactivitycancallmethodsonthecursortoplaceitintoproperstate.Forexample,theactivitywillcalldeactivate()onthecursorwhenitisstoppedandlatercallsrequery()whenitisstarted.Thecursorwillbeclosedwhentheactivityisdestroyed.YoucanchoosetocallstopManagingCursor()onthatcursorifyouwanttocontrolthebehaviorofthecursoryourself.Becausetheactivityclosesthecursor,don’tcloseamanagedcursor.Ifyourintentionistoreadalltherowsonetimeandclosethecursor,thenusethequery()
methodoftheContentResolverasopposedtotheActivity.managedQuery()methodandexplicitlyclosethecursor.
SinceHoneycomb,thecursorreadsarewrappedintoamoregeneralapproachcalled“Loaders,”whichallowyoutoreaddatainanasynchronousthreadthroughcallbacksexposedtofragmentsoractivities.Thisistherecommendedandpreferredmethod.Wewillcoverthisapproachinthenextchapter,Chapter26,onLoaders.
YouhaveseenhowwehaveusedupdateAPIsonacontentproviders.Theseupdateoperationscanbeinefficientifdoneonebyonethroughacontentprovider.InChapter27wewillcoverhowtheseindividualupdateoperationscanbesentasabatchtoacontentproviderforefficiencyreasons.
ResourcesHerearesomeadditionalAndroidresourcesthatcanhelpyouwiththetopicscoveredinthischapter:
http://developer.android.com/guide/topics/data/data-storage.html:VariousdatastorageoptionsfromAndroiddocumentation.
http://www.androidbook.com/item/4437:AsummaryofoptionsforpersistenceonAndroid.
http://www.androidbook.com/item/4876:ExploringtoolsandtechniquesfordirectSQLstorageonAndroid.ThisincludesresearchonO/Rmappingtoolsaswell.
http://www.androidbook.com/item/4877:StoringdatainthecloudthroughParseforAndroid.
http://www.androidbook.com/item/4440:UsingGSON/JSONformobileappstorage.
http://www.androidbook.com/item/4438:Usingsharedpreferencesforapplicationstatemanagement.
http://developer.android.com/guide/topics/providers/content-providers.html:Androiddocumentationoncontentproviders.
http://developer.android.com/reference/android/content/ContentProvider.htmlAPIdescriptionforaContentProvider,whereyoucanlearnaboutContentProvidercontracts.
http://developer.android.com/reference/android/content/UriMatcher.htmlInformationthatisusefulforunderstandingUriMatcher.
http://developer.android.com/reference/android/database/Cursor.htmlInformationthathelpsyoureaddatafromacontentproviderora
databasedirectly.
http://developer.android.com/guide/components/loaders.htmlDeveloper’sguideforLoaders.
http://developer.android.com/reference/android/app/Activity.html#startManagingCursor(android.database.CursorAPIdocumentationofwhatamanagedcursoris.
http://www.sqlite.org/sqlite.html:HomepageofSQLite,whereyoucanlearnmoreaboutSQLiteanddownloadtoolsthatyoucanusetoworkwithSQLitedatabases.
http://androidbook.com/proandroid5/projects:DownloadabletestprojectforthischapterisaccessiblefromthisURL.ThenameofthezipfileisProAndroid5_Ch25_TestProvider.zip.
SummaryThischapterhascoveredalotofaspectsaboutavitalneedofyourapplications:persistence.WehavegivenyouaplethoraofoptionsavailableinAndroidforpersistenceandhowtochooseanappropriateoption.WehavecoveredhowtouseSQLiteforinternalpersistenceneedsinsignificantdetail.Wehaveshownyouanindustrial-strengthAPIpatternforpersistenceusingSQLitewhichcanbeextendedtoanypersistenceimplementation.Importantly,thispatternshowedyouhowtoexternalizetransactionstokeepyourpersistencecodesimple.Wehavethencoveredwhatcontentprovidersare,thenatureofcontentURIs,MIMEtypes,howtouseSQLitetoconstructprovidersthatrespondtoURIs,howtowriteanewcontentprovider,andhowtoaccessanexistingcontentprovider.
Chapter26
UnderstandingLoadersThischapterlooksatloadingdatafromdatasourcesthroughtherecommendedmechanismofLoaders.TheAPIofLoadersisdesignedtodealwithtwoissueswithloadingdatabyactivitiesandfragments.
Thefirstisthenon-deterministicnatureofactivitieswhereanactivitycanbehiddenpartiallyorfully,restartedduetodevicerotation,orremovedfrommemorywheninbackgroundduetolow-memoryconditions.Theseeventsarecalledactivitylifecycleevents.Anycodethatretrievesdatamustworkinharmonywiththeactivitylifecycleevents.PriortotheintroductionofLoadersin3.0(API11),thiswashandledthroughManagedCursors.ThismechanismisnowdiscontinuedinfavorofLoaders.
Thesecondissuewithloadingdatainactivitiesandfragmentsisthatdataaccesscouldtakelongeronthemainthreadresultinginapplication-not-responding(ANR)messages.Loaderssolvethisbydoingtheworkonaworkerthreadandprovidingcallbackstotheactivitiesandfragmentstorespondtotheasynchronousnatureofdatafetch.
UnderstandingtheArchitectureofLoadersLoadersmakeiteasytoasynchronouslyloaddatainanactivityorafragment.Multipleloaders,eachwithitsownsetofdata,canbeassociatedwithanactivityorafragment.Loadersalsomonitorthesourceoftheirdataanddelivernewresultswhenthedatacontentchanges.Loadersautomaticallyreconnecttothepreviouslyretrieveddatastructure,likeacursor,whenbeingre-createdafteraconfigurationchange.Asthepreviouscursorisnotdestroyed,dataisnotrequeried.
Whenwetalkaboutloadersinthischapterallaspectsofloadersapplytobothactivitiesandfragmentsunlessweindicateotherwisefromnowon.
EveryactivityusesasingleLoaderManagerobjecttomanagetheloadersassociatedwiththatactivity.Oncealoaderisregisteredwithaloadermanager,theLoaderManagerwillfacilitatethenecessarycallbackstoa)createandinitializetheLoader,b)readthedatawhentheLoaderfinishesloadingthedata,andc)closetheresourcewhentheloaderisabouttobedestroyedastheactivityisnolongerneeded.TheLoaderManagerishiddenfromyouandyouworkwithitthroughcallbacksandLoaderManagerpublicAPIs.ThecreationoftheLoaderManageriscontrolledbytheactivity.LoaderManagerisalmostlikeanintegralpartoftheactivityitself.
ItistheresponsibilityoftheregisteredLoadertoworkwithitsdatasourceandalsowiththeLoaderManagertoreadthedataandsendtheresultsbacktothe
LoaderManager.TheLoaderManagerwilltheninvokethecallbacksontheactivitythatdataisready.TheLoaderisalsoresponsibleforpausingthedataaccessormonitoringdatachangesorworkingwiththeLoaderManagertounderstandandreacttotheactivitylifecycleevents.
WhileyoucanwritealoaderfromscratchforyourspecificdataneedsbyextendingtheloaderAPI,youtypicallyusetheLoadersthatarealreadyimplementedintheSDK.MostloadersextendtheAsyncTaskLoaderwhichprovidesthebasicabilitytodoitsworkonaworkerthreadfreeingthemainthread.Whentheworkerthreadreturnsdata,theLoaderManagerwillinvokethemaincallbackstotheactivitythatthedataisreadyonthemainthread.
ThemostusedoftheseprebuiltloadersistheCursorLoader.WiththeavailabilityofCursorLoader,usingLoadersbecomesreally,reallytrivialwithafewlinesofcode.ThisisbecauseallthedetailsarehiddenbehindtheLoaderManager,Loader,AsyncTaskLoader,andtheCursorLoader.
ListingBasicLoaderAPIClassesListing26-1liststhekeyclassesinvolvedintheLoaderAPI.
Listing26-1.AndroidLoaderAPIKeyParticipatingClasses
LoaderManagerLoaderManager.LoaderCallbacksLoaderAsyncTaskLoaderCursorLoader
TheAPIsthataremostoftenusedaretheLoaderManager.LoaderCallbacksandtheCursorLoader.However,letusbrieflyintroduceeachoftheseclasses.
ThereisoneLoaderManagerobjectperactivity.ThisistheobjectthatdefinestheprotocolofhowLoadersshouldwork.SoLoaderManageristheorchestratorfortheloadersassociatedwithanactivity.LoaderManager'sinteractionwiththeactivityisthroughtheLoaderManager.LoaderCallbacks.TheseloadercallbacksarewhereyouaregiventhedatabytheLoaderviatheLoaderManagerandexpectedtointeractwiththeactivity.
TheLoaderclassdefinestheprotocolthatmustbeadheredtoifonewantstodesigntheirownloader.AsyncTaskLoaderisoneexamplewhereitimplementstheloaderprotocolinanasynchronousmanneronaworkerthread.ItistypicallytheAsyncTaskLoaderthatisthebaseclasstoimplementmostofyourloaders.CursorLoaderisanimplementationofthisAsyncTaskLoaderthatknowshowtoloadcursorsfromcontentproviders.IfoneisimplementingtheirownloaderitisimportanttounderstandthatallinteractionwiththeloaderfromaLoaderManagerhappensonthemainthread.EventheLoaderManagercallbacksthatareimplementedbytheactivity
takeplaceonthemainthread.
DemonstratingtheLoadersWewillnowshowyouhowtouseLoadersbyimplementingasimpleone-pageapplication(Figure26-1)thatloadscontactsfromthecontactproviderdatabaseonanAndroiddevice.ThisapplicationistypicalofhowonewoulddevelopAndroidactivities.Youcouldevenusethissampleprojectasastarterapplicationtemplate.
Figure26-1.Filteredlistofcontactsloadedthroughloaders
WewanttheactivityinFigure26-1toexhibitthefollowingcharacteristics:1)Itshoulddisplayallthecontactsonthedevice;b)Itshouldretrievedataasynchronously;c)Whiledataisbeingretrieved,theactivityshouldshowaprogressbarviewinplaceofthelistview;d)Onretrievingdatasuccessfully,thecodeshouldreplacetheprogressviewwiththefilled-inlistview;e)Theactivityshouldprovideasearchmechanismtofilterthenecessarycontacts;f)Whenthedeviceisrotated,itshouldshowthecontactsagainwithoutmakingarequerytothecontactscontentprovider;g)Thecodeshouldallowustoseetheorderofcallbacksalongwiththeactivitylifecyclecallbacks.
Wewillfirstpresentthesourcecodefortheactivityandthenexplaineachsection.BytheendofthechapteryouwillhaveaclearunderstandingofhowLoadersworkandhowto
usetheminyourcode.Withthat,Listing26-2showsthecodefortheactivityofFigure26-1.PleasenotethatthecodeinListing26-2reliesonanumberofresourcesthatarepresentedhere.SomeofthesestringresourcesyoucanseeinFigure26-1,butforothersandthecodethatisnotincludedhere,pleaseseethedownloadableproject.Asalways,thecodepresentedhereissufficientforthetopicathand.Listing26-2.AnActivityLoadingDatawithLoaders
publicclassTestLoadersActivity
extendsMonitoredListActivity//verysimpleclasstologactivitycallbacksimplementsLoaderManager.LoaderCallbacks<Cursor>//LoaderManagercallbacks,OnQueryTextListener//Searchtextcallbacktofiltercontacts{privatestaticfinalStringtag="TestLoadersActivity";
//Adapterfordisplayingthelist'sdata
//InitializedtonullcursorinonCreateandsetonthelist//Useitinlatercallbackstoswapcursor//ThisisreinitializedtonullcursorwhenrotationoccursSimpleCursorAdaptermAdapter;
//SearchfilterworkingwithOnQueryTextListener
StringmCurFilter;
//Contactscolumnsthatwewillretrieve
staticfinalString[]PROJECTION=newString[]{ContactsContract.Data._ID,ContactsContract.Data.DISPLAY_NAME};
//selectcriteriaforthecontactsURI
staticfinalStringSELECTION="(("+ContactsContract.Data.DISPLAY_NAME+"NOTNULL)AND("+ContactsContract.Data.DISPLAY_NAME+"!=''))";
publicTestLoadersActivity(){super(tag);}@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
this.setContentView(R.layout.test_loaders_activity_layout);
//Initializetheadapterthis.mAdapter=createEmptyAdapter();this.setListAdapter(mAdapter);
//Hidethelistviewandshowtheprogressbar
this.showProgressbar();
//Initializealoaderforanidof0getLoaderManager().initLoader(0,null,this);
}//Createasimplelistadapterwithanullcursor//ThegoodcursorwillcomelaterintheloadercallbackprivateSimpleCursorAdaptercreateEmptyAdapter(){
//Forthecursoradapter,specifywhichcolumnsgointowhichviewsString[]fromColumns={ContactsContract.Data.DISPLAY_NAME};int[]toViews={android.R.id.text1};//TheTextViewinsimple_list_item_1//ReturnthecursorreturnnewSimpleCursorAdapter(this,android.R.layout.simple_list_item_1,null,//cursorfromColumns,toViews);}//ThisisaLoaderManagercallback.ReturnaproperlyconstructedCursorLoader//Thisgetscalledonlyiftheloaderdoesnotpreviouslyexist.//Thismeansthismethodwillnotbecalledonrotationbecause//apreviousloaderwiththisIDisalreadyavailableandinitialized.//Thisalsogetscalledwhentheloaderis"restarted"bycalling//LoaderManager.restartLoader()@OverridepublicLoader<Cursor>onCreateLoader(intid,Bundleargs){
Log.d(tag,"onCreateLoaderforloaderid:"+id);UribaseUri;if(mCurFilter!=null){baseUri=Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));}else{baseUri=Contacts.CONTENT_URI;
}String[]selectionArgs=null;StringsortOrder=null;returnnewCursorLoader(this,baseUri,PROJECTION,SELECTION,selectionArgs,sortOrder);}//ThisisaLoaderManagercallback.Usethedatahere.//Thisgetscalledwhenheloaderfinishes.Calledonthemainthread.//Canbecalledmultipletimesasthedatachangesunderneath.//Alsogetscalledafterrotationwithoutrequeryingthecursor.@OverridepublicvoidonLoadFinished(Loader<Cursor>loader,Cursorcursor){
Log.d(tag,"onLoadFinishedforloaderid:"+loader.getId());Log.d(tag,"Numberofcontactsfound:"+cursor.getCount());this.hideProgressbar();this.mAdapter.swapCursor(cursor);
}//ThisisaLoaderManagercallback.Removeanyreferencestothisdata.//Thisgetscalledwhentheloaderisdestroyedlikewhenactivityisdone.//FYI-thisdoesNOTgetcalledbecauseofloader"restart"//Thiscanbeseenasa"destructor"fortheloader.@OverridepublicvoidonLoaderReset(Loader<Cursor>loader){
Log.d(tag,"onLoaderResetforloaderid:"+loader.getId());this.showProgressbar();this.mAdapter.swapCursor(null);
}@OverridepublicbooleanonCreateOptionsMenu(Menumenu){
//Placeanactionbaritemforsearching.MenuItemitem=menu.add("Search");item.setIcon(android.R.drawable.ic_menu_search);item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);SearchViewsv=newSearchView(this);sv.setOnQueryTextListener(this);item.setActionView(sv);returntrue;}
//ThisisaSearchviewcallback.Restarttheloader.//Thisgetscalledwhenuserentersnewsearchtext.//CallLoaderManager.restartLoadertotriggertheonCreateLoader@OverridepublicbooleanonQueryTextChange(StringnewText){
//Calledwhentheactionbarsearchtexthaschanged.Update//thesearchfilter,andrestarttheloadertodoanewquery//withthisfilter.mCurFilter=!TextUtils.isEmpty(newText)?newText:null;Log.d(tag,"Restartingtheloader");getLoaderManager().restartLoader(0,null,this);
returntrue;}@OverridepublicbooleanonQueryTextSubmit(Stringquery){returntrue;}privatevoidshowProgressbar(){//showprogressbarViewpbar=this.getProgressbar();pbar.setVisibility(View.VISIBLE);//hidelistviewthis.getListView().setVisibility(View.GONE);
findViewById(android.R.id.empty).setVisibility(View.GONE);}privatevoidhideProgressbar(){//showprogressbarViewpbar=this.getProgressbar();pbar.setVisibility(View.GONE);//hidelistviewthis.getListView().setVisibility(View.VISIBLE);}privateViewgetProgressbar(){returnfindViewById(R.id.tla_pbar);}}//eof-class
WewillexplaineachsectionfromListing26-2afterweshowyouthesupportinglayoutfortheactivitycodeinListing26-3.ThislayoutinListing26-3shouldclarifytheviewinFigure26-1(pleasenotethatanumberofresourcesarenotincludedherebutareavailableinthedownloadablefileatapress.com/9781430246800).
Listing26-3.ATypicalListActivityLayoutforLoaders
<?xmlversion="1.0"encoding="utf-8"?><!--**********************************************/res/layout/test_loaders_activity_layout.xml
*correspondingactivity:TestLoadersActicity.java*prefix:tla_(Usedforprefixinguniqueidentifiers)**Use:*Demonstrateloadingacursorusingloaders*Structure:*Headermessage:textview(tla_header)*ListViewHeading,ListView(fixed)*ProgressBar(Toshowwhendataisbeingfetched)*EmptyView(Toshowwhenthelistisempty):ProgressBar*Footer:textview(tla_footer)************************************************--><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="2dp"android:paddingRight="2dp"><!--HeaderandMaindocumentationtext-->
<TextViewandroid:id="@+id/tla_header"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/box2"android:layout_marginTop="4dp"android:padding="8dp"android:text="@string/tla_header"/><!--Headingforthelistview-->
<TextViewandroid:id="@+id/tla_listview_heading"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/gray"android:layout_marginTop="4dp"android:padding="8dp"android:textColor="@color/black"style="@android:style/TextAppearance.Medium"android:text="ListofContacts"/><!--ListViewusedbytheListActivity.Usesastandardidneededby
alistview-->
<!--Fixtheheightofthelistviewinaproductionsetting--><ListViewandroid:id="@android:id/list"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/box2"android:layout_marginTop="4dp"android:layout_marginBottom="4dp"
android:drawSelectorOnTop="false"/><!--ProgressBar:Toshowandhidetheprogressbarasloadersload
data-->
<ProgressBarandroid:id="@+id/tla_pbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center"android:indeterminate="true"/><!--EmptyList:Usesastandardidneededbyalistview--><TextViewandroid:id="@android:id/empty"android:layout_width="match_parent"android:layout_height="wrap_content"android:visibility="gone"android:layout_marginTop="4dp"android:layout_marginBottom="4dp"android:padding="8dp"android:text="NoContactstoMatchtheCriteria"/><!--Footer:Additionaldocumentationtextandthefooter--><TextViewandroid:id="@+id/tla_footer"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/box2"android:padding="8dp"android:text="@string/tla_footer"/></LinearLayout>
Let’snowturntounderstandingthecodeinListing26-2.Wewillexplainthiscodethroughaseriesofstepsyouwouldfollowtocodewithloaders.Let’sstartwithstep1,wheretheactivityneedstobeextendedtosupporttheLoaderManagercallbacks.
Step1:PreparingtheActivitytoLoadDataCodenecessarytoloaddatausingloadersisremarkablysmall,becausemostoftheworkisdonebytheCursorLoader.ThefirstthingyouneedtodoistohaveyouractivityextendtheLoaderManager.LoaderCallbacks<Cursor>andimplementthethreeneededmethods:onCreateLoader(),onLoadFinished(),andonLoaderReset().YoucanseehowinListing26-2.Byimplementingthisinterface,youhaveenabledtheactivitytobecomeareceiverfortheLoaderManagereventsthroughthesethreecallbacks.
Step2:InitializingtheLoaderNext,youhavetotelltheactivitythatyouwantaLoaderobjecttoloadthedata.ThisisdonebyregisteringandinitializingaLoaderduringtheonCreate()methodoftheactivityasshowninListing26-4.YoucanalsoseethisintheonCreate()ofListing
26-3aswell,incontextoftheoverallcode.
Listing26-4.InitializingaLoader
intloaderid=0;BundleargBundle=null;LoaderCallbacks<Cursor>loaderCallbacks=this;//thisactivityitselfgetLoaderManager().initLoader(loaderid,argBundle,loaderCallbacks);
Theloaderidargumentisadeveloper-assigneduniquenumberinthecontextofthisactivitytouniquelyidentifythisLoaderfromotherLoadersregisteredwiththisactivity.Notethatintheexamplehere,weareusingonlyoneLoader.
ThesecondargsBundleargumentisusedtopassadditionalargumentstotheonCreateLoader()callbackifneeded.This“bundleofarguments”approachfollowstheusualpatternofdifferedfactoryobjectconstructioninmanyofthemanagedcomponentsinAndroid.Activities,fragments,andloadersareallexamplesofthispattern.
Thethirdargument,loaderCallbacks,isareferencetoanimplementationofthecallbacksrequiredbytheLoaderManager.InListings26-2and26-4,theactivityitselfisplayingthisrole,sowepassthisvariablereferringtotheactivityastheargumentvalue.
OncetheLoaderisregisteredandinitialized,theLoaderManagerwillscheduleacalltotheonCreateLoader()callbackifnecessary.IfacallwaspreviouslymadetotheonCreateLoader()andaloaderobjectisavailablecorrespondingtothisloaderID,thenthemethodonCreateLoader()willnotbetriggered.Asstatedearlier,theexceptionisifthedeveloperoverridesthisbehaviorbycallingLoaderManager.restartLoader().Youwillseethiscallexplainedlaterwhenwetalkedaboutprovidingsearch-basedfilteringcapabilitiestolocateasub-selectionofcontacts.
DelvingintotheStructureofListActivityTheListActivityinFigure26-1isextendingalistactivitywithacontentviewthatisacustomlayoutthroughsetContentView().Thisgivesusalotmoreflexibilitytoplaceothercontrolsinadditiontothelistviewontheactivity.Forexample,wehaveprovidedaheaderview,afooterview,andalsoaprogressbartoshowthatweareintheprocessoffetchingdata.TheonlyconstraintplacedbyaListActivityistonameacontrolwiththereserved@android:id/listviewtoidentifythelistviewthatthelistactivitywouldbeusing.InadditiontothelistviewID,wecanalsoprovideaviewthatthelistactivityusesifthelistisempty.ThisviewisidentifiedbythepredefinedID@android:id/empty.
WorkingwithAsynchronousLoadingofDataLoadersloaddataasynchronously.Becauseofthiswehaveanaddedresponsibilityin
theActivity.onCreate()tohidethelistviewandshowtheprogressindicatoruntilthelistdataisready.Todothis,wehaveaProgressBarcomponentinthelayoutinListing26-3.IntheActivity.onCreate()method,wesettheinitialstateofthelayoutsothatthelistviewishiddenandtheprogressbarisshown.ThisfunctionalityiscodedinthemethodshowProgressbar()inListing26-2.InthesameListing26-2,whenthedataisreadywecallhideProgressbar()tohidetheprogressbarandshowthepopulatedlistvieworanemptylistviewifthereisnodata.
Step3:ImplementingonCreateLoader()TheonCreateLoader()istriggeredbytheinitializationoftheloader.YoucanseethesignatureandimplementationofthismethodinListing26-2.ThismethodconstructsaLoaderobjectforthecorrespondingloaderIDthatispassedinfromtheinitializationstemmingfromthecalltoLoaderManager.initLoader().ThismethodalsoreceivestheargumentbundlethatisprovidedduringtheloaderinitializationforthisloaderID.
Thismethodreturnsaproperlytyped(throughJavagenerics)LoaderobjecttotheLoaderManager.InourcasethistypeisLoader<Cursor>.TheLoaderManagercachestheLoaderobjectandwillreuseit.ThisisusefulbecausewhenthedevicerotatesandtheloaderisreinitializedduetoActivity.onCreate(),LoaderManagerrecognizestheloaderIDandthepresenceofanexistingloader.TheLoaderManagerthenwillnottriggeraduplicatecalltotheonCreateLoader().However,iftheactivityistorealizethattheinputdatatotheloaderhaschanged,theactivitycodecancalltheLoaderManager.restartLoader(),whichwilltriggeracalltotheonLoaderCreate()again.Inthatcase,theLoaderManagerwillfirstdestroytheoldloaderandusethenewonereturnedbytheonLoaderCreate().TheLoaderManagerdoesguaranteethattheolderloaderwillhangarounduntilthenewloaderiscreatedandavailable.
TheonCreateLoader()methodhasfullaccesstothelocalvariablesoftheactivity.Soitcanusetheminanyconceivablemannertoconstructtheneededloaders.IncaseofaCursorLoaderthisconstructionislimitedtotheargumentsavailabletotheconstructoroftheCursorLoader,whichisspecificallybuilttoallowcursorsfromanAndroidcontentprovider.
Inourexample,wehaveusedthecontentURIsprovidedbythecontactscontentprovider.RefertoChapter25,oncontentprovider,forhowtousecontentURIstoretrievecursorsfromcontentproviderdatasources.Itisquitesimple:justindicatetheURIyouwanttogetthedatafrom,supplythefilterstringasanargumentorapathsegmentonthatURIasperthedocumentationavailableforthecontactscontentprovider,specifythecolumnsyouwant,specifythewhereclauseasastring,andconstructtheCursorLoader.
Step4:ImplementingonLoadFinished()
OncetheCursorLoaderisreturnedtotheLoaderManager,theCursorLoaderwillbeinstructedtostartitsworkonaworkerthreadandthemainthreadwillgoontotheUIchores.AtalaterpointthismethodonLoadFinished()iscalledwhenthedataisready.
Thismethodcouldbecalledmultipletimes.Whenthedatafromacontentproviderchanges,astheCursorLoaderhasregistereditselfwiththedatasource,itwillbealerted.CursorLoaderthenwilltriggertheonLoadFinished()again.
IntheonLoadFinished()method,allyouneedtodoistoswapthedatacursorthatisheldbythelistadapter.Thelistadapterwasinitializedoriginallywithanullcursor.Swappingwithapopulatedcursorwillshowthenewdataonthelistview.AswehavehiddenthelistviewinActivity.onCreate(),weneedtoshowthelistviewandhidetheprogressbar.Subsequentlywecangoonswappingthenewcursorsforoldcursorsasdatachanges.Thechangeswillreflectautomaticallyonthelistview.
Whenthedevicerotates,acoupleofthingshappen.TheActivity.onCreate()willbecalledagain.Thiswillsetthelistcursortonullandalsohidethelistview.ThecodeinActivity.onCreate()willalsoinitializetheloaderagain.TheLoaderManagerisprogrammedsothatthisrepeatinitializationisharmless.TheonCreateLoader()willnotbecalled.TheCursorwillnotberequeried.However,theonLoadFinished()getscalledagain,whichiswhatweneededtobreakoutofthisconundrumofinitializingthedatatonullfirstandwonderinghowandwhenitwillbepopulatedifwewerenottorequery.AstheonLoadFinished()getscalledagainonrotation,weareabletoremovetheProgressBar,showthelistview,andswapthevalidcursorfromthenullcursor.Allworks.Yes,itissneakyandround-about,butitworks.
Step5:ImplementingonLoaderReset()Thiscallbackisinvokedwhenapreviouslyregisteredloaderisnolongernecessaryandhencedestroyed.Thiscanhappenwhenanactivityisdestroyedduetoabackbuttonorexplicitlyinstructedtobefinishedbycode.Insuchcases,thiscallbackallowsanopportunitytocloseresourcesorreferencesthatarenolongerneeded.However,itisimportantnottoclosethecursorsastheyaremanagedbythecorrespondingloadersandwillbeclosedforyoubytheframework.ThismightsuggestthattheLoaderManager.restartLoader()mightresultinacalltotheonLoaderReset()astheargumentstotheoldloaderarenolongervalid.Buttestsshowthatthisisnotthecase.ThemethodLoaderManager.restartLoader()willnottriggeracalltothemethodonLoaderReset().TheonLoaderReset()methodisonlycalledwhentheloaderisactivelydestroyedbytheactivitynolongerbeingneeded.YoucanalsoexplicitlyinstructtheLoaderManagertodestroytheloaderbycallingLoaderManager.destroyLoader(loaderid).
UsingSearchwithLoaders
Wewillusesearchinoursampleapplicationtodemonstratethedynamicnatureofloaders.Wehaveattachedasearchviewtothemenu.YoucanseethisinthemethodonCreateOptionsMenu()inListing26-2.HerewehaveattachedaSearchViewtothemenuandprovidedtheactivityasthecallbacktotheSearchViewwhennewtextisprovidedintheSearchView.TheSearchViewcallbackishandledinthemethodonQueryTextchange()ofListing26-2.
IntheonQueryTextChange()method,wetakethenewsearchtextandsetthelocalvariablemCurFilter.WethencallLoaderManager.restartLoader()withthesameargumentsastheLoaderManager.initializeLoader().ThiswilltriggertheonCreateLoader()again,whichwillthenusethemCurFiltertoaltertheparameterstotheCursorLoaderresultinginanewcursor.ThisnewcursorwillreplacetheoldoneintheonLoadFinished()method.
UnderstandingtheOrderofLoaderManagerCallbacksBecauseAndroidprogrammingislargelyevent-based,itisimportanttoknowtheorderofeventcallbacks.TohelpyouunderstandthetimingoftheLoaderManagercallbacks,wehaveriggedthesampleprogramwithlogmessages.Herearesomeresultsshowingtheorderofcallbacks.
Listing26-5showstheorderofcallswhentheactivityisfirstcreated.
Listing26-5.LoaderCallbacksonActivityCreation
Application.onCreate()Activity.onCreate()LoaderManager.LoaderCallbacks.onCreateLoader()Activity.onStart()Activity.onResume()LoaderManager.LoaderCallbacks.onLoadFinished()
Whenthesearchviewfiresanewsearchcriteriathroughitscallback,theorderofcallbacksisasshowninListing26-6.
Listing26-6.LoaderCallbacksonaNewSearchCriteriatriggeredbyRestartLoader
RestartLoader//logmessagefromonQueryTextChangeLoaderManager.LoaderCallbacks.onCreateLoader()LoaderManager.LoaderCallbacks.onLoadFinished()//Notice,nocalltoonLoaderReset()
Listing26-7showstheorderofcallsonconfigurationchange.
Listing26-7.LoaderCallbacksonaConfigurationChange
Application:configchanged
Activity:onCreateActivity.onStart[NocalltotheonCreateLoader]LoaderManager.LoaderCallbacks.onLoadFinished[optionallyifsearchviewhastextinit]SearchView.onQueryChangeTextRestartLoader//justalogmessageLoaderManager.LoaderCallbacks.onCreateLoaderLoaderManager.LoaderCallbacks.onLoadFinished
Listing26-8showstheorderofcallbacksonnavigatingbackornavigatingHomeasthoseactionresultsintheactivityarebeingdestroyed.
Listing26-8.LoaderCallbackswhentheActivityisdestroyed
ActivityonStop()Activity.onDestroy()LoaderManager.LoaderCallbacks.onLoaderReset()//Noticethismethodiscalled
WritingCustomLoadersAsyouhaveseenwiththeCursorLoader,Loadersarespecifictotheirdatasources.Soyoumayneedtowriteyourownloaders.VerylikelyyouwillneedtoderivefromtheAsyncTaskLoaderandspecializeitusingtheprinciplesandcontractslaidoutbytheLoaderprotocol.SeetheSDKdocumentationfortheLoaderclasstogetmoredetails.YoucanalsousetheCursorLoadersourcecodeasaguideinwritingyourownloaders.Thesourcecodeisavailableonlinefrommultiplesources(youcanjustgoogleit)oraspartoftheAndroidsourcedownload.
ResourcesHereareadditionalresourcesforthetopicscoveredinthischapter:
http://www.androidbook.com/item/4890:Researchnotesonloaders.Youwillseeherelinkstoreferences,research,samplecode,images,keyquestions,andongoingnotes.
http://developer.android.com/guide/components/loaders.htmlPrimaryguideforloadersfromAndroid.
http://developer.android.com/guide/components/loaders.html#callbackKeyloaderAPIcallbackstobeimplementedbyanactivityorafragment.
http://developer.android.com/reference/android/content/Loader.htmlLoaderJavaclassAPItounderstandwhatmethodsareavailableona
loaderobjectwhichisoftenpassedtotheloaderAPIcallbacks.
http://developer.android.com/reference/android/app/LoaderManager.htmlLoaderManagerJavaclassAPIwhichisusefultocontroltheloader,suchasinitialing,restarting,orremoving.
http://developer.android.com/reference/android/content/CursorLoader.htmlCursorLoaderJavaclassAPIwhichisusefultoloaddatausingcursors.CursorLoadersarealsopassedasargumentstotheLoaderManagercallbacks.YoucanusethepublicAPIontheCursorLoadertogetitsID,canceltheload,andgettheinputargumentsusedtostartthecursor.
http://developer.android.com/guide/topics/ui/layout/listview.htmlYouwillfindherehowtouseloaderstopopulateandworkwithaListView.
http://developer.android.com/reference/android/provider/ContactsContract.Contacts.htmlContentproviderAPIsavailabletoworkwiththeAndroidcontactsdatabase.
http://developer.android.com/reference/android/app/Activity.html#startManagingCursor(android.database.CursorAPIdocumentationofwhatamanagedcursoris.Thisisusefultoseewhatisdonetoacursorinamanagedenvironment.Thisappliestocursorsmanagedbyloadersaswell.
http://www.androidbook.com/proandroid5/projects:DownloadabletestprojectforthischapterisaccessiblefromthisURL.ThenameofthezipfileisProAndroid5_Ch26_TestLoaders.zip.
SummaryLoadersareessentialtoloaddatafromdatasourcesbothfromatimingperspectiveandalsointermsoftheabilitytodealwiththemanagedlifecycleofactivitiesandfragments.Youhaveseeninthischapterhoweasyitistouseloaderstoloaddatafromcontentproviders.Theresultingcodeisresponsive,abletodealwithconfigurationchanges,andsimple.
Chapter27
ExploringtheContactsAPIInChapters25and26,wecoveredcontentprovidersandtheirclosecousins,theloaders.Welistedthebenefitsofexposingdatathroughcontentproviderabstraction.Inacontentproviderabstraction,dataisexposedasaseriesofURLs.ThesedataURLscanbeusedtoread,query,update,insert,anddelete.TheseURLsandtheircorrespondingcursorsbecometheAPIforthatcontentprovider.
TheContactsAPIisonesuchcontentproviderAPIforworkingwithcontactdata.ContactsinAndroidaremaintainedinadatabaseandexposedthroughacontentproviderwhoseauthorityisrootedat
content://com.android.contacts
TheAndroidSDKdocumentsthevariousURLsandthedatatheyreturnusingasetofJavainterfacesandclassesthatarerootedattheJavapackage
android.provider.ContactsContract
YouwillseenumerousclasseswhoseparentcontextisContactsContract;theseareusefulinquerying,reading,updating,andinsertingcontactsintoandfromthecontentdatabase.TheprimarydocumentationforusingtheContactsAPIisavailableontheAndroidsiteat
https://developer.android.com/guide/topics/providers/contacts-provider.html
TheprimaryAPIentrypointContactsContractisappropriatelynamedbecausethisclassdefinesthecontractbetweentheclientsofthecontactsandtheproviderandprotectorofthecontactsdatabase.
Thischapterexploresthiscontractinafairamountofdetailbutdoesnotcovereverynuance.TheContactsAPIislargeanditstentaclesfar-reaching.However,whenyouapproachtheContactsAPI,itwilltakeafewweeksofresearchtorealizethatitissimpleinitsunderlyingstructure.Thisiswherewewouldliketocontributethemostandexplainthesebasicsinthetimeittakestoreadthischapter.
Android4.0hasextendedtheideaofcontactstoincludeauserprofile,similartoauserprofileinasocialnetwork.Auserprofileisadedicatedcontactthatrepresentstheownerofthedevice.Mostofthegeneralcontact-basedconceptsremainthesame.WewillcoverhowtheContactsAPIisextendedtosupportauserprofile.
UnderstandingAccountsAllcontactsinAndroidworkinthecontextofanaccount.Whatisanaccount?Well,for
example,ifyouhaveyoure-mailthroughGoogle,youaresaidtohaveanaccountwithGoogle.IfyousetupyourselfasauserofFacebook,youaresaidtohaveanaccountwithFacebook.Youwillbeabletosetuptheseaccountsthroughthe“Accounts&sync”Settingsoptiononthedevice.SeetheAndroidUser’sGuidetogetmoredetailsaroundaccountsandhowtosetthemup.
Thecontactsyoumanagearetiedtoaspecificaccount.Anaccountownsitssetofcontacts—oranaccountissaidtobetheparentofacontact.Anaccountisidentifiedbytwostrings:theaccountnameandtheaccounttype.IncaseofGoogle,youraccountnameisyoure-mailusernameatGmailandyouraccounttypeiscom.google.Theaccounttypemustbeuniqueacrossthedevice.Youraccountnameisuniquewithinthataccounttype.Together,anaccounttypeandanaccountnameformanaccount,andonlyoncetheaccountisformedcanasetofcontactsbeinsertedforthataccount.
EnumeratingAccountsTheContactsAPIprimarilydealswithcontactsthatexistinvariousaccounts.ThemechanismofcreatingaccountsisoutsideoftheContactsAPI,soexplainingtheabilitytowriteyourownaccountprovidersandhowtosyncthecontactswithinthoseaccountsisoutsidethescopeofthischapter.Youcanunderstandandbenefitfromthischapterwithoutgoingintothedetailsofhowaccountsgetsetup.However,whenyouwanttoaddacontactoralistofcontacts,youdoneedtoknowwhataccountsexistonthedevice.YoucanusethecodeinListing27-1toenumeratetheaccountsandtheirproperties(theaccountnameandtype).CodeinListing27-1liststheaccountnameandtypegivenacontextvariablesuchasanactivity.
Listing27-1.CodetoDisplayaListofAccounts
publicvoidlistAccounts(Contextctx){AccountManageram=AccountManager.get(ctx);Account[]accounts=am.getAccounts();for(Accountac:accounts){Stringaccount_name=ac.name;Stringaccount_type=ac.type;Log.d("accountInfo",account_name+":"+account_type);}}
TorunthecodeinListing27-1,themanifestfileneedstoaskforpermissionusingthelineinListing27-2.
Listing27-2.PermissiontoReadAccounts
<uses-permissionandroid:name="android.permission.GET_ACCOUNTS"/>
ThecodefromListing27-1willprintsomethinglikethefollowing:
Your-email-at-gmail:com.google
Thisassumesthatyouhaveonlyoneaccount(Google)configured.Ifyouhavemorethanoneaccount,allofthoseaccountswillbelistedinasimilarmanner.
Usingthecontactsapplicationonthedeviceyoucanadd,edit,anddeletecontactstoanyofyourexistingaccounts.
UnderstandingContactsContactsownedbyanaccountarecalledrawcontacts.Arawcontacthasavariablesetofdataelements(forexample,e-mailaddress,phonenumber,name,andpostaladdress).Androidpresentsanaggregatedviewofrawcontactsbylistingonlyonceanyrawcontactsthatseemstomatch.Theseaggregatedcontactsformthesetofcontactsyouseewhenyouopenthecontactsapplication.
Wewillnowexaminehowcontactsandtheirrelateddataarestoredinvarioustables.UnderstandingthesecontacttablesandtheirassociatedviewsiskeytounderstandingtheContactsAPI.
ExaminingtheContactsSQLiteDatabaseOnewaytounderstandandexaminethecontactsdatabasetablesistodownloadthecontactsdatabasefromthedeviceortheemulatorandopenitusingoneoftheSQLiteexplorertools.
Todownloadthecontactsdatabase,usetheFileExplorershowninFigure30-17andnavigatetothefollowingdirectoryonyouremulator:
/data/data/com.android.providers.contacts/databases
Dependingontherelease,thedatabasefilenamemaydifferslightly,butitshouldbecalledcontacts.db,contacts2.db,orsomethingsimilar.In4.0,thecontactsproviderusesasimilarlystructuredbutseparatedatabasefilecalledprofile.dbtoholdthecontactsrelatedtothepersonalprofile.
UnderstandingRawContactsThecontactsyouseeinthecontactsapplicationarecalledaggregatedcontacts.Underneatheachaggregatedcontactliesasetofcontactscalledrawcontacts.Anaggregatedcontactisaviewonasetofsimilarrawcontacts.
Thesetofcontactsbelongingtoanaccountarecalledrawcontacts.Eachrawcontactpointstothedetailsofonepersoninthecontextofthataccount.Thisisincontrasttoanaggregatedcontact,whichcrossesaccountboundariesandbelongstothedeviceasawhole.Thisrelationshipbetweenanaccountanditssetofrawcontactsismaintainedintherawcontactstable.Listing27-3showsthestructureoftherawcontactstableinthe
contactsdatabase.
Listing27-3.RawContactTableDefinition
CREATETABLEraw_contacts(_idINTEGERPRIMARYKEYAUTOINCREMENT,is_restrictedINTEGERDEFAULT0,account_nameSTRINGDEFAULTNULL,account_typeSTRINGDEFAULTNULL,sourceidTEXT,versionINTEGERNOTNULLDEFAULT1,dirtyINTEGERNOTNULLDEFAULT0,deletedINTEGERNOTNULLDEFAULT0,contact_idINTEGERREFERENCEScontacts(_id),aggregation_modeINTEGERNOTNULLDEFAULT0,aggregation_neededINTEGERNOTNULLDEFAULT1,custom_ringtoneTEXTsend_to_voicemailINTEGERNOTNULLDEFAULT0,times_contactedINTEGERNOTNULLDEFAULT0,last_time_contactedINTEGER,starredINTEGERNOTNULLDEFAULT0,display_nameTEXT,display_name_altTEXT,display_name_sourceINTEGERNOTNULLDEFAULT0,phonetic_nameTEXT,phonetic_name_styleTEXT,sort_keyTEXTCOLLATEPHONEBOOK,sort_key_altTEXTCOLLATEPHONEBOOK,name_verifiedINTEGERNOTNULLDEFAULT0,contact_in_visible_groupINTEGERNOTNULLDEFAULT0,sync1TEXT,sync2TEXT,sync3TEXT,sync4TEXT)
AswithmostAndroidtables,therawcontactstablehasthe_IDcolumnthatuniquelyidentifiesarawcontact.Together,thefield’saccount_nameandaccount_typeidentifytheaccounttowhichthiscontact(specifically,therawcontact)belongs.Thesourceidfieldindicateshowthisrawcontactisuniquelyidentifiedintheaccount.
Thefieldcontact_idreferstotheaggregatedcontactthatthisrawcontactisoneof.Anaggregatedcontactpointstooneormoresimilarcontactsthatareessentiallythesamepersonsetupamongmultipleaccounts.
Thefielddisplay_namepointstothedisplaynameofthecontact.Thisisprimarilyaread-onlyfield.Itissetbytriggersbasedonthedatarowsaddedinthedatatable(whichiscoveredinthenextsubsection)forthisrawcontact.
Thesyncfieldsareusedbytheaccounttosynccontactsbetweenthedeviceandtheserver-sideaccountsuchasGooglemail.
AlthoughwehaveusedSQLitetoolstoexplorethesefields,thereismorethanonewayto
discoverthesefields.TherecommendedwayistofollowtheclassdefinitionsasdeclaredintheContactsContractAPI.Toexplorethecolumnsbelongingtoarawcontact,youcanlookattheclassdocumentationforContactsContract.RawContacts.
Thereareadvantagesanddisadvantagestothisapproach.AsignificantadvantageisthatyougettoknowthefieldspublishedandacknowledgedbytheAndroidSDK.Thedatabasecolumnsmaygetaddedordroppedwithoutchangingthepublicinterface.Soifyouusethedatabasecolumnsdirectly,theymayormaynotbethere.Instead,ifyouusethepublicdefinitionsforthesecolumns,youaresafebetweenreleases.
Onedisadvantage,however,isthattheclassdocumentationhasmanyotherconstantsinterspersedwithcolumnnames;wekindofgotlostinfiguringoutwhatwaswhat.ThesenumerousclassdefinitionsgivetheimpressionthattheAPIiscomplexwhen,inreality,80%oftheclassdocumentationfortheContactsAPIistodefineconstantsforthesecolumnsandtheURIstoaccesstheserows.
WhenweexercisetheContactsAPIinlatersections,wewillusetheclass-documentation-basedconstantsinsteadofdirectcolumnnames.However,wefeltthedirectexplorationofthetableswasthequickestwaytohelpyouunderstandtheContactsAPI.
Let’stalknextabouthowthedatarelatingtoacontact(suchase-mailandphonenumber)isstored.
UnderstandingtheContactsDataTableAsseenfromtherawcontacttabledefinition,therawcontact(inananticlimacticsense)isjustanIDindicatingwhataccountitbelongsto.Datapertainingtothecontactisnotintherawcontacttablebutsavedinthedatatable.Eachdataelement,suchase-mailandphonenumber,isstoredasseparaterowsinthedatatabletiedbytherawcontactID.Thedatatable,whosedefinitionisshowninListing27-4,contains16genericcolumnsthatcanstoreanytypeofdataelementsuchasane-mail.
Listing27-4.ContactDataTableDefinition
CREATETABLEdata(_idINTEGERPRIMARYKEYAUTOINCREMENT,package_idINTEGERREFERENCESpackage(_id),mimetype_idINTEGERREFERENCESmimetype(_id)NOTNULL,raw_contact_idINTEGERREFERENCESraw_contacts(_id)NOTNULL,is_primaryINTEGERNOTNULLDEFAULT0,is_super_primaryINTEGERNOTNULLDEFAULT0,data_versionINTEGERNOTNULLDEFAULT0,data1TEXT,data2TEXT,data3TEXT,data4TEXT,data5TEXT,data6TEXT,data7TEXT,data8TEXT,data9TEXT,data10TEXT,data11TEXT,data12TEXT,data13TEXT,data14TEXT,data15TEXT,data_sync1TEXT,data_sync2TEXT,data_sync3TEXT,data_sync4TEXT)
Theraw_contact_idpointstotherawcontacttowhichthisdatarowbelongs.Themimetype_idpointstotheMIMEtypeentryindicatingoneofthetypesidentifiedinthecontactdatatypesinListing27-4.Thecolumnsdata1throughdata15aregenericstring-basedtablesthatcanstoreanythingthatisnecessarybasedontheMIMEtype.Thesyncfieldssupportcontactsyncing.ThetablethatresolvestheMIMEtypeIDsisinListing27-5.
Listing27-5.ContactsMIMETypeLookupTableDefinition
CREATETABLEmimetypes(_idINTEGERPRIMARYKEYAUTOINCREMENT,mimetypeTEXTNOTNULL)
Aswiththerawcontactstable,youcandiscoverthedatatablecolumnsthroughthehelperclassdocumentationforContactsContract.Data.Althoughyoucanfigureoutthecolumnsfromthisclassdefinition,youwillnotknowwhatisstoredineachofthegenericcolumnsfromdata1throughdata15.Toknowthis,youwillneedtoseetheclassdefinitionsforanumberofclassesunderthenamespaceContactsContract.CommonDataKinds.
Someexamplesoftheseclassesfollow:
ContactsContract.CommonDataKinds.Email
ContactsContract.CommonDataKinds.Phone
Infact,youwillseeoneclassforeachofthepredefinedMIMEtypes.Theseclassesareasfollows:Email,Event,GroupMembership,Identity,Im,Nickname,Note,Organization,Phone,Photo,Relation,SipAddress,StructuredName,StructuredPostal,Website.Ultimately,alltheCommonDataKindsclassesdoisindicatewhichgenericdatafields(data1throughdata15)areinuseandwhatfor.
UnderstandingAggregatedContactsUltimately,acontactanditsrelateddataareunambiguouslystoredintherawcontactstableandthedatatable.Anaggregatedcontact,ontheotherhand,isheuristicandcouldbeambiguous.
Whenthereisacontactthatisthesamebetweenmultipleaccounts,youmaywanttoseeonenameinsteadofseeingthesameorsimilarnamerepeatedonceforeveryaccount.Androidaddressesthisbyaggregatingcontactsintoaread-onlyview.Androidstorestheseaggregatedcontactsinatablecalledcontacts.Androidusesanumberoftriggersontherawcontacttableandthedatatabletopopulateorchangethisaggregatedcontacttable.
Beforegoingintoexplainingthelogicbehindaggregation,letusshowyouthecontacttabledefinition(seeListing27-6).
Listing27-6.AggregatedContactTableDefinition
CREATETABLEcontacts(_idINTEGERPRIMARYKEYAUTOINCREMENT,name_raw_contact_idINTEGERREFERENCESraw_contacts(_id),photo_idINTEGERREFERENCESdata(_id),custom_ringtoneTEXT,send_to_voicemailINTEGERNOTNULLDEFAULT0,times_contactedINTEGERNOTNULLDEFAULT0,last_time_contactedINTEGER,starredINTEGERNOTNULLDEFAULT0,in_visible_groupINTEGERNOTNULLDEFAULT1,has_phone_numberINTEGERNOTNULLDEFAULT0,lookupTEXT,status_update_idINTEGERREFERENCESdata(_id),single_is_restrictedINTEGERNOTNULLDEFAULT0)
Noclientdirectlyupdatesthistable.Whenarawcontactisaddedwithitsdetail,Androidsearchesotherrawcontactstoseeiftherearesimilarrawcontacts.Ifthereisone,itwillusetheaggregatedcontactIDofthatrawcontactastheaggregatedcontactIDofthenewrawcontactaswell.Noentryismadeintotheaggregatedcontacttable.Ifnoneisfound,itwillcreateanaggregatedcontactandusethataggregatedcontactasthecontactIDforthatrawcontact.
Androidusesthefollowingalgorithmtodeterminewhichrawcontactsaresimilar:
1. Thetworawcontactshavematchingnames,bothfirstandlast.
2. Thewordsinthenamearethesamebutvaryinorder:“firstlast”or“first,last”or“last,first.”
3. Theshorterversionsofthenamesmatch,suchas“Bob”for“Robert.”
4. Ifoneoftherawcontactshasjustafirstorlastname,thiswilltriggerasearchforotherattributes,suchasphonenumberore-mail,andiftheotherattributesmatch,thecontactwillbeaggregated.
5. Ifoneoftherawcontactsismissingthenamealtogether,thiswillalsotriggerasearchforotherattributesasinstep4.
Becausetheserulesareheuristic,somecontactsmaybeaggregatedunintentionally.Theclientapplicationsneedtoprovideamechanismtoseparatethecontactsinsuchacase.IfyourefertotheAndroidUser’sGuide,youwillseethatthedefaultcontactsapplicationallowsyoutoseparatecontactsthatareunintentionallymerged.
Youcanalsopreventtheaggregationbysettingtheaggregationmodewhenyouinserttherawcontact.TheaggregationmodesareshowninListing27-7.
Listing27-7.AggregationModeConstants
AGGREGATION_MODE_DEFAULT
AGGREGATION_MODE_DISABLEDAGGREGATION_MODE_SUSPENDED
Thefirstoptionisobvious;itishowaggregationworks.
Thesecondoption(disabled)keepsthisrawcontactoutofaggregation.Evenifitisaggregatedalready,AndroidwillpullitoutofaggregationandallocateanewaggregatedcontactIDdedicatedtothisrawcontact.
Thethirdoption(suspended)indicatesthateventhoughthepropertiesofthecontactmaychange,whichwillmakeitinvalidfortheaggregationintothatbatchofcontacts,itshouldbekepttiedtothataggregatedcontact.
Thelastpointbringsoutthevolatiledimensionoftheaggregatedcontact.Sayyouhaveauniquerawcontactwithafirstnameandalastname.Rightnow,itdoesn’tmatchanyotherrawcontact,sothisuniquerawcontactgetsitsownallocationofanaggregatedcontact.TheaggregatedcontactIDwillbestoredintherawcontacttableagainstthatrawcontactrow.
However,yougoandchangethelastnameofthisrawcontact,whichmakesitamatchtoanothersetofcontactsthatareaggregated.Inthatcase,Androidwillremovetherawcontactfromthisaggregatedcontactandmoveittotheotherone,abandoningthissingleaggregatedcontactbyitself.Inthiscase,theIDoftheaggregatedcontactbecomesentirelyabandoned,asitwillnotmatchanythinginthefuturebecauseitisjustanIDwithoutanunderlyingrawcontact.
Soanaggregatedcontactisvolatile.ThereisnotasignificantvaluetoholdontothisaggregatedcontactIDovertime.
Androidofferssomerespitefromthispredicamentbyprovidingafieldcalledlookupintheaggregatedcontactstables.Thislookupfieldisanaggregation(concatenation)oftheaccountandtheuniqueIDofthisrawcontactinthataccountforeachrawcontact.ThisinformationisfurthercodifiedsothatitcanbepassedasaURLparametertoretrievethelatestaggregatedcontactID.AndroidlooksatthelookupkeyandseeswhichunderlyingrawcontactIDsarethereforthislookupkey.Itthenusesabest-fitalgorithmtoreturnasuitable(orperhapsnew)aggregatedcontactID.
Whileweareexplicitlyexaminingthecontactsdatabase,let’sconsideracoupleofcontact-relateddatabaseviewsthatareuseful.
Exploringview_contactsThefirstoftheseviewsistheview_contacts.Althoughthereisatablethatholdstheaggregatedcontacts(contactstable),theAPIdoesn’texposethecontactstabledirectly.Instead,itusesview_contactsasthetargetforreadingtheaggregatedcontacts.WhenyouquerybasedontheURIContactsContract.Contacts.CONTENT_URI,thecolumnsreturnedarebasedonthisviewview_contacts.Thedefinitionofview_contactsviewisshowninListing27-8.
Listing27-8.AViewtoReadAggregatedContacts
CREATEVIEWview_contactsAS
SELECTcontacts._idAS_id,contacts.custom_ringtoneAScustom_ringtone,name_raw_contact.display_name_sourceASdisplay_name_source,name_raw_contact.display_nameASdisplay_name,name_raw_contact.display_name_altASdisplay_name_alt,name_raw_contact.phonetic_nameASphonetic_name,name_raw_contact.phonetic_name_styleASphonetic_name_style,name_raw_contact.sort_keyASsort_key,name_raw_contact.sort_key_altASsort_key_alt,name_raw_contact.contact_in_visible_groupASin_visible_group,has_phone_number,lookup,photo_id,contacts.last_time_contactedASlast_time_contacted,contacts.send_to_voicemailASsend_to_voicemail,contacts.starredASstarred,contacts.times_contactedAStimes_contacted,status_update_id
FROMcontactsJOINraw_contactsASname_raw_contactON(name_raw_contact_id=name_raw_contact._id)
Noticethattheview_contactsviewcombinesthecontactstablewiththerawcontacttablebasedontheaggregatedcontactID.
Exploringcontact_entities_viewAnotherusefulviewisthecontact_entities_viewthatcombinestherawcontactstablewiththedatatable.Thisviewallowsustoretrieveallthedataelementsofagivenrawcontactonetime,oreventhedataelementsofmultiplerawcontactsbelongingtothesameaggregatedcontact.Listing27-9presentsthedefinitionofthisviewbasedoncontactentities.
Listing27-9.ContactEntitiesView
CREATEVIEWcontact_entities_viewAS
SELECTraw_contacts.account_nameASaccount_name,raw_contacts.account_typeASaccount_type,raw_contacts.sourceidASsourceid,raw_contacts.versionASversion,
raw_contacts.dirtyASdirty,raw_contacts.deletedASdeleted,raw_contacts.name_verifiedASname_verified,packageASres_package,contact_id,raw_contacts.sync1ASsync1,raw_contacts.sync2ASsync2,raw_contacts.sync3ASsync3,raw_contacts.sync4ASsync4,mimetype,data1,data2,data3,data4,data5,data6,data7,data8,data9,data10,data11,data12,data13,data14,data15,data_sync1,data_sync2,data_sync3,data_sync4,
raw_contacts._idAS_id,
is_primary,is_super_primary,data_version,data._idASdata_id,raw_contacts.starredASstarred,raw_contacts.is_restrictedASis_restricted,groups.sourceidASgroup_sourceid
FROMraw_contactsLEFTOUTERJOINdataON(data.raw_contact_id=raw_contacts._id)LEFTOUTERJOINpackagesON(data.package_id=packages._id)LEFTOUTERJOINmimetypesON(data.mimetype_id=mimetypes._id)LEFTOUTERJOINgroupsON(mimetypes.mimetype='vnd.android.cursor.item/group_membership'ANDgroups._id=data.data1)
TheURIsneededtoaccessthisviewareavailableintheclassContactsContract.RawContactsEntity.
WorkingwiththeContactsAPISofar,wehaveexploredthebasicideabehindtheContactsAPIbyexploringitstablesandviews.Wewillnowpresentanumberofcodesnippetsthatcanbeusedtoexplorecontacts.Thesesnippetsaretakenfromthesampleapplicationthatisdevelopedtosupportthischapter.Althoughthesnippetsaretakenfromthesampleapplication,theyaresufficienttoaidtheunderstandingofhowtheContactsAPIwork.YoucandownloadthefullsampleprogramusingtheprojectdownloadURLattheendofthischapter.
ExploringAccountsWewillstartourexercisebywritingaprogramthatcanprintoutthelistofaccounts.Wehavealreadygiventhecodesnippetsnecessarytogetalistofaccounts.ConsidertheclassAccountsFunctionTesterinListing27-10.
Listing27-10.AccountsFunctionTesterThatPrintsAvailableAccounts
//Javaclass:AccountsFunctionTester.java//Menutoinvokethis:Accounts//BaseTesterisasupportingbaseclassholdingtheparentactivity//andsomereusedcommonvariables.Seethesourcecodeifyouaremorecurious.publicclassAccountsFunctionTesterextendsBaseTester{privatestaticStringtag="tc>";
//IReportBackisasimplelogginginterfacethatwriteslogmessages//tothemainactivityandalsotothelog.publicAccountsFunctionTester(Contextctx,IReportBacktarget){super(ctx,target);}publicvoidtestAccounts(){AccountManageram=AccountManager.get(this.mContext);Account[]accounts=am.getAccounts();for(Accountac:accounts){Stringacname=ac.name;Stringactype=ac.type;this.mReportTo.reportBack(tag,acname+":"+actype);}}}
NoteAswepresentandexploretheJavacodenecessarytoworkwithcontacts,youwillseethreevariablesrepeatedlyusedinthepresentedsourcecode:
mContext:Avariablepointingtoanactivity
mReportTo:Avariableimplementingalogginginterface(IReportBack—youcanseethisJavafileinthedownloadableproject)thatcanbeusedtologmessagestothetestactivitythatisusedforthischapter
Utils:Astaticclassthatencapsulatesverysimpleutilitymethods
WehavechosennottolisttheseclassesherebecausetheywilldistractyoufromunderstandingthecorefunctionalityoftheContactsAPI.Youcanexaminetheseclassesinthedownloadableproject.
Allthecodeinthischapterusesanunmanagedqueryagainstthecontentprovider.ThisisdonebycallingActivity.getContentResolver().query().Thisisbecausewemerelyreadthedataandprintouttheresultsrightaway.IfyourgoalinsteadistouseUI(throughactivitiesorfragments)asatargettodisplayyourcontactsthenreadChapter27onloaders.Loadersshowtherightwaytodisplaycursorsfromanycontentprovider.
Whenyourunthesampleprogramthatyoucandownloadforthischapter,youwillseeamainactivitythatappearswithanumberofmenuoptions.Themenuoption“Accounts”willprintthelistofaccountsavailableonthedevice.
ExploringAggregatedContactsLet’sseehowwecanexploreaggregatedcontactsthroughcodesnippets.Toreadcontacts,youneedtorequestthefollowingpermissioninthemanifestfile:
android.permission.READ_CONTACTS
Asthefunctionalitywearetestingdealswithcontentproviders,URIs,andcursors,let’slookatsomeusefulcodesnippetspresentedinListing27-11.(Thesecodesnippetsareavailableeitherinutils.javaorinsomeofthebaseclassesderivedfromBaseTesterinthechapter’sdownloadableproject.)
Listing27-11.GettingaCursorGivenaURIandawhereClause
//Utils.java//RetrieveacolumnfromacursorpublicstaticStringgetColumnValue(Cursorcc,Stringcname){inti=cc.getColumnIndex(cname);returncc.getString(i);}//SeewhatcolumnsarethereinacursorprotectedstaticStringgetCursorColumnNames(Cursorc){intcount=c.getColumnCount();StringBuffercnamesBuffer=newStringBuffer();for(inti=0;i<count;i++){Stringcname=c.getColumnName(i);cnamesBuffer.append(cname).append(';');}returncnamesBuffer.toString();}
//FromURIFunctionTester.java,baseclassofsomeoftheothertesters
//GivenaURIandawhereclausereturnacursorprotectedCursorgetACursor(Uriuri,Stringclause){Activitya=(Activity)this.mContext;//mContextcoming
fromBaseTesterreturna.getContentResolver().query(uri,null,clause,null,null);}
Inthissection,weareprimarilyexploringthecursorreturnedbyaggregatedcontactURIs.Eachrowreturnedbytheresultingcontactcursorwillhaveanumberoffields.Forourexample,wearenotinterestedinallthefieldsbutonlyafew.YoucanabstractthisoutintoanotherclasscalledanAggregatedContact.Listing27-12showsthisclass.
Listing27-12.AnObjectDefinitionforaFewFieldsofanAggregatedContact
//AggregatedContact.javapublicclassAggregatedContact{publicStringid;publicStringlookupUri;publicStringlookupKey;publicStringdisplayName;publicvoidfillinFrom(Cursorc){id=Utils.getColumnValue(c,"_ID");lookupKey=Utils.getColumnValue(c,ContactsContract.Contacts.LOOKUP_KEY);lookupUri=ContactsContract.Contacts.CONTENT_LOOKUP_URI+"/"+lookupKey;displayName=Utils.getColumnValue(c,ContactsContract.Contacts.DISPLAY_NAME);}}
InListing27-12weusethecursortoloadupthefieldsthatweareinterestedin.
GettingtheAggregatedContactsCursorListing27-13showshowtoretrieveacursorthatisacollectionofaggregatedcontacts.
Listing27-13.GettingaCursorforAllAggregatedContacts
//Getacursorofallcontacts.Specifythewhereclauseasnulltoindicateallrows.//Javaclass:AggregatedContactFunctionTester.java//Menuitemtoinvoke:ContactsCursorprivateCursorgetContacts(){Uriuri=ContactsContract.Contacts.CONTENT_URI;//SpecifyascendingordescendingwaytosortnamesStringsortOrder=ContactsContract.Contacts.DISPLAY_NAME+"COLLATELOCALIZEDASC";Activitya=(Activity)this.mContext;//Localvariablepointingtoanactivity
returna.getContentResolver().query(uri,null,null,null,sortOrder);}
TheURIusedtoreadallthecontactsisContactsContract.Contacts.CONTENT_URI.YoucanpassthisURItothequery()functiontoretrieveacursor.Youcanpassnullasthecolumnprojectiontoreceiveallcolumns.Althoughthisisnotrecommendedinpractice,inourcase,itmakessensebecausewewanttoknowaboutallthecolumnsitreturns.Wehavealsousedthedisplaynameofthecontactasthesortorder.Noticehow,again,wehaveusedContactContract.Contactstogetthecolumnnameforthecontactdisplayname.IfyouweretoprintthefieldnamesfromthiscursoryouwillseethereturnedfieldsasthoseshowninListing27-14.Dependingonthereleasetheordermaybedifferentandmorecolumnsmaybeadded.Itisagoodpracticetoexplicitlyspecifyaprojectiontothequeryclause;thatwayyourcodewillworkacrossreleases.
Listing27-14.AggregatedContactsContentURICursorColumns
times_contacted;contact_status;custom_ringtone;has_phone_number;phonetic_name;phonetic_name_style;contact_status_label;lookup;contact_status_icon;last_time_contacted;display_name;sort_key_alt;in_visible_group;_id;starred;sort_key;display_name_alt;contact_presence;display_name_source;contact_status_res_package;contact_status_ts;photo_id;send_to_voicemail;
ReadingAggregatedContactDetailsNowthatwe’veexploredthecolumnsavailablewiththecontactscontentURI,let’spickafewcolumnsandseewhatcontactrowsareavailable.Weareinterestedinthefollowingcolumnsfromacontactcursor:displayname,lookupkey,andlookupURI.WeareconsideringthesefieldsbecausewewanttoseewhatthelookupkeyandlookupkeyURIlooklikebasedonwhatiscoveredinthetheorypartofthischapter.Specifically,weareinterestedinfiringoffthelookupURItoseewhattypeofacursoritreturns.
ThefunctionlistContacts()inListing27-15getsacontactscursorandprintsthesethreecolumnsforeachrowofthecursor.NotethatthislistingistakenfromaclassthatholdsalocalvariablecalledmContexttoindicatetheactivityandalocalvariablecalledmReportTotobeabletologanymessagestotheactivity.
Listing27-15.PrintingtheLookupKeysforanAggregatedContact
//Javaclass:AggregatedContactFunctionTester.java//Menuitemtoinvoke:ContactspublicvoidlistContacts(){
Cursorc=null;try{
c=getContacts();inti=c.getColumnCount();this.mReportTo.reportBack(tag,"Numberofcolumns:"+i);this.printLookupKeys(c);}finally{if(c!=null)c.close();}}privatevoidprintLookupKeys(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){Stringname=this.getContactName(c);StringlookupKey=this.getLookupKey(c);Stringluri=this.getLookupUri(lookupKey);this.mReportTo.reportBack(tag,name+":"+lookupKey);//logthis.mReportTo.reportBack(tag,name+":"+luri);//log}}privateStringgetLookupKey(Cursorcc){intlookupkeyIndex=cc.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);returncc.getString(lookupkeyIndex);}privateStringgetContactName(Cursorcc){returnUtils.getColumnValue(cc,ContactsContract.Contacts.DISPLAY_NAME);}privateStringgetLookupUri(Stringlookupkey){Stringluri=ContactsContract.Contacts.CONTENT_LOOKUP_URI+"/"+lookupkey;returnluri;}
ExploringtheLookupURI-BasedCursorNowthatweknowhowtoextractlookupURIsforagivenaggregatedcontact,let’sseewhatwecandowithalookupURI.
ThefunctionlistLookupUriColumns()inListing27-16willtakethefirstcontactfromthelistofallcontactsandthenformulatealookupURIforthatcontactandfireofftheURItoseewhatkindofacursoritreturnsbyprintingthecolumnnamesfromthatcursor.
Listing27-16.ExploringtheLookupURICursor
//Class:AggregatedContactFunctionTester.java,Menuitemto
invoke:SingleContactCursorpublicvoidlistLookupUriColumns(){
Cursorc=null;try{c=getContacts();StringfirstContactLookupUri=getFirstLookupUri(c);printLookupUriColumns(firstContactLookupUri);}finally{if(c!=null)c.close();}}privateStringgetFirstLookupUri(Cursorc){
c.moveToFirst();if(c.isAfterLast()){Log.d(tag,"Norowstogetthefirstcontact");returnnull;}StringlookupKey=this.getLookupKey(c);returnthis.getLookupUri(lookupKey);}publicvoidprintLookupUriColumns(Stringlookupuri){
Cursorc=null;try{c=getASingleContact(lookupuri);inti=c.getColumnCount();this.mReportTo.reportBack(tag,"Numberofcolumns:"+i);intj=c.getCount();this.mReportTo.reportBack(tag,"Numberofrows:"+j);this.printCursorColumnNames(c);}finally{if(c!=null)c.close();}}//Usethelookupuri,retrieveasingleaggregatedcontactprivateCursorgetASingleContact(StringlookupUri){
Activitya=(Activity)this.mContext;returna.getContentResolver().query(Uri.parse(lookupUri),null,null,null,null);}
Asitturnsout,itjustreturnsacursor(asinListing27-14)thatisidenticalincolumnsforthatoftheaggregatedcontactcursorasinListing27-13,exceptthatithasonlyonerowpointingtothecontactforwhichthisisthelookupkey.AlsonoticethatwehaveusedthefollowinglookupURIdefinition:
ContactsContract.Contacts.CONTENT_LOOKUP_URI
YouknowfromthediscussionofthecontactlookupURIsthateachlookupURIrepresentsacollectionofrawcontactidentitiesthathavebeenconcatenated.Thatbeing
thecase,youmighthaveexpectedthelookupURItoreturnaseriesofmatchingrawcontacts.However,thetestinListing27-16isshowingthatitisnotreturningacursorofrawcontactsbutinsteadacursorofcontacts.
NoteAlookupbasedonthecontactlookupURIreturnsanaggregatedcontactandnotarawcontact.
AnothertidbitisthatthelookupprocessfortheaggregatedcontactbasedonthelookupURIisnotlinearorexact.ThismeansAndroidwillnotlookforanexactmatchofthelookupkey.Instead,AndroidparsesthelookupkeyintoitsconstituentrawcontactsandthenfindstheaggregatedcontactIDthatmatchesthemostoftherawcontactrecordsandreturnsthataggregatedcontactrecord.
Oneconsequenceofthisisthatnopublicmechanismisavailabletogofromthelookupkeytoitsconstituentrawcontacts.Instead,youhavetofindthecontactIDforthatlookupkeyandthenfireoffarawcontactURIforthatcontactIDtoretrievethecorrespondingrawcontacts.
Hereisanothercodesnippetthatshowswhatisreturnedfromacursorasanobjectinsteadofasasetofcolumns.ThecodeinListing27-17returnsthefirstaggregatedcontactasanobject.
Listing27-17.CodeTestingAggregatedContacts
//Javaclass:AggregatedContactFunctionTester.javaprotectedAggregatedContactgetFirstContact(){
Cursorc=null;try{c=getContacts();c.moveToFirst();if(c.isAfterLast()){Log.d(tag,"Nocontacts");returnnull;}AggregatedContactfirstcontact=newAggregatedContact();firstcontact.fillinFrom(c);returnfirstcontact;}finally{if(c!=null)c.close();}}
ExploringRawContactsInListing27-18,thefileRawContact.java,capturesafewimportantfieldsfromtherawcontactstablecursor.(Thisfile,likeallothercodesnippetsinthischapter,isavailableinthedownloadableprojectforthischapter.)
Listing27-18.SourcecodeforRawContact.java
//Class:RawContact.javapublicclassRawContact{
publicStringrawContactId;publicStringaggregatedContactId;publicStringaccountName;publicStringaccountType;publicStringdisplayName;
publicvoidfillinFrom(Cursorc){rawContactId=Utils.getColumnValue(c,"_ID");accountName=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_NAME);accountType=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_TYPE);aggregatedContactId=Utils.getColumnValue(c,ContactsContract.RawContacts.CONTACT_ID);displayName=Utils.getColumnValue(c,"display_name");}publicStringtoString(){//..printsthepublicfields.Seethedownloadprojectfordetails}}//eof-class
ShowingtheRawContactsCursorAswiththeaggregatedcontactURIs,let’sfirstexaminethenatureoftherawcontactURIandwhatitreturns.ThesignaturefortherawcontactURIisdefinedasfollows:
ContactsContract.RawContacts.CONTENT_URI
ThefunctionshowRawContactsCursor()inListing27-19printsthecursorcolumnsforarawcontactsURI.
Listing27-19.ExploringtheRawContactsCursor
//Javaclass:RawContactFunctionTester.java;Menuitem:RawContactsCursorpublicvoidshowRawContactsCursor(){
Cursorc=null;try{c=this.getACursor(ContactsContract.RawContacts.CONTENT_URI,null);this.printCursorColumnNames(c);}finally{if(c!=null)c.close();}}
CodeinListing27-19willshowthattherawcontactcursorhasthefieldsshowninListing27-20(thislistseemtovarysomewhatwitheachdevice).
Listing27-20.RawContactsCursorFields
times_contacted;phonetic_name;phonetic_name_style;contact_id;version;last_time_contacted;aggregation_mode;_id;name_verified;display_name_source;dirty;send_to_voicemail;account_type;custom_ringtone;sync4;sync3;sync2;sync1;deleted;account_name;display_name;sort_key_alt;starred;sort_key;display_name_alt;sourceid;
SeeingtheDataReturnedbyaRawContactsCursorListing27-21showsthemethodshowAllRawContacts(),whichprintsalltherowsintherawcontactscursor.
Listing27-21.DisplayingRawContacts
//Javaclass:RawContactFunctionTester.java;Menuitem:AllRawContactspublicvoidshowAllRawContacts(){
Cursorc=null;try{c=this.getACursor(getRawContactsUri(),null);this.printRawContacts(c);}finally{if(c!=null)c.close();}}privatevoidprintRawContacts(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){RawContactrc=newRawContact();rc.fillinFrom(c);this.mReportTo.reportBack(tag,rc.toString());//log}}
ConstrainingRawContactswithaCorrespondingSetofAggregatedContactsUsingthecolumnsofthecursorinListing27-20,let’sseeifwecanrefineourquerytoretrievethecontactsforagivenaggregatedcontactID.ThecodeinListing27-22willlookupthefirstaggregatedcontactandthenissuearawcontactURIwithawhereclausespecifyingavalueforthecontact_idcolumn.
Listing27-22.GettingRawContactsforanAggregatedContact
//Javaclass:RawContactFunctionTester.java;Menuitem:RawContactspublicvoidshowRawContactsForFirstAggregatedContact(){
AggregatedContactac=getFirstContact();Cursorc=null;try{c=this.getACursor(getRawContactsUri(),getClause(ac.id));this.printRawContacts(c);}finally{if(c!=null)c.close();}}privateStringgetClause(StringcontactId){return"contact_id="+contactId;}
ExploringRawContactDataBecauseadatarowbelongingtoarawcontactcontainsanumberoffields,wehavecreatedaJavaclasscalledContactData.java,showninListing27-23,tocapturearepresentativesetofthecontactdata,andnotallfields.
Listing27-23.SourcecodeforContactData.java
//ContactData.javapublicclassContactData{publicStringrawContactId;publicStringaggregatedContactId;publicStringdataId;publicStringaccountName;publicStringaccountType;publicStringmimetype;publicStringdata1;
publicvoidfillinFrom(Cursorc){rawContactId=Utils.getColumnValue(c,"_ID");accountName=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_NAME);accountType=Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_TYPE);aggregatedContactId=
Utils.getColumnValue(c,ContactsContract.RawContacts.CONTACT_ID);mimetype=Utils.getColumnValue(c,ContactsContract.RawContactsEntity.MIMETYPE);data1=Utils.getColumnValue(c,ContactsContract.RawContactsEntity.DATA1);dataId=Utils.getColumnValue(c,ContactsContract.RawContactsEntity.DATA_ID);}
publicStringtoString(){//justaconcatenationoffieldsforlogging}}
AndroidusesaviewcalledaRawContactEntityviewtoretrievedatafromarawcontacttableandthecorrespondingdatatablesasindicatedinthesection“contact_entities_view”inthischapter.TheURItoaccessthisviewisinListing27-24.
Listing27-24.RawEntitiesContentURI
ContactsContract.RawContactsEntity.CONTENT_URI
Let’sseehowthisURIcanbeusedtodiscoverfieldnamesreturnedbythisURI:
//Javaclass:ContactDataFunctionTester.java;Menuitem:ContactEntityCursorpublicvoidshowRawContactsEntityCursor(){Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.CONTENT_URI;c=this.getACursor(uri,null);this.printCursorColumnNames(c);}finally{if(c!=null)c.close();}}
ThecodeinListing27-24printsoutthelistofcolumnsshowninListing27-25.SothecolumnsinListing27-25arethecolumnsthatarereturnedbytherawcontactsentitycursor.Theremaybeadditionalcolumnsdependingonvendor-specificimplementations.
Listing27-25.ContactEntitiesCursorColumns
data_version;contact_id;version;data12;data11;data10;mimetype;res_package;_id;data15;data14;data13;name_verified;is_restricted;is_super_primary;data_sync1;dirty;data_sync3;data_sync2;data_sync4;account_type;data1;sync4;sync3;data4;sync2;data5;sync1;data2;data3;data8;data9;deleted;group_sourceid;data6;data7;account_name;data_id;starred;sourceid;is_primary;
Onceyouknowthissetofcolumns,youcanfiltertheresultsetofthiscursorbyformulatingaproperwhereclause.However,youwanttousetheContactsContractJavaclasstousethedefinitionsforthesecolumnnames.Forexample,inListing27-26weretrievethedataelementspertainingtocontactIDs3,4,and5.
Listing27-26.DisplayingDataElementsfromRawContactsEntity
//Javaclass:ContactDataFunctionTester.java;Menuitem:ContactDatapublicvoidshowRawContactsData(){Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.CONTENT_URI;c=this.getACursor(uri,"contact_idin(3,4,5)");this.printRawContactsData(c);}finally{if(c!=null)c.close();}}protectedvoidprintRawContactsData(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){ContactDatadataRecord=newContactData();dataRecord.fillinFrom(c);this.mReportTo.reportBack(tag,dataRecord.toString());}}
CodeinListing27-26willprintsuchthingsasname,e-mailaddress,andMIMEtypeasdefinedintheContactDataobjectinListing27-23.
AddingaContactwithItsDetailsLet’slookatacodesnippettoaddacontactwithname,e-mail,andphonenumber.Towritetocontacts,youneedthefollowingpermissioninthemanifestfile:
android.permission.WRITE_CONTACTS
CodeinListing27-27addsarawcontactfollowedbyaddingtwodatarows(nameandphonenumber)forthatcontact.
Listing27-27.AddingaContact
//Javaclass:AddContactFunctionTester.java;Menuitem:AddContactpublicvoidaddContact(){
longrawContactId=insertRawContact();this.mReportTo.reportBack(tag,"RawcontactId:"+rawContactId);insertName(rawContactId);insertPhoneNumber(rawContactId);showRawContactsDataForRawContact(rawContactId);}privatelonginsertRawContact(){ContentValuescv=newContentValues();cv.put(RawContacts.ACCOUNT_TYPE,"com.google");
cv.put(RawContacts.ACCOUNT_NAME,"--useyourgmailid—");UrirawContactUri=this.mContext.getContentResolver().insert(RawContacts.CONTENT_URI,cv);longrawContactId=ContentUris.parseId(rawContactUri);returnrawContactId;}privatevoidinsertName(longrawContactId){ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);cv.put(Data.MIMETYPE,StructuredName.CONTENT_ITEM_TYPE);cv.put(StructuredName.DISPLAY_NAME,"JohnDoe_"+rawContactId);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatevoidinsertPhoneNumber(longrawContactId){ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);cv.put(Data.MIMETYPE,Phone.CONTENT_ITEM_TYPE);cv.put(Phone.NUMBER,"123123"+rawContactId);cv.put(Phone.TYPE,Phone.TYPE_HOME);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatevoidshowRawContactsDataForRawContact(longrawContactId){Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.CONTENT_URI;c=this.getACursor(uri,"_id="+rawContactId);this.printRawContactsData(c);}finally{if(c!=null)c.close();}}
CodeinListing27-27doesthefollowing:
1. Addsanewrawcontactforapredefinedaccountusingtheaccount’snameandtype,representedbythemethodinsertRawContact().NoticehowitusestheURIRawContact.CONTENT_URI.
2. TakestherawcontactIDfromstep1andinsertsanamerecordusingtheinsertName()methodinthedatatable.NoticehowitusestheURIData.CONTENT_URI.
3. TakestherawcontactIDfromstep1andinsertsaphonenumberrecordusingtheinsertPhoneNumber()methodinthedatatable.Beingadatarow,itusesData.CONTENT_URIastheURI.
Listing27-27alsodemonstratesthecolumnaliasesusedininsertingrecords.NoticehowconstantslikePhone.TYPEandPhone.NUMBERpointtothegenericdatatablecolumnnamesdata1anddata2.
ControllingAggregationofContactsClientsthatupdateorinsertcontactsdonotexplicitlychangethecontactstable.Thecontactstableisupdatedbytriggersthatlookintotherawcontacttableandrawcontactdatatable.
Rawcontactsthatgetaddedorchanged,inturn,affecttheaggregatedcontactsinthecontactstable.However,youmaynotwanttoallowtwocontactstobeaggregated.
Youcancontroltheaggregationbehaviorofarawcontactbysettingtheaggregationmodewhenthatcontractiscreated.AsyoucanseefromtherawcontacttablecolumnsinListing27-20,therawcontacttablecontainsafieldcalledaggregation_mode.ValuesfortheaggregationmodeareshowninListing27-7andexplainedinthesection“AggregatedContacts.”
Youcanalsokeeptwocontactsalwaysapartbyinsertingrowsintoatablecalledagg_exceptions.TheURIsneededtoinsertintothistablearedefinedintheJavaclassContactsContract.AggregationExceptions.Thetablestructureofagg_exceptionsisshowninListing27-28.
Listing27-28.AggregateExceptionsTableDefinition
CREATETABLEagg_exceptions(_idINTEGERPRIMARYKEYAUTOINCREMENT,typeINTEGERNOTNULL,raw_contact_id1INTEGERREFERENCESraw_contacts(_id),raw_contact_id2INTEGERREFERENCESraw_contacts(_id))
ThetypecolumninListing27-28holdsoneoftheintegerconstantsinListing27-29.
Listing27-29.AggregationTypesintheAggregationExceptionTable
ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHERContactsContract.AggregationExceptions.TYPE_KEEP_SEPARATEContactsContract.AggregationExceptions.TYPE_AUTOMATIC
TYPE_KEEP_TOGETHERsaysthetworawcontactsshouldneverbebrokenapart.TYPE_KEEP_SEPARATEsaysthattheserawcontactsshouldneverbejoined.TYPE_AUTOMATICsaystousethedefaultalgorithmtoaggregatecontacts.
TheURIyouwillusetoinsert,read,andupdatethistableisdefinedas
ContactsContract.AggregationExceptions.CONTENT_URI
ConstantsforfielddefinitionstoworkwiththistablearealsoavailableintheJavaclassContactsContract.AggregationExceptions.
UnderstandingPersonalProfileApersonalprofile,introducedinAPI14,islikeacontact,exceptthereisonlyonepersonalprofilecontact.Thatisthesingularyou,onyourdevice.
However,asanimplementationdetail,alldatapertainingtothesingularpersonalprofilecontactismaintainedinaseparatedatabasecalledprofile.db.Ourresearchshowsthatthisdatabasehasastructureidenticaltocontacts2.db.Thismeansyoualreadyknowwhatrelevanttablesareavailableandwhatthecolumnsofeachtableare.
Beingasinglecontact,theaggregationisstraightforward.Everyrawcontactthatisaddedtothepersonalprofileisexpectedtobelongtothesingularaggregatedcontact.Ifonedoesn’texist,thenanewaggregatedcontactiscreatedandplacedinthenewrawcontact.Ifoneexists,thatcontactIDisusedastheaggregatedcontactIDfortherawcontact.
TheAndroidSDKusesthesamebaseclassContactsContracttodefinethenecessaryURIstoread/update/delete/addrawcontactstothepersonalprofile.TheseURIsparalleltheircounterpartsbutwiththestring“PROFILE”somewhereinthem.Listing27-30showsafewoftheseURIs.
Listing27-30.Profile-BasedURIsIntroducedin4.0
//RelatestoprofileaggregatedcontactContactsContract.Profile.CONTENT_URI
//RelatestoprofilebasedrawcontactContactsContract.Profile.CONTENT_RAW_CONTACTS_URI
//Relatestoprofilebasedrawcontact+profilebaseddatatableContactsContract.RawContactsEntity.PROFILE_CONTENT_URI
Listing27-30showswehaveseparateURIswhendealingwithaggregatedcontactandarawcontact.However,thereisn’tacorrespondingpersonalprofileURIfortheDatatable.ThesameDataURI,Data.CONTENT_URI,isapplicabletobothregularcontactdataandalsotheprofilecontactdata.
Alsonotethatthesamecontentproviderservestheneedsofboththepersonalprofileandregularcontacts.Internally,thiscontentproviderknowsbasedontherawcontactIDifthedataURIbelongstotheprofiledataortheregularcontactdata.
Let’slooknextatcodesnippetstoreadandaddcontactdatatothepersonalprofile.YouwillneedthepermissionsfromListing27-31toreadfromandwritetotheprofiledata.
Listing27-31.PermissionsReading/WritingProfileData
<uses-permissionandroid:name="android.permission.READ_PROFILE"/><uses-permissionandroid:name="android.permission.WRITE_PROFILE"/>
ReadingProfileRawContactsLet’susethefollowingURItoreadtherawcontactsthatbelongtothepersonalprofile:
ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI
Listing27-32showshowtoreadprofilerawcontactentries.
Listing27-32.ShowingAllProfileRawContacts
//Javaclass:ProfileRawContactFunctionTester.java;Menuitem:PRawContacts//InthedownloadthismethodisnamedshowAllRawContacts//Itisexpandedhereforclarity.publicvoidshowAllRawProfileContacts(){Cursorc=null;try{StringwhereClause=null;c=this.getACursor(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,whereClause);this.printRawContacts(c);}finally{if(c!=null)c.close();}}//InthedownloadthismethodisnamedprintRawContacts//Itisexpandedhereforclarity.privatevoidprintRawProfileContacts(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){RawContactrc=newRawContact();rc.fillinFrom(c);this.mReportTo.reportBack(tag,rc.toString());}}
Noticethatonceweretrievethecursor,thedataitcontainsmatchestheRawContactthatwedefinedearlierforaregularrawcontact.
ReadingProfileContactDataLet’susethefollowingURItoreadthevariousdataelements(suchase-mail,MIMEtype,andsoon)ofrawcontactsthatbelongtothepersonalprofile:
ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI
Noticehowweareusingasimilarviewasinthecaseofregularcontacts.TheRawContactEntityisajoinbetweenrawcontactsandthedatarowsbelongingtothatrawcontact.Wewillseeonerowforeachdataelementsuchasname,e-mail,MIMEtype,andsoon.
Listing27-33showsthecodesnippettoreadprofilerawcontactentries.
Listing27-33.ShowingDataElementsforProfileContacts
//Javaclass:ProfileContactDataFunctionTester.java;Menuitem:allprawcontactspublicvoidshowProfileRawContactsData(){
Cursorc=null;try{Uriuri=ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI;StringwhereClause=null;c=this.getACursor(uri,whereClause);this.printProfileRawContactsData(c);}finally{if(c!=null)c.close();}}protectedvoidprintProfileRawContactsData(Cursorc){for(c.moveToFirst();!c.isAfterLast();c.moveToNext()){ContactDatadataRecord=newContactData();dataRecord.fillinFrom(c);this.mReportTo.reportBack(tag,dataRecord.toString());}}
Noticethatonceweretrievethecursor,thedataitcontainsmatchestheContactDataobject(Listing27-23)thatwedefinedearlierforaregularrawcontactdataelement.
AddingDatatothePersonalProfileLet’susethefollowingURItoaddarawcontacttoapersonalprofile:
ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI
Wewillalsoaddafewdataelementssuchasaphonenumberandanicknametothatrawcontactsotheyappearinthedetailsofyourpersonalprofileonthedevice.Listing27-34showsthecodesnippet.
Listing27-34.AddingaProfileRawContact
//Javaclass:AddProfileContactFunctionTester.java;Menuitem:allprawcontacts
//Inthesourcecodeyouwon'tseetheword"profile"inthefollowingmethodnames//ItisaddedheretoaddclarityasthewholeclassisnotincludedpublicvoidaddProfileContact(){
longrawContactId=insertProfileRawContact();this.mReportTo.reportBack(tag,"RawcontactId:"+rawContactId);insertProfileNickName(rawContactId);insertProfilePhoneNumber(rawContactId);showProfileRawContactsDataForRawContact(rawContactId);}privatevoidinsertProfileNickName(longrawContactId){
ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);//cv.put(Data.IS_USER_PROFILE,"1");
cv.put(Data.MIMETYPE,CommonDataKinds.Nickname.CONTENT_ITEM_TYPE);cv.put(CommonDataKinds.Nickname.NAME,"PJohnNickname_"+rawContactId);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatevoidinsertProfilePhoneNumber(longrawContactId){
ContentValuescv=newContentValues();cv.put(Data.RAW_CONTACT_ID,rawContactId);cv.put(Data.MIMETYPE,Phone.CONTENT_ITEM_TYPE);cv.put(Phone.NUMBER,"P123123"+rawContactId);cv.put(Phone.TYPE,Phone.TYPE_HOME);this.mContext.getContentResolver().insert(Data.CONTENT_URI,cv);}privatelonginsertProfileRawContact(){
ContentValuescv=newContentValues();cv.put(RawContacts.ACCOUNT_TYPE,"com.google");cv.put(RawContacts.ACCOUNT_NAME,"--useyourgmailid--");UrirawContactUri=this.mContext.getContentResolver()
.insert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,cv);longrawContactId=ContentUris.parseId(rawContactUri);returnrawContactId;}privatevoidshowProfileRawContactsDataForRawContact(longrawContactId){
Cursorc=null;
try{Uriuri=ContactsContract.RawContactsEntity.PROFILE_CONTENT_URI;c=this.getACursor(uri,"_id="+rawContactId);this.printRawContactsData(c);}finally{if(c!=null)c.close();}}
ThecodeinListing27-34parallelsthecodeweusedtoaddaregularcontactanditsdetails(Listing27-27).Althoughwehaveusedaprofile-specificURItoaddarawcontact,wehaveusedthesameData.CONTENT_URItoaddtheindividualdataelements.
Notethefollowingcommented-outcodeinListing27-34:
//cv.put(Data.IS_USER_PROFILE,"1");
BecauseData.CONTENT_URIisnotspecifictotheprofile,howdoestheunderlyingcontentproviderknowwhethertoinsertthisdataintoaregularrawcontactorapersonalprofilerawcontact?WethoughtthatspecifyingacolumncalledIS_USER_PROFILEwouldhelpthecontentprovider.Apparentlynot.Thisnewcolumnisavailableprimarilyforreadpurposes.Yourinsertswillfailifyouspecifythisduringinserts.TheonlyconclusionthenisthatthecontentproviderisrelyingontherawcontactIDtoseewhetherthatrawcontactcamefromprofile.dborcontacts2.db.
RoleofSyncAdaptersSofar,wehavemainlytalkedaboutmanipulatingthecontactsonthedevice.However,accountsandtheircontactsonAndroidworkhandinhandwithserver-basedcontacts.Forexample,ifyouhavecreatedaGoogleaccountonyourAndroidphone,theGoogleaccountwillpullyourGmailcontactsandmakethemavailableonthedevice.TodothissyncingAndroidprovidesasynchronizationframeworkwhichdoesmostofthegroundworkaslongasyouwriteaconformingSyncadapter.Android’ssynchronizationframeworktakescareofnetworkavailability,optionalauthentication,andscheduling.
ImplementingasyncadapterinvolvesimplementingaservicebyextendingtheSDKclassAbstractThreadedSyncAdapteranddoingtheworkinthemethodonPerformSync().WorkinvolvedinthismethodistoloaddatafromserversandupdatethecontactsusingtheContactsAPIthatisdiscussedinthischapter.Then,async-adapterresourcefile(XML)needstobecreatedonthedevicethatwilldescribehowthisserviceistiedtotheaccountthatneedstobesynched.
Outsideofthisbasicunderstanding,duetospacelimitationswehavenotcoveredthesyncingAPIinthiseditionofthebook.AndroidSDKdocumentationhassomedocumentationandsamples.
Synchronizationofcontactshasimpactsondeletingcontactsonthedevice.Whenyou
deleteacontactusingtheaggregatedcontactURI,itwilldeleteallitscorrespondingrawcontactsandthedataelementsofeachofthoserawcontacts.However,Androidwillonlymarkthemasdeletedonthedeviceandexpectsthebackgroundsynctoactuallysyncwiththeserverandthendeletethecontactspermanentlyfromthedevice.Thiscascadingofdeletesalsohappensattherawcontactlevelwherethecorrespondingdataelementsofthatrawcontactaredeleted.
UsingBatchOperationstoOptimizeContentProviderUpdatesWhilecoveringcontentprovidersinChapter26weindicatedthatwewouldcoverthebatchoperationsinthischapter.
Reconsiderhowarawcontactanditsassociateddataelementsarecreatedearlierinthechapter.Noticethemultiplecommandsweneedtosendtothecontactsprovidertoinsertarawcontact.Firstwehavetoinsertrawcontact.ThenusethatIDtoinsertmultipledataelementsbelongingtothatrawcontact.Eachoftheseinsertsisaseparatecommandsenttothecontentproviderindependently.
Therearetwoissueswhenwesendthesemultiplecommandssequentially.Thefirstissueisthatthecontentproviderisnotawarethattheybelongtoasinglecommitunit.Thesecondissueisthatitwilltakelongertoupdatethecontentproviderdatabaseaseachtransactioniscommittedbyitself.
ThesetwoissuesareaddressedbythebatchupdateAPIavailableforanycontentproviderincludingthecontactprovider.
IdeaofBatchingContentProviderUpdatesInthebatchingapproach,eachcontentproviderupdateoperationisencapsulatedinanobjectcalled“ContentProviderOperation”alongwiththeURIandallthenecessarykey/valuepairstoperformthatoperation.Thenyougathertheseoperationsintoalistobject.Youthentellthecontentresolvertosendtheentirebatchorlistofcommandstothecontentprovideratthesametime.Becausethecontentproviderknowsthesecommandsareinabatch,itappliesthetransactionsappropriatelyeitherattheendorsooftenbasedonhints.
Ifanoperationindicatesthatatransactioncanbeappliedattheendofthatoperation,thentheoperationscompletedthusfarwillbecommitted.Thisallowsyoutosub-batchlongupdatesofmanyrowsintosmallersetofsub-rows.Youcanalsoindicateinanoperationthatoneofthecolumnstobeupdatedneedstousethekeyreturnedbyanindexedpreviousoperation.Wewillpresentnowsomesamplecodeshowinghowtheseideaswork.
Listing27-35showsanexampleofcreatingalistobjecttoholdalistofoperations.
Listing27-35.AContainerforContentProviderOperations
ArrayList<ContentProviderOperation>ops=newArrayList<ContentProviderOperation>();
LetusseenowhowtoconstructtheindividualoperationstobeaddedtothatlistinListing27-36.
Listing27-36.BatchingContentProviderOperations
ContentProviderOperation.Builderop=ContentProviderOperation.newInsert(acontentURI);op.withValue(key,value);//...moreoftheseContentProviderOperationop1=op.build();ops.add(op1);
ThekeyclassisContentProviderOperationanditscorrespondingBuilderobject.Intheexamplehereweareusingtheinsertoperation.Fortherestofthemethodsseetheclassreference.OncewehaveabuilderalongwithitsassociatedcontentURI,wetellthebuildertoaddsetofkey/valuepairsthatgoalongwiththatcontentURI.Oncefinishedaddingallthekey/valuepairsweproducetheContentProviderOperationfromthebuilderandaddittothelist.WethenaskthecontentresolvertoapplythebatchofoperationsusingthecodeinListing27-37.
Listing27-37.UsingaContentResolvedtoApplytheBatchofOperations
activity.getContentResolver().applyBatch(contentProviderAuthority,ops);
InListing27-37theargumentcontentProviderAuthorityistheauthoritystringpointingthecontentproviderandtheargumentopsisthelistofoperationsthatshouldbeappliedasabatchtothatcontentprovider.Thisisanexampleofaddingaseriesofupdateoperationsasasingletransaction.Letusseenowhowtoprovidecommithintstosothatcommitoperationscanbedoneonsmallersubsetsfromthegivenbatch.
BatchingCommitsbyYieldingOneproblemwithcommittingalargebatchofcommandsasasingletransactionisthatthisworkcanblockotheroperationsonthedatabase.Tohelpwiththisandalsotohelpwithtoomuchworktobecommittedinasingletransactionyoucaninstructanoperationtoyield.Whenthecontentproviderrecognizestheyieldparameteronanoperationitcommitstheworkdoneandpausestoyieldforotherprocessestorun.
NoticehowinthecodeinListing27-38oneoftheoperationsissettoallowyield.
Listing27-38.UsingYieldinaContentProviderOperation
ContentProviderOperation.BuilderoperationBuilder=ContentProviderOperation.newInsert(acontentURI);operationBuilder.withValue(key,value);//...moreofthesekey/valuepairswhenyouhavethem
ContentProviderOperationop1=operationBuilder.build();
//...AddMoreoperations
//MarkthenextoperationasyieldallowedoperationBuilder=ContentProviderOperation.newInsert(acontentURI);operationBuilder.withValue(key,value);operationBuilder.withYieldAllowed(true);//itisoktocommitContentProviderOperationoperationWithYield=operationBuilder.build();ops.add(operationWithYield);
//...AddMoreoperationsandyieldpointsasneeded
//Finallyapplythelistofoperationsactivity.getContentResolver().apply(contentProviderAuthority,ops);
UsingBackReferencesForoneoftheoperationsaboveyoucanuseabackreferenceasshowninListing27-39.
Listing27-39.UsingaBackReferenceinaContentProviderOperation
//Takethekeycomingoutofop1andadditasthevalueintindexOfTheOperationWhoseKeyYouNeed=0;op.withValueBackReference(mykey,indexOfTheOperationWhoseKeyYouNeed);
CodeinListing27-39isaskingthecontentprovidertoruntheoperationindicatedbylistindexindexOfTheOperationWhoseKeyYouNeedandtakeitsgeneratedprimarykeyanduseitasavalueforthecolumnthatissetonthetargetoperation.Thisishowyoutaketheinsertfromrawcontactanduseitsprimarykeyasthekeyvalueforthedataitemsbelongingtothatrawcontact.
OptimisticLockingInoptimisticlocking,youfirstapplythetransactionswithoutlockingtheunderlyingrepositoryandseeifanyupdateshavebeenmadesinceyouknowitsvaluebefore.Ifso,cancelthetransactionandretryit.
Tomakethisinthebatchmode,theAPIoffersatypeofoperationcalledanassertquery.Inthistypeofoperationthecontentprovidermakesthequeryandcomparesthevaluesoftheretrievedcursorforeitherthecountorthevaluesofcertainkeys.Iftheydon’tmatch,itrollsbackthetransactionandraisesanexceptionbreakingthecodeflow.SeethisdemonstratedinthecodeshowninListing27-40.
Listing27-40.UsingOptimisticLockingthroughnewAssertQuery
try{//ReadarawcontactforaparticularrawcontactidContentProviderOperation.BuilderassertOpBuilder=ContentProviderOperation.newAssertQuery(rawContactUri);//MakesurethereisonlyonerawcontactwiththatdetailsassertOpBuilder.withExpectedCount(1);//Makesuretheversioncolumnmatcheswithyoustartedwith//Ifnotthrowanexception.Wechosetocomparetheversionnumber//column(field)intherawcontactstabletoassert.assertOpBuilder.withValue(SyncColumns.VERSION,mVersion);//getthisoperationandaddittotheoperationslistattheend//Applythebatch…activityInstance.getContentResolver().applyBatch(...);}//forthisorotherexceptionscatch(OperationApplicationExceptione){//Thebatchisalreadycancelled//Telltheusertheupdatefailed//Showtheuserthenewdetailsandrepeattheprocess}
ReusingtheContactProviderUIContactprovidercapabilityinAndroidalsodefinesasetofintentsthatcanbeusedtoreusetheUIavailableinthecontactsapplication.
Therearethreekindsofintents.ThereisasetofintentsthatthecontactproviderfiresbasedontheeventstakingplaceinthecontentproviderUIapplication.Forexample,theintentINVITE_CONTACTisfiredwhentheuserclicksthe“invitetothenetwork”buttononacontactinthecontactapplication.Anapplicationcanregisterforthiseventandreadthecontactdetails.
Thereisanothersetofintentsthatareusedwhenthecontactprovideractsasasearchproviderforyourcustomactivities.Usingthisfacilityyoucansearchforacontactinyourcustomapplicationthroughsearchsuggestions.
ThereisanothersetofintentsthatexternalapplicationscanfiretoreusetheUIthatisprovidedbythecontactapplication.Youcanusetheseintentstopickfromalistofcontacts,orfromalistofphonenumbers,orfromalistofaddresses,orfromalistofe-mails.YoucanalsousetheseintentstoupdateacontactorcreateacontactusingtheUIprovidedtheAndroidapplication.
TheseintentsaredocumentedintheclassreferenceforContactsContract.Intents.
UsingGroupFeaturesContactsAPIprovidesthecontractsshowninListing27-41toworkwiththeGroupFeaturesofcontacts
Listing27-41.GroupContactContracts
ContactsContract.GroupsContactsContract.CommonDataKinds.GroupMembership
Thegroupstableholdsthingslikenameofthegroup,notesaboutthatgroup,andsomegrouplevelcountsofthemembership.Thegroupsarawcontactbelongstoarekeptinthedatatables.
UsingPhotoFeaturesYoucanexplorethephoto-relatedinformationforacontactusingtheclasscontractshowninListing27-42.
Listing27-42.ContactPhotoContracts
ContactsContract.Contacts.PhotoContactsContract.RawContacts.DisplayPhoto
Theclassdocumentationforthesecontractshassamplecodethatdescribeshowtousethesefeatures.
ReferencesHereareadditionalresourcesforthetopicscoveredinthischapter:
https://developer.android.com/guide/topics/providers/contacts-provider.html:TheprimarydocumentationforallaspectsoftheContactsAPIfromGoogle.ThisURLalsoincludesasectiononperformingbatchoperationsonthecontactsdatabase,optimisticlocking,andreusingthecontactsapplicationUI.
http://developer.android.com/reference/android/provider/ContactsContract.htmlJavadocforthekeyJavaclassContactsContract.YouwillneedthisURLoftenasyoucodetotheContactsAPI.
https://play.google.com/store/books/details/Google_Android_Quick_Start_Guide_Android_5_0_Lolli?id=dnzVBAAAQBAJ:Android5.0QuickStartguide.TheseAndroiduserguidesthatarepreparedforeachreleaseareusefulin
understandinghowthestockcontactsapplicationworkfromaUIperspective.
https://developer.android.com/guide/topics/providers/contacts-provider.html#SyncAdapters:SyncAdaptersaredocumentedhere.
http://developer.android.com/sdk/android-4.0.html#Contacts:DocumentationforthechangestotheContactsAPIin4.0.
http://developer.android.com/reference/android/provider/ContactsContract.Profile.htmlAreferenceonhowtousethenewProfileURIsintroducedin4.0.
http://www.androidbook.com/item/3917:EntrypointforourresearchontheContactsAPI.Youwillfindhereourresearch,asummaryoftheContactsAPI,tablesusedinthecontactsdatabase,howtoexplorethecontactsdatabases,contactsapplicationscreenshots,howtoexploresourcesforcontactproviders,andotherusefullinks.
http://developer.android.com/guide/topics/search/index.htmlSDKdocsonSearchAPI.Usefultoreviewthistoknowhowtosearchforcontacts.
http://www.androidbook.com/proandroid5/projects:YoucanusethisURLtodownloadthetestprojectdedicatedforthischapter.ThenameoftheZIPfileisProAndroid5_ch27_TestContacts.zip.
SummaryInthischapter,wehavecoveredthefollowing:thenatureoftheContactsAPI,exploringthecontactsdatabase,exploringtheContactsAPIURIsandtheircursors,readingandaddingcontacts,aggregatingrawcontacts,therelationshipbetweenthepersonalprofileandcontacts,andreadingandaddingcontactstoapersonalprofile.Wehavealsobrieflycoveredbatchingprovideroperations,usingthecontactproviderasasearchproviderforcontacts.
Chapter28
ExploringSecurityandPermissionsNoexplorationofmoderndevelopmentplatformsoroperatingsystemsiscompletewithoutdiscussingsecurity.InAndroid,securityspansallphasesoftheapplicationlifecycle—fromdesign-timepolicyconsiderationstoruntimeboundarychecks.Inthischapteryou’lllearnAndroid’ssecurityarchitectureandunderstandhowtodesignsecureapplications.
Let’sgetstartedwiththeAndroidsecuritymodel.
UnderstandingtheAndroidSecurityModelLet’sdiverightin,tocoversecurityduringthedeploymentandexecutionofanyAndroidapplication.TodeployanAndroidapplication,itmustbesignedwithadigitalcertificateinorderforyoutoinstallitontoadevice.Withrespecttoexecution,Androidrunseachapplicationwithinaseparateprocess,whereeachprocesshasauniqueandpermanentuserID(assignedatinstalltime).Thisplacesaboundaryaroundtheprocessandpreventsoneapplicationfromhavingdirectaccesstoanother’sdata.Moreover,Androiddefinesadeclarativepermissionmodelthatprotectssensitivefeatures(suchasthecontactlist).
Inthenextseveralsections,wearegoingtodiscussthesetopics.Butbeforewegetstarted,let’sprovideanoverviewofsomeofthesecurityconceptsthatwe’llrefertolater.
OverviewofSecurityConceptsAndroidrequiresthatapplicationsbesignedwithadigitalcertificate.Oneofthebenefitsofthisrequirementisthatanapplicationcannotbeupdatedwithaversionthatwasnotpublishedbytheoriginalauthororholderofthesigningcertificate.Ifwepublishanapplication,forexample,thenyoucannotupdateourapplicationwithyourversion(unless,ofcourse,yousomehowobtainourcertificate).Thatsaid,whatdoesitmeanforanapplicationtobesigned?Andwhatistheprocessofsigninganapplication?
Yousignanapplicationwithadigitalcertificate.Adigitalcertificateisanartifactthatcontainsinformationaboutyou,suchasyourcompanyname,address,andsoon.Afewimportantattributesofadigitalcertificateincludeitssignatureandpublic/privatekey.Apublic/privatekeyisalsocalledakeypair.Notethatalthoughyouusedigitalcertificatesheretosign.apkfiles,youcanalsousethemforotherpurposes(suchasencryptedcommunication,signingdocuments,andsoforth).Youcanobtainadigitalcertificatefromatrustedcertificateauthority(CA)andyoucanalsogenerateoneyourselfusingtoolssuchasthekeytool,whichwe’lldiscussshortly.Digitalcertificatesarestoredinkeystores.Akeystorecontainsalistofdigitalcertificates,eachofwhichhasanaliasthatyoucanuse
torefertoitinthekeystore.
SigninganAndroidapplicationrequiresthreethings:adigitalcertificate,the.apkfilefortheapplicationyouwishtosign,andautilitythatknowshowtoapplyadigitalsignaturetothe.apkfile.WeuseafreeutilitythatispartoftheJavaDevelopmentKit(JDK)distributioncalledthejarsigner.Thisutilityisacommand-linetoolthatknowshowtosigna.jarfileusingadigitalcertificate,andan.apkfileisreallyjustazip-formattedfilethatcollectstogether.jarfilesandafewotherresourcesforyourproject.Othersigningtoolsareavailable,soyouarefreetochoosethetoolthatworksbestforyou.
Now,let’smoveonandtalkabouthowyoucansignan.apkfilewithadigitalcertificate.
SigningApplicationsforDeploymentToinstallanAndroidapplicationontoadevice,youfirstneedtosigntheAndroidpackage(.apkfile)usingadigitalcertificate.Thecertificate,however,canbeself-signed—youdonotneedtopurchaseacertificatefromacertificateauthoritysuchasVeriSign.Beawarethatself-signedcertificatesaregenerallyconsideredlesstrustworthy,andinsomeenvironmentsareconsideredinsecure.
Signingyourapplicationfordeploymentinvolvesthreesteps.Thefirststepistogenerateacertificateusingkeytool(orasimilartool).Thesecondstepinvolvesusingthejarsignertooltosignthe.apkfilewiththegeneratedcertificate.Thethirdstepalignsportionsofyourapplicationonmemoryboundariesformoreefficientmemoryusagewhenrunningonadevice.Notethatduringdevelopment,boththeADTplug-inforEclipseandAndroidDeveloperStudiotakecareofeverythingforyou:signingyour.apkfileanddoingthememoryalignment,beforedeployingontotheemulatororadevice.
GeneratingaSelf-SignedCertificateUsingtheKeytoolThekeytoolutilitymanagesadatabaseofprivatekeysandtheircorrespondingX.509certificates(astandardfordigitalcertificates).ThisutilityshipswiththeJDKandresidesundertheJDKbindirectory.IfyoufollowedtheinstructionsinChapter2regardingchangingyourPATH,theJDKbindirectoryshouldalreadybeinyourPATH.Inthissection,we’llshowyouhowtogenerateakeystorewithasingleentry,whichyou’lllaterusetosignanAndroid.apkfile.Togenerateakeystoreentry,dothefollowing:
1. Createafoldertoholdthekeystore,suchasc:\android\release\.or/opt/android/release(dependingonyouroperatingsystem).
2. Openashellorcommandwindow,andexecutethekeytoolutilitywiththeparametersshowninListing28-1.
Listing28-1.GeneratingaKeystoreEntryUsingthekeytoolUtility
keytool-genkey-v-keystore"c:\android\release\release.keystore"-aliasandroidbook-keyalgRSA-validity14000
AlloftheargumentspassedtothekeytoolaresummarizedinTable28-1.
Table28-1.ArgumentsPassedtothekeytoolUtility
Argument Description
genkey Tellskeytooltogenerateapublic/privatekeypair.
v Tellskeytooltoemitverboseoutputduringkeygeneration.
keystore Pathtothekeystoredatabase(inthiscase,afile).Thefilewillbecreatedifnecessary.
alias Uniquenameforthekeystoreentry.Thisaliasisusedlatertorefertothekeystoreentry.
keyalg Algorithm.
validity Validityperiod.
keytoolwillpromptyoufortwopasswordsduringthecreationofthekeystoreandtheentryyouarecreating.Thefirstpasswordpromptedisforthekeystoreitselfandcontrolsaccesstoallthekeymaterialyouwillstore.Thiscanalsobespecifiedusingthestorepassparameter.Thesecondpasswordisthepasswordfortheprivatekeyandrelatedcertificateyouarecreating,alsoavailableviathekeypassparameter.Youshouldgetusedtonotincludingtheseasparametersonthecommandline,andinsteadprefertoallowkeytooltopromptyouasgoodgeneralsecuritypractice.
Beaware,thatifyoudousetheparametersforpasswordtokeytool,anyonewhogetsaccesstoyourshellorcommand-linehistorycanseethepasswords,ascananyonewhocanlisttherunningprocessesonyourmachinewhilekeytoolruns.ThecommandinListing28-1willgenerateakeystoredatabasefileinyourkeystorefolder.Thedatabasewillbeafilenamedrelease.keystore.Thevalidityoftheentrywillbe14,000days(orapproximately38years)—whichisalongtimefromnow.Youshouldunderstandthereasonforthis.TheAndroiddocumentationrecommendsthatyouspecifyavalidityperiodlongenoughtosurpasstheentirelifespanoftheapplication,whichwillincludemanyupdatestotheapplication.Itrecommendsthatthevaliditybeatleast25years.IfyouplantopublishtheapplicationonGooglePlay,yourcertificatewillneedtobevalidthroughatleastOctober22,2033.GooglePlaycheckseachapplicationwhenuploadedtomakesureitwillbevalidatleastuntilthen.
CautionBecauseyourcertificateinanyapplicationupdatemustmatchthecertificateyouusedthefirsttime,makesureyousafeguardyourkeymaterial.Keepeitheryourkeystorefile,orthekeypairifyouchoosetoexportthem,safe!Ifyouloseaccesstothekeystoreorunderlyingkeys,andyoucan’tre-createit,youwon’tbeabletoupdateyourapplication,andyou’llhavetoissueawholenewapplicationinstead.
Goingbacktothekeytool,theargumentaliasisauniquenamegiventotheentryinthekeystoredatabase;youwillusethisnamelatertorefertotheentry.WhenyourunthekeytoolcommandinListing28-1,keytoolwillaskyouafewquestions(seeFigure28-1)andthengeneratethekeystoredatabaseandentry.
Figure28-1.Additionalquestionsaskedbykeytool
Onceyouhaveakeystorefileforyourproductioncertificates,youcanreusethisfiletoaddmorecertificates.Justusekeytoolagain,andspecifyyourexistingkeystorefile.
TheDebugKeystoreandtheDevelopmentCertificateWementionedthattheADTplug-inforEclipse,andAndroidDeveloperStudio,bothtakecareofsettingupadevelopmentkeystoreforyou.However,thedefaultcertificateusedforsigningduringdevelopmentcannotbeusedforproductiondeploymentontoarealdevice.Thisispartlybecausetheautomaticallygenerateddevelopmentcertificateisonlyvalidfor365days,whichclearlydoesnotgetyoupastOctober22,2033.Sowhathappensonthethreehundredsixty-sixthdayofdevelopment?You’llgetabuilderror.Yourexistingapplicationsshouldstillrun,buttobuildanewversionofanapplication,youneedtogenerateanewcertificate.Theeasiestwaytodothisistodeletetheexistingdebug.keystorefile,andassoonasitisneededagain,theADT(forinstance)willgenerateanewfileandcertificatevalidforanother365days.
Tofindyourdebug.keystorefile,assumingyouareusingEclipsewithADT,openthePreferencesscreenofEclipseandgotoAndroid Build.Thedebugcertificate’slocationwillbedisplayedintheDefaultDebugKeystorefield,asshowninFigure28-2(seeChapter2ifyouhavetroublefindingthePreferencesmenu).
Figure28-2.Thedebugcertificate’slocation
Ofcourse,nowthatyou’vegotanewdevelopmentcertificate,youcannotupdateyourexistingapplicationsinAndroidVirtualDevices(AVDs)orondevicesusinganewdevelopmentcertificate.EclipsewillprovidemessagesintheConsoletellingyoutouninstalltheexistingapplicationfirstusingadb,whichyoucancertainlydo.IfyouhavealotofyourapplicationsinstalledontoanAVD,youmayfeelitiseasiertosimplyre-createtheAVD,soitdoesnotcontainanyofyourapplicationsandyoucanstartfresh.Toavoidthisproblemayearfromnow,youcouldgenerateyourowndebug.keystorefilewithwhatevervalidityperiodyoudesire.Obviously,itneedstohavethesamefilenameandbeinthesamedirectoryasthefilethatADTwouldcreate.Thecertificatealiasisandroiddebugkey,andthestorepassandkeypassareboth“android”.ADTsetsthefirstandlastnameonthecertificateas“AndroidDebug”,theorganizationalunitas“Android”,andthetwo-lettercountrycodeas“US”.Youcanleavetheorganization,city,andstatevaluesas“Unknown”.
Ifyouacquiredamap-apikeyfromGoogleusingtheolddebugcertificate,youwillneedtogetanewmap-apikeytomatchthenewdebugcertificate.Wecoveredmap-apikeysinChapter19.
Nowthatyouhaveadigitalcertificatethatyoucanusetosignyourproduction.apkfile,youneedtousethejarsignertooltodothesigning.Here’showtodothat.
UsingtheJarsignerTooltoSignthe.apkFileThekeytoolutilitydescribedintheprevioussectioncreatedadigitalcertificate,whichisoneoftheparametersforthejarsignertool.TheotherparameterforjarsigneristheactualAndroidpackagetobesigned.TogenerateanAndroidpackage,youneedtousetheExportUnsignedApplicationPackageutilityintheADTplug-inforEclipse(orequivalentfunctioninAndroidDeveloperStudio).Youaccesstheutilitybyright-clicking
anAndroidprojectinEclipse,selectingAndroidTools,andselectingExportUnsignedApplicationPackage.RunningtheExportUnsignedApplicationPackageutilitywillgeneratean.apkfilethatwillnotbesignedwiththedebugcertificate.
Toseehowthisworks,runtheExportUnsignedApplicationPackageutilityononeofyourAndroidprojects,andstorethegenerated.apkfilesomewhere.Forthisexample,we’llusethekeystorefolderwecreatedearlierandgeneratean.apkfilecalledc:\android\release\myappraw.apk.
Withthe.apkfileandthekeystoreentry,runthejarsignertooltosignthe.apkfile(seeListing28-2).Usethefullpathnamestoyourkeystorefileand.apkfileasappropriatewhenyourunthis.
Listing28-2.UsingjarsignertoSignthe.apkFile
jarsigner-keystore"PATHTOYOURrelease.keystoreFILE"-storepasspaxxword-keypasspaxxword"PATHTOYOURRAWAPKFILE"androidbook
Tosignthe.apkfile,youpassthelocationofthekeystore,thekeystorepassword,theprivate-keypassword,thepathtothe.apkfile,andthealiasforthekeystoreentry.Thejarsignerwillthensignthe.apkfilewiththedigitalcertificatefromthekeystoreentry.Torunthejarsignertool,youwillneedtoeitheropenatoolswindow(asexplainedinChapter2)oropenacommandorTerminalwindowandeithernavigatetotheJDKbindirectoryorensurethatyourJDKbindirectoryisonthesystempath.Forsecurityreasons,itissafertoleaveoffthepasswordargumentstothecommandandsimplyletjarsignerpromptyouasnecessaryforpasswords.Figure28-3showswhatthejarsignertoolinvocationlookslike.YoumayhavenoticedthatjarsignerpromptedforonlyonepasswordinFigure28-3.Jarsignerfiguresoutnottoaskforthekeypasspasswordwhenthestorepassandkeypassarethesame.Strictlyspeaking,thejarsignercommandinListing28-2onlyneeds–keypassifithasadifferentpasswordthan–storepass.
Figure28-3.Usingjarsigner
Aswepointedoutearlier,Androidrequiresthatanapplicationbesignedwithadigitalsignaturetopreventamaliciousprogrammerfromupdatingyourapplicationwiththeirversion.Forthistowork,Androidrequiresthatupdatestoanapplicationbesignedwiththesamesignatureastheoriginal.Ifyousigntheapplicationwithadifferentsignature,Androidtreatsthemastwodifferentapplications.Soweremindyouagain,becarefulwithyourkeystorefilesoit’savailabletoyoulaterwhenyouneedtoprovideanupdatetoyour
application.
AligningYourApplicationwithzipalignYouwantyourapplicationtobeasmemoryefficientaspossiblewhenrunningonadevice.Ifyourapplicationcontainsuncompresseddata(perhapscertainimagetypesordatafiles)atruntime,Androidcanmapthisdatastraightintomemoryusingthemmap()call.Forthistowork,though,thedatamustbealignedona4-bytememoryboundary.TheCPUsinAndroiddevicesare32-bitprocessors,and32bitsequals4bytes.Themmap()callmakesthedatainyour.apkfilelooklikememory,butifthedataisnotalignedona4-byteboundary,itcan’tdothatandextracopyingofdatamustoccuratruntime.Thezipaligntool,foundintheAndroidSDKbuildorbuild-tools/<version>directory,looksthroughyourapplicationandmovesslightlyanyuncompresseddatanotalreadyona4-bytememoryboundarytoa4-bytememoryboundary.Youmayseethefilesizeofyourapplicationincreaseslightlybutnotsignificantly.Toperformanalignmentonyour.apkfile,usethiscommandinatoolswindow(seealsoFigure28-4):
zipalign–v4infile.apkoutfile.apk
Figure28-4.Usingzipalign
Notethatzipaligndoesnotmodifytheinputfile,sothisiswhywechosetouse“raw”aspartofourfilenamewhenexportingfromEclipse.Now,ouroutputfilehasanappropriatenamefordeployment.Ifyouneedtooverwriteanexistingoutfile.apkfile,youcanusethe–foption.Alsonotethatzipalignperformsaverificationofthealignmentwhenyoucreateyouralignedfile.Toverifythatanexistingfileisproperlyaligned,usezipaligninthefollowingway:
zipalign–c–v4filename.apk
Itisveryimportantthatyoualignaftersigning;otherwise,signingcouldcausethingstogobackoutofalignment.Thisdoesnotmeanyourapplicationwouldcrash,butitcouldusemorememorythanitneedsto.
UsingtheExportWizardInEclipse,youmayhavenoticedamenuchoiceunderAndroidToolscalledExport
SignedApplicationPackage.Thislauncheswhatiscalledtheexportwizard,anditdoesallofthepreviousstepsforyou,promptingonlyforthepathtoyourkeystorefile,keyalias,thepasswords,andthenameofyouroutput.apkfile.Itwillevencreateanewkeystoreornewkeyifyouneedone.Youmayfinditeasiertousethewizard,oryoumayprefertoscriptthestepsyourselftooperateonanexportedunsignedapplicationpackage.Nowthatyouknowhoweachworks,youcandecidewhichisbetterforyou.
ManuallyInstallingAppsOnceyouhavesignedandalignedan.apkfile,youcaninstallitontothevirtualdevicemanuallyusingtheadbtool.Asanexercise,startthevirtualdevicefromtheAVDManager,whichwillstartwithoutcopyingoveranyofyourdevelopmentprojectsfromEclipse.Now,openatoolswindowandruntheadbtoolwiththeinstallcommand:
adbinstall"PATHTOAPKFILEGOESHERE"
Thismayfailforacoupleofreasons,butthemostlikelyarethatthedebugversionofyourapplicationwasalreadyinstalledontheemulator,givingyouacertificateerror,orthereleaseversionofyourapplicationwasalreadyinstalledontheemulator,givingyouan“INSTALL_FAILED_ALREADY_EXISTS”error.Inthefirstcase,youcanuninstallthedebugapplicationwiththiscommand:
adbuninstallpackagename
Notethattheargumenttouninstallistheapplication’spackagenameandnotthe.apkfilename.ThepackagenameisdefinedintheAndroidManifest.xmlfileoftheinstalledapplication.
Forthesecondcase,youcanusethiscommand,where–rsaystoreinstalltheapplicationwhilekeepingitsdataonthedevice(oremulator):
adbinstall–r"PATHTOAPKFILEGOESHERE"
Now,let’sseehowsigningaffectstheprocessofupdatinganapplication.
InstallingUpdatestoanApplicationandSigningEarlier,wementionedthatacertificatehasanexpirationdateandthatGooglerecommendsyousetexpirationdatesfarintothefuture,toaccountforalotofapplicationupdates.Thatsaid,whathappensifthecertificatedoesexpire?WouldAndroidstillruntheapplication?Fortunately,yes—Androidteststhecertificate’sexpirationonlyatinstalltime.Onceyourapplicationisinstalled,itwillcontinuetorunevenifthecertificateexpires.
Butwhataboutupdates?Unfortunately,youwillnotbeabletoupdatetheapplicationoncethecertificateexpires.Inotherwords,asGooglesuggests,youneedtomakesurethelifeofthecertificateislongenoughtosupporttheentirelifeoftheapplication.Ifacertificatedoesexpire,Androidwillnotinstallanupdatetotheapplication.Theonlychoiceleftwillbeforyoutocreateanotherapplication—anapplicationwithadifferentpackagename—
andsignitwithanewcertificate.Soasyoucansee,itiscriticalforyoutoconsidertheexpirationdateofthecertificatewhenyougenerateit.
Nowthatyouunderstandsecuritywithrespecttodeploymentandinstallation,let’smoveontoruntimesecurityinAndroid.
PerformingRuntimeSecurityChecksRuntimesecurityinAndroidhappensattheprocessandoperationlevels.Attheprocesslevel,Androidpreventsoneapplicationfromdirectlyaccessinganotherapplication’sdata.ItdoesthisbyrunningeachapplicationwithinadifferentprocessandunderauniqueandpermanentuserID.Attheoperationallevel,Androiddefinesalistofprotectedfeaturesandresources.Foryourapplicationtoaccessthisinformation,youhavetoaddoneormorepermissionrequeststoyourAndroidManifest.xmlfile.Youcanalsodefinecustompermissionswithyourapplication.
Inthesectionsthatfollow,wewilltalkaboutprocess-boundarysecurityandhowtodeclareandusepredefinedpermissions.Wewillalsodiscusscreatingcustompermissionsandenforcingthemwithinyourapplication.Let’sstartbydissectingAndroidsecurityattheprocessboundary.
UnderstandingSecurityattheProcessBoundaryUnlikeyourdesktopenvironment,wheremostoftheapplicationsrununderthesameuserID,eachAndroidapplicationgenerallyrunsunderitsownuniqueID.ByrunningeachapplicationunderadifferentID,Androidcreatesanisolationboundaryaroundeachprocess.Thispreventsoneapplicationfromdirectlyaccessinganotherapplication’sdata.
Althougheachprocesshasaboundaryaroundit,datasharingbetweenapplicationsisobviouslypossiblebuthastobeexplicit.Inotherwords,togetdatafromanotherapplication,youhavetogothroughthecomponentsofthatapplication.Forexample,youcanqueryacontentproviderofanotherapplication,youcaninvokeanactivityinanotherapplication,or—asyou’llseeinChapter15—youcancommunicatewithaserviceofanotherapplication.Allofthesefacilitiesprovidemethodsforyoutoshareinformationbetweenapplications,buttheydosoinanexplicitmannerbecauseyoudon’tdirectlyaccesstheunderlyingdatabase,files,andsoon.
Android’ssecurityattheprocessboundaryisclearandsimple.Thingsgetinterestingwhenwestarttalkingaboutprotectingresources(suchascontactdata),features(suchasthedevice’scamera),andourowncomponents.Toprovidethisprotection,Androiddefinesapermissionscheme.Let’sdissectthatnow.
DeclaringandUsingPermissionsAndroiddefinesapermissionschememeanttoprotectresourcesandfeaturesonthedevice.Forexample,applications,bydefault,cannotaccessthecontactslist,makephonecalls,andsoon.Toprotecttheuserfrommaliciousapplications,Androidrequires
applicationstorequestpermissionsiftheyneedtouseaprotectedfeatureorresource.FromtheintroductionofAndroidKitKat,andcontinuinginAndroidLollipop,permissionswhenpresentedtotheenduserarenowclusteredintogroupstoaddresstheirconstantlygrowingnumberandcomplexity.Thisgroupingbringswithitsomecompromisesasyouwillobserve.
Aswewillcovershortly,permissionrequestsgointhemanifestfile.Atinstalltime,theAPKinstallereithergrantsordeniestherequestedpermissionsbasedonthesignatureofthe.apkfileand/orfeedbackfromtheuser.Ifpermissionisnotgranted,anyattempttoexecuteoraccesstheassociatedfeaturewillresultinapermissionfailure.
Table28-2showssomecommonlyusedfeaturesandthepermissionstheyrequire.Althoughyouarenotyetfamiliarwithallthefeatureslisted,youwilllearnaboutthemlater(eitherinthischapterorinsubsequentchapters).
Table28-2.FeaturesandResourcesandthePermissionsTheyRequire
Feature/ResourceRequiredPermission Description
Camera android.permission.CAMERA Enablesyoutoaccessthedevice’scamera.
Internet android.permission.INTERNET Enablesyoutomakeanetworkconnection.
User’scontactdata
android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTSEnablesyoutoreadfromorwritetotheuser’scontactdata.
User’scalendardata
android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAREnablesyoutoreadfromorwritetotheuser’scalendardata.
Recordingaudio
android.permission.RECORD_AUDIO Enablesyoutorecordaudio.
Wi-Filocationinformation
android.permission.ACCESS_COARSE_LOCATIONEnablesyoutoaccesscoarse-grainedlocationinformationfromWi-Fiandcelltowers.
GPSlocationinformation
android.permission.ACCESS_FINE_LOCATION
Enablesyoutoaccessfine-grainedlocationinformation.ThisincludesGPSlocationinformation.ItisalsosufficientforWi-Fiandcelltowers.
Batteryinformation
android.permission.BATTERY_STATS Enablesyoutoobtainbattery-stateinformation.
Bluetooth android.permission.BLUETOOTH EnablesyoutoconnecttopairedBluetoothdevices.
Foracompletelistofpermissions,seethefollowingURL:
http://developer.android.com/reference/android/Manifest.permission.html
Applicationdeveloperscanrequestpermissionsbyaddingentriestothe
AndroidManifest.xmlfile.Forexample,Listing28-3askstoaccessthecameraonthedevice,toreadthelistofcontacts,andtoreadthecalendar.
Listing28-3.PermissionsinAndroidManifest.xml
<manifest…><application>...</application><uses-permissionandroid:name="android.permission.CAMERA"/><uses-permissionandroid:name="android.permission.READ_CONTACTS"/><uses-permissionandroid:name="android.permission.READ_CALENDAR"/></manifest>
Notethatyoucaneitherhard-codepermissionsintheAndroidManifest.xmlfileorusethemanifesteditor.Themanifesteditoriswireduptolaunchwhenyouopen(double-click)themanifestfile.Themanifesteditorcontainsadrop-downlistthathasallofthepermissionspreloadedtopreventyoufrommakingamistake.AsshowninFigure28-5,youcanaccessthepermissionslistbyselectingthePermissionstabinthemanifesteditor.
Figure28-5.TheAndroidmanifesteditortoolinEclipse
YounowknowthatAndroiddefinesasetofpermissionsthatprotectsasetoffeaturesandresources.Similarly,youcandefineandenforcecustompermissionswithyourapplication.Let’sseehowthatworks.
UnderstandingandUsingURIPermissions
Contentproviders(discussedinChapter4)oftenneedtocontrolaccessatafinerlevelthanallornothing.Fortunately,Androidprovidesamechanismforthis.Thinkaboute-mailattachments.Theattachmentmayneedtobereadbyanotheractivitytodisplayit.Buttheotheractivityshouldnotgetaccesstoallofthee-maildataanddoesnotneedaccesseventoallattachments.ThisiswhereURIpermissionscomein.
PassingURIPermissionsinIntentsWheninvokinganotheractivityandpassingaURI,yourapplicationcanspecifythatitisgrantingpermissionstotheURIbeingpassed.Butbeforeyourapplicationcandothis,itneedspermissionitselftotheURI,andtheURIcontentprovidermustcooperateandallowthegrantingofpermissionstoanotheractivity.ThecodetoinvokeanactivitywithgrantingofpermissionslookslikeListing28-4,whichisactuallyfromtheAndroidEmailprogram,whereitislaunchinganactivitytoviewane-mailattachment.
Listing28-4.CodetoLaunchanActivitywithGrantingofPermission
try{Intentintent=newIntent(Intent.ACTION_VIEW);intent.setData(contentUri);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);startActivity(intent);}catch(ActivityNotFoundExceptione){mHandler.attachmentViewError();//TODO:Addaproperwarningmessage(andlotsofupstreamcleanuptoprevent//itfromhappening)inthenextrelease.}
TheattachmentisspecifiedbycontentUri.NoticehowtheintentiscreatedwiththeactionIntent.ACTION_VIEW,andthedataissetusingsetData().Theflagissettograntreadpermissionoftheattachmenttowhateveractivitywillmatchontheintent.Thisiswherethecontentprovidercomesintoplay.Justbecauseanactivityhasreadpermissiontocontentdoesn’tmeanitcanpassalongthatpermissiontosomeotheractivitythatdoesnothavethepermissionalready.Thecontentprovidermustallowitaswell.AsAndroidfindsamatchingintentfilteronanactivity,itconsultswiththecontentprovidertomakesurethatpermissionscanbegranted.Inessence,thecontentproviderisbeingaskedtoallowaccesstothisnewactivitytothecontentspecifiedbytheURI.Ifthecontentproviderrefuses,thenaSecurityExceptionisthrown,andtheoperationfails.InListing28-4,thisparticularapplicationisnotcheckingforaSecurityException,becausethedeveloperisnotexpectinganyrefusalstograntpermission.That’sbecausetheattachmentcontentproviderispartoftheEmailapplication!Thereisapossibilitythoughthatnoactivitycanbefoundtohandletheattachment,sothatistheonlyexceptionbeingwatchedfor.
InthecasewheretheactivitybeingcalledtoprocesstheURIalreadyhaspermissiontoaccessthatURI,thecontentproviderdoesnotgettodenyaccess.Thatis,thecallingactivitycangrantpermission,andiftheactivityonthereceivingendoftheintentalready
hasthenecessarypermissionsforcontentURI,thecalledactivitywillbeallowedtoproceedwithnoproblems.
InadditiontoIntent.FLAG_GRANT_READ_URI_PERMISSION,thereisaflagforwritepermissions:Intent.FLAG_GRANT_WRITE_URI_PERMISSION.ItispossibletospecifybothinanIntent.Also,theseflagscanapplytoservicesandBroadcastReceiversaswellasactivitiesbecausetheycanreceiveintentstoo.
SpecifyingURIPermissionsinContentProvidersSohowdoesacontentproviderspecifyURIpermissions?ItdoessointheAndroidManifest.xmlfileinoneoftwoways:
Inthe<provider>tag,theandroid:grantUriPermissionsattributecanbesettoeithertrueorfalse.Iftrue,anycontentfromthiscontentprovidercanbegranted.Iffalse,thesecondwayofspecifyingURIpermissionscanhappen,orthecontentprovidercandecidenottoletanyoneelsegrantpermissions.
Specifypermissionswithchildtagsof<provider>.Thechildtagis<grant-uri-permission>,andyoucanhavemorethanonewithin<provider>.<grant-uri-permission>hasthreepossibleattributes:
Usingtheandroid:pathattribute,youcanspecifyacompletepathwhichwillthenhavepermissionsthataregrantable.
Similarly,android:pathPrefixspecifiesthebeginningofaURIpath.
android:pathPatternallowswildcards(theasterisk,*,character)tospecifyapath.
Aswestatedbefore,thegrantingentitymustalsohaveappropriatepermissionstothecontentbeforebeingallowedtograntthemtosomeotherentity.Contentprovidershaveadditionalwaysofcontrollingaccesstotheircontent,throughtheandroid:readPermissionattributeofthe<provider>tag,theandroid:writePermissionattribute,andtheandroid:permissionattribute(aconvenientwaytospecifybothreadandwritepermissionswithonepermissionStringvalue).ThevalueforanyofthesethreeattributesisaStringthatrepresentsthepermissionacallermusthaveinordertoreadorwritewiththiscontentprovider.BeforeanactivitycouldgrantreadpermissiontoacontentURI,thatactivitymusthavereadpermissionfirst,asspecifiedbyeithertheandroid:readPermissionattributeortheandroid:permissionattribute.Theentitywantingthesepermissionswoulddeclarethemintheirmanifestfilewiththe<uses-permissions>tag.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
http://developer.android.com/guide/topics/security/security.htmlTheAndroidDeveloper’sGuidesection“SecurityTips.”Itprovidesanoverviewwithlinkstolotsofreferencepages.
http://developer.android.com/guide/publishing/app-signing.html:TheAndroidDeveloper’sGuidesection“SigningYourApplications.”
SummaryThissecuritychaptercoveredthefollowingtopics:
UniqueapplicationuserIDsthathelpseparateappsfromeachothertoprotectprocessinganddata
DigitalcertificatesandtheiruseinsigningAndroidapplications
Thatanapplicationcanonlybeupdatediftheupdateissignedwiththesamedigitalcertificateastheoriginal
Managingcertificatesinakeystoreusingkeytool
Runningjarsignertoapplyacertificatetoanapplication.apkfile
zipalignandmemoryboundaries
TheEclipseplug-inwizardtakescareofgeneratingtheapk,applyingthecertificateandzipalign-ingforyou
Manuallyinstallingappsontodevicesandemulators
Permissionsthatapplicationscandeclareanduse
URIpermissionsandhowcontentprovidersusethem
Chapter29
UsingGoogleCloudMessagingwithAndroidAsweapproachtheendofthebook,youwillhavealreadydevelopedagoodunderstandingof,andappreciationfor,themanycommunicationprotocolsandarchitecturaloptionsyouhaveavailablewithinAndroidwhenitcomestodealingwithoff-deviceservices.Inthischapter,wewillexploreGoogle’sCloudMessaging(orGCM)platformandhowyoucanuseitastheplumbingforyourapplication’sremotecommunicationandserviceinteractionneeds.
WhatIsGoogleCloudMessaging?GCMisaserviceofferedbyGoogletoenableyoutowritemultipleapplicationsacrossdifferentplatformsthatexchangemessagesinordertofurthertheirfunctionality.TheprimaryexamplesofthemultipleapplicationsareanAndroidclientapplicationexchangingmessageswitharemoteserverapplication.
Theactualmessagessent,andtheirpurpose,areuptoyouasadeveloper.Itcouldbeamessagefromtheremoteserverlettingyourclientapplicationknow(a“downstream”message)thatanewupdatetoanewsfeed,musicservice,orsimilarsubscriptionisavailable.Messagesfromtheclienttotheserver(an“upstream”message)couldbesendingachatmessage,picturethumbnail,orothernewpieceofdatayouruserhascapturedorgeneratedontheclient.Thesearejustexamples—youarefreetoimagineanyuse,andanypayload,forthemessagesexchangedinGCM.
UnderstandingtheKeyBuildingBlocksofGCMHavingreadtheintroductiontoGCM,youarealreadyawareoftwoofthekeycomponentsinanycompleteGCMconfiguration.ThefinalparttocompletethepictureistheGCMserver(infact,servers)hostedbyGooglethatperformthemessagequeuing,forwarding,andsoforth.
Torecap,thethreekeybuildingblocksforGCMareasfollows:
ClientApplication—Anapplicationyouwrite,suchasanAndroidapplication,thatsendsand/orreceivesmessagesfromaremoteservertohelpwithfunctionality.
GCMConnectionServers—Google’smessaginginfrastructure,whichmanagesallmessagingtraffic,messagequeuingintheeventofdeliverydelays,ultimatedeliveryguarantees,etc.
RemoteApplicationServer—Anapplicationyourwriteasaserver,hostedinanInternet-accessiblefashion,responsibleforsendingand/orreceivingmessagesfromclientapplications.
Wecanalsorepresentthisarchitectureinvisualform.Figure29-1showsthecomponentsandmessageflowinacompleteGCMsetup.
Figure29-1.GCMarchitecturaloverview
PreparingtoUseGCMinYourApplicationWherepreviouslywehavejumpedstraightintoJavacode,layoutXML,andsoforthwhenconstructingexampleapplications,forGCM-baseddevelopmentweneedtoundertakeafewpreparatorystepsinordertohaveGoogle’sserversaccepttrafficfromourclientandserver.
CreatingorConfirmingYourGCMProjectinGoogleDeveloperConsoleTouseanyofGoogle’sonlineservicesandAPIs,includingGCM,youwillneedtocreateanAPIProjectwithintheGoogleDeveloperConsole,atcloud.google.com/console.Youmightalreadyhaveaprojectyoucanreuse,butlet’sassumeyouarecreatingoneforthefirsttime.NavigatetotheconsoleURLandclicktheCreateProjectbutton.Followthepromptsforaccountandbillingdetails,etc.,andyoushouldendupwithanewproject(orconfirmanexistingone)asshowninFigure29-2.
Figure29-2.YourGoogleDeveloperConsolewithAPIProjectinPlace
ActivatingtheGCMAPIsforYourProjectWiththeAPIProjectinplace,younowneedtoactivatethespecificGCMAPIs.GooglesupportsdozensofseparateAPIs,allofwhicharedisabledbydefaulttoensureyoudonotaccidentallytriggerbehaviororincurcostsyouwerenotexpecting.ClickonyourAPIProject(inourcase,api-project-589435632025)andundertheAPIs&Authsectionontheleftside,selectAPIsandscrolluntilyouseeGoogleCloudMessagingforAndroid.TurnthisonusingtheEnableAPIbutton.YouwillknowyouhavesuccessfullyenabledtheGCMAPIwhenthebuttonchangesfromEnableAPItoDisableAPI,asshowninFigure29-3.
Figure29-3.YourGoogleDeveloperConsoleAPIProjectwithGCMEnabled
GeneratingYourAPIKeyAswithotherGoogleAPIs,accesstotheGCMAPIforyourprojectrequiresyourkey.Thishelpsensureeverythingfromtrafficseparation,soyourGCMmessagesarenotinadvertentlymixedwiththoseofotherapplications,throughtobilling,analytics,andsoforth.
Togenerateyourkey,choosetheCredentialsoptionunderAPIs&Auth.ChoosetheCreateNewKeyoption,andwhenpromptedforkeyproperties,selectServerforthekeytype.IfyouknowthepublicIPaddressofyourintendedserver,youcanusethatintheconfigurationsection,otherwiseyoucanuse0.0.0.0/0fortestingpurposes.ThenchooseCreatetohavethekeygenerated.Whenyouarereturnedtotheconsole,youshouldseeyourAPIkeyavailableundertheCredentialssubmenu.Notedownthekeyvalueasyouwillneeditshortly.
AuthenticatingGCMCommunicationTheAPIkeyisnottheonlypieceofidentifyinginformationusedtoauthenticateandauthorizemessagetransferwithinaGCMenvironment.YoucanfindmoredetailontheusesofGCMtokensandkeysonthedeveloper.google.comwebsite.Inbrief,thefollowingfourtokentypesareusedinyourGCMapplications:
SenderIDTheprojectIDcodeavailablefromtheGoogledeveloper
console.YourserverapplicationwillusethisaspartoftheregistrationprocesswithGoogle’sGCMserverstoenableittosendmessagestotheAndroidclientapplicationandyourusers.
SenderAuthTokenYourAPIkey,usedineverymessagesenttotheGCMserverstodemonstratetheauthenticityofthemessageanditsprovenancefromyourserverapplication.
ApplicationIDForyourAndroidapplication,thisisthefullyqualifiedJavapackagename,forinstancecom.androidbook.gcm.BecausethisisuniqueacrossallAndroidapplications,itallowstheGCMecosystemtoknowwhichapplicationsreceivewhichtypesofmessages.
RegistrationIDAllocatedtoyourclientAndroidapplicationwhenitregisterswiththeGCMserversformessagedelivery.TheregistrationIDissensitiveinformationandshouldbestoredsecurelyandnotdisclosed.
AlloftheseitemsincombinationallowclientandserverapplicationstoregisterwithGCMandbeidentifiedbyit,andalsotouniquelyidentifytheapplicationsandtheirmessages.
BuildinganAndroidGCM-EnabledApplicationBuildingameaningfulGCM-basedAndroidapplicationandthesupportingserver-sidethird-partyserviceisalargeundertaking—solargethatwecouldalmostwriteasmallbookjustonthattopic.Belowwewillcoverthemainconfigurationandcodingpointsyouneedtoconsiderwhenbuildingtheapplication,andyoucancheckthebookwebsiteforamorein-depthdiscussiononGCMwithfullexamples.
CodingtheClientComponentforGCMTheclientAndroidapplicationneedstoconsiderthreebroadareas.First,haveyourdevelopmentenvironmentsetupcorrectly.Second,configuretheAndroidprojecttoincludetherightdependenciesandprivileges.Lastly,writetheGCMregistrationmethodsandmessagehandlingmethodsintoyourJavacodeforyouractivity(oractivities).
ConfigureProjectDependenciesforYourProjectBeforewecanwritetheactualJavacodeandanyrelatedXMLlayoutforourdesiredGCM-basedapplication,weneedtoconfigureourprojecttohavethenecessaryAPIsavailableandinvokeyourIDE’sbuildtool(e.g.,gradle)withthenecessarydependenciestoensureasuccessfulbuild.
Yourdevelopmentenvironment(AndroidStudio,Eclipse,etc.)willneedtohavethe
GooglePlayServicesSDKinstalled.Double-checkthiswiththeSDKManagerfromtheIDEorcommandprompt.
Next,yourprojectwillneedtobeconfiguredtoworkwiththeGoogleCloudMessagingAPIprovidedbytheGooglePlayServicesSDK.Asanexample,toaddthistoanAndroidStudioproject,openyourproject’sbuild.gradlefileandensuretheAPIisincludedasadependency,asshowninListing29-1.
Listing29-1.build.gradlefileFragmentShowingPlayServicesDependency
dependencies{//yourotherdependenciesherecompile"com.google.android.gms:play-services:3.1.+"}
ForEclipseusers,theequivalenttaskistoaddgoogle-play-services.jarasanexternallibrarydependencytoyourproject,fromyourGooglePlayServiceslibrarycollection.Lastly,anyGCMapplicationmustrunonAndroid2.2(withPlayStoreinstalled)orlater.Updateyourmanifest’suses-sdkelementtosetandroid:minSdkVersiontoatleast8.
SettingManifestPropertiesforGCMOverandabovetheminimumSDKversionrequiredforGCM,yourapplicationwillrequirespecificpermissionsinordertodothefollowing:
RegisterwithGCMserverstoreceivemessages,usingcom.google.android.c2dm.permission.RECEIVEpermission.
Usethedevice’sinternetconnectiontosendmessages,usingandroid.permission.INTERNETpermission.
Exclusivelyreservemessagesintendedfortheapplicationandpreventotherapplicationsregisteringforthem.ThisusesthecustomC2D_MESSAGEpermissionblockwiththeapplicationnameprepended.
GCM-specificpermissionsforthereceiveryouwillalsodefine,sothattheGCMserversareallowedtosendmessagestoyourapplication.Thisusesthecom.google.android.c2dm.permission.RECEIVEsetting.
NoteTheearlierincarnationofGCMwasknownasC2DM,orCloudtoDeviceMessaging.ThusthereferencestotheearliernameofC2DMandC2D.
Thereceiveryoudefineshoulddeclareitsintent-filtertoactoncom.google.android.c2dm.intent.RECEIVEandusetheapplicationPackagenameasitscategory.
ItcanoftenbebettertoperuseasnippetofanexampleAndroidManifest.xmlfiletoseeallofthesesettingsinplace.Listing29-2showsexamplesettingsfromthefourkeypermissionsyourGCMapplicationwillrequire.
Listing29-2.SampleAndroidManifest.xmlEntriesforaGCMAndroidApplication
<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.androidbook.gcm">
...
<uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="21"/><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.GET_ACCOUNTS"/><uses-permissionandroid:name="com.google.android.c2dm.permission.RECEIVE"/>
...
<permissionandroid:name="com.androidbook.gcm.permission.C2D_MESSAGE"android:protectionLevel="signature"/><uses-permissionandroid:name="com.androidbook.gcm.permission.C2D_MESSAGE"/>
...
<application…><receiverandroid:name=".GcmBroadcastReceiver"android:permission="com.google.android.c2dm.permission.SEND"><intent-filter><actionandroid:name="com.google.android.c2dm.intent.RECEIVE"/><categoryandroid:name="com.androidbook.gcm"/></intent-filter></receiver><serviceandroid:name=".GcmIntentService"/></application>...</manifest>
CodingYourMainActivitytoRegisterforGCM
BeforeyourapplicationcanreceivemessagesfromGCMservers(andyourserver-sideapplicationthatsendsthem),andbeforeitcansendmessagesofitsownbackthroughGCM,yourapplicationmustregisterwiththeGCMservers.ThisissotheGCMinfrastructureknowshowtorouteyourmessages,preventtrafficmix-ups,andsoon.Listing29-3showsanexampleactivityfragmentthatinitiatesregistrationintheonCreate()override.ThisexamplecodeismodeledonthegithubexampleGCMprojectGooglemakesavailableatdeveloper.android.com.
Listing29-3.RegisteringwithGCMfromJava
packagecom.google.android.gcm.demo.app;//importsfromadefaultactivity,andtheGCMspecificlibraries
publicclassGCMExampleActivityextendsActivity{
publicstaticfinalStringEXTRA_MESSAGE="message";publicstaticfinalStringPROPERTY_REG_ID="registration_id";privatestaticfinalintPLAY_SERVICES_RESOLUTION_REQUEST=9000;
StringSENDER_ID="a123b456c789d012";//RemembertouseyourID
TextViewmyMessageDisplay;GoogleCloudMessaginggcm;AtomicIntegermessageID=newAtomicInteger();Contextcontext;StringregistrationID;
@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);
setContentView(R.layout.main);myMessageDisplay=(TextView)findViewById(R.id.display);context=getApplicationContext();
//RegisterwithGCMserversgcm=GoogleCloudMessaging.getInstance(this);finalSharedPreferencesmyAppPrefs=getGcmPreferences(context);registrationID=myAppPrefs.getString(PROPERTY_REG_ID,"");//youcouldalsoperformversionandotherchecksifdesired
if(registrationID.isEmpty()){//Notregistered,dosoasyncsoasnottoblockmainthreadtry{registerAppInBackground();}catch(NameNotFoundExceptione){//logdetailshere,assomethingfailedduringregistration}}}
privatevoidregisterAppInBackground(){newAsyncTask<Void,Void,String>(){@OverrideprotectedStringdoInBackground(Void…params){StringregStatus="Unregistered";try{if(gcm==null){gcm=GoogleCloudMessaging.getInstance(context);}registrationID=gcm.register(SENDER_ID);regStatus="RegisteredwithID:"+registrationID;//addyourcalltosecurelystoretheregistrationIDforlaterreuse//...}catch(IOExceptionex){//performyourerrorhandlinghere,e.g.retry}returnregStatus;}
}.execute(null,null,null);}...
Thisisagreatlysimplifiedexamplesoyoucanfocusontheabsolutelymandatorycomponentsandbuildfromthere.OuronCreate()methodfirstinstantiatesagcmobjectandaSharedPreferencesobject.Itthenretrievestheregistration_idfromthepreferences.Atthispointyourapplicationmaynotberegistered,whichwouldmeanthereturnedvaluefromthepreferenceswouldbeempty.Wetestforthisemptyvalue,andwhereanemptyregistration_idisdetectedweinitiateourregistrationprocessbyinvokingtheprivatemethodregisterAppInBackground().
TheimplementationofregisterAppInBackground()followsGoogle’srecommendationofperformingthefirst-timeregistrationasynchronously.Wedothisbecausewedon’twanttoblockthemainthreadwhilewewaitforthehandshakeandregistrationprocesstocomplete.Theprocesscouldpotentiallytakeseveralsecondsormore.Youcouldenhancetheapplicationherebyaddingarangeofintermediatestatusupdates,errorchecking,andmore.
Oncewehaveanapplicationthat’sregistered,wecancarryoutmessageexchange,andthenalltheotherlogicyoumightwantyourapplicationtohave,basedonordrivenbythemessagingaspect.Listing29-4showsanexamplemethodtosendamessagebasedonaBundleobjectconstructedtoholdyourmessagedetails.
Listing29-4.ExampleMessageSendingfromYourAndroidClient
privatevoidsendMessage(BundlemessagePayload){newAsyncTask<Void,Void,String>(){@OverrideprotectedStringdoInBackground(Void…params){Stringstatus="";try{Stringid=Integer.toString(messageID.incrementAndGet());gcm.send(SENDER_ID+"@gcm.googleapis.com",id,messagePayload);status="messagesent";}catch(IOExceptionex){//yourerrorhandlinghere//setstatusstring}returnstatus;}}}
ThelogicofthesendMessage()methodisentirelyconcernedwithsendingwhateveritisyouhaveconstructedinthemessagePayloadparameter.ThisBundleobjectislefttoyourimagination,butitcouldbetheinstantmessage,photo,voicemessage,orothercontentthatyourapplicationisactuallyhelpingtheuserwith.
WeonceagainuseAsyncTasktoensurewedon’tblockonmessagedelivery.Thisisauniversaldesignpatternwhenworkingwithanykindofmessagebusormessagedeliveryservice.Withintheasynchronouslogic,wegenerateauniquemessageidentifierwiththemessageID.incrementAndGet()methodandtheninvokethegcm.send()method,passingittheuniqueIDandourmessagepayload.
Errortrappingandretrylogicareeasytoaddatthispoint.IfyouaregoingtoassumeanyhighervaluetothemessageID(whichisnormallynotrecommended),itisbesttoplacetheretrylogicwithinthesendMessage()methodsoastobeabletoreusethemessage
IDgeneratedbeforeitfallsoutofscopeonmethodreturn.
CodingtheServerComponentforGCMYourthird-partyservicecanbewritteninbasicallyanylanguage,solongasitcanmakecallstotheGCMcloudendpointsandsupporttheauthorizationmessageprotocolswehavedescribedintheearliersectionsofthischapter.
BecausesuchservicesarenotstrictlyAndroidproductsorcode,wewillsavesomepreciouspagesfromthebookandpointyoutoexcellentexamplesthatGoogleprovidestogiveyouinspirationintowritingtheback-endservices.
Youcanreviewtheoptionsandapproachesforthisnon-Androidthird-partyserviceatdeveloper.android.com/google/gcm/server.html.
MovingBeyondtheGCMIntroductionSuchashortchaptercannotcovertheenormousbreadthofpossibilitiesandnuancesforGCM-basedapplications.Formoredetailsonwhatispossible,checkouttheAndroidStackExhangesite,android.stackechange.com,andthedeveloper.android.comsite.
Chapter30
DeployingYourApplication:GooglePlayStoreandBeyondCreatingagreatapplicationthatpeoplewillloveisonething,butyoualsoneedaneasywayforpeopletofindanddownloadit.GooglecreatedthePlayStoreforthispurpose.Fromaniconrightonthedevice,userscanclickstraightintothePlayStoretobrowse,search,review,anddownloadapplications.UserscanalsoaccessPlayStoreovertheInternettodothosesamethings,althoughthedownloadingisnottothecomputerbutratherappsaresentdirectlytotheuser’sdevice.Manyapplicationsarefree;forthosethatarenot,thePlayStoreprovidespaymentmechanismsforeasypurchasing.
ThePlayStoreisevenaccessiblefromintentsinsideofapplications,makingiteasyforapplicationstoreachouttothePlayStoretoguideusersintogettingwhattheyneedforyourapplicationtobesuccessful.Forexample,whenanewversionofyourapplicationbecomesavailable,youcanmakeiteasyfortheusertogostraighttothatPlayStorepagetogetorbuythenewversion.GooglePlayStoreisnottheonlywaytogetapplicationstodevices,however;otherchannelsareallovertheInternet.
TheGooglePlayStoreapplicationisnotavailablefromwithintheemulator(althoughhacksexisttomakeitavailable).Thismakesthingsalittlemoredifficultforadeveloper.IdeallyyouwillhaveadeviceofyourownthatyoucanusewithGooglePlayStore.Inthischapter,we’llexplorehowtogetyousetupforpublishingapplicationstothePlayStore;howtoprepareyourapplicationforsalethroughthePlayStore;howyoucanprotectyourselffrompiracy;howuserswillfind,download,anduseyourapplications;andfinally,alternativewaystomakeyourapplicationsavailable.
BecomingaPublisherBeforeyoucanuploadanapplicationtoGooglePlayStore,youneedtobecomeapublisher.Todoso,youmustcreateaGooglePlayPublisherAccount.Oncethat’sdone,youwillbeabletouploadyourapplicationstothePlayStoresotheycanbefoundanddownloadedbyusers.Ifyouwillbechargingmoneyforyourapp,oracceptingin-apppurchases,youwillalsoneedtosetupaGoogleWalletMerchantAccount.Googlehasmadetheprocesstogettheseaccountsrelativelypainlessandreasonablypriced.
Agoodplacetostartisthispage:http://developer.android.com/distribute/googleplay/start.htmlFromhereyoucanclickthebigStartbuttontobegintheprocess.Ifyoudon’talreadyhaveaGoogleAccountyouwillbepromptedtocreateone.Tobeapublisher,youwillalsoneedtoprovideadevelopername,ane-mailaddress,awebsiteaddress,andaphonenumberwhereyoucanbecontacted.Youwillbeabletochangethesevalueslater,onceyouraccountissetup.Youwillalsoneedtopaytheregistrationfee.Thisisdonevia
GoogleWallet.Inordertocompletethepaymenttransaction,youwillneedtouseyourGoogleaccount.
Oneoftheoptionspresentedtoyouduringthepaymentprocessis“Keepmyemailaddressconfidential.”ThisreferstothecurrenttransactionbetweenyouandGooglePlayStoretopurchasepublisheraccess.Ifyouchooseyes,you’llkeepyoure-mailaddresssecretfromGooglePlayStore.Thishasnothingtodowithkeepingyoure-mailaddresssecretfrombuyersofyourapplication.Buyers’abilitytoseeyoure-mailaddresshasnothingtodowiththisoption.Moreonthatlater.
NextupistheGooglePlayDeveloperDistributionAgreement(GPDDA).ThisisthelegalcontractbetweenGoogleandyou.Itspellsouttherulesfordistributingapps,collectingpayments,grantingrefunds,feedback,ratings,userrights,developerrights,andsoon.There’smoreontheseinthe“FollowingtheRules”sectionofthischapter.
UponacceptingtheAgreement,youwillbetakentoapagecommonlycalledtheDeveloperConsoleathttps://play.google.com/apps/publish/.
FollowingtheRulesTheGPDDAspellsoutalotofrules.Youmightwantlegalcounseltoreviewthecontractbeforeagreeingtoit,dependingonhowseriouslyyouplantooperatewithintheGooglePlayStore.Thissectiondescribessomehighlightsyoumightbeinterestedin:
YouhavetobeadeveloperingoodstandingtousetheGooglePlayStore.Thismeansyoumustgothroughtheprocessasdescribedtogetregistered,youmustaccepttheAgreement,andyoumustabidebytherulesintheAgreement.BreakingtherulescouldgetyoubarredandyourproductsremovedfromthePlayStore.
Youcandistributeproductsforfreeorforaprice.TheAgreementapplieseitherway.PaymentsmustbecollectedviaanauthorizedGooglePlayStorePaymentProcessor.ThisincludesGoogleCheckout(credit,debit,GooglePlaygiftcards),carrierbilling(e.g.,Verizon,AT&T),andPayPal.
Paidappswillincuratransactionfee,andpossiblyafeefromthedevicecarrier,tobedeductedfromthesaleprice.AsofMarch2015,thetransactionfeeis30percent,soifthesalepriceis$10,Googlecollects$3andyouget$7(assumingnocarrierfees).
ForEUcountries,Googleisrequiredtoremitthetaxesforyou.OutsideoftheEU,itisyourresponsibilitytoremitappropriatetaxestoyourtaxingauthorities.Forsomeofthosenon-EUcountries,youcanchoosetoletGoogleremitthetaxesforyou.Whenyousetupyourmerchantaccount,youspecifytheappropriatetaxratestoapplytopurchases.GoogleCheckoutwillcollecttheappropriatetaxesbasedonhowyousetupGoogleCheckout.ThismoneywillbeprovidedtoyouifGoogleisnotremittingforyou,andyoumust
remititappropriately.ForadditionalinformationonsalestaxesintheUnitedStates,tryhttp://biztaxlaw.about.com/od/businesstaxes/f/onlinesalestax.htmandwww.thestc.com.
Youareallowedtodistributeafreedemoversionofyourapplication,withanoptiontopaytounlocktheapplication’sfullsetoffeatures;however,youmustcollectthepaymentviaanauthorizedGooglePlayStorePaymentProcessor.Youarenotallowedtoredirectusersofyourfreeapplicationtosomeotherpaymentprocessortocollectupgradefees.Youcouldthinkofitthisway:ifyou’remakingmoneyviaGooglePlayStore,Googlewantsitsshare.
In-appbillingallowsanapplicationtochargefordigitalgoodsorassetsusedwithintheapplication.Adigitalassetcouldbesomethinglikeavirtualweaponornewlevelsforagame,oramusicorgraphicsfile.Thecheckoutprocessisthesameasforpurchasingapplications.
Ifyourapplicationrequiresausertohavealoginonawebserversomewhere,andthatwebserverchargestheuserasubscriptionfee,thatwebservercouldcollectthesubscriptionfeeanywayitwantsto.Inthisway,youhavedisconnectedthesubscriptionfeefromtheapplication,andit’sOKbyGoogletomaketheapplicationavailableinGooglePlayStore—aslongasyourfreeapplicationisnotdirectinguserstothewebsite.SomepeoplejustdecidetodistributetheirfreeAndroidappfromthesamewebserverastheservice,butthisdoesrequiretheusertoenableinstallationofappsfromunknownsources,whichcandiscouragesomeusersfrominstalling.
Itseemsthatyoucanusealternatepaymentprocessorstoacceptdonationsfromusersofyourfreeapp,butyoucannotcreateincentiveswithinyourapptoencouragethosedonations.
WhiletheGPDDAsaysrefundscanberequestedupto48hoursafterpurchase,asofMarch2015refundscanberequestedbytheuserupto2hoursafterpurchasingforanautomaticrefund.Refundsarenotgiventouserswhocanpreviewtheproductpriortodownload.Thisincludesringtonesandwallpapers.
GoogleCheckout,however,doesallowthedevelopertoissuearefundeveniftherefundwindowhaspassed.TheusercangototheirGooglePlayactivityhistory,andfromtherecanrequestarefundevenwellaftertheinitial2hours.Ifitislessthan48hoursfromthepurchase,therefundwillprobablybeautomatic.Otherwise,itisuptothedeveloperwhetherornottoreturnanymoney.
Youarerequiredtoprovideadequatesupportforyourproduct.Ifadequatesupportisnotprovided,userscanrequestrefundsthroughGoogle,andthesewillbechargedbacktoyou,possiblyincluding
handlingfees.
UsersgetunlimitedreinstallsofapplicationsdownloadedfromtheGooglePlayStore.Ifauserdoesafactoryresetoftheirdevice,thisfeatureallowsthemtogetalltheirappsbackwithouthavingtorepurchase.
Developersagreetoprotecttheprivacyandlegalrightsofusers.Thisincludesprotecting(securing)anydatathatmightbecollectedintheprocessofusingtheapplication.Itispossibletochangetherulesregardingusers’dataprotection,butonlybydisplayingandhavingtheuseracceptaseparateagreementbetweenyouandthatuser.
YourapplicationmustnotcompetewiththeGooglePlayStore.GoogledoesnotwantanapplicationfromwithinGooglePlayStoretosellAndroidproductsfromoutsideGooglePlayStore,thusbypassingitspaymentprocessor.Thisdoesnotmeanthatyoucan’talsosellyourapplicationthroughotherchannels,butyourapplicationonGooglePlayStorecannotitselfbedoingthesellingofAndroidproductsoutsideofGooglePlayStore.
Googlewillassignproductratingstoyourproducts.Theratingscouldbebasedonuserfeedback,installrates,uninstallrates,refundrates,and/oraDeveloperCompositeScore.TheDeveloperCompositeScoremaybecalculatedbyGoogleusingpasthistoryacrossapplications,andthiscouldinfluencetheratingofnewapplications.Forthisreason,itisimportanttoreleasegood-qualityapplicationsassociatedwithyou,eventhefreeones.It’snotclearthattheDeveloperCompositeScoreevenexists,butifitdoesthere’snowaytoseeyours.
BysellingyourapplicationthroughGooglePlayStore,youaregrantingtheusera“non-exclusive,worldwide,perpetuallicensetoperform,displayandusetheProductonthedevice.”However,itisquitealrightforyoutowriteaseparateEndUserLicenseAgreement(EULA)thatsupersedesthisstatement.MakethisEULAavailableonyourwebsite,orprovideanotherwayforshoppersanduserstobeabletoreadit.
GooglerequiresthatyouabidebythebrandingrulesforAndroid.TheseincluderestrictionsontheuseofthewordAndroid,aswellasuseoftherobotgraphic,logo,andcustomtypeface.Formoredetails,gotohttp://developer.android.com/distribute/tools/promote/brand.html
DeveloperConsoleTheDeveloperConsoleisyourlandingpageforcontrollingyourapplicationsinGooglePlayStore.FromtheDeveloperConsole,youcansetupamerchantaccountinGoogle
Checkout(soyoucanchargeforyourapplications),uploadapplications,andgetinformationaboutyouruploadedapplications.Youcanalsoedityouraccountdetailsincludingdevelopername,e-mailaddress,webaddress,andphonenumber.Figure30-1showstheDeveloperConsole.
Figure30-1.TheGooglePlayStoreDeveloperConsole
Ifyoudonotsetupamerchantaccount,youwillbeunabletochargeforyourproductsinGooglePlayStore.Settingupamerchantaccountisnotdifficult.ClickthelinkfromtheDeveloperConsole,fillouttheapplication,agreetotheTermsofService,andyou’reallset.YouwillneedtoprovideaUSFederaltaxID(EIN),acreditcardnumberplusaUSSocialSecurityNumber(SSN),orjustacreditcardnumber.Thetaxinformationisusedtoverifyyourcreditstatustoensuretimelydeposits.ThecreditcardinformationisusedtohandlechargebacksduetobuyerdisputeswhenthereareinsufficientfundsinyourGoogleCheckoutaccount.Youcanalsosupplybankaccountinformationtoenableelectronicfundstransfersfromtheproceedsofyoursales.
NotethatGoogleCheckoutisaserviceformorethanjustGooglePlayStore.Therefore,donotgetconfusedbythetransactionfeeinformationforGoogleCheckoutfornon–GooglePlayStoresales.The30percentmentionedpreviouslyisthetransactionfeerateforGooglePlayStore.ThereisalsoadditionalGoogleCheckouttransactionfeeinformationfornon–GooglePlayStoresales,andthosedonotapplytoGooglePlayStore.
UploadingandmonitoringyourapplicationsareprobablythemainfunctionsoftheDeveloperConsolethatyouwilluse,althoughtheConsoleisalsowhereyoucansignupforaccesstoGoogleAPIsandgameservicesandlinktoyourAdWordsaccount(s).
Formonitoring,thePlayStoreprovidestoolstoseehowyourapplicationisdoingintermsoftotaldownloadsandhowmanyusersstillhaveitinstalled.Youcanseetheoverallratingofyourappsintermsof0to5stars,andhowmanypeoplehavesubmittedarating.Therearevariousreports,charts,andgraphsintheDeveloperConsolesoyoucanseehowyourapplicationisdoingindifferentversionsofAndroid,ondifferentdevices,indifferentcountries,andindifferentlanguages.
Userscansubmitcommentsinadditiontoratingyourapplication.Itisinyourbestinteresttoreadthecommentsinordertoaddressanyproblemsquickly.Includedwithacommentistheuser’sratingofyourapp,anameoftheuserastypedbythem,andthedatethecommentwassubmitted.Youareabletoreplytospecificcommentsandthatuserwillreceiveane-mailtoletthemknow.Youcanonlyleaveonereplyperreview,butyoucanalwaysedityourreplylater.Inanextremecase,whereacommentisparticularlyharmfulorinappropriate,youcancontactGooglesupportbystartinghereattheHelp
Centerwebsite:https://support.google.com/googleplay/android-developer/.
TheDeveloperConsoleallowsyoutorepublishyourapplication—forupgrades,forexample—ortounpublishtheapplication.Unpublishingdoesnotremoveitfromdevices,nordoesitevennecessarilyremovetheappfromtheGoogleservers,especiallyifit’sapaidapp.Auserwhohaspaidforyourapplicationandwhohasuninstalledit,butnotrequestedarefund,isallowedtoreinstallitlaterevenifyou’veunpublishedit.TheonlywayitistrulyunavailabletousersisifGooglepullsitduetoaviolationoftherules.
Youcanalsolookaterrorsthatweregeneratedbyyourapplicationandseeapplicationfreezesandcrashes.Figure30-2showstheCrashes&ANRsscreen.
Figure30-2.TheCrashes&ANRsscreen
Drillingintothedetailsofacrashreport,youcanseethestacktraceofthecrash,aswellaswhichtypeofdevicewasrunningtheapplicationandthetimeofthecrash.Unfortunately,youcannotcommunicatebacktotheuserwhoexperiencedtheproblemtogetadditionaldetailsortohelpthemgettheissueresolved.Youhavetohopethattheaffecteduserswillgetintouchwithyouthroughcomments,e-mail,oryourwebsite.Otherwise,you’lljusthavetofigureoutfromthecrashreportwhatwentwrongandtrytofixit.
Ifyoureallywanttoknowhowausergottoacrash,you’llwanttoimplementoneofthemobileanalyticspackagesintoyourapp.Thesewillgenerateeventrecordsasauserstepsthroughyourapplication,andwillalsoreportcrashes.Thebreadcrumbs(eventrecords)willletyouknowthestepsausertookuptothepointofthecrash.ThiscapabilityisseparatefromtheGooglePlayStore,however.
There’sonemorefeatureoftheDeveloperConsoleyoumayneedtouse:theHelpportionofthewebsite.TheHelpbuttonisintheupper-rightcorner.Clickingitshowsyousomeinlinehelp,butalsohasalinktotheHelpCenterwebsite.Therearealsolinksfor
submittinge-mailorforanonlinechat(duringbusinesshours).
We’venowintroducedyoutosomeofthenicefeaturesoftheDeveloperConsole,butyouprobablywanttogetintothemostusefulpart,whichisgettingyourapplicationsintotheGooglePlayStoresouserscanfindthemanddownloadthem.Butbeforewedothat,let’sgooverhowtoprepareyourapplicationforuploadandsale.
PreparingYourApplicationforSaleTherearequiteafewthingstothinkaboutanddototakeanapplicationfromcodecompletetoGooglePlayStore.Thissectionwillhelpyouthroughthoseitems.
TestingforDifferentDevicesWithmoreandmoreAndroiddevicesbecomingavailable,andeachonepotentiallyhavingsomenewhardwareconfiguration,itisveryimportantthatyoutestforavarietyofdevicesyouwanttosupport.Youcouldpurchasesomeofthedevicesthatyouwanttosupport,butyouprobablycan’tpurchasethemall.TherearesomeonlineservicesthatmakerealdevicesavailableovertheInternet.YourotheroptionistoconfigureAndroidVirtualDevices(AVDs)foreachtypeofdevice,specifytheappropriatehardwareconfiguration,andthentestwiththeemulatorandeachAVD.SomedevicemanufacturersmakeAndroidemulatorpackagesavailablethatarespecifictotheirdevices,socheckouttheirwebsitesfordownloadoptions.
TheAndroidSDKprovidesvariousclassestoassistwithtesting,aswellastheUI/ApplicationExerciserMonkey.Thesetoolswillhelpyoudoautomatedtestingsoyoudon’tspendforevertestingyourapplicationmanually.Seethesewebpagesformoredetails:
https://developer.android.com/tools/testing/index.htmlhttps://developer.android.com/tools/testing-support-library/index.html
Beforeyoubegintesting,youprobablywanttoremovefromyourcodeanydevelopmentartifactsthatyounolongerneed,andalsoanydevelopmentartifactsfrom/res.Youwantyourapplicationtobeassmallaspossibleandtorunasquicklyaspossiblewiththeleastamountofmemory.Finally,besuretodisableorremoveanydebuggingfeaturesfromyourapplicationthatyoudon’twantdistributedtoproduction.
SupportingDifferentScreenSizesAndroidsupportsmanyscreensizes.Inordertorunonthesmallestsize,youmustsetaspecific<supports-screens>elementasachildelementof<manifest>withintheAndroidManifest.xmlfile.Withoutthistagspecifyingthatyourapplicationsupportsthesmallscreensize,yourapplicationwillnotbevisibleinthePlayStoretodevicesthathaveasmallscreen.
Tosupportdifferentscreensizes,youmayneedtocreatealternateresourcefilesunder/res.Forexample,forfilesin/res/layout,youmayneedtocreatecorrespondingfilesin/res/layout-smalltosupportsmallscreens.Thisdoesnotmeanyoumustalsocreatecorrespondingfilesin/res/layout-largeand/res/layout-normal,sinceAndroidwilllookin/res/layoutifitcan’tfindwhatitneedsinamorespecificresourcedirectorysuchas/res/layout-large.Remember,too,thatyoucanhavecombinationsofqualifiersfortheseresourcefiles;forexample,/res/layout-small-landwouldcontainlayoutsforsmallscreensinlandscapemode.Supportingsmallscreensprobablymeanscreatingalternateversionsofdrawablessuchasicons,too.Fordrawables,youmayneedtocreatealternateresourcedirectories,takingintoaccountscreenresolutionaswellasscreensize.
Tabletsofcoursegointheoppositedirectionintermsofscreensize,usingthelabelxlarge.Thesame<supports-screens>tagasbeforeisusedtospecifyifyourapplicationwillrunonextra-largescreens,andtheattributetouseinsideofthistagisandroid:xlargeScreens.Insomecases,youmayhaveatablet-onlyapplication,inwhichcaseyouwouldspecificallyindicatethatfortheothersizes,theattributevalueisfalse.
PreparingAndroidManifest.xmlforUploadingYourAndroidManifest.xmlfilemayneedtobetweakedalittlebitbeforeyoucanuploadittoGooglePlayStore.ADTnormallyputstheandroid:iconattributeinthe<application>tag,andnotin<activity>tags.Ifyouhavemorethanoneactivitythatcanbelaunched,you’llwanttospecifyseparateiconsforeachactivitysotheusercanmoreeasilytellthemapart.Butyou’llstillneedaniconspecifiedin<application>,whichalsoservesasthedefaultactivityiconforanyactivitiesthatdon’tspecifytheirownicon.Yourapplicationwillworkfineondevicesandintheemulatorwiththeandroid:icononlyspecifiedinthe<activity>tags,butwhenGooglePlayStoreinspectsyourapplication’s.apkfilewhenuploading,itlooksforiconinformationinthe<application>tag.
GooglePlayStorepreventsuploadingyourapplicationifthepackagenameyou’veusedstartswithcom.google,com.android,android,orcom.example,butwehopeyoudidn’tuseoneofthoseinyourapplication.
Therearemanyothercompatibilitiestoconsiderforyourapplication.Somedeviceshavecameras,somedon’thavephysicalkeyboards,andsomehavetrackballsinsteadofdirectionalpads.Use<uses-configuration>and<uses-feature>tagsinyourAndroidManifest.xmlfileasneededtodefinewhathardware/platformrequirementsyourapplicationhas.GooglePlayStorewillenforcethisandnotletyourapplicationbeshowntoauseronadevicethatwon’tsupportyourapplication.Notethatthesetagsaredifferentandseparatefromthe<uses-permission>tagsoftheAndroidManifest.xmlfile.Inmostcases,youwouldendupwithbothtagsinyourAndroidManifest.xmlfile,forspecifyingthatacameraisrequired,andforspecifyingthatpermissiontousethecameraisrequired.Butnotallfeaturesrequire
permission,soitisinyourbestinteresttospecifythefeaturesyouneed.
Thereisanotherbigdifferencebetween<uses-permission>and<uses-feature>:the<uses-feature>tagcansaythatyourapplicationrequiresthatfeatureorthatyourapplicationcanfunctionwithoutit.Thatis,thereisanattributecalledandroid:requiredthatcanbesettoeithertrueorfalse;bydefaultit’strue.Ifthereisapermissionforafeature,butyoudon’tsupplythecorresponding<uses-feature>tag,thenbydefaultit’sasifyouspecified<uses-feature>andthefeatureisrequired.Forexample,yourapplicationmaytakeadvantageofBluetoothifit’savailable,butwillworkjustfineifitisnot.Therefore,inthemanifestfile,inadditiontotheBluetoothpermissionelement,you’dhavesomethinglikethis:
<uses-featureandroid:name="android.hardware.bluetooth"android:required="false"/>
Withinyourapplication’scode,youshouldmakeacalltothePackageManagertofindoutifBluetoothisavailableornot,whichyoucoulddowiththefollowing:
booleanhasBluetooth=getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
ThentakeappropriateactioninyourapplicationifBluetoothisnotthere.TheAndroiddocumentationcanbeconfusinginthisarea.IfyoulookattheDeveloperGuidepagefor<uses-feature>,youwillnotseeasmanyfeaturesasaredescribedonthePackageManagerreferencepage,whichdefinesaFEATURE_*constantforeachavailablefeature.
The<uses-configuration>tagisalittledifferent.Itspecifieswhatsortofkeyboard,touchscreen,and/ornavigationalcontrolsthedevicemusthave.Butinsteadofbeingindependentchoicessuchas<uses-feature>,youwouldputthecombinationsofconfigurationchoicestogetherintowhatyourapplicationrequires.Forexample,ifyourapplicationrequiresafive-waynavigationcontrol(thatis,aD-padoratrackball)andatouchscreen(usingeitherastylusorafinger),youwouldspecifytwotagsasfollows:
<uses-configurationandroid:reqFiveWayNav="true"android:reqTouchScreen="stylus"/><uses-configurationandroid:reqFiveWayNav="true"android:reqTouchScreen="finger"/>
LocalizingYourApplicationIfyourapplicationwillbeusedinothercountries,youmightwanttoconsiderlocalizingit.Thisisrelativelyeasytodotechnically.Findingsomeonetodothelocalizingisanothermatter.Fromthetechnicalpointofview,yousimplycreateanotherfolderunder/res—forexample,/res/values-frtoholdaFrenchversionofstrings.xml.Takeyourexistingstrings.xmlfile,translatethestringvaluestothenewlanguage,andsavethenewtranslatedfileunderthenewresourcefolderusingthesamefilenameastheoriginal
file.Atruntime,ifthedevice’slanguageissettoFrench,Androidwilllookforstringsthatwereplacedunder/res/values-fr.Ifitcan’tfindstringsfromthere,itwillthenlookforstringsfrom/res/values.
Thesametechniqueworksfortheothertypesofresourcefiles—forexample,drawablesandmenus.Imagesandcolorsmayworkbetterforyourusersiftheyaredifferentfordifferentcountriesorcultures.Forthisreason,itisagoodideatonotusetruecolornamesforyourresourcenamesforcolors.Intheonlinedocumentationforcolors,itiscommontoseesomethinglikethis:
<colorname="solid_red">#f00</color>
Thismeansthatinyourcodeorotherresourcefiles,you’rereferringtothecolorbytheactualnameofthecolor,inthiscase,solid_red.Inordertolocalizethecolortosomethingmoreappropriatefortheothercountryorculture,itwouldbebettertouseacolornamesuchasaccent_color1oralert_color.InEnglish,redmightbetheappropriatecolorvaluetouse,whileinSpanishitmightbebettertouseashadeofyellow.Becauseacolornamelikealert_colordoesnotrevealtheactualcolorthatyou’reusing,itislessconfusingwhenyouwanttochangetheactualcolorvaluetosomethingelse.Atthesametime,youcandesignapleasingcolorscheme,withbasecolorsandaccentcolors,andbemoreconfidentthatyou’reusingthecorrectcolorsinthecorrectplaces.
Menuchoicesmightneedtobechangedindifferentcountries,usingfewerormoremenuitems,orbeorganizeddifferently,dependingonwheretheapplicationisbeingused.Menusaretypicallystoredunder/res/menu.Ifyouarefacedwiththissituation,youareprobablybetteroffputtingallyourstringtextintostrings.xml,orotherfileslocatedunderthe/res/valuesdirectory,andusingstringIDsintheappropriateresourcefileseverywhereelse.Thismakesitfarlesslikelythatyouwillmisstranslatingastringvalueinsomeobscureresourcefile.Yourlanguagetranslationworkisthenlimitedtothefilesunder/res/values.
PreparingYourApplicationIconShoppersandyouruserswillseeyourapplication’siconandlabelprominentlyinbothGooglePlayStoreandontheirdeviceoncethey’vedownloadedit.Pleasetakespecialcaretocreategoodiconsandgoodlabelsforyourapplicationanditsactivities.Localizethemasnecessaryordesired.Andrememberthatfordifferentscreensizes,youriconsmayneedtobetweakedtolookgood.Checkoutwhatotherdevelopershavedonewiththeiricons,especiallythoseapplicationsinthesamecategoryasyourapplication.Youwantyourapplicationtogetnoticed,soit’sbetternottoblendinwithalltheothers.Atthesametime,youwantyouriconandlabeltoworkwellonadevicewhensurroundedbylotsofotherapplicationiconsthatdootherthings.Youdon’twantausertobeconfusedaboutwhatyourapplicationdoes,somaketheiconrepresentativeofthefunctionalityofyourapplication.
Whencreatinganyimageforyourapplication,butespeciallyyouricon,youneedto
considerthescreendensityofthetargetdevice.Densitymeansthenumberofpixelsperinch.Don’tthinkthatasmallscreenislowdensityandalargescreenishighdensity—youcouldseeanycombinationofsizeanddensity.Forahigh-densityscreen,youwillprobablychooseaniconwith72×72pixels.Themedium-densityiconwillusuallybeofsize48×48pixels.Andforextra-highdensity,it’s96×96pixels.Foralow-densityscreen,makinganiconappeartobetherightsizemeansmakingtheiconwithfewerpixels,typically36×36.Androidhelpsyouinthelow-densitycasebecauseitwillautomaticallydownscaleyourHDPIiconbyhalf,soyoudon’tneedtoprovidealow-densityiconyourself.Ingeneral,you’llfinditeasiesttoonlyworryaboutdensityforimagessuchasicons.You’llworryaboutscreensizewhendefininglayouts.
DirectingUsersBacktothePlayStoreAndroidhasaURIschemetohelpfacilitatefindingapplicationsinGooglePlayStore:market://.[GooglePlayStorewasformerlycalledAndroidMarket.]Forexample,ifyouwanttodirectyouruserstothePlayStoretolocateaneededcomponent,ortoupselltoanadditionalappthatunlocksfeaturesinyourapplication,youwoulddosomethingasshownhere,whereMY_PACKAGE_NAMEwouldbereplacedbyyourrealpackagename:
Intentintent=newIntent(Intent.ACTION_VIEW,Uri.parse("market://search?q=pname:MY_PACKAGE_NAME"));startActivity(intent);
ThiswilllaunchthePlayStoreapponthedeviceandtaketheusertothatpackagename.Theusercanthenchoosetodownloadorbuytheapplication.Notethatthisschemedoesnotworkinanormalwebbrowser.Inadditiontosearchingusingpackagename(pname),youcansearchbydevelopernameusingmarket://search?q=pub:\“FnameLname\”oragainstanyofthepublicfields(applicationtitle,developername,andapplicationdescription)inGooglePlayStoreusingmarket://search?q=<querystring>.
TheAndroidLicensingServiceThewaythatAndroidappsareconstructedunfortunatelymakesthemtargetsforpiracy.ItispossibletomakecopiesofAndroidappsthatcanthenbedistributedtootherdevices.Sohowcanyouensurethatuserswhohavenotpurchasedyourapplicationcannotrunit?TheAndroidteamhascreatedsomethingcalledtheLicenseVerificationLibrary(LVL)tomeetthisneed.Here’showitworks.
IfyourapplicationwasdownloadedviaGooglePlayStore,thentheremustbeacopyoftheGooglePlayStoreapponthedevice.Inaddition,theGooglePlayStoreapphaselevatedpermissionstobeabletoreadvaluesfromthedevicesuchastheuser’sGoogleaccountname,theIMSI,andotherinformation.TheGooglePlayStoreappwillrespondtoalicenseverificationrequestfromanapplication.YoumakecallsintotheLVLfromyourapplication,LVLcommunicateswiththeGooglePlayStoreapp,theGooglePlay
StoreappcommunicateswithGoogleservers,andyourapplicationgetsananswerbackindicatingwhetherornotthisuseronthisdeviceislicensedtouseyourapplication.ThismeanstheappmusthavebeenpurchasedthroughGooglePlayStore;otherwisetheGoogleserverswon’tknowaboutit.Therearesettingsunderyourcontroltodecidewhattodoifthenetworkisunavailable.AfulldescriptionoftheprocessofimplementingLVLcanbefoundathttps://developer.android.com/google/play/licensing/index.html
Onethingtobeawareof,though,isthattheLVLmechanismissubjecttohacking.Ifsomeonecangettoyourapplication’s.apkfile,theycandisassembletheappandthenpatchitiftheyknowwheretolookforthereturnvaluefromtheLVLcall.IfyouusetheobviouspatternofaswitchstatementaftergettingtheresponsefromLVL,tobranchtotheappropriatelogicbasedonthereturncode,ahackercansimplyforceasuccessfulreturncodevalue,andtheyownyourapp.Forthisreason,theAndroidteamhighlyrecommendsthatyouimplementobfuscationofyourapptohidethepartofyourapplicationwhereyoucheckthereturncodefromLVL.Thisgetsfairlycomplicated,asyoucanimagine.
UsingProGuardforOptimization,FightingPiracyGoogleprovidessomesupportforobfuscationintheformoftheProGuardfeature.ProGuardisnotaGoogleproduct,buthasbeenintegratedintoADTandAndroidStudiosoit’seasytouse.ProGuarddoesmorethanjustprovideobfuscationforfightingpiracy;italsomakesyourapplicationsmallerandfaster.Itdoesallthisbystrippingoutdebugginginformation,cuttingoutcodethatwillneverrun,andchangingnames(ofclasses,methods,andsoon)tomeaninglessstrings.Examplesofcodethatwillneverrunincludelibraryclassesandmethodsthatarenevercalled,andloggingthatdependsonaconstantthatyousettofalse(forproduction).Itcanalsorecognizeoptimizationssuchasbinary-shiftingavalueleftbyonebitpositioninsteadofmultiplyingitby2.Bystrippingoutdebugginginformationandchangingthenames,theresultingcompiled.apkfilewon’trevealvariablenames,classnames,methods,andsoon,soitbecomesextremelydifficulttofigureoutwhatthecodedoesandthereforehowtostealit,modifyit,andreleaseitassomethingelse.
Whenyoucreateyourapplication,itshouldautomaticallygetaproguard-project.txtfile.ThedefaultfilewilllooksomethinglikeListing30-1.
Listing30-1.Sampleproguard-project.txtFile
#ToenableProGuardinyourproject,editproject.properties#todefinetheproguard.configpropertyasdescribedinthatfile.##AddprojectspecificProGuardruleshere.#Bydefault,theflagsinthisfileareappendedtoflagsspecified
#in${sdk.dir}/tools/proguard/proguard-android.txt#YoucanedittheincludepathandorderbychangingtheProGuard#includepropertyinproject.properties.##Formoredetails,see#http://developer.android.com/guide/developing/tools/proguard.html
#Addanyprojectspecifickeepoptionshere:
#IfyourprojectusesWebViewwithJS,uncommentthefollowing#andspecifythefullyqualifiedclassnametotheJavaScriptinterface#class:#-keepclassmembersclassfqcn.of.javascript.interface.for.webview{#public*;#}
Youalsoneedtouncommenttheproguard.configpropertyintheapplication’sproject.propertiesfiletothelocationoftheproguard-project.txtfile.Thelinelookslikethis:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
Asyoucansee,thereisastocksetofProGuardconfigurationsprovidedtoyoubyafileunderthetools/proguarddirectoryoftheAndroidSDK.YoucanthenaugmenttheProGuardconfigurationintheproguard-project.txtfileaspartofyourapplicationproject.Notethattheprovidedconfigurationdoesnotinfactenableoptimizations,astheserequiremoretestingtobesurethatyourapplicationstillworkscorrectly.Ifyouwanttotryoptimizations,changethereferenceintheproject.propertiesfileto${sdk.dir}/tools/proguard/proguard-android-optimize.txt.
Asmentioned,ProGuarddoesitsworkbystrippingstuffout.Sometimesitstripsouttoomuch,andthatiswhyyouseethe-keepoptionsspecifiedintheproguard-android.txtfile.Whenyouproducean.apkfile,youneedtotestittomakesureProGuarddidn’ttakeouttoomuch.Ifyoufinderrorsduetomissingclassesormethods,youcanedittheproguard-project.txtfiletoincludeanother-keepoptionfortheitemyou’remissing.Rebuildyour.apkfile,andtestagain.WerecommendusingtheExportSignedApplicationPackageoptionundertheAndroidToolsmenuoptioninEclipse,becauseitwilltakecareofcallingProGuardforyouasitbuildsthe.apkfile.Exportingiscoveredinthenextsection.
YoucanalsoconfigureAnttoobfuscateusingProGuardifyouuseAnttodoyourbuilds.
WhenProGuarddoesitsthing,you’llgetafilecalledmapping.txtalongwithyour.apkfile.Hangontothisfilebecauseyouwillneedittode-obfuscateastacktracefromyourapplication.IfyouuseEclipsetoexportyour.apkfile,youwillseeanewproguarddirectorycreatedwithinyourEclipseproject.Themapping.txtfilewillbeinthere.Thecommandtouseisretrace,andit’slocatedintheAndroidSDKdirectoryundertools/proguard/bin.Theargumentstoretraceincludethemapping.txtfileandthestacktracefile,butbeawarethatyouneedtospecifythefullpathnametoeach.Also,youshouldkeeptrackofwhichversionofyourapplicationgoeswithwhichmapping.txtfile.
Onemorecautionabouttestingyourapplication.AndroidKitKatintroducedanexperimentalruntimeenginecalledtheAndroidRunTime(ART),andinLollipopitbecametheoneandonlyruntimeengine.Youshouldtestyourapplicationwithboth,especiallyifyouuseProGuardanddooptimizations.
PreparingYour.apkFileforUploadingTogetyourtestedapplicationreadyforuploading—thatis,tocreatethe.apkfiletoupload—youneedtocreateasignedexportofyourapplication.Thiscanbedoneanumberofways,butthesimplestaretousethebuilt-inIDEfeatures.ForEclipseyouwouldright-clickontheprojectnameandchooseAndroidTools ExportSignedApplicationPackage….ForAndroidStudio,youwouldselecttheprojectnameandchoosetheBuildmenu GenerateSignedAPK…FollowthedialogstochooseapropersigningcertificatekeyandcreateyourproductionAPK.
UploadingYourApplicationUploadingiseasytodobuttakessomepreparation.Beforeyoubeginanupload,therearesomethingsyouwillneedtohavereadyanddecisionsyouhavetomake.Thissectioncoversthatpreparationandthosedecisions.Then,whenyou’vegoteverythingyouneed,gototheDeveloperConsoleandchoose+Addnewapplication.You’llbepromptedtosupplylotsofinformationaboutyourapplication,thePlayStorewillrunsomeprocessingonyourapplicationandtheinformation,andthenyourapplicationwillbereadytopublishtothePlayStore.
Theprevioussectioncoveredpreparingyourapplication.apkfileforuploading.Makingyourapplicationattractivetoshoppersrequiressomemarketingonyourpart.Youneedgooddescriptionsofwhatitisanddoes,andyouneedgoodimagessoshoppersunderstandwhattheymightdownload.
TheGooglePlayStoreunderstandsthatyoucouldmarketyourapplicationindifferentcountries.Therefore,youhavetheabilitytoprovidetextandgraphicslocalizedforthedifferentcountrieswithjustoneapplication.
Graphics
You’llbeaskedtouploadscreenshotsforyourapplication.TheeasiestwaytocapturescreenshotsofyourapplicationistouseDDMS.FireupEclipse,launchyourapplicationintheemulatororonarealdevice,andthenswitchEclipseperspectivestoDDMSandtheDeviceview.FromwithintheDeviceview,selectthedevicewhereyourapplicationisrunningandthenclicktheScreenCapturebutton(itlookslikealittlecameraintheupper-rightcorner)orchooseitfromtheViewmenu.Ifyouhaveachoicewhensaving,choose24-bitcolor.TheAndroidDeviceMonitorisverysimilartoDDMSandisavailableasastand-alonetool(calledmonitor)fromundertheSDKtoolsdirectory,orfromtheToolsmenuofAndroidStudio.
GooglePlayStorewillconvertyourscreenshotstocompressedJPEG;startingwith24-bitwillproducebetterresultsthanstartingwith8-bitcolor.Choosescreenshotsthatwillmakeyourapplicationstandoutfromtherestbutthatalsoshowtheimportantfunctionality.Youmustsupplyatleasttwoscreenshots,andyoucanprovideuptoeight.Beawarethatyouhavetheabilitytouploadscreenshotsforyourapplicationforotherlanguages.Ifyourapplicationhasbeenlocalizedforanothercountryand/orlanguage,you’llwantthescreenshotstocorrespond.
Nextupisahigh-resapplicationicon.Thiscouldbetheexactsamedesignasyourapplicationicon,butGooglePlayStorewantsa512×512pixeliconimage.Thisisrequired.
Thefeaturegraphicisrequiredandisalarge1024×500pixelsinsize.ThisgraphicisusedintheFeaturedsectionofGooglePlayStoresoyouwantthistolookreallygood.
Youcanprovideapromotionalgraphicaswell,butitssizeissmallerthanascreenshot.Althoughthisgraphicisoptional,itisagoodideatoincludeit.Youneverknowwhenthegraphiccouldbedisplayed;withoutone,youdon’tknowwhatwillbedisplayedinitsplace,ifanything.OneplacethePromoGraphicappearsisatthetopofyourapplication’sDetailspageinGooglePlayStore.
Bythetimeyoureadthis,therecouldbeothergraphicsyoucouldupload.Forexample,GooglenowacceptsaTVBannergraphicforappsthatwouldbeviewedonaTV.
ThelastbitofgraphicsrelatedtoyourapplicationisanoptionalvideothatyoucanputoutonYouTubeandlinktofromyourGooglePlayStorepage.
ListingDetailsTheGooglePlayStoreasksfortextualinformationaboutyourapplicationtodisplaytoshoppers,includingthetitle,shortdescription(formerlycalledpromotionaltext),andfulldescription.
There’saShortDescriptionfieldthathasonly80characters,andit’smandatory.WhenyourappisshownatthetopofalistinGooglePlayStore,it’sthePromoGraphicandtheShortDescriptionthatgetdisplayed.
Thefulldescriptionisalsomandatory,anditallowsupto4,000characters.IfyouhavewrittenaseparateEULAforyourusers,providealinktoitinyourfulldescriptiontextsoshopperscanviewitpriortodownloadingyourapplication.Considerthatshopperswill
likelyusesearchtolocateapplications,sobesuretoputappropriatewordsintoyourtexttomaximizeyourhitrateonsearchesrelatedtoyourapplication’sfunctionality.It’sworthwhiletoputashortcommentinthetextthatsaystoe-mailyouiftheuserrunsintoproblems.Withoutthissimpleprompt,peoplearemorelikelytoleaveanegativecomment,andanegativecommentreallylimitsyourabilitytotroubleshootandsolvetheproblem,ascomparedtoane-mailexchangewiththeaffecteduser.
Onedrawbacktotheusercommentsmechanismdescribedearlieristhatitdoesnotdistinguishtousersthespecificversionofyourapplication.Ifnegativereviewsarereceivedagainstversion1,andyoureleaseversion2witheverythingfixed,thereviewsfromversion1arestillthere,andshoppersmaynotrealizethatthosecommentsdon’tapplytothenewversion.Whenreleasinganewversionofanapplication,theapplicationrating(numberofstars)doesnotgetreset,either.Partlyforthisreason,GooglestartedprovidingaRecentChangestextfieldwhereyoucandescribewhat’snewinthisrelease.Thisiswhereyoucouldindicatethatacertainproblemhasbeenfixedortellwhatthenewfeaturesare.ThePlayStorealsoprovidestheabilitytoseejustthereviews/commentsforthelatestversion,butbydefaultthereviewsandcommentsareshownforallversions.
Oneofyourresponsibilitieswhenwritingthetextforyourapplicationistodisclosethepermissionsthatarerequired.Thesearethesamepermissionsassetinthe<uses-permission>tagsofyourAndroidManifest.xmlfilewithinyourapplication.Whentheuserdownloadsyourapplicationtotheirdevice,AndroidwillchecktheAndroidManifest.xmlfileandasktheuseraboutalloftheuses-permissionrequirementsbeforecompletingtheinstall.Soyoumightaswelldisclosethisupfront.Otherwise,yourisknegativereviewsfromuserssurprisedthatanapplicationrequiressomepermissionthattheyarenotpreparedtogrant,nottomentiontherefunds,whichalsocountagainstyourDeveloperCompositeScore.Similartopermissions,ifyourapplicationrequiresacertaintypeofscreen,acamera,orotherdevicefeature,thisshouldbedisclosedinyourtextdescriptionsofyourapplication.Asabestpractice,youshoulddisclosenotonlywhatpermissionsandfeaturesyourapplicationneeds,butalsowhatyourapplicationwilldowiththem.Youshouldanswertheuser’squestioninadvance:whydoesthisapprequireX?
Whenuploadingyourapplication,youwillneedtochooseanapplicationtypeandacategory.Asthesevalueschangewithtime,wewon’tlistthemhere,butit’seasytogototheAddnewapplicationscreentoseewhattheyare.
PublishingOptionsYoumustchoosetwocontentratings.Theideaistogiveconsumersanideaoftheappropriatenessofanapplicationforcertainagegroups.Thescaleforthefirst(older)contentratingincludesHigh,Medium,andLowMaturity,andEveryone.Choosingtherightleveldependsonthecontentinyourapplicationandhowmuchofthatcontentthereis.Googlehasrulesaboutlocation-awarenessandpostingorpublishinglocations.It’sbesttoreadtherulesforyourselfhere:https://support.google.com/googleplay/android-developer/answer/188189.Thesecondcontentratingisderivedafteryou
completeaquestionnaire.Youwillactuallygetseveralcontentratings,bycountry,dependingonhowyouanswerthequestionnaire.Thequestionnairetakessomeofthesubjectivityoutofthecontentrating.
Nextyousetthepriceofyourapplication.BydefaultthepriceisFree,andyoumusthavepreviouslysetupaMerchantAccountinGoogleCheckoutifyouwanttochargeforyourapplication.Settingtherightpriceforanapplicationistricky,unlessyou’vegotsomesophisticatedmarketresearchcapabilities,andeventhenit’sstilltricky.Pricessettoohighcouldturnpeopleoff,andyourisktheeffectsofrefundsifpeopledon’tfeelthepricewasworthit.Pricessettoolowcouldalsoturnpeopleoffbecausetheymightthinkit’sacheapapplication.
Oneofthelastdecisionstomakebeforeuploadingyourapplicationistochoosethelocationsandcarriersforyourapplicationtobevisibleto.BychoosingAll,yourapplicationwillbeavailableeverywhere.However,youmaywanttorestrictdistributiongeographicallyorbycarrier.Dependingonwhatfunctionalityisinyourapplication,youmayneedtorestrictbylocationinordertocomplywithUSexportlaw.Youmaychoosetorestrictyourapplicationbycarrierifyourapplicationhascompatibilityissueswithcertaincarriers’devicesorpolicies.Toseecarriers,clicktheShowoptionslinknexttothecountry,andtheavailablecarriersforthatcountrywillbedisplayed,allowingyoutochoosetheonesyouwant.ChoosingallalsomeansthatanynewlocationsorcarriersthatGoogleaddswillautomaticallyseeyourapplicationwithnointerventionfromyou.
Inadditiontocountryandcarrierchoices,GooglePlayStorealsoallowsyoutorestrictyourapplicationtocertaindevices.Bydefault,thedeviceslistisfilteredbasedonyourmanifestfile,inwhichyou’vespecifiedthefeaturesandsoonthatyourapplicationrequires.ThissectionoftheUploadscreenallowsyoutofurtherrestrictotherdevices.Youwouldprobablyonlywanttodothisiftherewasaknownissuewithaparticulardevicesuchthatyouwereunabletogetyourapplicationtoworkonthatdeviceeventhoughitoughtto.
AndroidalsoofferstheoptiontouploadmultipleAPKsforthesameapplication.ItenablesyoutohaveasingleentryonGooglePlayStorebuttohaveseparatebuildforphonesandtablets.Seehttp://android-developers.blogspot.com/2011/07/multiple-apk-support-in-android-market.htmlandhttp://developer.android.com/google/play/publishing/multiple-apks.html.
ContactInformationEventhoughyourdeveloperprofilecontainsyourcontactinformation,youcansetdifferentinformationwhenuploadingeachapplication.ThePlayStoreasksforawebsite,e-mailaddress,andphonenumberascontactinformationrelatedtothisapplication.Youmustsupplyatleastoneofthesesobuyerscangetsupport,butyoudon’tneedtosupplyallthree.Itisagoodideatonotuseyourpersonale-mailaddresshere,justasyouprobablywouldn’treallywanttogiveoutyourpersonalphonenumber.Whenyou’vemademillionsofdollarsfromsellingyourapplication,you’llwanttoletsomeoneelse
receiveanddealwiththee-mailsfromusers.Bysettingupanapplication-supporttypeofe-mailaddressinadvance,youcaneasilyseparatethesupporte-mailsfromyourpersonale-mails.Ofcourse,youcanalwayschangethesevalueslaterifyouneed/wantto.
ConsentWithallthesedecisionsmade,youmustthenattestthatyourapplicationabidesbyAndroid’sContentGuidelines(basicallynonastystuff)andmakeasecondattestationthatthesoftwareisOKforexportfromtheUnitedStates.USexportlawsapplybecauseGoogle’sserversarelocatedinsidetheUnitedStates,evenifyouareoutsideoftheUnitedStates,andevenifbothyouandyourcustomerareoutsideoftheUnitedStates.Rememberthatyoucanalwayschoosetodistributeyourapplicationthroughotherchannels.Whenallyourinformationisinandyourgraphicsareuploaded,goaheadandclicktheSavebutton.Thiswillprepareeverythingforyourapplicationtobereadytogolive.
YoucanthenpublishyourapplicationbyclickingthePublishbutton.GooglePlayStorewillperformsomechecksonyourapplication—forinstance,checkingyourapplication’scertificatefortheexpirationdate.Ifallgoeswell,yourapplicationwillsoonbeavailablefordownload.Congratulations!
UserExperienceonGooglePlayStoreThePlayStoreapphasbeenavailableondevicesforsometimenow,anditisavailableovertheInternet.Developersdon’thaveanycontroloverhowGooglePlayStoreworks,otherthantoprovidegoodtextandgraphicsfortheirapplication’slistinginthePlayStore.Therefore,theuserexperienceisprettymuchuptoGoogle.Fromadevice,ausercansearchbykeyword;lookattopdownloadedapplications(bothfreeandpaid),featuredapplications,ornewapplications;orbrowsebycategories.Oncetheyfindanapplicationtheywant,theysimplyselectit,whichpopsupanitemdetailsscreenallowingthemtoinstallitorbuyit.BuyingwilltaketheusertoGoogleCheckouttoconductthefinancialpartofthetransaction.Oncedownloaded,thenewapplicationshowsupwithalltheotherapplications.
FromtheInternetwebsiteforGooglePlayStore(https://play.google.com),theuserinterfacelooksaboutthesame,albeitmuchlargerthanmostdevicescreens.Onedifferenceisthattheweb-basedGooglePlayStoreexpectstheusertologintotheirGoogleaccounttousethePlayStore.ThisallowsGoogletoconnectyourwebexperienceonGooglePlayStoretoyouractualdevice.Thismeanstwothings:whenusingthewebsite,GooglePlayStoreknowswhatapplicationsarealreadyinstalledonyourdevice;andwhenyoumakeapurchaseontheGooglePlayStorewebsite,thedownloadcanbesenttoyourdevice(ordevices)andnottowhatevercomputeryouhappentobebrowsingon.
GooglePlayStorehasanoptiontoviewdownloadedapplicationsinMyApps.Thisareacontainsallinstalledappsandanyappsthatyou’vepurchased,evenifyou’veremovedthem(perhapsyouremovedthemjusttomakeroomforotherapplications).Thismeansyoucoulddeleteapaidappfromyourphoneandthenreinstallitlaterwithouthavingto
repurchaseit.Ofcourse,ifyouoptedforarefund,theappwillnotshowupinMyApps.
ThelistofappsinMyAppsistiedtoyourGoogleAccountusedacrossallyourdevices.Thismeansyoucouldswitchtoanewphysicaldeviceandstillhaveaccesstoalltheappsyou’vepaidfor.Butbeware.SinceyoumighthavemultipleidentitieswithGoogle,youmustusetheexactsameidentityasbeforetogetyourappsonanewdevice.WhenviewingappsinMyApps,anythathaveupgradesavailablewillindicatethisandallowyoutogettheupgrade.
GooglePlayStorefiltersapplicationsavailabletousers.Itdoesthisinanumberofways.UsersinsomecountriescanonlyseefreeapplicationsbecauseofthecommercelegalitiesinvolvedforGoogleinthatcountry.Googleistryinghardtoovercomecommercehurdlessoallpaidappswillbeavailableeverywhere.Untilthattimecomes,usersinsomecountrieswillbeunabletoaccesspaidapps.UserswithdevicesrunningolderversionsofAndroidwillnotbeabletoseeapplicationsthatrequireanewerversionoftheAndroidSDK.Userswithdeviceconfigurationsthatarenotcompatiblewiththerequirementsoftheapplication(expressedvia<uses-feature>tagsintheAndroidManifest.xmlfile)willnotbeabletoseethoseapplications.Forexample,applicationsnotspecificallysupportingsmallscreenscannotbeseeninGooglePlayStorebyusersondeviceswithsmallscreens.Thisfilteringismostlyintendedtoprotectusersfromdownloadingapplicationsthatwillnotworkontheirdevice.
IfyouarepurchasingappsinGooglePlayStorefromothercountries,yourtransactionmaybesubjecttocurrencyconversion,whichcanalsocarryanadditionalfee,unlessthesellerhasspecifiedpricinginyourlocalcurrency.You’rereallypurchasingusingtheGoogleCheckoutfromtheseller’scountry.GooglePlayStorewilldisplayanapproximateamount,buttheactualchargescouldvarydependingonwhenthetransactionisplacedandwithwhichpaymentprocessor.Buyersmaynoticeapendingtransactionagainsttheiraccountforasmallamount(forexample,US$1).ThisisdonebyGoogletoensurethatthepaymentinformationprovidediscorrect,andthispendingchargewillnotactuallygothrough.
AfewwebsitesareavailablethatmirrortheGooglePlayStoreapplistings.Shopperscansearch,browsecategories,andfindoutaboutGooglePlayStoreapplicationsovertheInternetwithouthavingadevice.ThisgetsaroundthefilteringthatGooglePlayStoredoesbasedonyourdeviceconfigurationandlocation.However,thisdoesnotgetappsontoyourdevice.Examplesofthesemirrorsitesarewww.androlib.com,andwww.appszoom.com.
BeyondGooglePlayStoreGoogle’sPlayStoreisnottheonlygameintown.YouarenotforcedintousingGooglePlayStoreatall.Youshouldconsiderutilizingotherchannelsofdistribution,notonlytomakeyourappavailabletomorepeopleinmorecountries,butalsototakeadvantageofotherpaymentprocessorsandopportunitiestomakemoney.
ThereareAndroidappstorescompletelyseparatefromGooglePlayStore,thebiggestofwhichisprobablyAmazon.OtherexamplesofAndroidappstoresare
http://mall.soc.io/apps,http://slideme.org,www.getjar.com,andhttps://f-droid.org/.Fromthesesites,youcansearch,browse,findoutaboutapps,andalsodownloadapps,eitherfromadeviceorviaawebbrowser.Thesesitesdon’thavetoabidebyGoogle’srules,includingthetransactionfeesforpaidappsandmethodsofpayment.PayPalandotherpaymentprocessorscanbeusedtopurchaseappsontheseseparatesites.Thesesitesalsodon’tnecessarilyrestrictbylocationordeviceconfiguration.SomeofthemprovideanAndroidclientthatcanbeinstalled,orinsomecasesmaycomepreinstalledonadevice.Userscansimplylaunchabrowserontheirdeviceandfindtheapptheywanttodownloadviathewebsite;whenthefileissavedtothedevice,Androidknowswhattodowithit.Thatistosay,adownloaded.apkfileistreatedasanAndroidapplication.IfyouclickitintheDownloadhistoryofthebrowser(nottobeconfusedwithMyApps,coveredearlier),youwillbepromptedtoseeifyouwanttoinstallitornot.ThisfreedommeansyoucansetupyourownmethodsofdownloadingAndroidapplicationstousers,evenfromyourownwebsiteandwithyourownpaymentmethods.Youmuststilldealthoughwithcollectinganynecessarysalestaxandremittingittotheappropriateauthorities.
WhilenotrestrictedbyGoogle’srules,thesealternatemethodsofappdistributionmaynotofferthesamesortofbuyerprotectionsthatarefoundinGooglePlayStore.Itmaybepossibletopurchaseanapplicationthroughanalternatemarketthatwillnotworkonthebuyer’sdevice.Buyersmaybeatgreaterriskofmalwareonthealternatemarkets.Thebuyermayalsoberesponsibleforcreatingbackups,incasetheylosetheapplicationfromtheirdevice,orfortransferringapplicationsiftheyswitchtoanewdevice.
Theseothermarketsallowyoutomakemoneyonthesaleofeachapp.You’vealsogottheabilitywithintheseothermarketstoimplementalternatepaymentmechanisms,ortoimplementadsandmakemoneythatway.
RememberthatGoogledoesnotrestrictdevelopersfromsellingtheirapplicationsinmultiplemarketsatthesametimetheysellthroughGooglePlayStore.Soconsiderallyouroptionstomakethemostofyourefforts.
ReferencesHerearesomehelpfulreferencestotopicsyoumaywishtoexplorefurther:
http://developer.android.com/guide/topics/manifest/manifest-intro.html:TheDeveloperGuidepagetotheAndroidManifest.xmlfile,withdescriptionsofhowtousethesupports-screens,uses-configuration,anduses-featuretags.
http://developer.android.com/guide/practices/screens_support.htmlTheDeveloperGuidepage“SupportingMultipleScreens,”whichcontainslotsofgoodinformationondealingwithdifferentscreensizesanddensities.
http://developer.android.com/design/style/iconography.htmlTheDesignGuidepage“Iconography,”whichcontainslotsofgoodinformationondesigningeffectiveiconsforyourapplication.
http://android-developers.blogspot.com/2010/09/securing-android-lvl-applications.htmlandhttp://android-developers.blogspot.com/2010/09/proguard-android-and-licensing-server.html:BlogpostsonhowtousetheLicenseVerificationLibrary(LVL)inwaysthatpreventpiracy.
http://proguard.sourceforge.net/:ThemainsiteforProGuard,whichincludesdocumentation.
SummaryYouarenowequippedtotakeontheworldwithyourAndroidapplications!Hereisarundownofthetopicswecoveredinthischapter:
HowtogetestablishedasaGooglePlayStorePublisher(thatis,Developer)soyoucanpublishtoGooglePlayStore.
TherulesaslaidoutintheGooglePlayDeveloperDistributionAgreement.
GivingGoogleitsshareofyourrevenueifyouaresellingthroughGooglePlayStore.WealsodiscussedhowGoogledoesnotwanttoseecompetitionfromwithinthePlayStore.
Yourresponsibilityforpayingtaxesonrevenuesfromyourapplications.
TheGooglePlayStorerefundpolicy,boththepublishedandtherealone.
Howuserscangetcopiesofyourapplicationanytimeinthefutureaslongastheypaidforitonce.
TheAndroidbrandingrules.Makesureyoudon’tviolateanycopyrightassociatedwithAndroid,images,orfonts.
TheDeveloperConsoleanditsfeatures.TheDeveloperConsolecollectsuserfeedbackanderrorreportsfromusers.
Preparingyourapplicationforproduction,includingtesting,LVLandProGuardtofightpiracy,andusingresourcevariationsandtagsinAndroidManifest.xmltofilterwhichdevicesyourapplicationwillbeavailableto.
Adviceregardinglocalizingyourapplicationbylanguageand/orculture.
TheGooglePlayStoreuserinterface,bothondeviceandontheInternet/Web.
ThefactthatGooglePlayStoreisnottheonlygameintown,andthatyoucansellyourapplicationinotherplacesontheInternet,allatthesametime.
IndexA
Accelerometers
coordinatesystem
deviceangle
displayorientation
gravity
landscapemode
magneticfieldsensor
AccountsFunctionTester
acos()method
Actionbar
definition
list-basedactionbar
searchviewwidget
manifestfile
menuitem
searchableXMLfile
searchresultsactivity
searchtarget
standardactionbar
tabbedactionbar
ACTION_DOWNevent
ACTION_DRAG_ENDED
ACTION_DRAG_ENTERED
ACTION_DRAG_EXITED
ACTION_DRAG_LOCATION
ACTION_DRAG_STARTED
ACTION_DROP
ACTION_MOVEevent
ACTION_UPevent
Activity.getResources()
Activity.managedQuery()method
Activity.onCreate()
Adapters
ArrayAdapter
createFromResource()method
notifyDataSetChanged()method
classhierarchy
GalleryControl
GridViewcontrol
definition
JavaImplementation
ListAdapter
setContentView()
ListViewcontrol
AcceptUserInput
additionalbutton
codeimplementation
doClick()method
getCheckItemIds()method
getItemAtPosition()method
LinearLayout
ListActivity
makeText()method
notifyDataSetChanged()method
onItemClick()method
setListAdapter()method
simple_list_rowlayout
UIdefinition
SimpleCursorAdapter
SpinnerControl
addProfileContact()
AggregatedContact()
Aggregatedcontacts
cursorfor
URI-basedcursor
definition
listContacts()
LookupURI-BasedCursor
Alarmmanager
broadcastreceiver
Calendarobject
IntentPointing
PendingIntentclass
persistence
repeatingalarm
RTCtime
setExact()method
set()method
TestReceiver
am.getAccounts()
@android\:id/empty
@android\:id/listview
Androidactivitylifecycle
activitycallbacks
ObjectonRetainNonConfigurationInstance()
voidonCreate
voidonDestroy()
voidonPause()
voidonRestart()
voidonRestoreInstanceState
voidonResume()
voidonSaveInstanceState
voidonStart()
voidonStop()
Androidappstores
Androidarchitecture
calculatorapplication
ACTION_GET_CONTENT
activitylifecycle.Androidactivitylifecycle
directorystructure
implicit/explicit
intentactivities
intent/activities
intents
ProgrammingLogic(seeProgrammingLogic)
realdevice
savingstate
UI.UserInterface(UI)
finesseapps
integration
robustapplications
UIessentials
Androidcustomadapter
ManateeAdapter
GridView
javaimplementation
XMLlayout
AndroidDevelopmentTools(ADT)
Androideclipseenvironment
AndroidSDK
eclipse4.2
installingADT
JDK6
PATH
pointingADT
toolswindow
AndroidInterfaceDefinitionLanguage(AIDL)
definition
IStockQuoteServiceinterface
bindService()method
Compiler-GeneratedJavaFile
implementation
MainActivity.javafile
manifestdeclaration
ServiceConnectioninterface
StockQuoteClient
unbindService()method
Messenger/Handler
clientactivitycode
clientfragmentcode
servicecode
nonprimitivetypes
Parcelableinterface
AndroidManifest.xmlfile
implementation
IStockQuoteService.aidl
MainActivity.java
main.xml
Person.aidlfile
StockQuoteService2implementation
StockQuoteService2layout
Androidlayoutmanagers
definition
FrameLayout
GridLayout
LinearLayout
gravity
gravityvs.layout_gravity
horizontalconfiguration
textfields
weight
weightconfigurations
RelativeLayout
TableLayout
AndroidManifest.xmlfile
Androidresources
applicationclass
applicationcontext
arbitraryXMLfile
assetsdirectory
directorystructure
drawableXMLresourcefile
Javacode
qualifiers
rawresourcefile
AndroidRunTime(ART)
Androidsecuritymodel
digitalcertificate
keypair
keystore
runtimesecurity
Androidmanifesteditortool
featuresandresources
processboundary
requiredpermission
URIpermissions
signingapplications
adbtool
debug.keystorefile
exportwizard
installingupdates
jarsignertool
self-signedcertificate
VeriSign
zipaligntool
Androidservice
AIDL(seeAndroidInterfaceDefinitionLanguage)
AsyncTask
local
AndroidManifest.xmlfile
BackgroundService.java
bindService()
Context.startService()
definition
displayNotificationMessage()method
drawablefile
e-mailapplication
IntentService
interrupt()method
MainActivity
main.xmllayoutfile
onBind()method
onCreate()method
onDestroy()method
onStartCommand()method
ServiceWorkerclass
startIdparameter
startService()
stop*()methods
ThreadGroupclass
remote
bindService()
definition
languagetranslationapplication
RPCmechanism
Android’sfundamentalcomponents
activity
AndroidManifest.xml
AVD
contentprovider
fragment
intent
service
view
Androidsoftwaredevelopmentkit(SDK)
JDK
packages
savingstate
contentproviders
externalfiles
internalfiles
networkstorage
O/Rmappinglibraries
sharedpreferences
SQLite
tools
Androidstudio
definition
homescreen
Javainstallation
Androidstyles
definition
EditText
parentstyle
TextView
Androidthemes
Androidvirtualdevice(AVD)
animate()method
Animation
APIresource
types
AnimationBuilder
AnimatiorSetBuilder
AnimatorSet
apkfile
ApplicationNotResponding(ANR)
Applicationprogramminginterface(API)
AppWidgetProviderInfoclass
argsBundleargument
AsyncTask
activitypointer
devicerotation
implementation
concretetypes
definition
doInBackground()method
execute()method
onPostExecute()callbackmethod
onPreExecute()method
parameters
progressdialog
publishProgress()method
sourcecode
threadpools
manageddialogs
pseudocode
retainedobjectsandfragmentdialogs
activitylifecycle
AsyncTesterFragmentobject
attach()method
fragmentapproach
keycodesnippets
layoutfile
onCreateDialog()method
onDestroy()method
onRetainNonConfigurationInstance()method
onStart()method
pseudocode
releaseResources()method
retainedheadlessfragment
rootRADO
sampling
setProgress()method
AsyncTaskLoader
autoPause()method
BBackgroundService’sonDestroy()method
BaseAdapter
BDayWidgetModelclass
bindService()method
BirthdayWidget
Bluedot
BooleanButtonclass
Broadcastreceiver
in-processreceivers
long-runningservices
abstractclass
ALongRunningReceiver
codeexecution
getLRSClass()method
handleBroadcastIntent()method
IntentService
LightedGreenRoomabstraction
nonstickyservice
onCreate()method
onDestroy()method
onStartCommand()method
protocols
redelivermode
stickyservice
Test60SecBCR
Test60SecBCRService
manifestfile
notificationmanager
monitoring
sendingnotification
startActivity()method
out-of-processreceivers
sendBroadcast()method
sendOrderBroadcastmethod
TestReceiver2
Broadcastreceiversamplecode
CCalendar.getInstance()
c.close()
c.getColumnCount()
c.getCount()
Chronometer
clearCheck()method
Clientapplication
ClipData
c.moveToFirst()
cnamesBuffer.toString()
Compatibilitylibrary
APIs
retrofitting
tablets
v4supportlibrary
v7supportlibrary
v8supportlibrary
v13supportlibrary
Complementaryfilters
Configurationchanges
configurationfactors
destroy/createcycle
FragmentManager
fragments
onCreate()callback
onRestoreInstanceState()callback
onSaveInstanceState()callback
putInt()
putParcelable()
putString()
saveFragmentInstanceState()
setInitialSavedState()
setRetainInstance()
features
getLastNonConfigurationInstance()
onConfigurationChanged()callback
onRetainNonConfigurationInstance()
UIelements
Consumingservices
Android(seeAndroidservice)
HTTP(seeHttpClient)
ContactData()
contact_entities_viewdatabaseview
ContactsAPI
accounts
AccountsFunctionTester
aggregatedcontacts
cursorfor
URI-basedcursorfor
contact_entities_viewdatabaseview
ContactProviderUI
ContentProviderOperation
backreferences
committingviayielding
containerfor
definition
optimisticlocking
controllingaggregation
detailsadding
groupfeatures
personalprofile
contactdata
dataadding
profile-basedURIs
rawcontacts
reading/writing
photofeatures
rawcontacts(seeRawcontacts)
syncadapters
Contentproviders
Androidresources
bookdatabase
BookProvidercontentprovider
deletemethod
implementation
insertmethod
MIME-typecontracts
projectionmaps
querymethod
register
structureof
updatemethod
UriMatcherclass
ContentValues
ContentValues()
CONTEXT_INCLUDE_CODEflag
Contextmenu
onContextItemSelected()
Populating
TextView
Context.NOTIFICATION_SERVICE
CONTEXT_RESTRICTEDflag
createPackageContext()API
createScaledBitmap()method
cursor
foraggregatedcontacts
URI-basedcursor
CursorLoader
DDalvikDebugMonitorService(DDMS)
databaseviews
contact_entities_view
dataRecord.toString()
Datatable
describeEvent()method
DetailsFragment.java
DeveloperConsole
DialogFragment
AlertDialogFragment
communication
construction
newInstance()method
dismiss()method
implications
onCancel()callback
onDismiss()callback
embeddeddialogs
HelpDialogFragment
MainActivity
MyDialogFragment
onCreateDialog()
onCreateView()
OnDialogDoneListener
PromptDialogFragment
show()method
Dialogs
Android
dialogfragments(seeDialogFragment)
toast
Digitalcertificate
DirectAccessBookDBHelper
Display.getRotation()
doInBackground()method
Dot.java
doWhenMapIsReady()method
Drag-and-dropimplemention
ACTION_UPevent
Android3.0
ACTION_DRAG_ENDED
ACTION_DRAG_ENTERED
ACTION_DRAG_EXITED
ACTION_DRAG_LOCATION
ACTION_DRAG_STARTED
ACTION_DROP
DragEventobject
ClipData
customview
Dot
Dot.java
DragShadowBuilder
draw()method
DropZone.java
dropzone.xml
FrameLayout
invalidate()method
layout_height
layout_width
layoutXML
LinearLayout
main.xmlfile
ObjectAnimatorclass
onCreateView()method
onDrag()
onMeasure()method
palette.xml
startDrag()
TextView
TouchDragDemo
userinterface
Viewobject
DragEvent.getResult()method
DragShadowBuilder
draw()method
DropTarget
DropZone.java
dropzone.xml
Dynamicmenus
EEndUserLicenseAgreement(EULA)
execute()method
executeOnExecutor()method
Expandedmenu
FfalseBtnTop
FILL_PARENTvs.MATCH_PARENT
findViewById()
for(c.moveToFirst()
Fragments
definition
FragmentManager
enableDebugLogging()method
getFragment()method
MainActivity
onCreateView()method
persistence
portraitmode
putFragment()method
referencingfragments
saveFragmentInstanceState()method
setContentView()
setRetainInstance()
showDetails()method
TitlesFragmentclass
FragmentTransactions
findFragmentById()method
FrameLayout
setCustomAnimations()method
setTransition()method
showDetails()method
ViewGroupclass
lifecycle
newInstance()method
onActivityCreated()callback
onAttach()callback
onCreate()callback
onCreateView()callback
onDestroy()callback
onDestroyView()callback
onDetach()callback
onInflate()callback
onPause()callback
onResume()callback
onSaveInstanceState()callback
onStart()callback
onStop()callback
onViewCreated()callback
onViewStateRestored()callback
setRetainInstance()
StaticFactoryMethod
setTargetFragment()
startActivity()
structure
tabletandsmartphoneUI
tabletUI
XMLlayout
DetailsFragmentclass
FrameLayout
getShownIndex()method
landscapemode
MainActivity
newInstance()method
onCreateView()method
Shakespeareclass
Frame-by-frameanimation
addFrame()method
AnimationDrawableclass
setOneShot(true)method
sourcecode
GgatherControls()
GCMConnectionServers
Geocoding
definition
latitudeandlongitude
mapfragment
methods
Geofencing
addProximityAlert()method
GeofencingApiDemo
geoMagField.getDeclination()
Gestures
GestureDetectorandOnGestureListeners
pinchgesture
getActionBar()
getAction()method
getContacts()
getCount()method
getEdgeFlags()method
getFirstContact()
getFragmentManager()method
getFragment()method
getFromLocationName()method
getHttpClient()method
getItemId()method
getItem()method
getItemViewType()method
getLastNonConfigurationInstance()
getMinDelay()
getOrientation()
getPointerCount()
getProgressBar()method
getQuaternionFromVector()
getResult()
getRotationMatrix()
getSearchableInfo
getSupportFragmentManager()
getView()method
getViewTypeCount()method
getXVelocity()method
getYVelocity()method
GoogleCloudMessaging(GCM)
authenticatingGCMcommunication
buildingAndroidapplication
configureprojectdependencies
gcm.send()method
manifestproperties
messageID.incrementAndGet()method
onCreate()method
registerAppInBackground()
registering
sendMessage()method
third-partyservice
clientapplication
componentsandmessageflow
definition
GCMAPI
GCMConnectionServers
GoogleDeveloperConsole
remoteapplicationserver
GooglePlayDeveloperDistributionAgreement(GPDDA)
GooglePlayServices
GooglePlayStore
AndroidManifest.xmlfile
apkfile
applicationicon
developerconsole
GPDDArules
licensingservice
localizing
ProGuard
publisher
screensizes
testing
uploading
consent
contactinformation
graphics
listingdetails
publishingoptions
URIscheme
userexperience
Gravitysensors
Gyroscopesensor
HHandlers
AsyncTaskclass
DeferWorkHandlersourcecode
definition
doDeferredWork()method
handleMessage
messageobject
obtainMessage()method
respondToMenuItem()call
setData()method
sleepmethod
workerthread
HelloAndroidApp
artifacts
debuggingtools
launchoptions
lifecycle
realdevice
structure
hideProgressbar()
Homescreenwidgets
AppWidgetProviderclass
BDayWidgetProviderclass
collections
configurationactivity
BDayWidgetModelobject
BirthdayWidget
ConfigureBDayWidgetActivity
layoutdefinition
tasks
configureattribute
definition
filesimplementation
initialLayoutattribute
onClickarea
onDeleted()method
onDisabled()method
onEnabled()method
onUpdate()method
previewImageattribute
resizemodeattribute
uninstallingpackages
userexperience
viewmouseclickeventcallbacks
widgetinstancedeletion
widgetprovider
BDayWidgetModel
boundaryboxshape
ConfigureBDayWidgetActivityactivity
implementation
layoutfile
XMLfile
howRawContactsDataForRawContact()
HttpClient
AndroidHttpClient
Apacheversion
HTTPGETrequest
ANR
HttpGetDemo.java
parameters
HTTPPOSTrequest
execute()method
HTTPPOSTcall
MultipartPOSTCall
NameValuePairobject
setEntity()method
UrlEncodedFormEntityobject
HttpURLConnection
JSON
multithreadingissues
BasicResponseHandler
getHttpClient()method
ThreadSafeClientConnManager
protocolexceptions
SOAPwebservice
timeouts
transportexceptions
XMLPullParser
IIconmenu
inflate()method
insertName()method
insertPhone()method
insertProfileRawContact()
insertRawContact()
Interpolators
interrupt()method
invalidate()method
invokePick()
isEnabled()method
J,KJavafile
JavaScriptObjectNotation(JSON)
JavaSEDevelopmentKit(JDK)
LLAUNCHER
Layoutanimation
AccelerateInterpolator
ActivityCode
alpha.xmlfile
BounceInterpolator
interpolators
list_layout.xmlfile
ListView
ListViewXMLfile
rotateXMLfile
scaleanimationXMLfile
translateandalphaanimationsfile
types
XMLfile
LicenseVerificationLibrary(LVL)
Lightsensor
Linearaccelerationsensor
listContacts()
listLookupUriColumns()
ListPreference
entryValuesarray
flight_sort_options_valuesarray
userinterface
XML
loaderCallbacks
LoaderManager
LoaderManager.destroyLoader(loaderid)
LoaderManager.initLoader()
LoaderManager.LoaderCallbacks
LoaderManager.restartLoader()
Loaders
activity/fragment
activityloadingdata
Activity.onCreate()
APIclasses
argsBundleargument
AsyncTaskLoader
characteristics
cursor
CursorLoader
developer-assigneduniquenumber
ListActivity
ListActivityLayout
loaddata
loaderCallbacks
LoaderManagerCallbacks
LoaderManagerobject
onCreateLoader()method
onCreate()method
onCreateOptionsMenu()method
onLoaderReset()method
onLoadFinished()method
resources
stringresources
Location-basedservices
AVD
DDMS
emulatorconsole
FusedLocationProviderApi
geocoding
definition
latitudeandlongitude
mapfragment
methods
geofencing
addProximityAlert()method
GeofencingApiDemo
getLastLocation()method
GoogleDirections
GooglePlayServices
GoogleClientApiclient
intentslaunching
isGooglePlayServicesAvailable()method
isUserRecoverableError()method
onConnectionFailed()callback
showErrorDialogFragment()method
tryToConnect()method
hasAccuracy()method
locationproviders
locationupdates
MapFragment
coding
FragmentActivity
FrameLayout
locationupdation
mapdisplay
maptiles
maptypes
MyLocation
panmaps
trafficlayer
WhereAmI
zoom
MapsAPIkey
manifestfile
markers
CameraUpdateFactoryclass
LatLngBoundsobject
MarkerOptionsfeatures
MyMapFragment.java
onConnected()callback
requestLocationUpdates()method
Settings.Secureclass
staticmethod
WhereAmILocationAPI
LogCat
Long-runningreceiversandservices
abstractclass
ALongRunningReceiver
codeexecution
getLRSClass()method
handleBroadcastIntent()method
IntentService
LightedGreenRoomabstraction
nonstickyservice
onCreate()method
onDestroy()method
onStartCommand()method
protocols
redelivermode
stickyservice
Test60SecBCR
Test60SecBCRService
Low-passfilter
MMagneticfieldsensor
accelerometers
compass
MainActivity’sstopService()method
Mapping.txtfile
MediaAPIs
android.media.MediaPlayerclass
contentformats
playingaudio
AsyncPlayer
AudioTrack
create()method
getExternalStoragePublicDirectory()method
JetPlayer
killMediaPlayer()method
layoutandcode
onCreate()method
playAudio()method
prepareAsync()method
release()method
setDataSource()method
setLooping()method
setVolume()method
SoundPool
stop()method
userinterface
playingvideo
SDCards
Menu.addSubMenu()
Menugroups
creation
removeGroup()
setGroupCheckable()
setGroupEnabledmethod()
setGroupVisible()
MenuInflater
MenuItem
icon
intent
JavaCode
listener
MenuXMLresourcefile
creation
menuitems
populatingactivity
MotionEvents
ACTION_CANCELevent
ACTION_DOWNevent
ACTION_MOVEevent
ACTION_OUTSIDEevent
ACTION_UPevent
LogCatrecords
mainactivity,Javacode
onTouchEvent()method
onTouch()method
recycle()method
ReturnsFalsebutton
setOnTouchListener()method
TouchDemo1application
Javacode,Buttonclasses
sampleLogCatmessages
UI
XMLlayoutfile
VelocityTracker
Multitouch
ACTION_MOVEevent
ACTION_SCROLL
ACTION_UPvalue
getAction()method
LogCatmessages
XMLlayout
MusicalInstrumentDigitalInterface(MIDI)
NnewInstance()method
Non-configurationinstancereference
Non-streamingsensor
OObjectAnimator
ObjectonRetainNonConfigurationInstance()
obtain()
onAccuracyChanged()
onActivityCreated()callback
onAttach()callback
onBind()method
onCheckedChanged()method
onContextItemSelected()
onCreate()
onCreate()callback
onCreateContextMenu()
onCreateLoader()method
onCreate()method
onCreateOptionsMenu()method
onCreateView().callback
onCreateView()method
onDestroy()callback
onDestroy()method
onDestroyView()callback
onDetach()callback
onDrag()
onHandleIntent()call
onLoaderReset()method
onLoadFinished()method
onLocationChanged()method
onMapReady()callback
onMeasure()method
onMenuItemClick()
onOptionsItemSelected()
onPause()
onPause()callback
onPerformSync()method
onPostExecute()callbackmethod
onPreExecute()method
onPrepareOptionsMenu()
onProgressUpdate()method
onQueryTextchange()method
onReceive()method
onRestoreInstanceState()
onResume()callback
onResume()method
onRetainNonConfigurationInstance()
onSaveInstanceState()callback
onScale()
onSensorChanged()method
onStart()callback
onStartCommand()method
onStop()callback
onTouchEvent()method
onTouch()method
onUpdate()method
onViewCreated()callback
onViewStateRestored()callback
O/Rmapping
PPackages
libraryproject
compile-timeconcept
design
hard-codedconstants
propertiesdialog
switchstatement
LinuxuserID
manifestfile
shareduserID
shareresourcesanddata
palette.xml
parseResult()
PendingIntentclass
Pop-upmenus
PreferenceActivity
Preferences
AndroidManifest.xml
application’ssavedpreferences
CheckBoxPreference
dependency
DialogPreference
EditTextPreference
flight_sort_option
getSharedPreferences()method
headers
ListPreference(seeListPreference)
mainsettings
main.xml
MultiSelectListPreference
onCreate()method
OnPreferenceChangeListenerinterface
packagename
PreferenceCategory
PreferenceScreens
setOptionText()method
stringresourcevalue
SwitchPreference
XML
Pressuresensor
Processmodel.SeePackages
ProgrammingLogic
activityclass
AndroidManifest.xml
calculatorbuttons
gatheringcontrols
intentobject
layoutfile
onClick()method
placementfiles
ProgressBar
ProGuard
PropertyanimationAPI
activity
AnimatiorSetBuilder
AnimatorListener
AnimatorSet
AnimatorSetBuilderclass
AnimatorSetclass
class
Keyframes
layouttransitionclass
layouttransitionmethods
ObjectAnimator
PropertyValueHolderclass
PropertyValuesHolder
toggleAnimation(View)method
TypeEvaluatormethod
ValueAnimator
ViewPropertyAnimator
ViewPropertyAnimatorclass
XMLfile
XMLtags
PropertyValuesHolder
Proximitysensor
publishProgress()method
Qquery()function
queueSound()method
RRatingBar
RawContact()
Rawcontacts
advantagesanddisadvantages
aggregatedcontacts
ContactData.java
contact_entities_view
cursor
datatable
definition
RawContact.java
SQLiteDatabase
structureof
view_contacts
rc.toString()
ReceiveTransitionsIntentServicemethod
registerListener()method
Remoteapplicationserver
Remoteprocedurecall(RPC)
RemoteViewsclass
removeGroup()
requestLocationUpdates()method
Rotationvectorsensor
SScaleGestureDetector
ScrollView
searchable.xml
SearchManager.QUERY
sendBroadcast()method
Sensors
accelerometers(seeAccelerometers)
definition
detectionmethods
GeomagneticField
gettingsensordata
gravitysensor
gyroscopesensor
lightsensor
LightSensorMonitorApp
linearaccelerationsensor
magneticdeclination
magneticfieldsensor
pressuresensor
proximitysensor
rotationvectorsensor
SensorListApp
javacode
output
temperaturesensor
types
setContentView()
setCustomAnimations()method
setDataSource()method
setEdgeFlags()method
setEntity()method
setGroupCheckable()
setGroupEnabledmethod()
setGroupVisible()
setIntent(intent)
setMapType()method
setMeasureAllChildren()
setMyLocationEnabled()method
setOnClickListener()method
setOnTouchListener()method
setRepeating()method
setResult()
setRetainInstance()method
setTabListener()
setTrafficEnabled()method
setTransition()method
setupButtons()
showAllRawContacts()
showAllRawProfileContacts()
showDetails()method
showProfileRawContactsData()
showProgressbar()method
showRawContactsCursor()
Sleepmethod
SOAPwebservice
SpinnerAdapterinterface
SQLException
SQLite
databasecreation
databasemigration
DDLs
deletingrows
insertingrows
packagesandclasses
readingrows
sampleSQLcode
SQLitesqlite_masterTable
transactionsmethod
updatingrows
SQLiteCursor
SQLiteDatabase
SQLiteOpenHelper
SQLiteQueryBuilder
startDrag()
Statusbar
stop*()methods
Streamingsensor
StringBuffer()
Styles
Android-providedstyle
attributename
collectionofViewattributes
dynamicstyling
ErrorText.Danger
errorTextView
parentandchildstyle
spannable
staticstyling
SubMenu
SupportMapFragment
System-levelservices
TTabListenerinterface
Temperaturesensor
testAccounts()
Themes
this.getACursor(getRawContactsUri()
this.mContext.getContentResolver()
Threads
Activity.startService
broadcastreceiver
components
TitlesFragment.java
Toast
toString()
TouchDragDemo
Touchscreens
gestures
GestureDetectorandOnGestureListeners
pinchgesture
MotionEventobject
ACTION_CANCELevent
ACTION_DOWNevent
ACTION_MOVEevent
ACTION_OUTSIDEevent
ACTION_UPevent
Javacode,Buttonclasses
LogCatrecords
mainactivity,Javacode
onTouchEvent()method
recycle()method
ReturnsFalsebutton
sampleLogCatmessages
setOnTouchListener()method
UIobjects
VelocityTracker
XMLlayoutfile
multitouch
ACTION_MOVEevent
ACTION_SCROLL
ACTION_UPvalue
getAction()method
LogCatmessages
XMLlayout
Transformationmatrix
trueLayoutTop
Tweeninganimation
TYPE_AMBIENT_TEMPERATURE
TypeEvaluator
UudpateAppWidgetfunction
unbindService()method
URI-basedcursor
foraggregatedcontacts
URLparameter
Userinterfacesandcontrols
buttoncontrols
basicbutton
CheckBox
clickhandler
ImageButton
ImageViews
RadioButton
switch
ToggleButton
dateandtimecontrols
AnalogClock/DigitalClock
DatePicker/TimePicker
MapView
textcontrols
AutoCompleteTextView
EditText
MultiAutoCompleteTextView
TextView
UIdevelopment
android.view.View
android.view.ViewGroup
code
XML
XMLwithcode
UserInterface(UI)layout
autogeneratedIDs
backgroundproperty
calculatorXML
colorresource
customcontrols
EditTextcontrol
file-basedresources
TextViewcontrol
value-basedresources
ViewGroup
widthandheight
XMLcommentspecification
Utils.logThreadSignature()method
VValueAnimator
VelocityTracker
Viewanimation
activity
AnimationListenerclass
Cameraobject
preandposttranslatemethod
rotatemethod
translatemethod
class
initializemethod
interpolatedTime
ListView
Matrixclass
preandposttranslatemethod
PreandPosttranslatepattern
preandposttranslatematrix
setScalemethod
ViewPropertyAnimator
voidonCreate
voidonDestroy()
voidonPause()
voidonRestart()
voidonRestoreInstanceState
voidonResume()
voidonStart()
voidonStop()
WWakefulIntentService
WhereAmI
WhereAmIMarkers
Whitedot
X,Y
XMLPullParser
ZZIPfile