Android编程实战


Android™ Programming Pushing the Limits Erik Hellman This edition first published 2014 © 2014 Erik Hellman Registered office John Wiley & Sons Ltd, The Atrium, Southern Gate, Chichester, West Sussex, PO19 8SQ, United Kingdom For details of our global editorial offices, for customer services and for information about how to apply for permission to reuse the copyright material in this book please see our website at www.wiley.com. The right of the author to be identified as the author of this work has been asserted in accordance with the Copyright, Designs and Patents Act 1988. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording or otherwise, except as permitted by the UK Copyright, Designs and Patents Act 1988, without the prior permission of the publisher. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books. This publication is designed to provide accurate and authoritative information in regard to the subject matter covered. It is sold on the understanding that the publisher is not engaged in rendering professional services. If professional advice or other expert assistance is required, the services of a competent professional should be sought. Trademarks: Wiley and the Wiley logo are trademarks or registered trademarks of John Wiley and Sons, Inc. and/ or its affiliates in the United States and/or other countries, and may not be used without written permission. Android is a trademark of Google, Inc. All other trademarks are the property of their respective owners. John Wiley & Sons, Ltd. is not associated with any product or vendor mentioned in the book. A catalogue record for this book is available from the British Library. ISBN 978-1-118-71737-0 (paperback); ISBN 978-1-118-71730-1 (ePDF); 978-1-118-71735-6 (ePub) Set in 9.5 pt Myriad Pro by Indianapolis Composition Services Printed in the United States by Bind-Rite Dedication This book is dedicated to my amazing dad, Ingemar Hellman, who taught me programming when I was nine years old. None of this would have been possible if it weren’t for all the amazing things he taught me. Erik Hellman, Second generation software developer Publisher’s Acknowledgements Some of the people who helped bring this book to market include the following: Editorial and Production VP Consumer and Technology Publishing Director: Michelle Leete Associate Director–Book Content Management: Martin Tribe Associate Publisher: Chris Webb Project Editor: M. Faunette Johnston Copy Editor: Melba Hopper Technical Editor: Erik Westenius Editorial Manager: Jodi Jensen Senior Project Editor: Sara Shlaer Editorial Assistant: Annie Sullivan Marketing Associate Marketing Director: Louise Breinholt Marketing Manager: Lorna Mein Senior Marketing Executive: Kate Parrett Marketing Assistant: Polly Thomas Composition Services Compositor: Jennifer Goldsmith, Andrea Hornberger Proofreader: Wordsmith Editorial Indexer: Potomac Indexing, LLC About the Author Erik Hellman grew up in the small town of Borlänge in the middle of Sweden and currently lives in the city of Malmö with his wife. From an early age he showed a great interest in all kinds of technology, much thanks to his father. This interest has carried on over the years and he likes to spend time tinkering with all kinds of technology, be it hardware or software. Erik currently works as a developer consultant at Factor10. He previously worked for Sony Mobile as one of the lead architects for their Android development and later as a research engineer where he participated in the development of the next generation of mobile solutions from Sony. Erik has a lifetime of experience with software development and programming. His father taught him how to write code in Turbo Pascal at the age of nine, as well as how to design relational databases in Oracle 3 on his old IBM AT 286. Much later, Erik studied Software Engineering at Blekinge Institute of Technology where he also worked as an assistant teacher in various software engineering courses. Erik can often be found at various developer conferences where he likes to speak about cutting-edge software technologies. You can also find out more about Erik and his other hobbies at his blog (http://blog.hellsoft.se) or follow him on Google+ (http://gplus.to/ErikHellman) or Facebook (https://www.facebook.com/ErikHellman). Acknowledgements First of all, I’d like to thank my wife Kaisa-Leena. Without her love, support, and infinite patience with me during the writing of this book, this book would never have been finished. A big thank you goes out to all my friends and former colleagues at Sony Mobile. Almost everything I’ve learned about Android and mobile technologies came from my years working with all of you. I’m extremely proud to have been part of the team that built the Xperia series of devices. A special thank you to my former boss, Henrik Bengtsson, and his team at the Sony Mobile Research in Lund. Finally, I’d like to thank my editors; Faunette, Melba, Erik, and all the people working at Wiley for helping me write this book. A big thanks goes to my technical editor Erik Westenius for his eye for details and making sure that my code and examples are understandable. Also, a thank you to Kristoffer Åberg at Sony Mobile for giving me valuable input on the UI design sections. Contents Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  1 Who This Book Is For. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 What This Book Covers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 How This Book Is Structured. . . . . . . . . . . . . . . . . . . . . . . . . . . .2 What You Need to Use This Book. . . . . . . . . . . . . . . . . . . . . . . . . 5 Source Code. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Errata. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5 Part I Building a Better Foundation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Chapter 1 Fine-Tuning Your Development Environment. . . . . . . . . . . . . . . . . . .  9 Operating Systems for Android Development. . . . . . . . . . . . . . . . . . . 9 Advanced Android SDK Tools. . . . . . . . . . . . . . . . . . . . . . . . . . . 9 The adb Tool. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Stress-Testing an Application’s UI with Monkey. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 The Gradle Build System for Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Optimize and Obfuscate with ProGuard. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Android Library Projects and Third-Party Libraries. . . . . . . . . . . . . . . . .17 Precompiled JAR Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Setting Up a Library Project. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Version Control and Source Code Management. . . . . . . . . . . . . . . . . .19 Mastering Your IDE. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22 Debugging Android Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Static Code Analysis with lint. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Refactoring Your Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Developer Options on Android Devices. . . . . . . . . . . . . . . . . . . . . .29 Understanding Developer Settings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 viii Contents Chapter 2 Efficient Java Code for Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Comparing Android’s Dalvik Java to Java SE. . . . . . . . . . . . . . . . . . . .33 Optimizing Java Code for Android. . . . . . . . . . . . . . . . . . . . . . . . 36 Type-Safe Enum on Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Enhanced For-Loop on Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Queues, Synchronization, and Locks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Memory Management and Allocations. . . . . . . . . . . . . . . . . . . . . . 40 Reducing Object Allocations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Multithreading on Android. . . . . . . . . . . . . . . . . . . . . . . . . . . .44 Thread. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 AsyncTask. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Handler. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Picking the Right Solution for Threads. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Online Sources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Part II Getting the Most Out of Components. . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Chapter 3 Components, Manifests, and Resources . . . . . . . . . . . . . . . . . . . . . . . 57 Android Components. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 The Activity Component. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 The Service Component. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 The BroadcastReceiver Component. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 The ContentProvider Component. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 The Application Component. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Application Architecture. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 The Android Application Manifest. . . . . . . . . . . . . . . . . . . . . . . . 62 The Manifest Element. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Google Play Filters and Permissions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 The Application Element. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Component Elements and Attributes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Intent Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 Resources and Assets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .69 Advanced String Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Localization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Using Resource Qualifiers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Using Assets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 ixContents Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Chapter 4 Android User Experience and Interface Design. . . . . . . . . . . . . . . . . 77 User Stories. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Understanding the User with Personas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 Android UI Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .79 Navigation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Prototyping User Interfaces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Android UI Elements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .81 Text in Android Applications . . . . . . . . . . . . . . . . . . . . . . . . . . .81 Font Matters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Text Layout. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Dimensions and Sizes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Recommended Dimensions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Icon Sizes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Text Size. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Colors. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .84 Color Blindness. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Images and Icons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Canonical Perspective. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Geons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Recognizing Faces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Usability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .87 Visual Cues. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Rewarding the User. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Gamification. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Chapter 5 Android User Interface Operations. . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Activities and Fragments. . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 Using Multiple Screens. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95 Designing Custom Views. . . . . . . . . . . . . . . . . . . . . . . . . . . . .97 View Life Cycle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Piano Keyboard Widget. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 x Contents Multi-Touching. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 PointerCoordinates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 Rotate Gesture. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 OpenGL ES. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .108 Scene Graphs and Graphics Engines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Chapter 6 Services and Background Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 When and How to Use a Service. . . . . . . . . . . . . . . . . . . . . . . . 111 Service Types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Understanding the Service Lifecycle. . . . . . . . . . . . . . . . . . . . . . 112 Service Creation and Destruction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Starting Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Binding Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Staying Alive. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Stopping Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Running in the Background . . . . . . . . . . . . . . . . . . . . . . . . . . 120 IntentService. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Parallel Execution. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Communicating with Services. . . . . . . . . . . . . . . . . . . . . . . . . 123 Asynchronous Messaging with Intents. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 Locally Bound Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Blog. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Chapter 7 Android IPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 The Binder Explained. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .131 Binder Address. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Binder Transactions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Parcel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Link to Death. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Designing APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .137 AIDL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Messenger. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 Wrapping APIs with Library Projects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Contents xi Securing Remote APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 Chapter 8 Mastering BroadcastReceivers and Configuration Changes. . . . 151 BroadcastReceivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 Local BroadcastReceivers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Normal and Ordered Broadcasts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Sticky Broadcasts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Directed Broadcasts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Enabling and Disabling Receivers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 System Broadcast Intents. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 Device Configuration Changes . . . . . . . . . . . . . . . . . . . . . . . . .162 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Chapter 9 Data Storage and Serialization Techniques . . . . . . . . . . . . . . . . . . . 165 Persistence Options for Android. . . . . . . . . . . . . . . . . . . . . . . . 165 Storing Data in Preference Files. . . . . . . . . . . . . . . . . . . . . . . . .166 User Options and Settings UI. . . . . . . . . . . . . . . . . . . . . . . . . .169 High-Performance ContentProviders . . . . . . . . . . . . . . . . . . . . . .171 Android Database Design. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 Creating and Upgrading Databases. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Implementing Query Methods. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Database Transactions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 Storing Binary Data in ContentProvider. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 Serializing Data for Persistence. . . . . . . . . . . . . . . . . . . . . . . . .179 JavaScript Object Notation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 Advanced JSON Handling with Gson. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 Google Protocol Buffers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Application Data Backup. . . . . . . . . . . . . . . . . . . . . . . . . . . .187 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 xii Contents Chapter 10 Writing Automated Tests. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Android Testing Principles. . . . . . . . . . . . . . . . . . . . . . . . . . . 191 What to Test. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 Basic Unit Testing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 Testing Activities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194 Testing Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 Testing ContentProviders. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 Running Tests. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Continuous Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Android Testing Guidelines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 Part III Pushing the Limits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 Chapter 11 Advanced Audio, Video, and Camera Applications. . . . . . . . . .  207 Advanced Audio Applications. . . . . . . . . . . . . . . . . . . . . . . . . 207 Low-Latency Audio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 OpenSL ES for Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Text-to-Speech (TTS). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Speech Recognition. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 Video Processing with OpenGL ES 2.0. . . . . . . . . . . . . . . . . . . . . .218 Camera Processing with OpenGL ES 2.0. . . . . . . . . . . . . . . . . . . . .222 Encoding Media. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Recording an OpenGL Scene. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Chapter 12 Secure Android Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 Android Security Concepts. . . . . . . . . . . . . . . . . . . . . . . . . . .231 Signatures and Keys. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 Android Permissions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 Protecting User Data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Verifying Calling Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 Contents xiii Client-Side Data Encryption. . . . . . . . . . . . . . . . . . . . . . . . . . 235 Android Crypto API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 Generating a Key. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 Encrypting Data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 Decrypting Data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Working with Encrypted Data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Android Key Chain Management. . . . . . . . . . . . . . . . . . . . . . . .239 Device Management API. . . . . . . . . . . . . . . . . . . . . . . . . . . .244 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 Books. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Chapter 13 Maps, Location, and Activity APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . 249 Fused Location Manager. . . . . . . . . . . . . . . . . . . . . . . . . . . .249 Google Maps v2 Integration. . . . . . . . . . . . . . . . . . . . . . . . . . 250 Working with Google Maps. . . . . . . . . . . . . . . . . . . . . . . . . . .253 Markers on Maps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 Drawing Circles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 Drawing Polygons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 Useful Location API Utilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Geocoding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 Using the LocationClient. . . . . . . . . . . . . . . . . . . . . . . . . . . .258 Geofencing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 Activity Recognition. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Chapter 14 Native Code and JNI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267 A Note on CPU Architecture. . . . . . . . . . . . . . . . . . . . . . . . . . 267 Writing Android Applications in C . . . . . . . . . . . . . . . . . . . . . . . 268 Android NDK Build Scripts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 Native Activities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 Working with JNI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .270 Calling Native Functions from Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Calling a Java Method from Native. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 xiv Contents Android Native APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276 The C Library. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276 Native Android Logging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 Native OpenGL ES 2 .0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 Native Audio with OpenSL ES. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 Porting a Native Library to Android. . . . . . . . . . . . . . . . . . . . . . .278 Building a Native Library. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Android Developers Site. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Oracle Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Chapter 15 The Hidden Android APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Official and Hidden APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Discovering Hidden APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . .288 Safely Calling Hidden APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . 290 Extracting Hidden APIs from a Device. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291 Calling Hidden APIs Using Reflections. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 Examples of Hidden APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . .294 Receiving and Reading SMS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 Wi-Fi Tethering. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 Hidden Settings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 Chapter 16 Hacking the Android Platform. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 Unlocking Your Device. . . . . . . . . . . . . . . . . . . . . . . . . . . . .300 Flashing Factory Images. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301 Unlocking Non-Google Nexus Devices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Community-Supported Firmwares. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 The Android Source Code. . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Setting Up the Build Environment. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 Building and Flashing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 Writing System Applications. . . . . . . . . . . . . . . . . . . . . . . . . . 305 Platform Certificates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 Writing Signature-Signed Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 Contents xv Hacking the Android Platform. . . . . . . . . . . . . . . . . . . . . . . . . 308 Setting Up Your IDE. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 Android Projects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Android Linux Kernel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 Adding a System Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 Speeding Up the Platform Development Cycle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 Contributing to AOSP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 Chapter 17 Networking, Web Service, and Remote APIs. . . . . . . . . . . . . . . . . 319 Android Networking. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .319 HttpUrlConnection. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320 Volley. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324 OkHttp and SPDY. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326 Web Sockets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 Integrating Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . .331 Google Static Maps v2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331 Foursquare API Using OAuth2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 Facebook SDK for Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 Finding Online Web Services and APIs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 Network and Power Consumption. . . . . . . . . . . . . . . . . . . . . . . 342 General Guidelines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343 Power Efficient Network Polling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343 Server-Side Push. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Chapter 18 Communicating with Remote Devices. . . . . . . . . . . . . . . . . . . . . . 349 Android’s Connectivity Technologies. . . . . . . . . . . . . . . . . . . . . .349 Android USB. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .350 Bluetooth Low Energy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352 Android Wi-Fi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 Network Service Discovery. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 WiFi Direct. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 xvi Contents On-Device Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . 361 RESTful with Restlet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Web Socket Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 Chapter 19 Google Play Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Authorization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Google Drive Application Data . . . . . . . . . . . . . . . . . . . . . . . . .372 Google Cloud Endpoints. . . . . . . . . . . . . . . . . . . . . . . . . . . .376 Google Cloud Messaging. . . . . . . . . . . . . . . . . . . . . . . . . . . .379 GCM Client. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379 Google Play Game Services. . . . . . . . . . . . . . . . . . . . . . . . . . .383 Data Messaging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 Messaging Strategy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388 Chapter 20 Distributing Applications on Google Play Store. . . . . . . . . . . . . . 389 In-app Billing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390 Consuming Products. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 In-app Subscriptions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 Ads in Android Applications. . . . . . . . . . . . . . . . . . . . . . . . . . 393 Targeting Ads. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395 Ad Colors. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396 Interstitial Ads. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396 Application Licensing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 APK Expansion Files. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398 Creating Expansion Files. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 Downloading Expansion Files. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 Websites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 Index .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. . 403 Introduction If you’re reading this book, you’re probably familiar with the Android platform, smartphones, and application development. You’re well aware of the tremendous growth of Android devices over the last few years and the potential that the platform holds for application developers. I could give you specific numbers and statistics about Android, but that wouldn’t serve much purpose because those numbers most likely would be invalid by the time you read this book. It’s enough to say that the Android platform is here to stay, and you can expect its growth to continue for the foreseeable future. All of this is great news to people like you and me. Those of us who are aiming to becoming experts at Android development face a bright future. The demand for skilled application developers for Android is growing every day, and at the same time, the demand on developers is growing because the features that users want and the possibilities provided by the new technologies require us to constantly think ahead. Although Google provides a great set of tools and APIs to help you perform your magic, you still need to push yourself on what you can now do with Android. That is the goal of this book: to push the limits of the Android platform and build ever more advanced applications. Who This Book Is For You’ve probably written a few Android applications already and are eager to learn more advanced APIs and tricks. Perhaps you generally program using Java, but you don’t have any problems learning a new language. As a developer, you’re not afraid to try out new things, be it a new IDE or a brand new API. You like to test all the latest features, and you don’t’ become discouraged when things initially fail. While you can find plenty of introductory books on Android development, this is not such a book. Instead, this book is targeted toward developers who have previous experience with application development for Android. You know all the basics and how to create an app using the Activity, Service, BroadcastReceiver, and ContentProvider classes. You’re familiar with the core concept of the application manifest and the different types of application resources for Android. If you can build a simple Android application in a matter of minutes, you’re well prepared for the contents of this book. My goal for this book is just what the title says: pushing the limits. In each chapter, I try to push the limits on what typical Android developers know and use in their daily work. Even though you may be familiar with some of this book’s topics, I provide an in-depth discussion on each topic; so, trust me, there’s something new for all Android developers in this book. 2 Android Programming: Pushing the Limits What This Book Covers The Android platforms have developed at an amazing pace. Even while writing this book, I had to change my original plans because Google kept coming out with new APIs, tools, and technologies for Android developers! While many of the examples in this book require a later version of Android, my assumption is that you are familiar with the different API levels and understand which Android version is required in each case. My goal is to discuss those aspects of Android that I believe are of value for developers. Some technologies were excluded, either because they aren’t “pushing the limits” or they wouldn’t add real value to the book. So, rather than being a collection of “recipes” for Android applications or an extensive walkthrough of all the features, each chapter goes into the deep technical details of their respective topic. Thus, rather than provide complete applications, I provide numerous pieces of substantial code that you can use to enhance your own applications. Depending on your experience, you might encounter some unfamiliar technologies. For example, Chapter 12 covers security issues and assumes a basic understanding of encryption and private/public keys, and Chapter 18 covers technologies such as USB communication, Bluetooth Low Energy, and WiFi Direct. But don’t worry. In such cases, I direct you to sources where you can find additional information. Each chapter has a Further Resources section where I’ve listed additional resources such as books and websites where you can find more information on the topics covered in each chapter. How This Book Is Structured This book is divided into three parts. The first part covers tools available to Android developers and the specifics on the Java programming language for Android. The second part focuses on core Android components and how to use them in an optimal way. The third and final part focuses on the latest and most advanced technologies, including both the Android platform and the services that can be used with an Android device. Part I: Building a Better Foundation Chapter 1: Fine-Tuning Your Development Environment deals with the Android development tools. You’ll find an introduction to the new IDE for Android called Android Studio. There is also a brief introduction to Gradle, which is now used as the standard build system for Android applications. Chapter 2: Efficient Java Code for Android focuses on the Java programming language and the specifics for Android. I discuss some tricks that can help you reduce the load on memory and on the Dalvik garbage collector. You’ll also find some examples showing the pros and cons of the different methods for multi- threading. Part II: Getting the Most Out of Components Chapter 3: Components, Manifests, and Resources gives a general overview of the components and goes on to describe some of the less frequently used parts of the application manifest. You’ll also find examples of advanced use of Android resources. Introduction 3 Chapter 4: Android User Experience and Interface Design focuses on the theory behind the design of user interfaces. I describe how you should work when designing your user interfaces, starting with user stories, personas, and the design-process for the different screens in your application. I explain how people think about, react to, and interpret the various aspects of user interfaces. I describe details about fonts and what makes one font more readable than another. This chapter gives a better understanding of the theories behind good design that you can apply while designing your own interfaces. Chapter 5: Advanced User Interface Operations focuses on the technical aspects of the Android UI. I show how to use the new APIs with multiple screens in an application. A complete example of how to build a custom View is covered followed by a section on advanced multi-touch handling. Chapter 6: Services and Background Tasks focuses on how to use the Service component for optimal background operations in your application. This chapter focuses on Services that aren’t published to other applications. Chapter 7: Android IPC covers Android IPC and how you can communicate between two separate Android applications running on the same device. I explain the details of the Binder and how you can use it to build applications that support plug-in functionality. Chapter 8: Mastering BroadcastReceivers and Configuration Changes focuses on the use of BroadcastReceivers and how to use them optimally to listen for system-wide events and configuration changes. I describe the different types of broadcasts and how to use them, and I give some guidelines on how to use the receiver component in a way that reduces the load on the device. Chapter 9: Data Storage and Serialization Techniques focuses on data persistence and on the ContentProvider component. I show how to use the SharedPreferences and how to create a Settings UI using the ready-made Android components. High-performance providers are covered as well as different methods for serializing data where the standard SQLite-based solution isn’t sufficient. Also, the serialization methods explained here are useful when communicating with Services online. I conclude this chapter with some details on how to use the Android backup agent in your application. Chapter 10: Writing Automated Tests is dedicated to building automated tests for your Android application. I give thorough examples of everything from simple unit tests to complete integration tests for the four different components. I highly recommend that all readers go through this chapter thoroughly because writing tests for your application will greatly improve your development cycle and the quality of your code. Part III: Pushing the Limits Chapter 11: Advanced Audio, Video, and Camera Applications deals with advanced graphics, audio, and video. I explain the use of the different audio APIs, including the use of Open SL ES for any high-performance and low-latency requirements you might have regarding audio. I explain the use of both Text-To-Speech and the Speech Recognition API for Android. The use of OpenGL ES for high-performance processing of camera input and video is demonstrated as well. Finally, I discuss a feature introduced in Android 4.3 for using an OpenGL ES surface as an encoding source, which you can employ to record a video of your OpenGL ES scene. 4 Android Programming: Pushing the Limits Chapter 12: Secure Android Applications looks at the different security aspects in Android with a focus on how to use the cryptographic APIs. I explain how to use key management in android in a secure way and how to encrypt data on a device. I conclude this chapter with a section covering the Device Management API. Chapter 13: Maps, Location, and Activity APIs focuses on the new maps and location API for Android. You find out about the new fused Location Provider and how to use features such as geofencing and activity recognition in order to build advanced location-based applications. Chapter 14: Native Code and JNI delves into native development for Android using the C programming language. I explain the use of the Android NDK (Native Development Kit) and how you can combine native code with Java code through the use of JNI. Chapter 15: The Hidden Android APIs looks at how the hidden APIs in Android work, how you can find them, and how you can safely invoke them in your application. You also find out how to search the Android source code in order to discover the hidden APIs. Chapter 16: Hacking the Android Platform describes how to work with the Android Open Source Project (AOSP), build your own custom firmware, and extend the Android platform. I explain how the AOSP is designed and to work when modifying the Android platform. You’ll also find an introduction on the process of contributing your changes to the AOSP so that they can become a standard part of the Android platform. Chapter 17: Networking, Web Services, and Remote APIs looks at integrating online web services in an Android application and how to optimize network operations. I cover the use of recommended third-party libraries for working with network operations, from standard HTTP to Web Sockets and SPDY, and I explain how to invoke three different types of web services. I explain the concept of authenticating toward third-party web services, including how to use OAuth2 in your Android application as well as how to integrate the Facebook SDK for Android. Chapter 18: Communicating with Remote Devices delves into the different methods for communicating with remote devices using the various connectivity technologies available on Android. I explain how to talk to USB devices using the built-in APIs in Android. I describe the APIs for communicating with Bluetooth Low Energy devices (also called Bluetooth Smart). You’ll find an introduction on utilizing the network discovery API for Android, and I show how you can implement ad hoc peer-to-peer communication using the WiFi Direct standard. You’ll also find a section on implementing on-device services supporting both RESTful web services and asynchronous communication using Web Sockets. Chapter 19: Google Play Services covers the use of some of the APIs from Google Play Services. You’ll find out how to get authorization for any of the online Google APIs and an example of using the Application Data feature for Google Drive to store your application’s data across multiple devices. You’ll also find a guide to building Google Cloud Endpoints using the built-in feature in Android Studio, and I show how you can extend this with your own services. This chapter also includes a guide on implementing Google Cloud Messaging. Also, I demonstrate how you can use the real-time multiplayer API that is part of the Google Play Game Services to build advanced multiplayer games. Chapter 20: Distributing Applications on Google Play Store, the final chapter, focuses on aspects of distributing your application on the Google Play Store and how to include the different monetization options. I explain how to add In-app Billing and ads in your application as well as the use of the licensing service for verifying the license of your application on the user’s device. The chapter ends with a guide to utilizing the APK Expansion Files feature to distribute application data that exceeds your application’s 50MB limit. Introduction 5 What You Need to Use This Book Although you can run many of the examples in this book on an emulator, I strongly recommend that you acquire a device with the latest version of Android because many examples in the book use hardware that isn’t available on the emulator. Although any Google-certified Android device is sufficient (that is, an Android device with the Google Play Store) for the examples in this book, given that it has the correct Android version, I always recommend purchasing a Google Nexus device so that you can try all the latest platform features as early as possible. You also need a computer running Linux, OS X, or Windows for your development environment, as well as an Internet connection to access the online resources needed in some chapters. You need to have Java SDK version 6 installed on your computer. (You can download it at http://java.oracle.com) in order to run the IDE and other tools. In Chapter 18, you’ll need additional hardware to implement the examples. You’ll need a device with Bluetooth Low Energy support, such as an activity tracker or a heartbeat monitor. For the samples on USB communication, you’ll need an Android device that supports USB On-The-Go, a USB OTG cable, and a USB-connected device that you can connect to. The easiest approach to testing USB is to use an Arduino Uno board. Source Code Most of the source code samples in this book aren’t complete implementations; rather they are snippets of code I use to demonstrate the most important parts of the topic being covered. My assumption is that you are familiar enough with Android development to understand where to fit these snippets of code into your own project. Some of the samples and source code snippets in the book are available on my GitHub site (https:// github.com/ErikHellman/apptl). However, I strongly recommend that you manually type the code you find in the book instead of simply copying a file. Doing so will give you a better understanding of how the code works. You can also download the code files from the book’s companion website at http://www.wiley.com/go/ ptl/androidprogramming. Errata I’ve tried to review and verify everything in the book as much as possible, but sometimes mistakes happen. It could be a typo in the text or the source code, but it could also be something that is missing or simply mistaken. I’ll update any errors in the sample code in the GitHub repository at https://github.com/ ErikHellman/apptl with proper bug fixes. Chapter 1 Fine-Tuning Your Development Environment Chapter 2 Efficient Java Code for Android Part I Building a Better Foundation Chapter 1 Fine-Tuning Your Development Environment Depending on what you’re developing, you have different choices when it comes to the tools you can use. Your requirements on the development environment differs if you’re writing an HTML5 application or if you’re developing a server-side application in Java. Some platforms offer more choice than others, and as I describe next, developing Android applications gives you a lot of choice for your development environment. I begin this chapter with some more advanced internals of the Android SDK and how you can use them in your daily development and how they can assist you in improving the quality of your application. I continue by describing how to structure your code projects in an optimal way for reuse by using library projects. You also learn how to take version control to a new level by integrating Git with a code-review tool called Gerrit. As a developer, you will spend most of your time using the IDE tool. While the Eclipse IDE is still supported by Google, they are now pushing developers to use their new Android Studio IDE for all Android projects. So, I give an introduction to Android Studio as well as to the new build system called Gradle. Finally, I go through the developer settings that are available on Android devices. Operating Systems for Android Development This is probably the one topic you don’t have to worry about. Either you can pick the operating system on your computer used for development, or it is limited by the IT-policies of your employer. For most Android developers, any of the officially supported operating systems works fine. However, there are situations where the choice will matter. Google supports Windows, Linux, and OS X for developing Android applications. Although Windows is officially supported by the Android SDK, you’ll have problems if you decide to do advanced development, especially when it comes to writing native applications or building your own custom ROM. The best choice is either Linux or OS X. If possible, try to have one of these as your primary operating system, and you’ll run into far fewer problems. Another reason for avoiding Windows on your Android development environment is that you won’t need to install new USB drivers for every Android device you work on. Advanced Android SDK Tools After you have your operating system and the required (and recommended) tools installed on your computer, you can focus on the Android SDK. You will find the download for your operating system and the latest installation instructions at http://developer.android.com/sdk. Android Studio comes with an SDK 10 Part I: Building a Better Foundation bundle that is completely managed from within the IDE, but if you prefer to have a standalone version you can download that as well. Make sure that you always keep the SDK up to date and that you download the APIs for all the Android versions that you’re developing for. The easiest way to update your standalone Android SDK is to run the update tool from the command prompt: $ android update sdk --no-ui Inside the Android SDK folder, you will find a number of subfolders. From a tools perspective, only the platform-tools and tools folders are of interest for now. I will introduce some of these tools and explain how to use them, starting with the adb (Android Debug Bridge) tool. If you are frequently using the command line tools in the Android SDK I recommend that you add the path to these folders in your local PATH variable. You can find the official documentation for most of the tools in the Android SDK at http:// developer.android.com/tools/help/index.html. The adb Tool In the platform-tools folder, you will find the adb tool that is used to communicate with your device to install and start apps during development. In earlier versions of the Android SDK, this tool was found in the tools directory, but it has since been moved. Besides being used for installing, starting, and debugging applications from your IDE, the adb tool enables you to manually call many of the low-level operations on your Android device for debugging purposes. To list all the commands available, simply type adb help all in your terminal (Linux or Mac OS X) or command prompt (Windows). Some common adb commands are ■ adb devices—List all connected Android devices and Emulators. ■ adb push —Copy a file from your computer to a device (usually on the SD card). ■ adb pull —Copy a file from the device to your computer. adb and Multiple Devices If you ever need to develop and debug an application for two or more devices simultaneously, such as for a multiplayer game or an instant-message application, adb needs an additional argument to know which device you want to address. You do so by adding –s as the first parameter to adb. You can see the serial number for your connected devices by executing the adb devices command. To run the logcat command on a specific device, run the following: $ adb devices List of devices attached 0070015947d30e4b device 015d2856b8300a10 device $ adb –s 015d2856b8300a10 logcat Chapter 1: Fine-Tuning Your Development Environment 11 When you have multiple devices connected or emulators started a dialog box will appear when you launch your application in your IDE. Mastering Logcat Filtering Logging is an important part of Android application development. Although using breakpoints and a debugger from your IDE is extremely powerful when you want to follow the execution flow and inspect the values and state of different variables, simply reading the output from logcat can sometimes be more efficient. Android logging is handled by the logcat function, which is usually integrated in your IDE but can also be called via the adb command. Because Android outputs all system and application log messages to the same stream, it can become quite complicated to find the messages that relate to your application. Luckily, there are some easy ways to filter the logs, as I show in the following code. Android log messages are prepended with a tag and a priority. Usually, you declare a separate log tag for each class in your application like this: private static final String TAG = “MyActivity”; You can then use this tag when printing a log message somewhere in the code for that class: Log.d(TAG, “Current value of moderation: “ + moderation); To filter out all log messages except those that have MyActivity as the value for the tag, you can execute logcat as follows. $ adb logcat MyActivity:* *:S The parameters after logcat are the filters you want to apply. They are formatted as : where an asterisk (*) indicates all possible values. One important thing that can easily be missed is that you must include the special filter *:S, which tells logcat to silence all messages. In combination with your own filters, you can easily control logcat to print only the messages you want to see. If you use filters when viewing the logcat output, I also recommend adding AndroidRuntime:*. This will show you relevant information from the Android system as well as exceptions that are generated by your application but caught by the platform. Connecting adb over Wi-Fi Usually, you connect to your Android device using a USB cable plugged into your computer. However, you can also connect to an Android device over Wi-Fi using a standard TCP/IP connection. This can be very useful when developing applications that are listening for events related to USB (since then the normal connection to your computer will interfere), like connected/disconnected or when you simply don’t want to bother with USB cables during development. 12 Part I: Building a Better Foundation In order to enable adb connection over Wi-Fi, you first need to plug your device into your computer using USB as you normally do. Also, make sure your computer and your Android device are connected to the same Wi-Fi. You also need to know the IP address of your Android device, which you can find on your device by choosing Settings➪ Wi-Fi➪Advanced. In the bottom of the list that appears you will find your device IP address for the current Wi-Fi. After you have this set up correctly, run the following commands from your terminal: $ adb devices List of devices attached 0070015947d30e4b device $ adb tcpip 5555 $ adb connect 192.168.1.104 $ adb devices List of devices attached 192.168.1.104:5555 device The first command is just to verify that your device is connected in debugging mode. The second command tells the device to restart the adb daemon on the device in TCP/IP mode and listen on port 5555 (the default port for adb). The third command tells the adb Service on your computer to connect to the IP address (using default port 5555) of the device. Finally, the fourth command verifies that the computer is connected to the device over TCP/IP. You can now unplug the USB cable from your device and start developing with your IDE as usual. The device will keep the adb daemon in TCP/IP mode until it is restarted or until you run the command adb usb, which restarts the adb daemon in USB mode. Connecting adb over WiFi is not guaranteed to work on all devices. Also, the communication performance with the device is much worse, which can be annoying when you need to deploy a large application. Executing Commands on an Android Device Because an Android system is basically a custom Linux distribution, you can interact with it in much the same way as you can with Linux on your PC. By running a special adb command, you can start a standard Linux-shell on your Android device that lets you perform command-line operations in the same way as you do on your other Linux installations as shown here. $ adb shell When running a shell on your Android device, two useful commands are am and pm. These commands aren’t related to the time of day; instead, they enable you to interact with the Application and Package Managers, respectively, which can be useful during early development and testing. For instance, if you’re implementing a Service that will be started by some externally triggered Intent, you can manually send this Intent using the am command. To start a Service using an Intent action, simply type the following on the command line: $ adb shell am startservice –a Chapter 1: Fine-Tuning Your Development Environment 13 You can add parameters for extras and even specify a certain component name. In addition to startservice, you can launch an Activity or send a broadcast Intent. Calling adb shell am without additional parameters will display a full list of possible commands. You will find that this command is especially useful when you start developing an Android application and need to test a Service before you create the Activity that will launch it. Also, it’s useful for simulating Intents that will be launched by other applications, such as ACTION_VIEW or ACTION_SEND. The Package Manager is a central Android component that manages the installed applications on a device. You can control use of the pm command in a similar way to how you work with the Application Manager. The Package Manager lets you interact with the installed applications (packages), allowing you to list, install, uninstall, and inspect the features and permissions on the device. Although the pm command isn’t as useful during development as the am command, it can sometimes be helpful if you want to find out details about the device that are otherwise a bit complicated to discover. For instance, if you want to list all the installed packages (that is, installed apps), you can type the following: $ adb shell pm list packages In addition to the adb commands just covered, there are many others you need to be familiar with. If you haven’t done so already, take some time to experiment with the adb commands. You can find a list of adb commands and their uses at http://developer.android.com/ tools/help/adb.html. Stress-Testing an Application’s UI with Monkey Most developers often consider testing a tedious and boring task, and Android developers are probably no exception. A good developer handles testing by writing automated tests that verify parts of the code in the application when running on the device. You can also find code inspection tools that search for common coding mistakes (you’ll look at one of these in the section “Static Code Analysis with Lint,” later in this chapter). However, as much as you might wish, writing automated tests and performing static code analysis is never 100% foolproof. A user doesn’t behave according to a certain pattern all the time. Users can click a button at an unexpected moment or accidentally click the wrong button, which can cause your application to crash. You basically need something that behaves like a user would, or better yet, like a monkey would! The Android SDK comes with a powerful tool called the Application Exerciser Monkey, or simply, Monkey. This is a command-line tool that allows you to generate pseudo-random user events like touches or system events on a device. The purpose is to stress test your application by simulating what a user could do. Although Monkey doesn’t simulate a typical use-case scenario for your application, it provides valuable feedback on how your application will work if the user interacts with your user interface in an unpredictable way. The following command executes the Monkey tool on an application with the specific and injects as many random events as specified by . $ adb shell monkey –p 14 Part I: Building a Better Foundation The default behavior for Monkey is to stop when an unhandled exception occurs and to report the error. This behavior is useful for finding things like unexpected NullPointerExceptions or similar issues in your code. You can fine-tune the behavior through various parameters and tell Monkey to halt only if, for instance, a security exception occurs. Scripting Monkey with Monkeyrunner A more advanced way of executing Monkey on your application is by writing a Python script that uses the Monkeyrunner API. This can be very useful in a continuous development environment where you want to run the Monkey tool and perform other actions as well. It also allows you to provide input values for keystrokes and capture screen shots programmatically that can be compared (also using the Monkeyrunner API) to a set of screen shots known to be correct. For a team of Android developers, this can prove to be a huge advantage because it provides a robust regression-testing solution with very little effort. Even small changes to your code can sometimes provide unexpected results that can be very difficult to detect when you’re the only person testing the application before release. Using the Monkey tool, and preferably building Monkeyrunner scripts for regression testing, is highly recommended before publishing your application. You can find the API for the Monkeyrunner at http://developer.android.com/tools/help/ monkeyrunner_concepts.html#APIClasses The Gradle Build System for Android With the release of Android Studio, Google also introduced a new modular build system that replaces the old Ant scripts that were generated by the older versions of the SDK. When you create a new project in Android Studio, it will also create all the Gradle scripts for your project. Gradle is a modular build system similar to Ivy and Maven. It combines the flexibility of Ant with the dependency management from Maven. Instead of writing build-scripts in complex XML-files, Gradle has its own Groovy DSL (Domain-Specific Language) that allows you to express more clearly your build configuration. The following code is the default build.gradle file that is generated for new projects. The first block tells gradle which repository to download plug-ins and dependencies for the build. (This is not the same as the dependencies for your project, which is defined in a later block.) The next part tells Gradle to apply a plug- in, in this case the Android plug-in, which enables the specifics for Android development. Next come the dependencies for your project, in this case only the support library that is located in the libs directory of your project. The final block, starting with android, specifies the configuration for your project. buildscript { repositories { maven { url ‘http://repo1.maven.org/maven2’ } } dependencies { classpath ‘com.android.tools.build:gradle:0.5+’ } Chapter 1: Fine-Tuning Your Development Environment 15 } apply plugin: ‘android’ dependencies { compile files(‘libs/android-support-v4.jar’) } android { compileSdkVersion 18 buildToolsVersion “18.0.0” defaultConfig { minSdkVersion 18 targetSdkVersion 18 } } The user guide for the new Gradle build system can be found at http://tools.android.com/ tech-docs/new-build-system/user-guide. The default directory structure for an Android project using the new build system is slightly different from what you may be used to. Instead of having a flat structure, there are two main source sets: source code and test sources. They are in the following directories: src/main/ src/instrumentTest/ Under the main directory the Java source code is placed in the java and resources are placed in the res directory. The AndroidManifest.xml file is located directly in the main directory (see Figure 1-1). The other directories for project files are assets (for binary assets), aidl (for Android IDLs), rs (RenderScript sources) and jni (native C/C++ code). While building and running your project is supported directly in the Android Studio IDE, you can interact with the build system through the command line as well. Gradle defines a number of tasks and to list all available tasks, simply type the following from the root of your Android project. $ ./gradlew tasks If you, for instance, want to build the application from scratch you would run the following. $ ./gradlew clean build This will execute first the clean task, followed by the build task. 16 Part I: Building a Better Foundation Figure 1-1: The directory structure and Gradle build file shown in the Android Studio IDE Migrating Existing Projects to Gradle Since most existing Android projects are not using the Gradle build system, a migration guide might be in order. The easiest way to migrate is to create a new Android project using Android Studio. Then, copy your own project to a sub-folder in the new project. Next, take the build.gradle file for the application created by Android studio and copy it to the root of your own application. android { sourceSets { main { manifest.srcFile ‘AndroidManifest.xml’ java.srcDirs = [‘src’] resources.srcDirs = [‘src’] aidl.srcDirs = [‘src’] renderscript.srcDirs = [‘src’] res.srcDirs = [‘res’] assets.srcDirs = [‘assets’] } } } Chapter 1: Fine-Tuning Your Development Environment 17 Edit the android section of the file so that it looks like the previous example. This should work well for all existing Android projects that follow the old standard directory structure. If your project has a different structure, simply change the paths from the previous example. (Note that all paths are relative.) Optimize and Obfuscate with ProGuard An Android application is the compiled result from your Java, XML, and other resource files. The Java code is compiled into a binary format called dex, which is what the Dalvik virtual machine on Android reads when executing your code. This format is not designed to be human-readable, tools are available for decompiling a dex file back to a human-readable format. In some cases, decompiling the code can be a security issue—for instance, when your code contains keys or other values that should not be easily accessible (such as integration with Licensing Server). Although it isn’t possible to completely prevent decompilation of your code, you can make it much more difficult by obfuscating it before you publish it. This method will make reverse engineering much more time-consuming and hopefully discourage hacking attempts on your application. You obfuscate your code for Android by using the ProGuard tool that’s integrated in the Android SDK. The tool is supported by the Gradle build system, and all you need to do is to add the following to the android section in build.gradle. buildTypes { release { runProguard true proguardFile getDefaultProguardFile(‘proguard-android.txt’) } } This will enable ProGuard to be applied to the release-build of your application. During normal development this step is excluded. Another reason to obfuscate your code is that doing so performs some additional optimizations, as well as shrinking the resulting dex binary by removing unused code. This is especially useful when you’ve included a large third-party library, because it can significantly reduce the final file size and memory usage during runtime. Android Library Projects and Third-Party Libraries Developers tend to write the same code over and over for new applications, which is why we create code libraries that can be reused in the next project. The result is a faster development cycle because there’s less code to write and test. Android offers you two ways to reuse code among your application projects: by using either a precompiled JAR file or a library project. The first method is preferable when you’re dealing with third-party code that you don’t control or when you have a stable and complete set of library functions that won’t change during the current project. The second method, using a library project, is useful if you’re developing multiple applications in the same project that will share some code—for example, when you’ve decided to create one application for smartphones and one for tablets, or when you have two different applications that will communicate (for example, client and server). 18 Part I: Building a Better Foundation Precompiled JAR Libraries Using a precompiled JAR file in your Android project is very simple. Simply copy the file to the libs directory in your project folder and then add it as a library within your IDE. The code in the JAR file will be available directly, and when you build the application, the Android tool chain will automatically include and package the included classes. If you’re obfuscating your application code with ProGuard, all included JAR files will be processed as well. This is especially useful when you’re including large third-party libraries where you use only part of the classes it provides. To include a local JAR file as a dependency to your project, simply add it to the dependency section in build.gradle like this: dependencies { compile files(‘libs/android-support-v4.jar’) } Another way of doing this is by using a remote dependency repository, such as the central Maven repository. To enable this and include a third-party library found in that repository, you update your build.gradle as follows: repositories { mavenCentral() } dependencies { compile ‘com.google.code.gson:gson:2.2.4’ } The string in the dependencies section is an identifier for a specific version of a library. The call to mavenCentral() will configure your build environment with the correct Maven settings. To search for third-party libraries you can use the Maven search site at http://search.maven. org/. Once you’ve found the right library, simply click on the version and copy the identifier string from the Grails section. Note that not all libraries found on the central Maven repository are supported on Android. Refer to the documentation first. Setting Up a Library Project An Android library project is basically a standard Android project that doesn’t declare any components that can be started (Activities, Services, BroadcastReceivers, or ContentProviders) and that won’t generate an APK when compiled or exported. The library’s sole purpose is to be shared by multiple application projects so that you don’t have to copy the same code between them, which is very useful for sharing constants, utility functions, common custom views, and other components. Because you’re dealing with the actual code and not a precompiled JAR file, the code can be changed, which will affect all the applications that include this library. Because Android development uses the Java programming language, using a library project to share code between an Android application and a server-side component written in Java (for instance, a Java Enterprise Chapter 1: Fine-Tuning Your Development Environment 19 application) can be especially useful. Common code to share in such a setup is the class representation of the shared data and how to serialize and deserialize objects of these classes. The Android SDK comes with a number of ready-to-use library projects that can be found under extras/ google in the SDK folder. More specifically, you can find library projects for the Play Services, APK extensions, In-app Billing and Licensing features. To use these, you simply have to import them into your IDE and add a dependency to your own project. Note: You can refer to multiple library projects in one application project. You can set up a library project from the Android Studio IDE. Simply create a new module and choose Android Library as the module type. A gradle.build will be generated for the new library project as shown next. buildscript { repositories { maven { url ‘http://repo1.maven.org/maven2’ } } dependencies { classpath ‘com.android.tools.build:gradle:0.4’ } } apply plugin: ‘android-library’ dependencies { compile files(‘libs/android-support-v4.jar’) } android { compileSdkVersion 17 buildToolsVersion “17.0.0” defaultConfig { minSdkVersion 7 targetSdkVersion 16 } } Note that the only difference from the default build file for Android project is the use of the plug-in android- library instead of android. To include a library project in your application’s build configuration, you simply refer to it as a dependency as shown in the following. dependencies { compile project(‘:libraries:MyLibrary’) } Version Control and Source Code Management Chances are that most development projects you’ll work on will involve other developers. A development team usually comprises four to eight people, all working on the same code in parallel. Although the team could 20 Part I: Building a Better Foundation decide who will make the changes for every file, it’s much more practical to apply a version control system to the project files that supports a parallel method for development. Another benefit of a version control system is that you can track changes back in time (who made what changes and when in a specific part of a file) and then merge changes from different developers into the same file. One of the most common version control systems used today is Git, and it’s also the system used to manage the source code of both the Android Open Source Project and the Linux kernel. Git is a distributed version control system that allows a developer to work independently from other developers. After the developer completes the changes to the source code for a certain feature, he can push the changes to a server where the other developers can retrieve it. You can read more about Git and download it for various platforms at http://git-scm.com. For a deeper introduction to Git and version control, I recommend the book, Version Control with Git (see the “Further Resources” section at the end of this chapter). A great feature with Git is that you don’t need a server in order to use it, which makes it suitable for all types of projects, from projects where you’re the only developer to projects consisting of multiple teams. I recommend that you always initiate a new Git repository for every new Android application you start developing. Although you can perform all Git operations from your IDE, it’s always good to be able to use the command-line tool as well. The example that follows shows how to initialize a new Git repository: $ git init Initialized empty Git repository in ./git After you set up a Git repository for your project, you can start to add and commit changes. Usually, you perform these operations through your IDE, but if you want, you can also perform the add and commit commands from the command line. If you’re new to Git, I recommend that you take a tutorial before you start using it. Git becomes really powerful when a team of developers needs to work together on the same source code. The team should set up a remote Git repository to which all the team members can synchronize their changes. The easiest way to do so is either to set up a gitolite server, which is dedicated server software that provides remote access to Git repositories, or to use a ready-made hosting solution like GitHub for remote Git repositories. Gitolite allows you to set up your own server for hosting Git repositories, as well as define fine-grained access control. If you need full control of your source code management or aren’t able to store your source code outside the company network, gitolite is your best choice. You can find the documentation and download for gitolite at http://gitolite.com/gitolite. An excellent quick installation guide can be found at http://gitolite.com/gitolite/ qi.html. Chapter 1: Fine-Tuning Your Development Environment 21 GitHub is a web-based hosting service for development projects that use Git. This online service provides both a free and a paid service for individuals and businesses. The great thing about GitHub is that it provides an easy way to share code with anyone on the Internet. If you want to be able to access your central repository when outside the office or share your code as open source, then GitHub is a good choice. Read more about GitHub at http://github.com. Regardless of the size of your project, version control is very important. Even if you’re making only a small prototype or test app, it’s good practice to always set up a Git repository. When you can track the history of your code, you not only have the ability to track your changes efficiently but also the ability to understand why a change occurred, because each commit will contain a message describing it. Take the little extra time required to set up version control every time you start a project. Doing so will pay off. Many developers working with software code will find one more very useful “tool” related to version control: namely, two pairs of eyes inspecting the work, rather than just one, because developers working alone can easily miss their own mistakes. This is why code reviewing is such a powerful technique when it comes to improving the quality of your code. The problem with code reviewing is that it has often taken a lot of effort. Fortunately, there are now excellent tools to support code review, and one of the best is Gerrit. Gerrit’s two main advantages are that it integrates with Git perfectly and it’s completely web-based. Gerrit is also the same code-review tool used for the Android Open Source Project. The basic idea with Gerrit is that a developer pushes a change up the main repository. Before the change gets merged to the main branch, Gerrit sends a notification to all project members that have “subscribed” for these changes, letting them know that there is something new to review. Each reviewer can then compare the change with the previous commit and give the change a ranking and a comment, indicating if something should be fixed or if it looks okay. If something needs to be fixed, the original developer can push up another update that gets tracked by Gerrit. When everything looks okay and is verified, the change can be pushed to the main branch and be available to all developers. You can find out more about Gerrit and download the server at https://code.google.com/p/ gerrit. When you’re working on a team with many developers, code reviewing with Gerrit is a great tool because it supports a distributed review method. The reviewer doesn’t need to be located near the developer to ask questions or give feedback but can do so directly in the tool. It might seem at first that this extra tool will add unnecessary overhead to a project, but anyone who has been doing code reviewing on a regular basis knows how much it improves the quality of the code. For an example of what the Gerrit tool looks like, see Figure 1-2. 22 Part I: Building a Better Foundation Figure 1-2 The web interface for the Gerrit code-review tool Mastering Your IDE In May 2013 at the Google IO conference, Google announced a new IDE for Android named Android Studio. This IDE is based on the open-source version of IntelliJ IDEA (Community Edition). The reason for switching from Eclipse, which was the previously supported IDE, to IntelliJ IDEA was due to the complexity involved in developing the Android plug-in for Eclipse. IntelliJ IDEA provided a far more superior platform for development and allowed the Android tools team at Google to take the development experience to a new level as they could integrate the Android tools into the IDE instead of simply providing a plug-in. In this section, I cover three useful features that are supported by Android Studio: debugging, static code analysis, and refactoring. Although these features have been available from both IntelliJ IDEA and Eclipse for some time, I keep seeing many developers, both beginners and experienced developers, not using them to their full extent. A developer who fully masters these features in his IDE will notice that he becomes much more efficient in producing high-quality code. Debugging Android Applications Debugging capabilities give you fine-grained control over the execution of an application. You can set breakpoints where execution will pause and inspect every aspect of the application state. This feature becomes most useful when it comes to discovering the source of a bug or when you need to carefully inspect exactly what happens during execution. The capability to debug Android applications is built into the IDEs, providing a simple user interface for stepping through the code, inspecting variables, and even changing their values. Chapter 1: Fine-Tuning Your Development Environment 23 You can debug Android applications on your device through the Android SDK. Your IDE will connect to the debugging Service (adb) running on your device, which in turn connects to the Dalvik virtual machine (VM) that your application is running. The procedure is basically the same one you follow when you run an application from your IDE. Figure 1-3 shows one of the sample applications from the Android SDK opened in Android Studio. As you can see, I set a break point in the code and started the debugging from my IDE. The figure shows how the IDE looks once the execution hits the breakpoint. Figure 1-3 Breakpoint reached when debugging sample application in Android Studio When a breakpoint is reached, you can inspect the state of the application at that specific point. All variables accessible in that scope are listed in the debugger UI in the IDE. While debugging applications, you can change the value of a variable in the current scope without having to restart the application and change the assignment in the code, which is useful for testing how an application works depending on different input values. For instance, if your application reads input from a sensor and you are unable to control the values from this sensor, setting a breakpoint after data from the sensor has been read can be a powerful tool during development. Android Studio contains a very powerful debugger tool that not only allows you to change the value of variables but also to execute arbitrary code at the current scope, as shown in Figure 1-4. This capability is especially helpful when you’re performing more advanced inspections that require the temporary injection of several lines of code at the breakpoint. 24 Part I: Building a Better Foundation Figure 1-4 Executing the code evaluation tool in Android Studio Remember that using the debugger is not a substitute for writing tests for your application. Static Code Analysis with lint Even the best developers make mistakes in their code, and over the years different ways of dealing with this fact have been developed. Writing unit tests has proven to be very effective and is something I highly recommend to all developers. However, even with carefully written unit tests, it’s hard to cover all possible situations that might occur in the code, which is why it’s important to complement unit testing with other methods@mdfor instance, static code analysis. Fortunately, Google has added such a tool, called lint, to the Android SDK. The lint tool performs checks on your project’s source files, both XML and Java, and searches for missing elements, poorly structured code, unused variables, and more. In your IDE, the results appear as highlighted areas (see Figure 1-5) indicating that something needs to be fixed. Hovering the cursor over these highlights shows you more details and in some cases allows you to perform the “quick fix” command in order to correct the problem. Chapter 1: Fine-Tuning Your Development Environment 25 Figure 1-5 Android Studio showing missing attributes highlighted in a XML layout file Although it’s a very powerful tool, sometimes lint can indicate a warning in code that may be correct. A warning could be about an unused method declaration or a missing recommended attribute in the manifest. In these cases, the lint warnings may become irritating, especially if several developers are involved in a project. In that case, it may be a good idea to actively suppress a warning for a specific part of the code to indicate that things are okay, despite what lint thinks. Suppressing lint warnings can be done for both Java and XML source files. For Java, you use the annotation @ SuppressLint with the warning you want to suppress as an argument, as shown in Figure 1-6. In XML files, you include the lint namespace and add tools:ignore=”WarningName” (where WarningName is the warning you want to suppress; see Figure 1-7) to the part where you want to suppress the warning. Remember that lint warnings are there for a purpose. They indicate areas that are potential bugs, so always carefully consider whether you really want to suppress the warning. It’s often better to leave the warning in place rather than remove it, as a reminder that it should be dealt with later. 26 Part I: Building a Better Foundation Figure 1-6 Warning suppressed in Java source Figure 1-7 Warning suppressed in XML source Chapter 1: Fine-Tuning Your Development Environment 27 Refactoring Your Code When you write code, you rarely get it perfect on the first try. You tend to change the structure and names of methods, variables, and classes. When the number of classes grows in a project, you might move them into a separate package. As you write more and more code, you suddenly realize that you have code that does the same thing written in several different places with just minor differences. Making changes in existing code can become a complicated process when that code is referenced in many places. If you want to add another parameter to a method, you need to find all the places where the method is used and update those. This can become a very complicated and error-prone process if done manually. Fortunately, your IDE has a built-in function called Refactoring that makes this task simple and eliminates the risk of introducing any bugs. Many refactoring tasks are available in your IDEs. Refactoring involves simple things like renaming and moving variables, methods, classes, and packages, as well as more complex operations such as changing the signature of methods, encapsulating fields, replacing code duplicates, and replacing inheritance with delegation. I cover some of the most common tasks in the following sections. To use refactoring in Android Studio, place the cursor at the position in the code where you want to change something and then select the refactoring task from the Refactor menu. Extracting Constants When you write code it is easy to ignore some best practices that we all know would benefit us in the long run. A common example is when writing constant values in the code (that is, using the actual value instead of a variable) instead of declaring a constant variable (a variable declared as public static final). The problem is that once you decide to change the value, you need to find all the occurrences of that value and replace it with the constant’s name. This is where the Extract Constant refactoring task comes in. As shown in Figure 1-8, this task allows you to quickly and correctly replace all occurrences of that value with a newly declared constant. Changing the Method Signature The method signature defines the access modifier (private, protected, package local or public), return type, name, and parameters for a method. If you have a commonly used method where you want to add an extra parameter, making the correct change in all places where the method is used in your project can become complicated. Figure 1-9 shows how you can change all the parts of a method signature as well as all the places where it’s used in your code. 28 Part I: Building a Better Foundation Figure 1-8 Extracting a repeated value to a constant in Android Studio Figure 1-9 Changing a method signature in Android Studio Chapter 1: Fine-Tuning Your Development Environment 29 Extracting the Method from Code Block It’s good practice to keep methods small to make code more readable and allow for more efficient reuse of the code. When you see that a method is growing out of proportion, you can mark off a block in the code and perform an Extract Method task as shown in Figure 1-10. After that, you can also perform the Change Method Signature task to make the method more generic and reusable if needed. Figure 1-10 Extracting a block of code to a separate method in Android Studio There are many more refactoring options available in the IDE. I recommend that you become familiar with all of these as they will make you a more productive developer. For more refactoring options in Android Studio, which is based on IntelliJ IDEA, users can find a comprehensive guide to most of the refactoring tools at http:// www.jetbrains.com/idea/features/refactoring.html. Developer Options on Android Devices In the Settings application on your Android device, you can find the Developer Menu. Depending on which version of Android you’re running, this can present different options. Also, some device manufacturers may add their own settings in the Developer menu. In this section, I cover the most important details of the Developer menu in Android 4.2 on a Nexus device. 30 Part I: Building a Better Foundation Starting with Android 4.2, Google hides the Developer menu by default, purely for security reasons: Showing the Developer menu could create some serious security issues if it’s enabled by mistake. Here’s how you enable the Developer options on an Android device with version 4.2 or later: 1. Go to the Settings application. 2. Scroll down the list of options and tap the About Phone option, located at the bottom of the list. 3. Locate the row with the label Build Number and tap the Build Number option seven times. A notification saying “You’re Now a Developer” appears, and the Developer Options menu now appears in the Settings list. Understanding Developer Settings In the Developer Options menu (see Figure 1-11), you will find many choices that may be confusing at first. Although they’re all useful in certain situations, I cover those that I believe are most useful for application developers. Figure 1-11 The Developer Options menu on a Nexus 4 device running Android 4 .2 Chapter 1: Fine-Tuning Your Development Environment 31 The first option you want to enable is Stay Awake, which causes your device to keep the screen on while it’s charging—of course, the USB cable needs to be connected to your laptop. This way, you don’t have to unlock your device whenever you want to deploy and run the application you’re developing, which saves a lot of frustration. A bit further down the list is the Debugging option, which contains a number of useful options that you need to understand. The first option you want to enable is USB Debugging. Without this option, you won’t be able to connect the device to your development environment using the adb tool. If you’ll be working with Google Maps or the Location API, enable Allow Mock Locations too, so that you can test different location parameters without having to travel all over the world. Under Input are two settings (Show Touches and Pointer Location) that you’ll find useful when writing applications that rely on advanced touch input, such as gestures and multi-touch interfaces. The Drawing option contains several settings options that allow you to get visual feedback on how the user interface is laid out and how fast and often a redraw occurs. You can also set scaling parameters for animations and transitions as well as disable them completely for debugging purposes. One setting that is new with Android 4.2 is the possibility to simulate a secondary screen. With the introduction of multiple displays in Android 4.2, this feature makes it easy to develop multi-screen applications without having to connect your device to a real secondary screen. Instead, the Android system will draw an overlay on top of the standard user interface that simulates this second display. Under Monitoring, you find some options that can prove invaluable for ensuring that your device runs smoothly. Most developers are usually equipped with the latest version of high-end smartphones. Normal users, however, may use older devices with less powerful CPUs and memory. An application that runs fine on your high-end device can become sluggish and even encounter the dreaded Application Not Responding (ANR) message if you’re not careful. The options under Monitoring in the Development Options menu can help you spot these problems early on. By checking Strict Mode Enabled, the screen on your device will flash when you’re performing a long operation on the main thread. Show CPU Usage is useful for tracking how much more the CPU is working when your application is running, which you can use for comparison with other applications on the same device to see if you’re doing better or worse than they are. Comparing the performance of your application with other similar and popular apps is a good practice. In the final section, named Apps, there are three options that are also useful for simulating the behavior of devices with less CPU and memory. The Don’t Keep Activities option makes sure that your Activities are restarted from scratch every time they’re launched. Normal behavior for Android is to keep Activities alive for as long as possible so that they can be launched faster. The system will completely remove (that is, destroy) Activities only when the device runs low on memory. By checking this option, you can simulate how your app will behave on a device with much less available memory. You can further simulate this kind of behavior by changing the Background Process Limit option, which forces background operations to stop much earlier. Finally, checking Show All ANRs is recommended for all developers, because it will become much more obvious when an application crashes in the background. Normally, ANRs aren’t shown unless the current foreground application causes it to show. 32 Part I: Building a Better Foundation Summary In this chapter, I covered the details of the Android SDK, some of the hidden features of the adb tool, how to stress test your application with the Monkey tool, how to use the ProGuard tool for obfuscating your code, and how the new build system based on Gradle works. I continued with more details on how to setup an Android Library Project or how to integrate a third-party library for your Android application. Because the use of Git as a Source Code Management system for software development is quite widespread today, I also described how you can combine Git with various Services in order to make it easier for a team to work together. The new IDE for developing Android applications, called Android Studio, contains a number of advanced features that many developers have rarely used. By understanding and mastering the refactoring features in this IDE, you can simplify complex tasks when you need to re-structure your code. It also helps to simplify existing code without the risk of introducing new bugs. Finally, I outlined some of the more important details about the developer options settings in Android. It’s important to have a full understanding of how these work and how they can assist in development, debugging, and testing. Just remember to switch them all off and test your application without them to ensure that everything works as intended on a normal user’s device. Developing software is more than simply writing code, regardless what kind of application or service you’re developing. For Android, mastering the tools is as important as the quality of your code, because when the tools are used to their fullest extent, the writing of the code becomes much easier and less error prone. Further Resources Books Loeliger, Jon, and Matthew McCullough. Version Control with Git. O’Reilly Media, Inc., 2012. Websites Android development resources from the official Android Developer site: http://developer. android.com The Gradle build system: http://www.gradle.org Chapter 2 Efficient Java Code for Android Today there are three versions of the Java platform, Java ME (Micro Edition, for certain mobile phones), Java SE (Standard Edition, for desktops), and Java EE (Enterprise Edition, for server-side applications). When talking about Java in general, I am usually referring to Java SE because this is the version that contains a virtual machine and a compiler. Java code is compiled to an intermediate format called byte-code. This byte-code is then parsed by a virtual machine on the target computer that can quickly translate it to the native format required for that particular hardware and operating system. Besides providing a “Write Once, Run Anywhere” advantage for developers, Java has automatic memory management through a garbage collector (GC), reducing the need for you, as a developer, to de-allocate memory from unused objects in your code. Although this feature is very useful and greatly reduces the risk of introducing memory bugs in your code, because the garbage collection process needs to execute continuously, it adds an overhead when running. I start this chapter by doing a high-level comparison of the differences between Java SE and the Java used for Android development. I focus on the Java SE language construct that you may be used to and how it works on Android. Then I focus on how to optimize Java code for Android, how to optimize memory allocations, and how to handle multithreading properly. Comparing Android’s Dalvik Java to Java SE Although developers were able to write applications for mobile devices using the Java programming language long before Android, it was a severely limited version of Java called Java ME (Micro Edition). Java ME also differed among different device manufacturers, making it almost impossible to write an application that would work on any phone supporting Java ME. Also, the distribution of apps was very complicated because no well- supported online stores existed at that time. The launch of Android gave developers the option to build very capable applications for smartphones by writing code using the Java programming language and using the same API they were used to in the standard Java domain. However, although Android developers still use the compiler from Java SE to compile their applications, you can find many differences between the Java that James Gosling developed and the way Java works on your Android device. The VM (virtual machine) that runs on an Android device is called Dalvik. It was originally developed by Dan Bornstein at Google and provided a virtual machine suitable for CPU and memory-constrained devices. There are several differences between Java SE and Dalvik Java, mostly regarding the virtual machine. Although Java SE uses a stack machine design, Dalvik was designed as a register-based machine. The Android SDK has a tool 34 Part I: Building a Better Foundation called dx that converts the Java SE stack machine byte-code to Dalvik register-based machine byte-code. The conversion step is done automatically by your IDE so that you don’t need to bother about it. The exact definition and technical difference between a stack-based machine and a register-based machine is beyond the scope of this book. Android uses a register-based machine for historical reasons. Although a register- based machine can be up to 32% faster than a stack-based machine, this is true only for virtual machines that are interpreting the byte-code at execution (that is, interpreted virtual machines). Up until Android version 2.2 (also known as Froyo), the Dalvik VM was purely interpreted. With the Froyo version of Android came the introduction of a JIT compiler (Just In Time), something that Java SE had benefitted from for a long time. JIT compilation, also known as dynamic translation, takes the byte-code and translates it into native code prior to execution (see Figure 2-1), which has two major benefits. First, it eliminates much of the overhead associated with pure interpreted VMs; second, it can perform optimizations to the native code that wouldn’t be possible with statically compiled code. For instance, the JIT compiler may choose the most appropriate optimizations for the CPU it is currently running. It’s also possible for a JIT compiler to analyze how the code is running to perform further optimizations based on the application’s input. Figure 2-1 Translation steps for Android Java and Java SE Chapter 2: Efficient Java Code for Android 35 As promising as this sounds, Android’s Dalvik JIT compiler has a long journey ahead before it reaches the same level of maturity as the JIT compiler in Java SE. Still, the presence of a JIT in Dalvik provides great performance benefits for Android, and it’s continuously being improved. Another difference between the Java SE VM and the Dalvik VM is that the latter is optimized for running in multiple instances on the same machine. This is handled by a process started at boot called zygote that creates the first Dalvik instance that will be the parent of all other instances. Once an application starts, the zygote process receives a request for a new VM instance and forks a new process that is assigned to the newly started application, as shown in Figure 2-2. This design may seem impractical if you’re used to working with Java SE, but it has a major advantage because it protects you from multiple application crashes in cases where one application has a runtime failure that will crash the Dalvik VM. Figure 2-2 Launching of new Dalvik VM instances in Android In addition to running with a different virtual machine than the one Java SE uses, Android has a different implementation of the APIs. All the APIs in Android that belong to the java or javax packages come from Apache Harmony, an open-source project aiming for reimplementation of the Java SE stack. (The Apache Harmony project has been retired since November 2011 and is no longer being actively developed.) In terms of developing, these APIs are identical to the ones that are found in Java SE, but some differences do exist. (For instance, Google has made major upgrades to the HttpUrlConnection class that is not present in the Java SE version.) Also, not all of the Java SE APIs are available on Android because they’re irrelevant for this platform. For instance, the Swing/AWT packages have been completely removed because Android uses a different UI framework. Other APIs that have been removed are RMI, CORBA, ImageIO, and JMX. These have either been replaced by an Android specific version (in the android package space) or simply don’t have an equivalent in Android for practical reasons. 36 Part I: Building a Better Foundation Optimizing Java Code for Android Java SE has evolved over the years with new features that simplify writing complicated code structures. Many of the features make it easier for developers, and you need to understand when and how to use them properly. Also, because Java SE has been used mostly for server-side development (using the Java Enterprise Edition APIs), Java code has been optimized to meet server-side requirements. Annotations and support for scripting languages in the Java SE virtual machine are examples of optimizations focused on server-side development. Although powerful tools when building your back end, these kinds of features serve little purpose and can even be fatal when writing client-side code as done in an Android application. Java developers have gotten used to unlimited amounts of RAM and CPU, whereas Android development requires close attention to performance and allocations. Simply put, you need a slightly different approach when writing code for Android as compared to developing back end solutions in Java. However, some recommendations have changed since Android was first released. Some modern Java constructs once avoided on Android are now recommended, mostly because of the modern JIT compiler for Android that removes many of the performance bottlenecks these constructs used to cause. This section deals with the aspects of Java code that you need to understand when writing Android applications. While the details of the Java programming language are outside the scope of this book; instead, I focus on what is important for Android development. Still, it’s important to understand that most of the rules and recommendations that apply to Java SE apply to Android and the Dalvik VM. Type-Safe Enum on Android With Java SE 5.0 came many new features in the Java programming language that made life easier for Java developers. One of the most anticipated features was the introduction of type-safe enumerations. Enumerations are used to represent a number of choices in code that belong to a common group. In earlier versions of Java, multiple integer constants were used to solve this. Although this works technically, it suffers from being quite error-prone. Take a look at the following code: public class Machine { public static final int STOPPED = 10; public static final int INITIALIZING = 20; public static final int STARTING = 30; public static final int RUNNING = 40; public static final int STOPPING = 50; public static final int CRASHED = 60; private int mState; public Machine() { mState = STOPPED; } public int getState() { return mState; } public void setState(int state) { mState = state; } } Chapter 2: Efficient Java Code for Android 37 The problem is that although the constants are the values you expect in the setter, nothing is preventing the method setState() from receiving a different value. If you add a check in the setter, you need to handle the error in case you get an unexpected value. What you want is a compile-time check that prevents you from ever assigning an illegal value. Type-safe Java enum solves this problem, as shown here: public class Machine { public enum State { STOPPED, INITIALIZING, STARTING, RUNNING, STOPPING, CRASHED } private State mState; public Machine() { mState = State.STOPPED; } public State getState() { return mState; } public void setState(State state) { mState = state; } } Notice the new inner enum class added where you declare different states as type-safe values. This solves the problem with unexpected values at compile time, so the code will be much less error-prone. Before the Dalvik VM had a JIT compiler that optimized the code, using type-safe enum was discouraged on Android because the memory and performance penalties associated with this design were greater than when using integer constants. This is why so many integer constants are in the older parts of the Android APIs. These days, with a capable JIT compiler and an ever-improving Dalvik VM, you don’t have to worry about that issue and are encouraged to use type-safe enum in your application code. However, there are still situations where integer constants are preferable. With basic types like the Java int, you won’t increase the amount of work for the GC. Also, many existing APIs in the Android SDK still rely on basic types as parameters—for example, the Handler class described in the “Multithreading on Android” section later in this chapter—in these cases, you don’t have much choice. Enhanced For-Loop on Android Java SE 5.0 also introduced the enhanced for-loop that provides a generic an abbreviated expression for looping over collections and arrays. First, compare the following five methods: void loopOne(String[] names) { int size = names.length; for (int i = 0; i < size; i++) { printName(names[i]); } } 38 Part I: Building a Better Foundation void loopTwo(String[] names) { for (String name : names) { printName(name); } } void loopThree(Collection names) { for (String name : names) { printName(name); } } void loopFour(Collection names) { Iterator iterator = names.iterator(); while (iterator.hasNext()) { printName(iterator.next()); } } // Avoid using enhanced for-loops for ArrayList void loopFive(ArrayList names) { int size = names.size(); for (int i = 0; i < size; i++) { printName(names.get(i)); } } These methods show four different ways of looping through collections and arrays. The first two methods have the same performance, so it’s safe to use the enhanced for-loop on arrays if you’re just going to read the entries. For Collection objects, you get the same performance when using the enhanced for-loop as when you manually retrieve an Iterator for traversal. The only time you should do a manual for-loop is when you have an ArrayList object. In the cases where you not only need the value of each entry but also the position, be sure to use either an array or an ArrayList because all other Collection classes are much slower in these situations. In general, if you need high-quality performance when reading sets of data that rarely change, use a regular array. However, arrays have a fixed size and will affect performance when adding data, so consider all factors when writing your code. Queues, Synchronization, and Locks Frequently, an application will produce data in one thread and consume them in another. A common example is when you’re reading data from the network on one thread and want to display the data to the user on a different thread (the main thread where UI operations occur). This pattern is usually called the consumer/ producer pattern, and in their object-oriented programming courses, programmers may spend several hours implementing this in their algorithm. In this section, I’ll show some of the ready-made classes that make it easier to implement this behavior. Chapter 2: Efficient Java Code for Android 39 Smarter Queues Many Java developers still choose to implement queues in their code using LinkedList and synchronized blocks, although there are ready-made classes that do so and require much less code. You can find the classes for performing concurrent programming in the java.util.concurrent package. In addition, you can find classes for semaphores, locks, and atomic operations on single variables. Consider the following code where a thread-safe queue using a standard LinkedList is implemented: public class ThreadSafeQueue { private LinkedList mList = new LinkedList(); private final Object mLock = new Object(); public void offer(String value) { synchronized (mLock) { mList.offer(value); mLock.notifyAll(); } } public synchronized String poll() { synchronized (mLock) { while(mList.isEmpty()) { try { mLock.wait(); } catch (InterruptedException e) { // Ignore for brevity } } return mList.poll(); } } } Although this code is correct and would probably merit a full score on an exam, it’s simply a waste of your time to implement and test it. Instead, you can replace all the preceding code with the following line: LinkedBlockingQueue blockingQueue = new LinkedBlockingQueue(); This one line gives you the same type of blocking queue as the previous example and even provides additional thread-safe operations. The java.util.concurrent package has a number of alternative queues and deuce classes as well as concurrent map classes, so in general, I suggest using them rather than using lengthier code similar to my earlier example. Smarter Locks The synchronized keyword in Java provides a powerful feature that allows you to make a method or call-block thread safe. Although easy to use, it is also easy to apply it too widely, which can have very negative impact on performance. When you need to differentiate between reading and writing data, the synchronized keyword is not the most efficient. Luckily, a utility class in the java.util.concurrent. locks package provides exactly that support. 40 Part I: Building a Better Foundation public class ReadWriteLockDemo { private final ReentrantReadWriteLock mLock; private String mName; private int mAge; private String mAddress; public ReadWriteLockDemo() { mLock = new ReentrantReadWriteLock(); } public void setPersonData(String name, int age, String address) { ReentrantReadWriteLock.WriteLock writeLock = mLock.writeLock(); try { writeLock.lock(); mName = name; mAge = age; mAddress = address; } finally { writeLock.unlock(); } } public String getName() { ReentrantReadWriteLock.ReadLock readLock = mLock.readLock(); try { readLock.lock(); return mName; } finally { readLock.unlock(); } } // Repeated for mAge and mAddress… } The preceding code exemplifies where to use a ReentrantReadWriteLock that allows read-only access from multiple concurrent threads while making sure that only one thread at a time can write to the same data. Using synchronized in your code is still a valid way of handling locks, but always consider whether a ReentrantReadWriteLock could be a more efficient solution. Memory Management and Allocations The automatic memory management in Java has effectively eliminated many of the most common bugs in software development. When you no longer have to remember to release every allocation of a new object in your code, you save time that you can use to improve the features and overall quality of software. But this feature doesn’t come for free because you now have an automatic garbage collector that runs in parallel with your application. The GC will run continuously and check whether any memory allocations can be reclaimed. This behavior means that the threads in your application will compete for CPU time with the GC, so it’s crucial to make sure that GC calls don’t take too long whenever it is running. Chapter 2: Efficient Java Code for Android 41 Also, automatic memory management does not remove the possibility for memory leaks. If you keep references to objects that are no longer needed, the GC will not collect them, and they will waste memory. If objects are being allocated continuously but are never being released, you’ll eventually run into an OutOfMemory exception, and your application will crash. So, try to avoid keeping references to objects in the main Android components; otherwise, they may never be “garbage collected” during the application’s lifetime. Reducing Object Allocations On Java and Android, the most common problem with automatic memory management is when you are allocating unnecessary objects that keep the GC working more than it should. Consider a case in which a simple class represents a pair of integers: public final class Pair { public int firstValue; public int secondValue; public Pair(int firstValue, int secondValue) { this.firstValue = firstValue; this.secondValue = secondValue; } } Now, say that you receive an array of integers in your application that you divide into pairs and then send to the method sendPair. Here is an example of when memory allocation is done badly: public void badObjectAllocationExample(int[] pairs) { if(pairs.length % 2 != 0) { throw new IllegalArgumentException(“Bad array size!”); } for(int i = 0; i < pairs.length; i+=2) { Pair pair = new Pair(pairs[i], pairs[i+1]); sendPair(pair); } } Although this is a very crude example of how to generate Pair objects (and potentially cause a crash if the length of the array is not an even size), it demonstrates a surprisingly common mistake: allocating objects inside a loop. The GC will have a lot of work to do inside the previous loop above and will most likely cause the application UI to stutter from CPU exhaustion. If you know that the sendPair method won’t keep a reference to the Pair object after it returns, your solution is simply to move the creation of the Pair object outside the loop and reuse the object, as shown as follows: public void betterObjectAllocationExample(int[] pairs) { if(pairs.length % 2 != 0) { throw new IllegalArgumentException (“Bad array size!”); } Point thePair = new Point(0,0); for (int i = 0; i < pairs.length; i+=2) { thePair.set(pairs [i], pairs [i+1]); sendPair(thePair); } } 42 Part I: Building a Better Foundation In this new version of the method, you ensure that the object is reused during the entire run of the method. There will be only one GC call once the method returns. Remember, when allocating objects, avoid doing so inside a loop when possible. Sometimes, however, you can’t avoid creating objects inside a loop, so you also need a way of handling those situations. The solution here is to allocate objects on demand using a static factory method. (Joshua Bloch describes this method in detail in Item 1 of his book Effective Java.) This approach is commonly used in the Android framework and APIs and allows you to use a behind-the-scenes object cache that is populated on demand. The only drawback is that you need to manually recycle the objects, or the cache will always be empty. Based on your previous example, you start by refactoring the Pair class so that you have a simple pool for reusing objects. public final class Pair { public int firstValue; public int secondValue; // Reference to next object in the pool private Pair next; // The lock used for synchronization private static final Object sPoolSync = new Object(); // The first available object in the pool private static Pair sPool; private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50; /** * Only allow new objects from obtain() */ private Pair() { } /** * Return recycled object or new if pool is empty */ public static Pair obtain() { synchronized (sPoolSync) { if (sPool != null) { Pair m = sPool; sPool = m.next; m.next = null; sPoolSize--; return m; } } return new Pair(); } Chapter 2: Efficient Java Code for Android 43 /** * Recycle this object. You must release all references to * this instance after calling this method. */ public void recycle() { synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } } } Note that a number of fields are added, both static and non-static. You use these fields to implement a traditional linked list of Pair objects. Objects of this class are created only through the obtain method. You prevent new objects from outside this class by making the constructor private. The obtain method first checks whether the pool contains any existing objects and removes the first element in the list and returns it. If the pool is empty, it simply creates a new object. To put the object back into the pool, you call recycle once you’re done. At that point, it is very important that you don’t touch that object again. With this modification of the Pair class, your previous method with the loop also needs some changes. public void bestObjectAllocationExample(int[] pairs) { if(pairs.length % 2 != 0) throw new IllegalArgumentException (“Bad array size!”); for (int i = 0; i < pairs.length; i+=2) { Pair pair = Pair.obtain(); pair.firstValue = pairs[i]; pair.secondValue = pairs[i+1]; sendPair(pair); pair.recycle(); } } The first time this method runs, you create a new instance of Pair, which will then be reused for every new iteration. However, the next time you run this method, no new object is created. Also, because the obtain and recycle methods are thread-safe, this method is safe to execute in multiple concurrent threads. The only drawback is that you have to remember to call recycle manually, but this is a small price to pay considering you’ve postponed all GC calls for the Pair class until the application exits. The example with the Pair class is very trivial, but it illustrates a pattern that is highly encouraging if you have a class that would otherwise be created frequently in your code. This design may look familiar because it’s in several places in the Android source code and APIs. Commonly used classes such as Message, MotionEvent, and Parcel all implement this pattern in order to reduce unnecessary GC calls. The example with the preceding Pair class is basically a copy of the Message class implementation. When using this approach, remember to call recycle once you’re done with an object that implements this, or the pool will be empty all the time. 44 Part I: Building a Better Foundation Multithreading on Android One of the most difficult parts of programming is when you have to write code that executes on multiple threads. It’s a requirement for modern applications because you can’t have everything executing in a serial order on one single thread. An Android application starts with a thread called main, also known as the UI thread (in this book, I use main and UI thread interchangeably). Unless you start another thread or implicitly call a function that starts a new thread, everything you do in an Android application will execute on the main thread. This means that if your code performs an operation on the main thread (say, running code in the onResume method) that will take a significant amount of time, all drawing and input events will be blocked until that operation is completed. So, the first thing to remember when you start writing your application code: Make sure that any code you execute won’t ever block the main thread. But how do you know if a method is executing on the main thread? The official Android documentation states: “By default, all components of the same application run in the same process and thread (called the ‘main’ thread)." More specifically, all the callbacks (basically, all the onX methods) in the Android components (Activity, BroadcastReceiver, Service, and Application) are executed on the same thread. The onStartCommand method of a Service is thus running on the same thread as the onResume of your Activity. Remember this when you design your code because blocking one of these methods will cause the system to “kill” your application. The main thread in Android will run as long as your application’s process is alive. It is kept alive during the application life cycle by the Looper class. This class creates a loop inside the current thread that queries a message queue (using the MessageQueue class). The query to this queue will block until a new message has arrived, which ensures that the thread sleeps when there’s nothing to do. All execution on the main thread is done by sending messages to this queue, either directly through a Handler object or indirectly through some part of the Android API (for instance, the runOnUiThread method). You can retrieve the Looper for the main thread of your application through Context.getMainLooper(). What is safe to execute on the main thread and what should be moved to a different thread? To be very strict, only the methods that must be executed on the main thread should be executed there. Everything else should go on a separate thread. For practical reasons, you can bend this rule in some cases where the operation you perform will never take a long time. If you make sure that any file, database, or network operations are performed on a separate thread, you are usually safe. Also, for certain applications and games, you will perform calculations at regular intervals that don’t directly have anything to do with the UI, and these should also execute on a separate thread. However, it’s also important to ensure that you don’t have too many threads running at the same time because a performance penalty is involved when the CPU switches from one thread to another. In later chapters in this book, I give more specifics on when and when not to put your code on a separate thread. How do you declare and manage your threads when writing Android code? In the following section I show you several ways to spawn new threads, and I explain each of these methods in detail, including their pros and cons. Thread This is the base class for all threads in Android. It is the same class as found in Java SE, and it behaves the same way. If you want to execute something inside a thread, you can either make a specialization (that is, a new class that extends Thread) or create a class implementing the Runnable interface that you pass to the constructor of Thread. This example uses the latter option. Chapter 2: Efficient Java Code for Android 45 In this example, you need to iterate over an array of Objects in order to “upload” data to a server (the actual code for uploading is not part of this example). You need to execute this operation on a separate thread; otherwise, it will block the user interface. Also, the operation needs to update its progress by increasing a ProgressBar. The following code shows an implementation of Runnable that resolves this issue: public class MyThread implements Runnable { private Object[] mInput; private Activity mActivity; private int mProgress = 0; public MyThread(Activity activity, Object ... input) { mActivity = activity; mInput = input; } @Override public void run() { mProgress = 0; Runnable runnable = new Runnable() { public void run() { mActivity.findViewById(R.id.progressBar). setVisibility(View.VISIBLE); ((ProgressBar) mActivity. findViewById(R.id.progressBar)).setProgress(0); } }; mActivity.runOnUiThread(runnable); // Loop through and process mInput for (Object input : mInput) { // Post the input to a server (fake it with a sleep) SystemClock.sleep(50); runnable = new Runnable() { public void run() { ((ProgressBar) mActivity. findViewById(R.id.progressBar)). setMax(++mProgress); ((ProgressBar) mActivity. findViewById(R.id.progressBar)). setProgress(mInput.length); } }; mActivity.runOnUiThread(runnable); } runnable = new Runnable() { public void run() { mActivity.findViewById(R.id.progressBar). setVisibility(View.INVISIBLE); } ) 46 Part I: Building a Better Foundation }; mActivity.runOnUiThread(runnable); } } As you can see in the preceding example, you need to create a new Runnable every time you want to update the UI. This makes the code messy and will also cause unnecessary object allocations that need to be garbage collected, which is always a bad thing. The Runnable is needed in order to post UI updates back to the main thread through the runOnUiThread method. There’s another problem with this solution: Because you can call start on an instance of Thread only once, you must create a new Thread object every time you need to perform this operation. A new thread is an expensive thing to create again and again, so there is definitively room for improvement here. All together, this is not a very flexible method, and I discourage the direct use of the Thread class. AsyncTask This class is probably one of the more popular classes in Android because it is so simple to use. It allows you to define a task that will run on its own thread and provide callbacks for the different stages of the task. The callbacks are also designed to remove the need for using the runOnUiThread method to update the UI while it’s running, which makes it very appropriate for indicating the progress of a long-running operation. Here is an AsyncTask that does the same thing as in the Thread example: public class MyAsyncTask extends AsyncTask { private Activity mActivity; public MyAsyncTask(Activity activity) { mActivity = activity; } @Override protected void onPreExecute() { super.onPreExecute(); // This will run on the main thread mActivity.findViewById(R.id.progressBar). setVisibility(View.VISIBLE); ((ProgressBar) mActivity.findViewById(R.id.progressBar)). setProgress(0); } @Override protected Integer doInBackground(String... inputs) { // This will run NOT run on the main thread int progress = 0; for (String input : inputs) { // Post the input to a server (fake it with a sleep) SystemClock.sleep(50); publishProgress(++progress, inputs.length); } Chapter 2: Efficient Java Code for Android 47 return progress; } @Override protected void onProgressUpdate(Integer... values) { // This will run on the main thread ((ProgressBar) mActivity.findViewById(R.id.progressBar)). setMax(values[1]); ((ProgressBar) mActivity.findViewById(R.id.progressBar)). setProgress(values[0]); } @Override protected void onPostExecute(Integer i) { super.onPostExecute(i); // This will run on the main thread mActivity.findViewById(R.id.progressBar). setVisibility(View.INVISIBLE); } } In the preceding example, you see the implementation of four callbacks with a comment regarding which thread they will execute on. As you can see, onPreExecute, onProgressUpdate, and onPostExecute are all executed on the main thread, so it is safe to update the UI from these. publishProgress is also called for each input to trigger the onProgressUpdate callback so that you can update a progress bar. This class makes it much easier to perform long-running operations on a different thread and still be able to communicate easily with the main thread when needed. The only problem with AsyncTask is that you can use each instance of this class only once, which means that you have to call new MyAsyncTask() every time you want to perform this operation. Although this class is not a heavyweight—the actual thread is managed by an ExecutorService behind the scenes—it is not suitable for operations that you perform frequently because you would quickly gather up objects that need to be garbage collected and eventually cause your application’s UI to stutter. In addition, you cannot schedule the time for the execution of this operation or perform the operation at a certain interval. The AsyncTask class is suitable for things like file downloads or similar situations that will happen relatively infrequently or by user interaction. Nevertheless, because it’s so easy to implement, this is likely to be the class you will use at first in your application. Handler When you need a more fine-grained control for executing operations on a separate thread, you have a great utility at your disposal in the Handler class. This class allows you to schedule operations with exact precision, and you can reuse it as many times as you want. The thread that executes the operations loops until you explicitly terminate it; the class Looper takes care of this behind the scenes. You will rarely need to set up your own Looper ; instead, you can create it through the wrapper called HandlerThread. The following example shows how to create a new Handler in your Activity. 48 Part I: Building a Better Foundation public class SampleActivity extends Activity implements Handler.Callback { private Handler mHandler; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Start a new thread with a Looper HandlerThread handlerThread = new HandlerThread(“BackgroundThread”); handlerThread.start(); // Create your new Handler mHandler = new Handler(handlerThread.getLooper(), this); } @Override protected void onDestroy() { super.onDestroy(); // Shut down the Looper thread mHandler.getLooper().quit(); } @Override public boolean handleMessage(Message message) { // Process incoming messages here... // Recycle your message object message.recycle(); return true; } } With your new Handler object, you can now safely schedule operations with exact precision. The usual way of working with a Handler is by sending Message objects, which are simple, cheap, and reusable objects for passing data and arguments to your background thread. A Message object is usually defined by its public integer field name what that works as a flag that can be used in a switch-case statement in the callback handleMessage. There are also two integer fields named arg1 and arg2 that are suitable for low-cost arguments, as well as a field name obj for storing a single arbitrary object reference. If needed, you can also add a set of more complex data using the setData method with a standard Bundle object. You can send your messages to the handler using a number of methods. The three most common ones are demonstrated here: public void sendMessageDemo(Object data) { // Create a new Message with data as a parameter // and send it for execution on the handler immediately Message.obtain(mHandler, SYNC_DATA, data).sendToTarget(); // Send a simple empty message on your handler immediately mHandler.sendEmptyMessage(SYNC_DATA); // Send a simple empty message to be executed 30 seconds from now mHandler.sendEmptyMessageAtTime(SYNC_DATA, THIRTY_SECONDS_IN_MILLISECONDS); Chapter 2: Efficient Java Code for Android 49 // Send a message with both arguments fields and // the obj set to be executed in two minutes. int recipient = getRecipientId(); int priority = 5; Message msg = mHandler.obtainMessage(SYNC_DATA, recipient, priority, data); mHandler.sendMessageDelayed(msg, TWO_MINUTES_IN_MILLISECONDS); } The first two examples show that you can create and send messages through both the Message class and the Handler object. In the third and fourth example, you see how to schedule the message for processing with millisecond precision. When the Message is processed from the message queue, it will be sent, on the looping thread, to the callback you’ve implemented. You can use the same callback for multiple Handler objects, which makes it useful as a proxy-like method for dealing with the messages in your application. You can even share the callback between Activities and Services. The most optimal way of implementing your callback is by keeping all the constants representing the what value of a Message in the same class that implements the callback and then use a standard switch-case statement to handle each message type. In the preceding example, I implemented the callback on the Activity, but it can often be useful to have a separate class for this that you pass your application’s Context to so that it can be used in all parts of your application. Here is an example of a typical callback: // Constants used for the what field in a Message public static final int SYNC_DATA = 10; public static final int PING_SERVER = 20; @Override public boolean handleMessage(Message message) { switch (message.what) { case SYNC_DATA: // Perform long-running network I/O operation syncDataWithServer(message.obj); break; case PING_SERVER: // Ping your server. This should be called at regular intervals pingServer(); break; } // Recycle your message object to save memory message.recycle(); return true; } The handleMessage callback in this example has only two operations implemented, SYNC_DATA and PING_SERVER. The first one will probably be triggered by a user event—for example, when a file is saved or new data is ready to be posted to a server. The second one is supposed to execute at regular intervals. However, there is no method on the Handler class for sending messages at an interval, so you need to implement that behavior. 50 Part I: Building a Better Foundation Scheduling Operations at Recurring Intervals Say that as soon as your Activity starts, you want to start pinging the server every minute. When the user exits the Activity, you should stop pinging. In the following example, I add calls to the Handler in onResume() and onPause() (see the earlier examples in this section for the setup of the Handler instance), effectively making these happen when the Activity is shown and dismissed. In onResume, I set the boolean for pinging the server to true and sent a PING_SERVER message to be executed immediately (the first ping should happen as soon as possible). The message arrives in your callback message shown in the previous example, which calls your pingServer() method. public class SampleActivity extends Activity implements Handler.Callback { private static final String PING_URL = “http://www.server.com/ping”; private static final int SIXTY_SECONDS_IN_MILLISECONDS = 60 * 1000; public static final int SYNC_DATA = 10; public static final int PING_SERVER = 20; private Handler mHandler; private boolean mPingServer = false; private int mFailedPings = 0; ... code from previous examples omitted for brevity @Override protected void onResume() { super.onResume(); mPingServer = true; mHandler.sendEmptyMessage(PING_SERVER); } @Override protected void onPause() { super.onPause(); mPingServer = false; mHandler.removeMessages(PING_SERVER); } private void pingServer() { HttpURLConnection urlConnection try { URL pingUrl = new URL(PING_URL); urlConnection = (HttpURLConnection) pingUrl.openConnection(); urlConnection.setRequestMethod(“GET”); urlConnection.connect(); if(urlConnection.getResponseCode() == 200) { mFailedPings = 0; } // Here you should also handle network failures... } catch (IOException e) { // Network error should be handled here as well... } finally { if(urlConnection != null) urlConnection.disconnect(); } Chapter 2: Efficient Java Code for Android 51 if(mPingServer) { mHandler.sendEmptyMessageDelayed(PING_SERVER, SIXTY_SECONDS_IN_MILLISECONDS); } } } In pingServer(), you make a simple HTTP call to see if your server is alive. Once your call is completed, you check whether you should keep pinging the server, and if so, you schedule a new message to be processed in 60 seconds. In the onPause() method, you change the boolean to false and then remove any pending messages with the what field set to PING_SERVER. Using the MainLooper with a Handler Because you assign a thread to a Handler by giving it a Looper object in the constructor, you can create a Handler that processes messages on the main thread. Doing so is especially useful when you want to avoid using the runOnUiThread() method, which tends to result in rather ugly and less optimal code if used often. I use this pattern frequently in my applications so that I can simply send messages between the main thread and the thread used for background operations. @Override public boolean handleMessage(Message message) { switch (message.what) { case SYNC_DATA: syncDataWithServer(message.obj); break; case SET_PROGRESS: ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar); progressBar.setProgress(message.arg1); progressBar.setMax(message.arg2); break; } message.recycle(); return true; } In the preceding handleMessage example, you can receive two types of messages, SYNC_DATA and SET_ PROGRESS. The first one needs to run on a separate thread, whereas the second one must run on the main thread because it will update the UI. To do this, you create an additional Handler object that will be used to send messages that are processed on the main thread. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mMainHandler = new Handler(getMainLooper(), this); HandlerThread handlerThread = new HandlerThread(“BackgroundThread”); handlerThread.start(); mHandler = new Handler(handlerThread.getLooper(), this); } 52 Part I: Building a Better Foundation Notice that this is basically the same onCreate method as shown earlier. The exception is the line where you create mMainHandler. Instead of starting a new HandlerThread, you simply retrieve the Looper for the main thread. This doesn’t affect how the main thread is running; you just connect an additional Handler and callback to the main thread. Messages posted to this Handler are processed by the same callback as the second Handler you create for background operations. When you want to update the ProgressBar, you send a simple message, as shown here: Message.obtain(mMainHandler, SET_PROGRESS, progress, maxValue). sendToTarget(); You can use this approach for any operation that must be executed on the main thread. You can either send simple Message objects as just shown or pass along more complex data in the obj field or as a Bundle in setData. You just need to make sure that you post messages to the correct Handler. Picking the Right Solution for Threads I’ve now shown three different methods for creating and using threads on Android. There are other utilities in the API for doing this as well, such as the ExecutorService and the Loader. The ExecutorService is useful if you need to have multiple operations running in parallel, which is usually the situation when writing server-side applications responding to multiple clients. This Service is also used behind the scenes in AsyncTask, and if you want to be able to execute multiple AsyncTasks in parallel, you can do so using the right ExecutorService. The three preceding examples are the ones to start with when you need a dedicated thread for an operation. Using the Thread class directly is discouraged unless you need full control of the entire execution of the thread. AsyncTask and Handler are recommended in most cases, and which one you choose depends on your needs. If the operation isn’t performed frequently, like more than once every minute, AsyncTask is probably a good choice. If you need to schedule operations or need to do something at a fast, recurring interval, Handler is a better choice. Working with the Handler tends to generate less code in the long run, whereas AsyncTask is easier to use. Summary In this chapter, I covered some of the advanced Java features in Java SE 5.0 that with the modern JIT-enabled Dalvik VM are now safe to use on Android. Understanding and using these features will likely simplify your code, which makes the code easier to test and maintain. I also showed how to use the concurrency API from java. util.concurrent, instead of implementing your own queues or locks. Whenever you reinvent the wheel by implementing your own version of a basic class, you’re making a common but big mistake: You’ll have more code to test, more code that can cause bugs, and more code to maintain. I also explained how to avoid many pitfalls when it comes to memory allocations. If your code creates a lot of temporary and short-lived objects, your application will most likely perform badly in the user interface. Understanding how to efficiently and safely reuse objects allows for a much smoother user experience. Chapter 2: Efficient Java Code for Android 53 To conclude the chapter, I covered three methods for using threads on Android but recommended using only two of them (AsyncTask and Handler). Multithreading is a complex topic and often the cause of many bugs that are hard to find. Always try to use the ready-made utility classes for threads because they will make things simpler and allow you to focus on the functions of your own code. Java is a powerful language that makes it easier for developers to express what they want to achieve and learning how to use the language most efficiently will make you a better developer and help you create high- quality code. Further Resources Documentation I recommend reading the performance tips chapter at: http://developer.android.com/ training/best-performance.html Books Bloch, Joshua. Effective Java, 2nd Edition. Addison-Wesley, 2008. Online Sources Google IO video sessions on YouTube at: www.youtube.com/user/GoogleDevelopers Chapter 3 Components, Manifests, and Resources Chapter 4 Android User Experience and Interface Design Chapter 5 Advanced User Interface Operations Chapter 6 Services and Background Tasks Chapter 7 Android IPC Chapter 8 Mastering BroadcastReceivers and Configuration Changes Chapter 9 Data Storage and Serialization Techniques Chapter 10 Writing Automated Tests Part II Getting the Most Out of Components Chapter 3 Components, Manifests, and Resources So far I’ve covered the more general aspects of development, much of which you can apply to generic Java development as well. In this chapter, I start going through some more Android-specific information. (Note: I’m assuming that you’re familiar with the basic concepts of Android and have already written applications for this platform.) The three core concepts of any Android application are the components, the manifest, and the resources. Every Android application uses all of these, so you need to fully understand them. Although your IDE will most likely assist you in setting up the basics, you can do a lot more by optimizing them yourself. I start with an overview of the Android components and how to use them optimally for the software architecture of your applications. Next, I cover some of the details of the Android manifest and explain how you can fine-tune the parameters to suit your application. Finally, I cover some advanced details of the resources and assets and how you can work with these. Android Components When you write an Android application, you usually start by defining a main Activity. In fact, your IDE will probably ask you for the name of the main Activity and create it for you. You continue by setting up Services, BroadcastReceivers, and ContentProviders and then tie them all together using the Intents. The common name for these parts is components. These four types of components (Activity, Services, BroadcastReceivers, and ContentProviders) are the ones most frequently used. Activities are responsible for the user interface, Services implement operations running in the background, BroadcastReceivers listen for system events, and ContentProviders store application data. They all come with a base class that you extend and a section in the application manifest XML file that exposes them to the Android system. There is also a fifth type of component with the name Application that, though rarely utilized, is useful in some situations. Allow me to walk you through these five components and put them into context before moving on to the software architecture for a standard Android application. (I cover each component in more detail in later chapters, as indicated in the following sections.) 58 Part II: Getting the Most Out of Components The Activity Component User interfaces for an Android application are managed by extensions of the Activity class. Your application can have several Activities for different functions, but only one Activity can be displayed at any given time. As with all other components, a number of lifecycle callbacks will notify you of the current state. The general guide regarding Activities is that they handle only UI-related operations. Although you can use AsyncTask or Handler (as I describe in Chapter 2) to execute things off the main thread, the preferred practice is to always delegate long-running operations to a Service and execute them on a separate thread there—because the state of an Activity is very much controlled by the user, and it becomes a bit complicated if you need to cancel a running operation because the user happens to press the Home button. Instead, let a Service handle these operations and focus on the user interface in your Activities. Starting with Android 3.0 (Honeycomb), the Fragments API became available for building dynamic user interfaces. This API was specifically designed to allow application developers to implement user interfaces that will adapt to the size of the devices they’ll be running on. Instead of simply scaling the entire application when running on a 10-inch tablet, you can use Fragments to show more details than when running the same application on a 4-inch device. The Service Component The second most-common component is the Service. Basically, anything that doesn’t involve normal user- interface operations goes into a Service. Remember that all components run on the same main thread, so you still have to make sure that your potentially long-running operations are executed on a separate thread using either a Handler or an AsyncTask. You want to move these operations to a Service, and not simply keep them in a background thread inside your Activity, because you want to complete the operation even if the user presses the Back or Home button during the Activity. By allowing a Service to control long- running operations, you can more easily keep track of the state of these operations. I recommend that each task have a dedicated Service. You can use one Service for storing data and another one for communicating with an online web Service. For instance, if you’re building a music player, create one Service for the playback and another Service for tasks that aren’t related to music. You want different Services for different operations so that Services can be started and restarted differently; it’s easier to have multiple Services than to try to build one Service that handles everything. You learn more about Services and background operations on Android in Chapter 6. The BroadcastReceiver Component BroadcastReceivers are special components because they are stateless, which means that the BroadcastReceiver object is valid only during the onReceive() call. Therefore, you can’t keep a reference to the BroadcastReceiver instance that’s declared in the manifest anywhere in your code. Also, you can’t add the instance as a listener or callback to an asynchronous operation. Basically, the only thing you can and should do in the onReceive() method of a BroadcastReceiver is to delegate the call to another component, either through the Context.startActivity() or the Context. startService() method. Chapter 3: Components, Manifests, and Resources 59 This limitation makes BroadcastReceivers useful for one thing only: listening to system events. Many BroadcastIntents are defined in the Android SDK, and they’re spread out in different classes in the API. All you can do is check the part of the API you’re interested in to see if a BroadcastIntent is there that provides what you’re looking for. The BroadcastReceiver component is also a bit special because it can also be programmatically declared inside a Service or an Activity. These instances are manually registered and unregistered from the system in your code. Always unregister a programmatically defined BroadcastReceiver or you will leak a reference and waste memory. A programmatically defined BroadcastReceiver is sometimes the only way you can listen for a certain broadcast. Some BroadcastIntents don’t allow you to register them through the manifest. For instance, the broadcast defined by Intent.ACTION_BATTERY_CHANGED can be received only from a BroadcastReceiver registered with Context.registerReceiver(). You find out more about BroadcastReceivers in Chapter 8. The ContentProvider Component You don’t need to define a ContentProvider in order to store data for your Android application. In many cases, a simple key/value storage using SharedPreferences is sufficient, and you can store data in an SQLite database using the SQLite API directly from your Services and Activities. Storing files inside your application’s data folder is another way of persisting application data. However, if the application data you’re going to store is suitable for SQL, it’s usually easier to implement a ContentProvider even if you won’t be sharing the data with other applications. Doing so becomes especially useful when you want to display the application data using an AdapterView (like a list or a grid) because the Loader API provides readymade implementations for loading data from a ContentProvider. I go into the details of different data storage techniques for Android in Chapter 9. The Application Component There is also a fifth component, simply called Application, that is rarely used but can come in handy sometimes. You can consider the Application component as a top-level component that’s created before Activities, Services, and BroadcastReceivers. Your Android app will always have an Application component, and unless you define one, a default one will be created for you. You can always retrieve a reference to the Application component through the method Context.getApplication(). Because all Android apps will have one and only one instance of this component, you can use it to share variables and communicate across the other components within your app. Although sharing a global state can be solved using a singleton class as well, using the Application component has the advantage that it also implements application lifecycle callbacks. The following code exemplifies code for a custom Application component. This example also shows how to use this component for sharing global variables and notifying listeners whenever these variables are added, changed, or removed. Although simplistic, this example demonstrates how you can utilize an already-existing Android component to solve a problem that would otherwise require a singleton class. 60 Part II: Getting the Most Out of Components public class MyApplication extends Application { private ConcurrentHashMap mGlobalVariables; private Set mAppStateListeners; @Override public void onCreate() { super.onCreate(); // Called before any other component is created mGlobalVariables = new ConcurrentHashMap(); mAppStateListeners = Collections.synchronizedSet(new HashSet()); } public String getGlobalVariable(String key) { return mGlobalVariables.get(key); } public String removeGlobalVariable(String key) { String value = mGlobalVariables.remove(key); notifyListeners(key, null); return value; } public void putGlobalVariable(String key, String value) { mGlobalVariables.put(key, value); notifyListeners(key, value); } public void addAppStateListener(AppStateListener appStateListener) { mAppStateListeners.add(appStateListener); } public void removeAppStateListener(AppStateListener appStateListener) { mAppStateListeners.remove(appStateListener); } private void notifyListeners(String key, String value) { for (AppStateListener appStateListener : mAppStateListeners) { appStateListener.onStateChanged(key, value); } } public interface AppStateListener { void onStateChanged(String key, String value); } } The manifest file (AndroidManifest.xml) will always have an application element. However, in order for Android to recognize that your custom Application component is to be used, instead of the default, you need to declare the custom Application component in a android:name attribute (see the bold text in the following example). Chapter 3: Components, Manifests, and Resources 61 As just shown, you declare your custom Application component much as you declare an Activity or a Service. Simply add the class name in the android:name attribute, and you’re set to go. public class MyActivity extends Activity implements MyApplication.AppStateListener { @Override protected void onResume() { super.onResume(); MyApplication myApplication = (MyApplication) getApplication(); myApplication.addAppStateListener(this); } @Override protected void onPause() { super.onPause(); MyApplication myApplication = (MyApplication) getApplication(); myApplication.removeAppStateListener(this); } @Override public void onStateChanged(String key, String value) { // Handle state change... } } When you need to access your custom Application component from somewhere in your code, you simply call Context.getApplication() and cast it to your custom class, as shown in the preceding example. Because you’re using a listener pattern in this example, make sure that you register and unregister the listener correctly in the onResume() and onPause() methods. Application Architecture Generally, Android developers start a new project by creating an Activity and designing the user interface. Although this approach is okay, my suggestion is that you first consider the overall application architecture from a broader perspective. If you plan to do network calls, moving them to a separate Service is a good approach. If you need to be notified about certain system broadcasts, it’s probably wise to set up a BroadcastReceiver and decide where to delegate those events. If your application needs to store data beyond simple key/value pairs, I suggest making a ContentProvider responsible for that scenario. Finally, if you have a complicated application, it may be worth your time to add a custom Application component as well to track global states and communicate between the other components. I usually sketch an overall architecture using the components as a base. I ask myself what Activities and Services are needed at first and then move on to consider broadcast events and ContentProviders. This 62 Part II: Getting the Most Out of Components architecture doesn’t need to reflect the actual user interface of your application because Activities can have a dynamic user interface with the help of Fragments. I tend to rely on the readymade extensions of the basic components as much as possible. For instance, the PreferenceActivity class is great for building the user interface for the settings part of your application. Figure 3-1 shows a simple diagram that exemplifies how you can combine the components in Android to create a well-designed architecture. By always making sure that each component handles only the operations it’s designed for, you’ll have a stable foundation for the development of your application. Settings Activity User settings Delegate events Update data Call web service Call service over local Binder Notify on data changes Query data Settings Activity Main Activity Service for background operations Content Provider Figure 3-1 A high-level diagram showing the relationships among components in a typical Android application You now have a general understanding of the components in Android, but along with the Java code that you write to implement your own versions of these components, you also need to know how to add them to the application manifest, which is the topic of the next section. The Android Application Manifest The manifest is an XML file where you define each component and declare everything else about your application. The manifest is the core of any Android application, so it’s important to fully understand how to structure such files. Small mistakes in manifests can cause performance issues with your application (which could affect the entire system), unnecessary power consumption (which could cause the battery to drain), and security issues (which could leak sensitive information to other applications), and under certain conditions, they can cause your application to crash. Fortunately, the lint tool (refer to Chapter 1) in the Android SDK provides inspections for the manifest file, so your IDE will let you know about areas in the file that need to be fixed. In this section, I provide some tips that you can use to optimize your application and that will be useful when you start doing more advanced development. The Manifest Element The top-level element in the AndroidManifest.xml file is named manifest. This is where the package name, the unique identifier for your application, is defined. You can also define the Linux user ID and name that your application will be running under, the version information about your application, and where the APK file should be installed. Chapter 3: Components, Manifests, and Resources 63 The following example shows the manifest tag with all the possible attributes set. The first attribute, package, defines the unique identifier for you application in the Android system, the name of the application process, and the name of the default task affinity for Activities. Be sure to choose the package name well because it cannot be changed after the application is released. When deciding on the package name, it’s best to use the domain name of the company or organization that publishes the application and append a shortened, lowercase version of the name the application will have on Google Play. If you’re working as a consultant or a freelance developer, be sure to use a name based on your customer’s domain name, and not your own. The android:sharedUserId and android:sharedUserLabel are the Linux user ID and name that your application will run on. By default, these are assigned by the system, but if you set them to the same value for all the applications you publish using the certificate, you can access the same data and even share the process of these applications. If you’re building a suite of applications or if you have a free and a paid version, this can be very helpful. In regard to free and paid versions of your game, when people try your free version and decide to purchase the paid version, they don’t want to lose all the progress they achieved before the purchase. To solve this issue, make sure that both your versions share the same user ID and simply synchronize the data from the free to the paid version on startup. When multiple applications share the same process, you can save the total amount of RAM your applications use. However, all the applications that share the same process will also crash if one of the applications experiences a runtime error. You can use the android:installLocation attribute to control where the application is installed. You can install it either on the device’s internal storage (the data partition) or the external storage (for instance, the SD card). Note: You control only the location of the APK file through this attribute. The application data is still stored safely on the internal storage. The version information is important when you publish your application on Google Play. android:versionCode is the value that is read by the Google Play Store, whereas android:versionName is what is presented to the user. My recommendation is to use a version code that makes it easy for you. The simplest solution is to start 64 Part II: Getting the Most Out of Components at 1 and simply increment it with 1 with every new version. You can set up your build script on your continuous integration system to increment this for each nightly build (highly recommended!), or you can increase it manually before you commit new code to your repository. Either way, increment the value before trying to publish a new version to Google Play. Google Play Filters and Permissions The next part of your manifest should contain the details about how it is made visible in Google Play and the features in the platform you’re using. Here, you also declare the permissions you use as well as the new permissions that your application requires when providing interfaces for other applications. You can find more about permissions in Chapters 12. At this point, just remember to declare the permissions you need, or you’ll get a nasty error when your application tries to access a restricted API. If your application will provide an interface (through a Service, content provider, or custom BroadcastIntent), be sure to define your own permissions in the manifest. One of the most important parts of your AndroidManifest.xml file, but which is also often forgotten, is the uses-features element. This is what Google Play uses when it filters which applications will be visible on each device. For example, if your application needs the capability to send SMS, you probably want to exclude the devices that lack support for this (Wi-Fi–only tablets), and add an element saying that you need telephony support. Also, with new types of Android devices appearing, some features we’ve come to expect on an Android device aren’t always there. For instance, although most Android devices have a touch screen, you won’t find one on a device that runs on TV. If your application requires a touch screen and cannot work via a mouse pointer, be sure to declare this in your manifest. The following example shows how to specify that your application needs telephony support and a touchscreen supporting full “Jazz Hands” multi-touch. You can find a complete list of all the standard features in Android at http://developer.android. com/guide/topics/manifest/uses-feature-element.html#features-reference Android supports many different screen sizes by default, but you won’t always want your application to be available for all of them. For instance, if you’re developing an application that’s intended for tablets, you may want to limit it to only devices with that screen size. You can do so by using the supports-screens element, which allows you to specify which screen sizes your application works on as well as the smallest width required for a device screen. The following is an example of what to use for a tablet-only application. Chapter 3: Components, Manifests, and Resources 65 Another important function of the manifest is determining the API levels your application supports. You can define minimum, target and maximum levels: Minimum defines the lowest Android version you support, whereas maximum defines the highest. The latter is useful when you have multiple APKs for different Android versions for a single application or if you want to delay publishing an application for new versions of Android until you’ve had time to verify your application. My recommendation is to avoid the android:maxSdkVersion attribute if possible. The interesting attribute in the uses-sdk element is the android:targetSdkVersion. This attribute tells a device which API level you’re targeting. Although android:minSdkVersion lets you limit the lowest Android version you support, it tells Android not to enable any compatibility behaviors with the target version. More specifically, this combination allows you to gracefully degrade feature support in your application for older versions while still maintaining an up-to-date user interface and functionality for later versions. My recommendation is to always specify android:minSdkVersion and android:targetSdkVersion, as shown in the preceding example. Unless you specify android:minSdkVersion, it will default to 1 (that is, Android version 1), which you probably want to avoid. By specifying android:targetSdkVersion, you can easily increase your support for new versions. You can find a complete list of all API levels and which Android version they represent at http:// developer.android.com/guide/topics/manifest/uses-sdk-element.html. The Application Element As I described earlier, there is a fifth, rarely used, type of component named Application. This component is represented by the application element in the manifest file. Unless you provide your custom Application class using the android:name attribute, the system default will be used. A number of other important attributes are on the application element. Some of these attributes provide additional functions to your application and others are only for information. I’ll go through those I consider most important, as shown here: 66 Part II: Getting the Most Out of Components Because users may have hundreds of different applications installed, it helps if you provide as much information about your application as possible to the system. I recommend that you set both the android:label and the android:description attributes to values that are localized (that is, with translations in different languages). The description attribute should also contain detailed text about what your application does so that when the user looks at your app in the settings, he immediately can tell what the app does. Eventually, users will change their smartphones, and they’ll want to be able to move all their applications, including the data, to their new device. Fortunately, Google provides a backup Service that helps to solve this issue, but you have to declare this in your application and implement your own backup agent. You do so using the android:backupAgent attribute, which points to the class that implements this. I go through the details of the backup agent in Chapter 9, but it’s a good idea to always provide this from the start so that you don’t forget it. If you’re building an application that requires a large amount of memory, you’ll soon run into a problem with the default limit of the heap size of the Dalvik VM. You can solve this issue by adding the android:largeHeap attribute to your application manifest to let the system know you require more memory. However, never add this unless you really need to; doing so will waste resources, and also the system will terminate your application much sooner. For most applications this attribute is not necessary and should be avoided. If you have a suite of applications sharing the same user ID (as described earlier for the manifest element), you can also force them to share the same process by setting the same value to the android:process attribute. This helps to reduce the use of your applications’ resources but also leaves them all vulnerable if one of them crashes; the most likely situation is when you have one application that supports plug-ins that can be installed from Google Play. Note: All applications must share the same user ID and be signed with the same certificate in order for this to work. Finally, you have the android:theme attribute that can be set either for the entire application (in the applications element) or on individual Activities. Component Elements and Attributes Each of the standard components (Activity, Service, BroadcastReceiver, and ContentProvider) has its own elements in the manifest. While the default attributes created by Android Studio is usually good enough for most situations, you should always review them in order to ensure you have the optimal values. Every component you define is enabled by default. You can change this by setting android:enabled=”false”, which will prevent them from receiving Intents. Disabled Activities won’t show up in the application launcher, Services won’t respond to startService() calls, BroadcastReceivers won’t listen to BroadcastIntents, and ContentProviders won’t respond to the ContentResolver. You can change this setting in your code, which is especially efficient if you want to make sure that parts of your application remain disabled until the user completes certain configuration steps. In the following XML, you see two Activities declared where the second one is disabled by default. You want the user to start and complete the setup process before you show the main Activity. You also want to hide the setup Activity once it’s completed. Chapter 3: Components, Manifests, and Resources 67 The following code is a simple example of where you use the PackageManager API to toggle the enabled state for the Activities. This way, you can change which Activity is shown in the launcher as well. private void toggleActivities() { PackageManager packageManager = getPackageManager(); // Enable the main activity packageManager.setComponentEnabledSetting(new ComponentName(this, MainActivity.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); // Disable the setup activity packageManager.setComponentEnabledSetting(new ComponentName(this, SetupActivity.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } Sometimes you have a component, usually a Service, that you don’t want to expose to the rest of the system for security purposes. To do this, you can set the android:exported attribute to false, which will effectively hide that component from the rest of the system. If you want a component to be available for other applications but want to provide a level of security, you can provide a permission that the calling application needs to specify in its own manifest (by using uses- permission). Usually, you define your own permission (using a permission element under the manifest element) and then apply it to the components that require it. You apply a permission requirement to a component through the android:permission attribute. 68 Part II: Getting the Most Out of Components Intent Filtering All components in Android are accessed using Intents. An Intent is an abstract description of an operation that you want to perform. Intents are either sent to an Activity, a Service, or a BroadcastReceiver. Once sent, Android’s Intent resolution kicks in to decide where the Intent should be delivered. The first thing during Intent resolution is to determine if it is an explicit or implicit Intent. Explicit Intents contain information about the package and the name of the component, and these can be delivered immediately because there can be only one match. This method is generally used for application-internal communication. Implicit Intent resolution depends on three factors: the action of the Intent, the data URI and type, and the category. Extras and flags on Intents carry no meaning when it comes to deciding where they should be delivered. Action is the most important test and usually the one you should focus on. Android has a number of predefined actions, and you can define your own actions as well. When defining your own actions, it’s customary to prepend the string with your package name so that you don’t end up with conflicting action strings between applications. Data doesn’t really carry actual data but contains a URI and a MIME-type, which is useful when you want to use an Activity to open only certain file types. The category is mostly relevant for Activities. All implicit Intents sent by startActivity() will always have at least one category defined (android.intent.category.DEFAULT), so unless the intent-filter for your Activity also this category, it won’t pass the Intent resolution test. The only exception here is for intent-filters for the launcher. The following XML shows an Activity with two intent-filters, one for the launcher and one for opening files with the MIME type starting with video. The first intent-filter doesn’t need a category android.intent.category.DEFAULT because it will be managed by the launcher application. The second intent-filter will match when the user triggers a VIEW action on a video file. The file can be stored on the local storage or on a web server as long as the MIME type starts with video. Chapter 3: Components, Manifests, and Resources 69 You can read more about Intents and Intent resolution for Android at http://developer. android.com/guide/components/intents-filters.html. Resources and Assets Although most of your development work for an application will go into the source code, there is also the embedded content like XML layouts, icons, and text values, to name a few. This static content is either stored as resources or assets in your application’s APK file. All resources belong to a certain type (such as layout, drawable, and so on), whereas assets are simply generic files stored inside your application. Most of your application’s content that is not code will be represented as resources because they are tightly integrated into the Android APIs. The resource feature in Android is one of the most powerful features of the platform from a developer’s standpoint. It allows you to provide multiple versions of each resource depending on screen size, localization parameters, device capabilities, and so on. The most common solution is to use the resource feature to support multiple languages by having several versions of the text strings in different files and to support various screen sizes through different layouts and icons of varying sizes. When you start to define your resources in an application, you need to keep a few rules in mind. First, always have a default resource for every resource type in your application, which means that you need to keep default images and icons, text strings, and layouts in a resource directory without any qualifiers. Doing so ensures that your application won’t crash if it runs on a device with a resource qualifier that doesn’t match those you’ve provided. The easiest way to ensure this is to start with the default resources and create the alternate versions once you know which variations you need. Also, use resources whenever possible and avoid hard-coded strings and values. Don’t keep strings in your application’s code that will be printed in the user interface; instead, externalize them by putting them in the strings.xml file. Values that can be placed in a resource file should be moved there. Doing so may require some extra effort, but once you get used to always defining strings and other values in the resources, you’ll see that other more complicated tasks become easier. Localization, adding support for more languages, becomes easier if everything is defined as resources from the start. Changing sizes on fonts, margins, paddings, and other properties becomes easier if they’re all defined in one place. Resources are filtered using a number of qualifiers that you specify. For instance, if you want to provide a high- resolution set of icons for devices with very high-density pixel displays, you can do so by storing a separate set of them in a directory named drawable-xhdpi. The most common qualifiers are for screen rotation and size, language, and region. It’s important to name the directory with the resource qualifiers in the correct order; otherwise, Android will ignore that directory. For instance, language and region should always come before screen size. You can find a list of all the resource qualifiers, their order of precedence, and their meaning at http://developer.android.com/guide/topics/resources/providing-resources. html#table2 70 Part II: Getting the Most Out of Components You can access resources in two ways: in XML or in Java code. When accessing the resources, you don’t need to specify qualifiers; the system handles this automatically. Now, it’s time to take a look at some more advanced examples of using resources. I cover user-interface resources, such as drawables, in Chapter 4 and focus on the more generic types here. Advanced String Resources Basically, every string in your application should be externalized into a string resource. As you can see in the following example, several options provide an advantage over Java’s String handling and eliminate many of the problems, especially when it comes to formatting and localization. Welcome %s! Your inbox is completely empty! You one message in your inbox! You two messages waiting to be read! You have %d messages waiting! %1$d messages in your inbox! %2$s, you should really login here more often! %1$d messages in your inbox! %2$s, you should really login here more often! Work Personal Private Spam Trash Draft This example defines three different types of string resources. The file should be named strings.xml (although the name doesn’t matter as long as it ends with .xml) and placed in the resources directory named values. You then duplicate this file for each language you support and put the file in a values- directory. The preceding example is a simple string that takes one string parameter as its input and inserts it where the %s is placed. The following code snippet shows how you can use the getString method to retrieve the resource and pass a String variable as input. public void showWelcomeMessage(String name) { ((TextView) findViewById(R.id.welcome_message_field)). setText(getString(R.string.personal_welcome_message, name)); } Chapter 3: Components, Manifests, and Resources 71 You can find detailed rules on formatting resource strings in the JavaDoc for the java.util. Formatter class at http://developer.android.com/reference/java/util/ Formatter.html. The preceding code snippet is a plurals resource that allows you to define different strings depending on the amount of input. This is especially useful because the grammar for a language can be very hard to manage using normal string operations. The following example shows how you first retrieve the Resources object and then call getQuantityString to get the right string value depending on the input. This method can also take formatting parameters if one of the choices requires that. public void showInboxCountMessage(int inboxCount, String name) { Resources res = getResources(); String inboxCountMessage = res. getQuantityString(R.plurals.inbox_message_count, inboxCount, name); ((TextView) findViewById(R.id.inbox_count_field)). setText(inboxCountMessage); } The preceding example shows how to provide an array of strings. These are useful when you want to provide an array of constant string values. In the following code, you see an example where you populate a ListView using an ArrayAdapter with the string array declared earlier. public void displayCategories() { ListView listView = (ListView) findViewById(R.id.category_list); Resources res = getResources(); String[] categories = res.getStringArray(R.array.default_categories); ArrayAdapter categoriesadapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, categories); listView.setAdapter(categoriesadapter); } Localization As I described earlier, resources in Android can be efficiently used for providing localization support. To do so, you copy the default string resources into a new directory with the appropriate language and region qualifier and then start translating the text. Although no tool is available in the Android SDK to help you with the actual translation of one language to another, the built-in lint tool (described in Chapter 1) shows whether you have a resource directory for a language without a translation. 72 Part II: Getting the Most Out of Components Because you probably don’t know all the languages you want to support (if you do, please let me know so we can be friends!), you will need some help translating your resources. Your first choice, especially if you’re on a tight budget, is to use Google Translator Toolkit (see Figure 3-2). This is a Service from Google that allows you to upload an Android resource XML and have it automatically translate your strings to a new language. The result might not be perfect, but the Service is getting better all the time, thanks to the efforts of Google. Figure 3-2 Google Translator Toolkit after translating a string.xml file from English to Swedish You can access the Google Translator Toolkit at http://translate.google.com/toolkit. You can also hire a professional translation service to do the work. Although the quality will most likely be much better than when using an automatic tool, a professional service will also cost quite a bit if you have many different languages. Also, if you update your application and add new strings in your default resources, you’ll need to submit the new strings, further increasing the cost of developing your application. Using Resource Qualifiers Providing alternative resources for different screen sizes and locales (language and region) is fairly straightforward, so I won’t cover that topic much more in this chapter. There are, however, other things you can use the resource qualifiers for. Chapter 3: Components, Manifests, and Resources 73 You can use Android resources in your application to control the application logic based on things that can be expressed as resource qualifiers. Consider the following example: You have a very popular game that you provide for free on Google Play. The game is financed through in-game advertisement that the user can disable by doing a simple in-game purchase. However, a major carrier has offered to buy an ad-free version of your game for all its customers. One way to approach this issue is to build a duplicate version of the game and publish that version on Google Play for a specific region and carrier. Although this will work technically, you’ll have to do more work because you’ll have to publish two distinct versions of the game. Also, customers who suddenly find two versions of the game on Google Play might become confused. A possible solution is to turn ads on or off in the game depending on which carrier the game belongs to. You can do this programmatically in Java, but a smoother way is to use a Boolean resource. A great site for finding the mobile country codes (MCC) and mobile network code (MNC) values for various carriers and countries is www.mcc-mnc.com. The following code shows the default resource file containing the Boolean expression. Put this file in the default values directory (that is, res/values) and then create a new resource directory with the correct qualifier. For instance, if the carrier is Vodafone UK, the resource directory is named values-mcc234-mnc15. Then you simply copy this resource file and change the value to true. false Whenever the application is opened on a device with a SIM-card belonging to Vodafone UK, the following code example returns true. public boolean disableAdsByDefault() { return getResources().getBoolean(R.bool.disable_ads_by_default); } The preceding code shows how to retrieve the resource in Java code. Although trivial, it serves as a useful example of how powerful Android’s resource feature is. The preceding example using Boolean could be replaced with an integer resource telling how much in-game cash a player should start with or by providing higher-quality icons and images (using drawables with qualifiers) for customers on a certain carrier. Using a resource with a MCC and MNC qualifier to control application logic isn’t actually secure. A technically skilled user could bypass this check with relative ease. However, for the most part, you can consider this safe enough. Remember, though, that filtering on MCC and MNC requires that the device have telephony support. 74 Part II: Getting the Most Out of Components Although the qualifiers are mostly used to provide localization and various screen size support, you can use them in many other ways as well. Don’t be afraid to experiment with resource qualifiers, as long as you always provide a default version as a fallback. Using Assets Until Android 2.3, the maximum limit of a resource file was 1MB, which caused problems if you used resources that were larger—which is why you also had the option of storing arbitrary files in your project’s assets directory. This limitation is no longer a problem, so you can now safely store arbitrary files in the raw resource directory. However, because the assets directory supports subfolders, in some situations, you may still want to use assets instead of resources. Consider an example where you have a game and you have a number of sound effects that you want to put in the game. You start by creating a directory in the assets folder named soundfx and place all your audio files for the effect there. The following code shows how to use the SoundPool API and the AssetManager to load the audio files contained in the soundfx directory of your assets. Remember that you need to close the files that you open from the AssetManager, or your application could leak memory. public HashMap loadSoundEffects(SoundPool soundPool) throws IOException { AssetManager assetManager = getAssets(); String[] soundEffectFiles = assetManager.list(“soundfx”); HashMap soundEffectMap = new HashMap(); for (String soundEffectFile : soundEffectFiles) { AssetFileDescriptor fileDescriptor = assetManager. openFd(“soundfx/” + soundEffectFile); int id = soundPool.load(fileDescriptor, 1); soundEffectMap.put(soundEffectFile, id); fileDescriptor.close(); } return soundEffectMap; } The preceding example could, of course, be changed to use raw resources as well, but I’m leaving that as an exercise for you to explore. Summary I’ve now covered the three central elements that are part of any Android application: the components, the manifest, and its resources. Understanding how these work and how to use the features they provide will take you a long way toward becoming a master Android developer. Try to design your application around your components because they provide lifecycle callbacks that are managed by the system, allowing you to focus on what is important in your application. By always making the components a first priority, you’ll find it easier to build an application without ending up with tons of boilerplate code, something I’ve encountered far too often. Chapter 3: Components, Manifests, and Resources 75 The manifest is the actual definition of your application, and you need to take extra care in structuring it and the attributes you use for each element. Remember to declare all the features that your application requires, even the ones that may be obvious, such as a touchscreen or camera. A common error that I’ve seen during development is missing permissions. Be sure you always add a uses-permission element for the permissions your application requires. By using the android:enabled and android:exported attributes on selected components, you have access to a powerful control for dynamically changing the behavior and protecting your components. You should, at the very least, make sure none of your Services and content providers are exported, unless they need to be accessed from other applications. Make sure you are fully aware of how Intent resolution works. A small mistake here could cause your app to respond in unexpected ways (or not respond at all). When it comes to the static content in your application, move as much as possible into the resources. Make sure every constant text string that is printed somewhere comes from the string resources, and not a constant value in your code. Remember to define the resource in the default resource directory (that is, a directory without any qualifiers appended to the name) and copy it to the new qualifier when needed. Google Translator Toolkit can help you with localization of your string resources. Also consider using resources for application logic where qualifiers like network carrier or region can be part of the factors. Further Resources Documentation http://developer.android.com/guide/components/fundamentals.html http://developer.android.com/guide/topics/manifest/manifest-intro.html http://developer.android.com/guide/topics/resources/index.html Chapter 4 Android User Experience and Interface Design Android’s user interface has changed quite a bit over the years. One of the biggest changes came with Android 3.0 (Honeycomb) where the new Holo theme was introduced. Since then, we’ve seen Android’s UI become a polished and sleek experience that easily appeals to users of all backgrounds. The team at Google behind the Android UI design has done some great work at explaining how to apply the same design into your own applications. This is covered in great detail at http://developer.android. com/design/index.html. I strongly recommend all Android developers to go through these guidelines to fully understand how to work with the different aspects of Android UI design. In this chapter I’m going to cover UI design for Android applications at a more theoretical level. While the Android design site I just recommended does cover many things a developer and UI designer need to know, there are still things I believe you need to have a better understanding of when designing state-of-the-art user interfaces. I start this chapter with a description of a process for identifying the different parts of your application UI and how they work together. I then move on to describe Android specific UI concepts like navigation, sizes and colors. I’ve also written a section covering usability and what you should consider when you do the detailed design of your user interface. The final part of this chapter covers the concept of gamification and explains how you can use this to increase the use of your application. User Stories The first thing you should do when you start to design your application is to write down the user stories that describe what a user can do with your application. A user story is commonly used in agile software development as the basis for defining product requirements. Typically, a user story uses the following form (sometimes with a slight variation): As a , I want so that . For instance, if you’re designing a sketching application, one of the user stories could be: As a user, I want to change the color used by the current drawing tool so that it only affects the next use of that tool. The final part that describes the benefit can sometimes be left out when it is obvious. Your user stories should be brief and concrete. Each story should focus on one thing. It is better to use two similar stories than a long and complicated one. Start with the obvious user stories, split them up, and add more as you see fit. You will probably end up with a lot of stories once you’re done. But this process will help 78 Part II: Getting the Most Out of Components you when you start to design your user interface as each story should be easily mapped to an action in the user interface. Although not every user story might start with a user interaction, they will most certainly affect the user interface in some way. For instance, a user story about an alarm is one such example: As a user, I want the screen to display the current time when a preset alarm goes off. User stories can then also be used to track the progress of your development work. Try to focus on completing one story at a time and once the entire story is implemented and tested, you can move that story to a “completed” state. There are a few things to keep in mind when writing user stories. First of all, different stories can be implemented using a common software component. However, this might not be obvious when you first write a user story, but becomes apparent when you start writing your code. For instance, if your application will list items in different categories and provide a search interface, the list of items for categories and search result can probably use the same Activity and View layout. This is why it is a good idea to categorize your user stories early in the process, so that you can more easily identify the places where a common Activity or View can be used for different stories. Understanding the User with Personas When you’re writing the user stories, it is easy to make assumptions of how things should work based on your own experiences and perspective. The problem is that real users lack the same knowledge about your application that you do. The way to deal with this is to create personas which are made-up users who each represent a typical user. For instance, if you’re designing a newsreader you can make up a number of personas that represent who you believe are the most common users. A persona should have a full name, description, age, sex, education and other relevant information. Also, you should list a number of goals that the user has when using your application (for instance, catching up on the news while commuting). Finally, you should define a priority for each persona based on their relative importance compared to every other persona. The following is an example of a persona that you can define when designing a newsreader application: Name: John Smith Sex: Male Age: 31 Description: ◾ Commutes to work by bus where he reads news ◾ Works in an office ◾ Uses his smartphone as his primary source for reading news Priority: High You should define a number of personas that define the users you want to target. Try to imagine how each persona would use your application and what their needs are. When you have the personas for your most important users, it’s easier to define the user stories that fulfill the most important requirements for your application. You can now easily prioritize each user story for your project. Chapter 4: Android User Experience and Interface Design 79 Android UI Design When planning and designing the user interface for your Android application, there are a few basic principles you should follow. First, you should think in terms of screens when you plan the user interface. Model your applications so that you have multiple screens, each focusing on a small set of actions that the user can perform. For instance, a typical newsreader application would have one screen for listing news items, one for adding news sources, one for reading news, and probably one for searching. When defining the different screens you don’t need to focus on the details of the user interface. Simply try to figure out what kind of information you want to display on each screen and leave the details about how to display them for later. Navigation Once you have the different screens figured out you should start to think about how navigation works between them. The navigation should be simple and straightforward so that the user will never be lost. Android provides a number of navigational concepts that makes this process easier. When you start designing the navigational structure you might also add additional screens to your application as needed. Navigation in Android applications can be divided into four different categories: temporal, ancestral, descendant, and lateral. Temporal navigation is usually done with the Android back button, moving to the previous screen. This navigation is usually handled automatically by the Android framework and doesn’t require any special code by a developer. However, there can be situations where you wish to override the default handling of the back button. For instance, when embedding a WebView for displaying web pages, you might wish to use the back button in Android as a back navigation for the web pages. Ancestral navigation refers to the navigation where the user moves up through the hierarchy to a parent screen, like moving back to the start screen for the application. Ancestral navigation is solved in Android 4.1 and later through the use of the android:parentActivityName attribute for each Activity in the application manifest. For earlier versions of Android you need to use the Android support library together with a meta-data tag (see http://developer.android.com/training/implementing-navigation/ancestral.html). Descendant navigation is when the user navigates down through a hierarchy, for instance clicking on an item in a list. Descendant navigation is the most common form in Android applications. It is typically handled through a combination of Fragments and Activity instances where the user triggers actions that change the screen. This kind of navigation will usually move down through the information hierarchy, but can also be used to display additional information about the current object. Lateral navigation is when the user moves to a sibling screen, for instance moving from one tab to another sideways. This type of navigation is useful when your application will contain multiple lists of similar types, but divided into different categories. One of the most common examples of this is the list of different types of applications in the Google Play Store application. Prototyping User Interfaces When you get your screens and navigation completed, you can start prototyping the user interfaces. The best way to start prototyping is with a paper and pencil. It is cheap, fast, and since users will interact with the 80 Part II: Getting the Most Out of Components application using their fingers, it is easy to get a feel for how easy it is to use. Make a paper sketch for each screen and add notes to clarify the UI. You can now “navigate” between your prototype screens in a way that’s similar to how a real user would navigate in your application on a real device. The paper prototype can start out simple with very little detail and evolve into a complete sketch of what the user interface will look like. The important thing is to try several designs and not just stick to the first that comes to mind. You should compare all your sketches to each other to find the best approach for each screen. Once you feel you’ve got a good paper prototype, you can move on to create a more advanced prototype of the user interface using Android Studio, as described in the next section. Android Studio UI Designer One of the best UI prototyping tools for Android applications is probably something that you’ve already installed. Android Studio comes with a great UI designer toolkit that lets you work directly with XML layouts for different screen sizes (see Figure 4-1). You can easily create a quick mockup that you can try out on your device or an emulator. Figure 4-1 Android Studio as a UI prototyping tool Chapter 4: Android User Experience and Interface Design 81 One advantage with Android Studio UI designer is the tight integration with your code. You can easily switch between different screen sizes, locale settings, and screen orientations and see the result immediately on the screen. By using Android Studio and its UI Designer tool you can create a working mockup of your user interface that you can try out on a real device. You might have to add a minimal amount of code to fill the mockup with fake data, but with only minor effort you can have a working mockup of your application in no time. Android UI Elements When developing Android applications you have a large set of ready-made UI elements, called widgets, in the Android SDK that you can use. These widgets can be found in the android.widget package of the Android APIs. You should try to use the existing widgets as much as possible. However, if your View hierarchy gets very complicated you should consider creating a custom View, as described in Chapter 5, instead. The main reason why you should use existing widgets as much as possible is because they are easily recognizable by the user. In many cases, you want to create an application that will be distributed on multiple platforms. When doing so, it can be tempting to re-use the same user interface design for all platforms. While the concept of designing once and running the app “everywhere” might seem tempting at first, you will likely create inconsistent user experiences that will have a very negative impact and alienate users. This is also the reason why you should avoid creating your own look and feel for existing widgets. A user can become confused if buttons suddenly change their appearance when they start your application. This concept of “Pure Android” is described in further detail here: http://developer.android.com/design/ patterns/pure-android.html. Text in Android Applications One of the most important aspects of any application is the text. While placement of UI components are important, it is the text together with images that is used to relay information to the user. Badly written text, the wrong font or a text size that is too small can lead to an overall bad user experience. Also, how you express your message in text matters, which is described in detail in the Writing Style section found here: http://developer.android.com/design/style/writing.html. Font Matters There’s been a lot of debate over the years about fonts, particularly about whether using serif or sans serif is better, even though studies have shown that neither appreciably affects how easy or fast people can read text. What does matter is how easily a font can be recognized. The human brain uses pattern recognition to identify letters in a text, so logic says that you should use an easily recognizable font. So, limit your use of highly decorative fonts and use them only for logos and such. 82 Part II: Getting the Most Out of Components Google has designed a default font for Android called Roboto (see Figure 4-2), which I recommend you use unless you have a well-designed font that is part of your brand. This font was designed with all the modern aspects of Android devices in mind, such as high-resolution screens. Figure 4-2 Example of the Roboto font for Android Text Layout Another interesting aspect of readability is the fact that people read faster when lines are long but they prefer to read short lines, which is one reason newspapers often use narrow columns for articles. So, consider what’s of the most importance in the text you present. Use longer lines only if reading speed is important, although text in applications is usually short, so this shouldn’t be much of a problem. The point is to keep lines of text between 45 and 72 characters. Length is less of a concern when you’re writing applications that will run on phones in portrait mode. The lines will rarely be too long in those cases. However, when building applications for tablets or for landscape mode on smartphones, keep this issue in mind. When the UI is wide, try to limit text to columns with between 45 to 72 characters within a given line. Figure 4-3 shows two examples of using long and short text lines. Figure 4-3 Two layouts showing the differences between long and short text lines Dimensions and Sizes If you’ve already written Android applications, you know that you’re supposed to specify the sizes of your UI elements using the dp (density-independent pixel) unit. The basic reason is to allow an Android application to scale across different screen sizes, resolutions, and pixel densities without losing quality. When using dp, the goal is to draw a View so that it has the same physical size on all screens. One dp is roughly equal to one physical pixel on a 160 dpi screen. You should specify all dimensions in your UI using the dp unit, with one exception: When specifying font sizes, use the unit sp, which is based on the dp unit but also scaled according to the users’ preferences for font sizes. Thus, by using sp for fonts, you can be sure that the text is printed with the size chosen by the user. Chapter 4: Android User Experience and Interface Design 83 The other dimension units supported by Android are pt (points), px (pixel), mm (millimeter), and in (inches). You should use these only in very specific cases and, as far as possible, provide all the default sizes in dp or sp. Recommended Dimensions How large does a button need to be in an Android application? How much margin and padding do I need to apply? How big should my icons be? If not for the dp unit, this question would have a very complicated answer. The short answer is “the 48-dp rhythm.” Basically, 48 dp translates to about 9 mm on the screen, which is within the suitable range for objects on a touchscreen that users need to interact with using their fingers. This doesn’t mean that everything should be 48 dp, but that the UI components the user will interact with should be at least that size. If a UI component needs to be larger than 48 dp, try to make it an even multiplier of 48 dp if possible to keep your UI layout consistent. (See Figure 4-4; from http://developer.android. com/design/style/metrics-grids.html.) Figure 4-4 An illustration of the 48 dp rhythm for UI objects on Android Icon Sizes Outside the world of Android, the dp unit doesn’t exist, and you have to create a version of each icon for every resolution you need to support. Android Studio will help you create the launcher icon for your application, and you can use these sizes as a template for the other icons in your application. The resolutions you need are 48 pixels for MDPI, 72 pixels for HDPI, 96 pixels for XHDPI, and 144 pixels for XXHDPI—this is called the 2:3:4:6 ratio, and you can use it as a guideline for other pixel sizes as well. You can see an example of the relative icon sizes in Figure 4-5 (from http://developer.android.com/design/style/iconography.html). Figure 4-5: The relative sizes of icons in Android for different screen sizes 84 Part II: Getting the Most Out of Components When you display a bitmap as an icon in Android, don’t use px as the size for the View. Instead, use dp. A regular icon, like the launcher icon on the Home screen, should be 48 dp by 48 dp, whereas icons in the action bar should be 32 dp by 32 dp. Icons in the notification bar should be 24 dp by 24 dp. Smaller icons should use the size 16 dp by 16 dp. Avoid icon sizes below 16 dp because some users may have trouble distinguishing them. Text Size Google created the font Roboto, which is used in all the standard Android applications. Although you can use a different font in your application, using Roboto is recommended in order to be consistent with the standard UI paradigm. The size of a font is very important when it comes to ease of reading. Figure 4-6 shows how the size of a font is measured. Different fonts with the same point size can seem to have different visual sizes. This comes from the measurement called x-height, which is literally the size of the lowercase letter x in the font. A large X-height makes a font look bigger and is easier to read. Other fonts that have large X-heights that we commonly use are Arial and Verdana. Figure 4-6 An illustration with the Roboto font showing how a font size is measured Google has also defined four standard font sizes for Android. These are micro (12 sp), small (14 sp), medium (18 sp), and large (22 sp). By using the same size in your application, you’ll stay consistent with the rest of the Android framework. Note that the sizes should be in the unit sp, not dp, to allow users to apply the scaling factor they prefer in the Settings application. Colors I am actually the last person who should write about colors, given that I’m severely colorblind and need my wife to help me whenever I buy clothes. Choosing colors for a UI is very difficult for people with normal color sight, and being colorblind makes things even more complicated. Luckily, there are some general guidelines that even I can understand and follow. First of all, black and white are great colors. They have very good contrast, especially on the screens of Android devices. If you’re uncertain about which colors to choose, start with these two and add other colors later only to emphasize certain details. When it comes to the color to use for text and its background, there are some governing rules. The LCD screens usually found on Android devices are very different from paper. First, they emit light, which can cause eye strain and make reading for a long time more difficult. Second, because the screen is refreshed continuously, the image on the screen is not stable, even if the device is still. The refresh rate for most LCD screens is usually 60 Hz, but some devices have a higher frequency. Chapter 4: Android User Experience and Interface Design 85 Studies have shown that when reading text on a LCD screen, you should have as much contrast between the text and the background as possible. This is why black and white are great colors to use when displaying text. The best choice is to use black for the text color and white as the background—the opposite has proven to be harder to read (see Figure 4-7). Figure 4-7 An example of how contrast affects readability . How the user may interpret a color is another reason to avoid using too many colors in your application. Colors have different cultural meanings all over the world. For instance, the color green represents luck in Western Europe, North America, and many Muslim countries, whereas Africa, Eastern Europe, and China use the color red instead. Picking the wrong color could affect a significant part of your user-base in a negative way. A great resource for interpreting what different colors mean in different cultures is the color wheel by David McCandles, which you can find at http://www.informationisbeautiful.net/visualizations/colours- in-cultures. Google recommends that you use primary colors for emphasis. The Android Design Guidelines (see http:// developer.android.com/design/style/color.html) contain a number of standard color swatches to use in your application. Pick the color scheme that matches your brand. Color Blindness There are different types of color blindness, and in most cases they all refer to an inability to distinguish the difference between certain colors. The most common type of color blindness involves the inability to distinguish between red and green, which can actually be difficult to do even when you’re not color blind. About 9% of the male population and 1% of the female population are color blind. A great tool for checking how your image looks to someone who is color blind is Vischeck, which you can find at http://www.vischeck.com. This tool also lets you correct an image with regard to color blindness. Images and Icons All applications will contain at least one image; the image used as the launcher icon for your application. Your application will most likely contain many other icons and images as well, and the way you design these can have a big impact on how users perceive your application. 86 Part II: Getting the Most Out of Components An icon consisting of many different and complex objects can have a negative impact on the perception of your application. You should carefully design the launcher icon for your application so that it is easily recognized and can be shown on both light and dark backgrounds, as you cannot be sure what kind of background the user will have on her device. Canonical Perspective When told to draw an object from memory, most people will draw it as though looking at it from above at a slight angle. This is called the canonical perspective, and it is also how we remember objects. For example, take a piece of paper and draw a cup. Chances are it will look very much like Figure 4-8. Figure 4-8 A teacup drawn from the canonical perspective If you’re going to provide an icon of a real-world object, do users a big favor by designing it from a canonical perspective; they’ll be able to quickly recognize and match the icon to the real-world object it represents. Geons Another interesting thing about objects is how we construct them in our memory. Studies have shown that we are able to recognize objects by separating them into their component parts, or geons, and then using those parts to build up more complex shapes. In Figure 4-9, you can see some geons and how our memories can reconstruct them as real-world objects. Chapter 4: Android User Experience and Interface Design 87 Figure 4-9 Objects and the geons they are constructed of When you design an icon for your application, it is a good idea to construct it from simple geometric figures, and keep in mind that the fewer geons you use for your icon, the faster the user will recognize what it represents. Recognizing Faces As humans, we are very good at recognizing faces. Most people can easily pick out a friend’s face in a crowd of people. We are actually faster at recognizing faces than other objects. Also, we are much slower at understanding text that we read than recognizing a person’s face. By using this knowledge you can greatly improve the user interface of your application. If your application will integrate with a social network such as Twitter or Facebook, use this to your advantage. Instead of having a long list of names with all the user’s friends, show a grid of profile photos. This way you can fit more information on the screen at once, while making it easier to find the person the user is looking for. Usability According to Steve Krug in his book Don’t Make Me Think, the first law of usability is simply that: “Don’t make me think!” A user shouldn’t have to spend a significant amount of time trying to figure out how to use your application. Steve argues that users seeing your application’s UI for the first time shouldn’t have to question what to do next; rather, the next step should be obvious. 88 Part II: Getting the Most Out of Components This principle applies from overall UI navigation all the way down to single UI components. For example, users can immediately recognize standard buttons; on the other hand, buttons with unfamiliar designs will require conscious processing. Although you may favor a particular design over the standard button in Android, your best bet is to use the standard button—because that’s what users will expect to see on their devices. So, it’s important to follow the Android design principles. Of course, you can provide your own unique look and feel to the user interface, but if you do so, make sure that you still provide a user-friendly interface that is obvious to any user. Basically, break the rules only if you know how to break them effectively. Visual Cues In order to make your application user friendly, you should consider the visual cues of the UI elements that a user will interact with. Visual cues are the “message” that an object signals to a user about how it is supposed to be interacted with. A visual cue that is hard to understand, because it is unclear or complicated, might cause the user to make mistakes or use the object in the wrong way. An Android application is full of visual cues. The Home and Back buttons on Android devices are easily recognizable and have clear visual cues on what they do and how to use them. The same goes for many of the common actions, like the “share” action which is built-in in many Android applications. Because people understand text slower than they understand a simple icon, you can make things much easier for the user by providing a suitable icon for all buttons in your application. If your buttons will only contain text, the visual cue that signals the action the button represents is harder to interpret and will require more time for the user to understand. For instance, consider a typical file browser application. When listing files, you provide two buttons, Send and Delete. If the buttons will only contain text, it is much easier for the user to press the wrong button by mistake than it would be if you also added an icon to each button. If you can’t fit both an icon and text, you should first provide an icon. The Android Design site contains a set of freely available icons that you can use in your application. You can find these icons here: http://developer.android.com/design/style/iconography.html. By using these you will provide the same easily recognizable icons that many other applications use. This will reduce the time required for a user to understand your application, as well as minimize the risk of a user performing the wrong action. Rewarding the User Whatever the purpose of your application, the user will expect some sort of outcome. You can consider the outcome as a kind of reward for the work they perform. Studies in this area have given us a number of tools that we can apply when designing an application. In the world of Internet-connected apps and websites, the term gamification has emerged as a way of describing how to apply rewards in an efficient way. Chapter 4: Android User Experience and Interface Design 89 Gamification The term gamification was coined in 2002 by a British computer programmer named Nick Pelling, but although it was applied successfully at the time, the term didn’t gain popularity until around 2010. The theory behind gamification is that game elements and mechanics outside the world of games (real-world or computer-based) can improve the user’s engagement. People will naturally strive to compete, reach new achievements, and achieve higher status. For example, when users are rewarded for using your application frequently, they will feel and become more deeply engaged. If you provide some sort of immediate satisfaction reward for every task completed, the users will respond at a faster rate than they would have without this element of gamification. If you also introduce a concept of competing with other users, such as a high-score list showing the “best players,” you can further increase their engagement. This in turn improves learning, increases usage (and thus the profit), and provides you with more data for further improvements. When applied to websites or applications, gamification can be a very powerful tool. One of the best-known examples of gamification in the world of mobile applications is Foursquare, the location- based social network service that lets users “check in” to locations. Foursquare badges are awarded to users when they check in to new locations, when they have frequently checked in to the same location over a period of time or when they manage to score the most number of check ins for a certain location. The result is that Foursquare gained an immense database of places of interest around the world. The use of the application shows Foursquare the places that are of most interest and how people move between these venues. This data can then be licensed to third-party services that want to provide information about relevant locations. Virtually any application can use gamification to increase the use and engagement of its users. I once used the example of a notebook application for this purpose. If you want users to use your notebook application as much as possible, you can introduce the achievements that are rewarded after they’ve written a certain number of words, notes, or pages (see Figure 4-10). If you also provide a public high-score list that the users can see, you will further improve the engagement. Using a gamification element in your application can certainly drive user engagement and quickly increase your user base. However, there are also risks involved. If it gets too easy, people will stop using it because there’s no challenge. If it’s too hard, you’ll lose users who simply give up. Also, you have to be aware that some users might turn to cheating, and if it’s easy to cheat, you’ll end up with a ruined game element. Avoid building the entire user experience around the game element. For instance, Foursquare lets users compete according to the amount of times they check in to a certain place and how many places they visit, but the app also provides a very good set of features for finding points of interest and giving reviews. You can also use the element of gamification to teach the user how to use your application. This can be especially useful if you have a complicated app that requires some training. 90 Part II: Getting the Most Out of Components Figure 4-10 An example of adding gamification elements to a notebook application Summary In this chapter, I focused on the theory behind some of the concepts of interface design. You can learn much more about this topic than I could ever cover by going through all the guidelines and tips on the Android Design site. Also, I strongly recommend the books and other references found in the upcoming “Further Resources” section. Being a great developer doesn’t automatically mean that you’re also a great UI designer. But by understanding the basics of how and why people react as they do to your user interface, you can avoid most design pitfalls. That said, nothing beats a good professional interaction and UI designer. If possible, make sure that you have one on your team and that she has an understanding of the Android Design guidelines. Chapter 4: Android User Experience and Interface Design 91 Further Resources Books Weinschenk, Susan M., Ph.D. 100 Things Every Designer Needs to Know About People. New Riders, 2012. Krug, Steve. Don’t Make Me Think: A Common Sense Approach to Web Usability, 2nd Edition. New Riders, 2005. Lehtimäki, Juhani. Juhani Lehtimäki. Smashing Android UI. Wiley, 2012 Websites Android Design at http://developer.android.com/design/index.html The Easy Way to Writing Good User Stories at http://codesqueeze.com/the-easy-way-to- writing-good-user-stories/ Susan M. Weinschenks blog on how people think and behave: http://www.blog.theteamw.com The Android Developers YouTube channel, specifically the Android Design in Action episodes: http:// www.youtube.com/user/androiddevelopers Chapter 5 Android User Interface Operations In Chapter 4, I discuss how to design a good user interface, but that’s only half the work. Equally important is knowing how to implement the interface using the Android APIs. Although the tutorials and guidelines available on the Android Developers site are useful for more general cases, writing custom Views or detecting advanced multi-touch gestures that aren’t directly available from the standard APIs is more complicated. This chapter begins with a brief discussion about the Activity component and Fragments, followed by an explanation of how you can use the new Presentation API to display a UI on a secondary screen. Activities and Fragments are central to the user interfaces on Android. However, I cover them only very briefly because they are covered to a great extent on the Android developer site and in other tutorials. Also, because they’re such a crucial component for Android applications, I assume that most readers are familiar with them. Then comes an overview of how the View system works on Android, including the crucial issues involved in implementing a custom View and how to manage the drawing across different screen sizes. Next, I explain how to implement advanced multi-touch features in your application. Android has built-in support for some multi-touch gestures, but for more advanced situations you need to build your own gesture detection components. Activities and Fragments If you’re familiar with the lifecycle diagram for the Activity and Fragment classes shown in Figure 5-1, you probably won’t have any problems with this part of Android UI. What is important with the lifecycles shown in these diagrams is that you should try to perform cleanup operations (stopping and unbinding a Service, closing connections etc.) in the callback method that matches the callback method where you performed the initialization. For instance, if you bind to a Service in Activity.onResume(), you should always perform the unbind operation in Activity.onPause(). This way, you reduce the chance that your application will leak resources accidentally. You’ll find Fragments beneficial when you need to reuse a part of your UI for different screen sizes. You aren’t required to use Fragments, but doing so is recommended if your application will have one UI for handsets and another UI for tablets. 94 Part II: Getting the Most Out of Components Figure 5-1 The lifecycle diagrams for Activity and Fragment Android Studio provides a great template for building a master/detail flow UI. This UI presents two Fragments side by side on tablets (see Figure 5-2) and uses two separate Activities on handsets with smaller screens. I recommend using this UI when you need to build an Android application that must support multiple screen sizes in the same Android application package file (APK). Chapter 5: Android User Interface Operations 95 Figure 5-2 The Master/Detail Flow template set up in Android Studio Using Multiple Screens With the release of Android 4.2 came the ability to use multiple screens to display a UI from an Activity. This action is managed through the Presentation API that enables you to enumerate the available Displays and assign a separate View to each. Android defines a secondary screen as an abstract Presentation output where you add a View. The actual physical display could be something connected by HDMI or a wireless display like a Miracast receiver. As a developer, you don’t need to be concerned about the different types of displays, and you can work easily using the Display class. The following example shows how to use the DisplayManager to enumerate all the available Displays: public class SecondDisplayDemo extends Activity { private Presentation mPresentation; @Override protected void onCreate(Bundle savedInstanceState) { 96 Part II: Getting the Most Out of Components super.onCreate(savedInstanceState); setContentView(R.layout.device_screen); } @Override protected void onResume() { super.onResume(); setupSecondDisplay(); } @Override protected void onPause() { super.onPause(); if (mPresentation != null) { mPresentation.cancel(); } } private void setupSecondDisplay() { DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); Display defaultDisplay = displayManager. getDisplay(Display.DEFAULT_DISPLAY); Display[] presentationDisplays = displayManager. getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); if (presentationDisplays.length > 0) { for (Display presentationDisplay : presentationDisplays) { if (presentationDisplay.getDisplayId() != defaultDisplay.getDisplayId()) { Presentation presentation = new MyPresentation(this, presentationDisplay); presentation.show(); mPresentation = presentation; return; } } } Toast.makeText(this, “No second display found!”, Toast.LENGTH_SHORT).show(); } private class MyPresentation extends Presentation { public MyPresentation(Context context, Display display) { super(context, display); // The View for the second screen setContentView(R.layout.second_screen); } } } Chapter 5: Android User Interface Operations 97 In executing this example, you pick the first Display that isn’t the default (that is, the physical screen on the device). You extend the Presentation class with your own implementation and add a different View as its content view. The Presentation instance is bound to the same lifecycle as your Activity, so when the user leaves the app, the default behavior for the second screen will kick in (usually cloning the default screen). It may be a bit tricky to test the use of multiple screens during development, but you can find a developer setting using the Settings➪Developer Options➪Simulate Secondary Displays commands. The simulated screen appears where you can create a simulated display to use during development. Designing Custom Views Although the Android APIs provide a number of UI widgets that can be combined to form more complicated components, sometimes it’s necessary to design your own custom View from scratch. Before you start implementing a custom View there are a few details that you should be aware of, which I will explain next. By following the example in this section, you’ll be constructing a custom View for a piano keyboard. This View will display the keyboard and also play the right tone and change the look of the key when it’s pressed. Before I show the code for this View, allow me to first take you through its lifecycle. View Life Cycle Just as a Fragment or an Activity has a lifecycle, so to do Views have their version of a lifecycle. This cycle is not directly connected to the Fragment or Activity that displays the View; instead, the cycle is connected to the state of the window it’s shown in and the rendering loop. The first thing that happens when a View is added to the View hierarchy is that it gets a call to View. onAttachedToWindow(), which signals to the View that it can now load things that it needs to work properly. When building a custom View, you should override this method where you perform the loading of all the resources and initiate any dependencies that the View needs. Basically, this is where you need to perform any time-consuming initialization. There is also a matching callback named View.onDetachedFromWindow(), which is called when the View is removed from the View hierarchy. Here, you need to take care of all resources loaded, Services started, or other dependencies that need explicit cleanup. When the View is added to a View hierarchy, it will go through a loop where animations are calculated, followed by View.onMeasure(), View.onLayout(), and finally View.onDraw(), as shown in Figure 5-3. The system guarantees that these methods will be called in exactly that order every time. 98 Part II: Getting the Most Out of Components View.onAttachedToWindow() Animate View View.onMeasure() View.onLayout() View.onDraw() View.onAttachedToWindow() View added View removed Rendering loop Figure 5-3 A diagram of the View lifecycle Piano Keyboard Widget The View described in this section (see Figure 5-4) allows a user to tap a key that will then play a note. Everything is managed by the View, so it needs to load all the audio clips for playback as well as manage the different states. Finally, the user should be able to play with more than one finger, which requires properly using the multi-touch features of the Android APIs. Figure 5-4 A custom View showing a piano keyboard The following is the first part of the new custom View: public class PianoKeyboard extends View { public static final String TAG = “PianoKeyboard”; public static final int MAX_FINGERS = 5; public static final int WHITE_KEYS_COUNT = 7; public static final int BLACK_KEYS_COUNT = 5; public static final float BLACK_TO_WHITE_WIDTH_RATIO = 0.625f; public static final float BLACK_TO_WHITE_HEIGHT_RATIO = 0.54f; private Paint mWhiteKeyPaint, mBlackKeyPaint, Chapter 5: Android User Interface Operations 99 mBlackKeyHitPaint, mWhiteKeyHitPaint; // Support up to five fingers private Point[] mFingerPoints = new Point[MAX_FINGERS]; private int[] mFingerTones = new int[MAX_FINGERS]; private SoundPool mSoundPool; private SparseIntArray mToneToIndexMap = new SparseIntArray(); private Paint mCKeyPaint, mCSharpKeyPaint, mDKeyPaint, mDSharpKeyPaint, mEKeyPaint, mFKeyPaint, mFSharpKeyPaint, mGKeyPaint, mGSharpKeyPaint, mAKeyPaint, mASharpKeyPaint, mBKeyPaint; private Rect mCKey = new Rect(), mCSharpKey = new Rect(), mDKey = new Rect(), mDSharpKey = new Rect(), mEKey = new Rect(), mFKey = new Rect(), mFSharpKey = new Rect(), mGKey = new Rect(), mGSharpKey = new Rect(), mAKey = new Rect(), mASharpKey = new Rect(), mBKey = new Rect(); private MotionEvent.PointerCoords mPointerCoords; public PianoKeyboard(Context context) { super(context); } public PianoKeyboard(Context context, AttributeSet attrs) { super(context, attrs); } public PianoKeyboard(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mPointerCoords = new MotionEvent.PointerCoords(); Arrays.fill(mFingerPoints, null); Arrays.fill(mFingerTones, -1); loadKeySamples(getContext()); setupPaints(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); releaseKeySamples(); } private void setupPaints() { ... omitted for brevity } private void loadKeySamples(Context context) { mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); 100 Part II: Getting the Most Out of Components mToneToIndexMap.put(R.raw.c, mSoundPool.load(context, R.raw.c, 1)); ... omitted for brevity } public void releaseKeySamples() { mToneToIndexMap.clear(); mSoundPool.release(); } This code shows the member variables needed for the PianoKeyboard class as well as the constructors and the attached/detached callbacks. Following this example, in onAttachedToWindows(), you reset the member variables and load the audio clips. You create the different Paint objects, one for each possible state for each key. The SoundPool is also populated with the audio clips stored in the raw resources. Also, note that you create the Rect objects for each key as member variables with a default initialization. Although you can create these objects on-the-fly in the onDraw() method, the method shown in the preceding example will reduce the number of garbage collection (GC) calls which might impact the performance negatively. The next part is the onLayout() and onDraw() methods: @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); // Calculate the sizes for the keys. int width = getWidth(); int height = getHeight(); int whiteKeyWidth = width / WHITE_KEYS_COUNT; int blackKeyWidth = (int) (whiteKeyWidth * BLACK_TO_WHITE_WIDTH_RATIO); int blackKeyHeight = (int) (height * BLACK_TO_WHITE_HEIGHT_RATIO); // Define the rectangles for each key mCKey.set(0, 0, whiteKeyWidth, height); mCSharpKey.set(whiteKeyWidth - (blackKeyWidth / 2), 0, whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight); //... Remaining keys omitted for brevity } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Start by drawing the white keys canvas.drawRect(mCKey, mCKeyPaint); canvas.drawRect(mDKey, mDKeyPaint); canvas.drawRect(mEKey, mEKeyPaint); canvas.drawRect(mFKey, mFKeyPaint); Chapter 5: Android User Interface Operations 101 canvas.drawRect(mGKey, mGKeyPaint); canvas.drawRect(mAKey, mAKeyPaint); canvas.drawRect(mBKey, mBKeyPaint); // Draw black keys last since they will “cover” the white keys canvas.drawRect(mCSharpKey, mCSharpKeyPaint); canvas.drawRect(mDSharpKey, mDSharpKeyPaint); canvas.drawRect(mFSharpKey, mFSharpKeyPaint); canvas.drawRect(mGSharpKey, mGSharpKeyPaint); canvas.drawRect(mASharpKey, mASharpKeyPaint); } In the onLayout() method, which is called during the layout pass, you calculate the size and position for each key. In the onDraw() method you should avoid performing any heavy calculations and focus on the actual drawing, which avoids potential drops in performance. Note: A custom onMeasure() method isn’t implemented; that’s because the default measuring is sufficient in this case. The final part is the onTouchEvent() callback that performs the actual playback of the audio clips and changes the drawing state of the keys. @Override public boolean onTouchEvent(MotionEvent event) { int pointerCount = event.getPointerCount(); int cappedPointerCount = pointerCount > MAX_FINGERS ? MAX_FINGERS : pointerCount; int actionIndex = event.getActionIndex(); int action = event.getActionMasked(); int id = event.getPointerId(actionIndex); // Check if we received a down or up action for a finger if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && id < MAX_FINGERS) { mFingerPoints[id] = new Point((int) event.getX(actionIndex), (int) event.getY(actionIndex)); } else if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP) && id < MAX_FINGERS) { mFingerPoints[id] = null; invalidateKey(mFingerTones[id]); mFingerTones[id] = -1; } for (int i = 0; i < cappedPointerCount; i++) { int index = event.findPointerIndex(i); if (mFingerPoints[i] != null && index != -1) { mFingerPoints[i].set((int) event.getX(index), (int) event.getY(index)); int tone = getToneForPoint(mFingerPoints[i]); if (tone != mFingerTones[i] && tone != -1) { 102 Part II: Getting the Most Out of Components invalidateKey(mFingerTones[i]); mFingerTones[i] = tone; invalidateKey(mFingerTones[i]); if (!isKeyDown(i)) { int poolIndex = mToneToIndexMap.get(mFingerTones[i]); event.getPointerCoords(index, mPointerCoords); float volume = mPointerCoords. getAxisValue(MotionEvent.AXIS_PRESSURE); volume = volume > 1f ? 1f : volume; mSoundPool.play(poolIndex, volume, volume, 0, 0, 1f); } } } } updatePaints(); return true; } // Check if the key touch by this finger is // already pressed by another finger private boolean isKeyDown(int finger) { int key = getToneForPoint(mFingerPoints[finger]); for (int i = 0; i < mFingerPoints.length; i++) { if (i != finger) { Point fingerPoint = mFingerPoints[i]; if (fingerPoint != null) { int otherKey = getToneForPoint(fingerPoint); if (otherKey == key) { return true; } } } } return false; } private void invalidateKey(int tone) { switch (tone) { case R.raw.c: invalidate(mCKey); break; // Remaining cases omitted for brevity... } } private void updatePaints() { // Start by clearing all keys Chapter 5: Android User Interface Operations 103 mCKeyPaint = mWhiteKeyPaint; ... remaining keys omitted for brevity // Set “hit” paint on all keys touched by a finger // starting with black keys for (Point fingerPoint : mFingerPoints) { if (fingerPoint != null) { if (mCSharpKey.contains(fingerPoint.x, fingerPoint.y)) { mCSharpKeyPaint = mBlackKeyHitPaint; } else if (mDSharpKey.contains(fingerPoint.x, fingerPoint.y)) { ... Remaining keys omitted for brevity } } } } private int getToneForPoint(Point point) { // Start by checking the black keys if (mCSharpKey.contains(point.x, point.y)) return R.raw.c_sharp; ... remaining keys omitted for brevity return -1; } } For each MotionEvent, you check to see whether a new finger (pointer) has touched the screen. If so, you create a new Point and store it in the array for tracking the user’s fingers. If an up event occurred, you remove the Point for that finger instead. Next, you go through all the pointers that are tracked by this MotionEvent and check whether they’ve moved since the last call to this method. If so, you play the tone for the key currently being pressed. There is also a check to ensure you don’t play a tone for a key that’s already being pressed when a second finger just moves over the key. Whenever a MotionEvent occurs, you also check which parts of the View need to be invalidated using the invalidateKey() method. This check takes the ID for a key as a parameter and invalidates only that rectangle. Whenever you need to invalidate your View in order to redraw it, call this method with only the affected region. Doing so will significantly speed up the rendering of your View. Multi-Touching Ever since Android first achieved support for multi-touch technology, users have become accustomed to doing some of the more advanced navigation in Android applications using two or more fingers. Most prominent in this area is probably the Google Maps application. It combines most of the touch gestures—for example, pinch-zoom, tilting, and rotating. Games requiring more than one finger for control is another area in which multi-touch has become popular, and as shown in the PianoKeyboard example in the previous section, multi-touch interaction is also useful in other areas. 104 Part II: Getting the Most Out of Components The challenge when working with multi-touch is to keep track of the individual fingers. Android’s MotionEvent class is the core for all pointer-related interactions, which means fingers, a stylus, a regular computer mouse, or an external touchpad. Take a look at another example for multi-touch. In this example, you create a custom View for finger painting. You use the Path class from the android.graphics package to keep track of what the user’s fingers are drawing. The same principle (that is, the methods involved in a custom View) that applied to the earlier piano example is used here, but in this case there’s much less code to keep track of. Specifically, the following code is a very simple example for creating a multi-touch–enabled finger painting application. public class PaintView extends View { public static final int MAX_FINGERS = 5; private Path[] mFingerPaths = new Path[MAX_FINGERS]; private Paint mFingerPaint; private ArrayList mCompletedPaths; private RectF mPathBounds = new RectF(); public PaintView(Context context) { super(context); } public PaintView(Context context, AttributeSet attrs) { super(context, attrs); } public PaintView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mCompletedPaths = new ArrayList(); mFingerPaint = new Paint(); mFingerPaint.setAntiAlias(true); mFingerPaint.setColor(Color.BLACK); mFingerPaint.setStyle(Paint.Style.STROKE); mFingerPaint.setStrokeWidth(6); mFingerPaint.setStrokeCap(Paint.Cap.BUTT); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (Path completedPath : mCompletedPaths) { canvas.drawPath(completedPath, mFingerPaint); } for (Path fingerPath : mFingerPaths) { Chapter 5: Android User Interface Operations 105 if (fingerPath != null) { canvas.drawPath(fingerPath, mFingerPaint); } } } @Override public boolean onTouchEvent(MotionEvent event) { int pointerCount = event.getPointerCount(); int cappedPointerCount = pointerCount > MAX_FINGERS ? MAX_FINGERS : pointerCount; int actionIndex = event.getActionIndex(); int action = event.getActionMasked(); int id = event.getPointerId(actionIndex); if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && id < MAX_FINGERS) { mFingerPaths[id] = new Path(); mFingerPaths[id].moveTo(event.getX(actionIndex), event.getY(actionIndex)); } else if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP) && id < MAX_FINGERS) { mFingerPaths[id].setLastPoint(event.getX(actionIndex), event.getY(actionIndex)); mCompletedPaths.add(mFingerPaths[id]); mFingerPaths[id].computeBounds(mPathBounds, true); invalidate((int) mPathBounds.left, (int) mPathBounds.top, (int) mPathBounds.right, (int) mPathBounds.bottom); mFingerPaths[id] = null; } for(int i = 0; i < cappedPointerCount; i++) { if(mFingerPaths[i] != null) { int index = event.findPointerIndex(i); mFingerPaths[i].lineTo(event.getX(index), event.getY(index)); mFingerPaths[i].computeBounds(mPathBounds, true); invalidate((int) mPathBounds.left, (int) mPathBounds.top, (int) mPathBounds.right, (int) mPathBounds.bottom); } } return true; } } Note how you use the Path class to add a new line for each new event. Although this class isn’t completely accurate (you should check that a pointer has actually moved before adding a new line), it illustrates how to create a more complex drawing application. 106 Part II: Getting the Most Out of Components PointerCoordinates Each MotionEvent contains all the information about each pointer. Because a pointer can be the result of many different types of input devices (such as a finger, a stylus, or a mouse), it can also contain more information than the x, y coordinates. The APIs in Android support all the input devices defined by the Linux kernel. Because the parameters for an input can vary between devices, the design is such that each pointer has a number of axes. The two most commonly used axes are the x, y coordinates for the pointer, but there is also axis information for pressure, distance, and orientation. Also, because the MotionEvent class can be used for inputs other than those for generating pointer coordinates, this class also has support for axis information related to gaming controllers and other ways of doing input, such as throttle, rudder, tilt, or scroll wheel. Use this class when you want to support external inputs such as game controllers. The following code is a snippet from the previous PianoKeyboard example: event.getPointerCoords(index, mPointerCoords); float volume = mPointerCoords.getAxisValue(MotionEvent.AXIS_PRESSURE); volume = volume > 1f ? 1f : volume; mSoundPool.play(poolIndex, volume, volume, 0, 0, 1f); This code shows how to populate a PointerCoords object with the data for a specific pointer. In this case, you use the pressure axis to set the volume for the audio clip playback. The pressure axis (AXIS_PRESSURE) in the preceding example is usually a virtual value that’s calculated by determining the surface covered by a finger. Normally, the capacitive touchscreens on modern smartphones don’t support a real touch pressure value. Rotate Gesture In the Android APIs, you can find two utility classes that help you detect a number of touch gestures: the GestureDetector and ScaleGestureDetector classes. The first class supports a number of simple single-touch gestures, such a long press, double tap, and fling. The second class provides a way to detect the pinch-zoom gesture used in Google Maps and when zooming in on images. However, one gesture that lacks support in the Android APIs is rotation. The following class exemplifies how to implement a rotation gesture detector for a View. The relevant code is marked in bold. public class RotateView extends View { public static final String TAG = “RotateView”; private static final double MAX_ANGLE = 1e-1; private Paint mPaint; private float mRotation; private Float mPreviousAngle; public RotateView(Context context) { super(context); Chapter 5: Android User Interface Operations 107 } public RotateView(Context context, AttributeSet attrs) { super(context, attrs); } public RotateView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mPaint = new Paint(); mPaint.setColor(Color.BLACK); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); mPaint.setAntiAlias(true); mPreviousAngle = null; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); int radius = (int) (width > height ? height * 0.666f : width * 0.666f) / 2; canvas.drawCircle(width / 2, height / 2, radius, mPaint); canvas.save(); canvas.rotate(mRotation, width / 2, height / 2); canvas.drawLine(width / 2, height * 0.1f, width / 2, height * 0.9f, mPaint); canvas.restore(); } @Override public boolean onTouchEvent(MotionEvent event) { if(event.getPointerCount() == 2) { float currentAngle = (float) angle(event); if(mPreviousAngle != null) { mRotation -= Math.toDegrees(clamp(mPreviousAngle – currentAngle, -MAX_ANGLE, MAX_ANGLE)); invalidate(); } mPreviousAngle = currentAngle; } else { mPreviousAngle = null; 108 Part II: Getting the Most Out of Components } return true; } private static double angle(MotionEvent event) { double deltaX = (event.getX(0) - event.getX(1)); double deltaY = (event.getY(0) - event.getY(1)); return Math.atan2(deltaY, deltaX); } private static double clamp(double value, double min, double max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } The trick comes from using Math.atan2() to calculate the current angle. This method implements the two argument arctangent function, which is used for calculating the angle between the positive x-axis on a plane and the coordinates defined by the two parameters. Next, you subtract the previously calculated angle from the current one and clamp the result between a maximum and a minimum for each event. Finally, because the rotation operations on the Canvas use degrees, you convert the result using Math.toDegrees(). OpenGL ES Although most Android applications can be built using the widgets provided by the SDK or through a custom View, at times you may need a more low-level and high-performance API for graphics, most often for games. The API you will use in this case is OpenGL ES (Embedded Subsystem) and Android supports all versions up to 3.0 (depending on the hardware and Android version). The latest OpenGL ES standard, version 3.0, was added to Android version 4.3. This is the first mobile platform to support OpenGL ES, and it’s expected to allow developers to create more advanced graphics for their games. Android is already using OpenGL ES 2.0 for its standard UI framework to enable hardware-accelerated graphics, but it’s hidden from developers. In this section, I introduce you to OpenGL ES 2.0 and 3.0. The 3.0 version is fully backward-compatible with 2.0, so you can build applications that do a graceful degradation to version 2.0 if the device doesn’t support the later API. For an introduction to OpenGL ES on Android, I recommend the guide from the Android Developers site (http://developer.android.com/guide/topics/graphics/opengl.html). However, when you need to use OpenGL ES in your application or game, you will most likely not write all the OpenGL ES code described in this guide. Instead, you should use a scene graph framework or game engine, which hides most of the complex details involved with OpenGL ES in an easy-to-use API. Chapter 5: Android User Interface Operations 109 Scene Graphs and Graphics Engines The easiest way to work with OpenGL ES is to use a scene graph, which is basically a graph where each node contains information about something being rendered in your scene. You could write your own scene graph, but using one of the many that exist online is usually better. Some of the different options are free and open source licensed and some are commercial. Scene graphs are also called 3D, graphics, or game engines because they also contain functionality specific for advanced graphics and games. One of the better and most active open source 3D engines is Rajawali, developed by Dennis Ippel. You can find all of the information, including a number of great tutorials, for Rajawali at https://github.com/ MasDennis/Rajawali. One of the better commercial alternatives you can find is Unity3D from Unity Technology. This is much more than a game engine and contains a complete development studio for game developers. You can find more information about Unity3D at http://unity3d.com. Always consider the needs of your application or game before you pick a 3D engine because changing it later can be very difficult. One of the most important things to consider is whether you want to be able to easily port your game to a different platform (for instance, iOS). In that case, choose one that supports all the platforms you intend to target (Unity3D has very good multi-platform support). Summary With the advanced UI operations covered in this chapter, you have a set of tools that can help you enhance your application’s performance. For example, perhaps your app would benefit by supporting a secondary screen using HDMI or Miracast, which is when the new Presentation API will be useful. Eventually, all Android developers create custom View classes for their applications. When you do so, be sure to follow the lifecycle for a View and use the different callbacks correctly. When you need to support advanced multi-touch interactions, you must distinguish between different pointers and track them individually. As I’ve shown in this chapter, all MotionEvents contain the current information about all the pointers. When you create advanced and high-performance graphics, you’ll probably need to use OpenGL ES. At the very least, use OpenGL ES 2.0; and for even more advanced graphics, consider using OpenGL ES 3.0 when it is available on the user’s device. Also, I strongly recommend utilizing a complete 3D or game engine when developing games; doing so will make your work much easier. The Android UI is probably the biggest single area of the entire platform. You can find numerous tutorials, guidelines, samples, and books online on this topic; I mention some of my favorites in the following “Further Resources” section. The important thing to remember when you implement your UI is to stick with what you can manage. 110 Part II: Getting the Most Out of Components Further Resources Books Lehtimäki, Juhani. Smashing Android UI. Wiley, 2012. Websites The OpenGL ES guide from the Android Developers site: http://developer.android.com/guide/ topics/graphics/opengl.html Training on working with the Fragments API: http://developer.android.com/training/ basics/fragments/index.html How to write a custom View: https://developers.google.com/events/io/ sessions/325615129 Chapter 6 Services and Background Tasks An important part of any Android application is making sure that long-running and blocking operations are executed in the background so that the main thread is left alone as much as possible. Although you can simply start a new thread in your Activities, a better approach is to move many of the background operations to a separate Service. The Service component in Android provides an efficient way of separating application logic for background tasks from the code that handles the user interface. In this chapter, I cover the use of a Service component when performing background operations. I start with explaining when it’s appropriate to use a Service and how to design and configure the component in an optimal way. I also give a detailed description of the lifecycles that a Service runs through. I explain communication with a Service from the other components in detail and finish this chapter with some best practices for how to move the work on a Service to a background thread. When and How to Use a Service The Android documentation for Service components starts by describing a Service as follows (I suggest keeping this in mind when developing your Android application): A Service is an application component representing either an application’s desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use. In this chapter, I focus on performing long-running operations and cover the second part of the quotation in Chapter 7, where I describe how to communicate between two applications. Because all Android components are running on the main thread of the application process, it might seem unnecessary to use a Service as you still need to use some thread mechanism to move the operation off the main thread. The reason for using a Service is based on the difference in how the lifecycle works in Services and in Activities. I go into the details of the Service lifecycle in the section “Understanding the Service Lifecycle” later in this chapter, but basically, the Service is better adapted to act as the component for managing long-running operations in Android than an Activity. Other questions to consider: Should an operation be long-running? When should you move an operation to a background thread? Is it ever okay to use a background thread like an AsyncTask from within the Activity, instead of moving it to a Service? 112 Part II: Getting the Most Out of Components I recommend moving any operation that doesn’t focus on the user interface to a background thread and making sure that this thread is started and controlled by a Service. That said, I tend to bend that rule depending on the situation. For instance, I always, with no exception, perform network operations from a Service. However, it’s usually okay to write data to local storage, such as a content provider or a preferences file, within the Activity if the data being written is also generated from the user interface, as in editing a contact’s information. Another example is when you’re playing audio in your app. A Service typically controls a music player’s MediaPlayer or AudioTrack object, whereas an Activity handles the sound effects in games and applications. If in doubt, it is usually safe to move the operation to a background thread in a Service. Service Types I like to divide Services into two different types. First is the one that performs work for the application independent of the user’s input. For instance, a music player is able to play music, even if the user doesn’t have the app in the foreground. When playback of the current track is complete, a Service in the music player app starts playing the next track in the playlist. Another example is a messaging application that needs to receive incoming messages at all times and needs to remain running until the user explicitly logs out. The other type of Service is one that’s directly triggered by an action from the user—for example, a photo- sharing application. The user takes a photo, and the application sends it (or rather, a Uri to the locally stored photo) to a Service using an Intent. The Service starts, parses the data in the Intent, and spawns a background thread that uploads the photo. When the operation is complete, the Service is stopped automatically by the system. Understanding the Service Lifecycle Services in Android have a slightly different lifecycle than Activities do. First of all, the Service lifecycle isn’t directly affected by user interactions. In an Activity, when the user taps the Home button, the onPause() callback is always called. For a Service, no callbacks are directly triggered in the same way by the user’s actions. Instead, there are only two callbacks that will always be called for a Service: onCreate() and onDestroy(). (There are also other callbacks that may be called depending on the interaction with the Service or at system and device changes such as, when the screen orientation changes from landscape to portrait mode.) Simplified, a Service is either started or stopped, which makes it much easier to handle than the more complicated lifecycle of an Activity. All you really need to remember is to create expensive objects in onCreate() and do all cleanup in onDestroy(). Service Creation and Destruction Because you have only two lifecycle callbacks for a Service, this is where you should do most of the initialization and cleanup. In the onCreate() method, you initialize new Handler objects, retrieve system Services, register new BroadcastReceivers, and perform other initializations that are required for the Service operations. Remember that this method is executed on the main thread, so you should still delegate Chapter 6: Services and Background Tasks 113 any long-running and potentially blocking operations to a background thread using an AsyncTask, Handler or one of the other methods described in Chapter 2. You do all the cleanup for your Service in the onDestroy() method. You especially need to stop any HandlerThreads that you started and unregister BroadcastReceivers that you registered earlier in the Service. Again, it’s important to remember that what you do here is executed on the main thread, so any cleanup that takes a long time may require you to launch a new thread where you perform that work. For instance, you may need an AsyncTask to gracefully shut down a network server. The onDestory() method is called when the system determines that the Service is ready to be shut down and removed. This usually happens when the application of the Service is no longer in the foreground. Starting Services A Service can be started in two ways: Either it receives an Intent through the Context.startService() method, or a client (either local or remote) binds to it using the Context.bindService() method. Both of these methods will result in the Service moving to the “started” state. When calling Context.startService(), an Intent is sent that must match the intent filter for the Service. (It’s also possible to use an explicit Intent with the ComponentName of the Service without having to define an intent filter.) This method doesn’t provide any reference to the Service for normal synchronized method calls but it is useful for performing message-based triggering of an operation. This is normally something you do as a result of a user interaction that could take an arbitrary amount of time, such as uploading a photo or sending a status update to a server. It’s also a useful way of providing a simple interface for other applications, as I describe in more detail in Chapter 7. When you start a Service using Context.startService(), the onStartCommand() callback is triggered on your Service. In this method, you receive the Intent that was sent to your Service, and you return a constant telling the Android system how your Service should react if the system shuts it down. This is one of the most complicated parts of how Services work and is easy to get wrong. You need to remember three return values (there’s also a fourth value for compatibility reasons that I won’t cover in this book): START_ STICKY, START_NOT_STICKY, and START_REDELIVER_INTENT. When you return START_STICKY, it signals that you want your Service to be restarted if the system shuts it down for some reason (usually when running low on memory). However, when the system restarts the Service, the onStartCommand() is called with the Intent parameters set to null, so you have to take care of this in your code. A typical example of this return value is a music player where the Service is always started in the same way. This means that you need to store the internal state of you Service when onDestroy() is called. When you return START_NOT_STICKY, your Service won’t restart after the system shuts it down. This is probably useful when you send an Intent to the Service to perform a one-shot operation, such as uploading something to a server. If the Service is shut down before completing its task, it shouldn’t try to repeat the operation. The third return constant, START_REDELIVER_INTENT, works like START_STICKY, except that the original Intent is redelivered when the system restarts your Service. 114 Part II: Getting the Most Out of Components Regardless of what you choose to return from onStartCommand() in your Service, consider not only how the Service is started but also how it will be stopped. Choosing START_REDELIVER_INTENT or START_ STICKY could have unforeseen consequences if you’re not careful. When you receive a call to the onStartCommand() in your Service, you have three parameters to deal with. The first is the Intent, which can be null depending on what you returned in previous calls to onStartCommand(). The second parameter is a flag indicating what this call represents. It can be either 0, START_FLAG_RETRY, or START_FLAG_REDELIVERY. The third parameter, named startId, can sometimes be useful when you need to safely stop the Service and you have received multiple calls to onStartCommand() during the lifecycle. Because using Context.startService() can be considered as asynchronous (see Figure 6-1), you may also need a way to signal back to the Activity that your operation is complete. One way of doing so is to use a programmatic BroadcastReceiver (see the section “Asynchronous Messaging with Intents” later in this chapter, where I briefly discuss this topic; see also Chapter 8, where I go into more detail). Figure 6-1 Sequence diagram over an asynchronous Service interaction Binding Services The second way to start a Service is through Context.bindService(). When you bind to a Service, it will continue operating until the last client disconnects. Binding to a Service in the same application process is a simple way to access a reference to the Service object from another component and call methods directly on it. This is called a local binder and is illustrated in Figure 6-2. Chapter 6: Services and Background Tasks 115 Figure 6-2 Sequence diagram illustrating the use of the local binder pattern The following code shows how to implement a local Binder for your Service that you use when it is accessed only within your application. This approach doesn’t work if your Service will be accessed from other applications. In those cases, you need to use an AIDL, Android’s method for serializing method calls across process, which I describe in detail in Chapter 7. public class MyLocalService extends Service { private LocalBinder mLocalBinder = new LocalBinder(); public IBinder onBind(Intent intent) { return mLocalBinder; } public void doLongRunningOperation() { // TODO Start new thread for long running operation... } public class LocalBinder extends Binder { public MyLocalService getService() { return MyLocalService.this; } } } When your Service returns only null in the method onBind(), you can bind to it from another component. When doing so in an Activity, you usually implement the binding and unbinding in the onResume() and onPause() methods, as shown here: public class MyActivity extends Activity implements ServiceConnection { private MyLocalService mService; @Override 116 Part II: Getting the Most Out of Components public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onResume() { super.onResume(); Intent bindIntent = new Intent(this, MyLocalService.class); bindService(bindIntent, this, BIND_AUTO_CREATE); } @Override protected void onPause() { super.onPause(); if (mService != null) { unbindService(this); } } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mService = ((MyLocalService.LocalBinder) iBinder).getService(); } @Override public void onServiceDisconnected(ComponentName componentName) { mService = null; } } In this Activity example, the call to bindService() is asynchronous, and the actual IBinder interface that you return on onBind() is passed to the callback onServiceConnected(). The second callback, onServiceDisconnected(), is triggered from the call to unbindService() in the onPause() method. This design provides an efficient binding and unbinding to the Service, for when the Activity is active and dismissed, respectively. You can also use the callback where you receive the actual reference to the Service, onServiceConnected(), to update the UI and enable buttons or other components that require the Service to be available. Once you have a reference to the actual Service object, you can treat it as any other Java object. However, it’s important to always release any references between an Activity and a Service in onPause(). Staying Alive When your Service starts and the application is running in the foreground (that is, when an Activity of the application is shown) your Service will be the last one shut down by the system. However, as soon as the user leaves your application, the Service will no longer be running in the foreground and may be targeted for shutdown by the system. If you need to keep your Service in the foreground even if your application is not the active one, you can do so by calling the method Service.startForeground() Chapter 6: Services and Background Tasks 117 The system tries to keep Services alive and running for as long as possible. Only when it is running out of resources, usually free RAM, does it start to stop Services. However, you should always assume that the system may stop your Services at any given point. In the following code, you see an example of the onStartCommand() method for a Service that uploads a photo to an online server. In this method, also make sure that you’re running in the foreground and that you provide the user with a notification that you have a background operation running. @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { String action = intent.getAction(); if (ACTION_SHARE_PHOTO.equals(action)) { // Build the notification to be shown Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.drawable.notification_icon); builder.setContentTitle(getString(R.string.notification_title)); builder.setContentText(getString(R.string.notification_text)); Notification notification = builder.build(); // Start the service in the foreground startForeground(NOTIFICATION_ID, notification); // Perform out background operation… String photoText = intent.getStringExtra(EXTRA_PHOTO_TEXT); Bitmap photoBitmap = intent.getParcelableExtra(EXTRA_PHOTO_BITMAP); uploadPhotoWithText(photoBitmap, photoText); } } return START_NOT_STICKY; } First, you create a notification that you will use for your background operation. Next, you call startForeground() together with a unique ID and the notification you just created. The result is a new notification that appears in the status bar until the Service is either stopped (see the next section) or you call stopForeground()with the parameter true. Although this is the recommended way of making sure your Service remains operating even though your application is running in the background, don’t do so unless you really need to—because you could easily end up wasting system resources. Also, when the operation is complete, make sure you exit everything properly so that, again, you won’t waste system resources. Stopping Services Once your Service starts, it will remain running for as long as possible. Depending on how it was started, the system will restart the Service if it’s shut down due to lack of resources. 118 Part II: Getting the Most Out of Components Some unexpected results can occur when a Service suddenly restarts, even though the user didn’t launch the application. So, it’s important to properly stop your Services once the user’s work is complete. If your Service started from Context.bindService(), it will be automatically stopped when the last client disconnects (that is, called Context.unbindService()). The exception is if you also call Service. startForeground() in your Service to keep it alive after the last client disconnects, which is why it’s important to call Service.stopForeground() properly. If you start your Service by using Context.startService(), then the only way to ensure that your Service is stopped is by calling either Service.stopSelf() or Context.stopService(). This signals to the system that it should stop and remove the Service. The only way to restart the Service is with an explicit call to Context.startService() or Context.bindService(). Also, calling Service.stopSelf() or Context.stopService() on a Service that was started with Context. startService() will always stop it, regardless of how many times onStartCommand() was executed (that is, calls to Context.startService() don’t stack). The code example that follows shows a (very) simple Service for a music player. Its only feature is adding tracks to a play queue. If the queue is empty when a new track arrives, it will start playing the track immediately; otherwise, it will place the track at the end of the queue. Once a track is finished, the callback onCompletion()is called, and a check is made to determine whether the queue is empty. If there are more tracks, the MediaPlayer is prepared with the next track, and playback starts again; if the queue is empty, you call Service.stopSelf(), which causes the Service to shut down and release the MediaPlayer. public class MyMusicPlayer extends Service implements MediaPlayer.OnCompletionListener { public static final String ACTION_ADD_TO_QUEUE = “com.aptl.services.ADD_TO_QUEUE”; private ConcurrentLinkedQueue mTrackQueue; private MediaPlayer mMediaPlayer; public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); mTrackQueue = new ConcurrentLinkedQueue(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String action = intent.getAction(); if (ACTION_ADD_TO_QUEUE.equals(action)) { Uri trackUri = intent.getData(); addTrackToQueue(trackUri); } Chapter 6: Services and Background Tasks 119 return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); if(mMediaPlayer != null) { mMediaPlayer.release(); mMediaPlayer = null; } } /** * Add track to end of queue if already playing, * otherwise create a new MediaPlayer and start playing. */ private synchronized void addTrackToQueue(Uri trackUri) { if(mMediaPlayer == null) { try { mMediaPlayer = MediaPlayer.create(this, trackUri); mMediaPlayer.setOnCompletionListener(this); mMediaPlayer.prepare(); mMediaPlayer.start(); } catch (IOException e) { stopSelf(); } } else { mTrackQueue.offer(trackUri); } } // Track completed, start playing next or stop service... @Override public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.reset(); Uri nextTrackUri = mTrackQueue.poll(); if(nextTrackUri != null) { try { mMediaPlayer.setDataSource(this, nextTrackUri); mMediaPlayer.prepare(); mMediaPlayer.start(); } catch (IOException e) { stopSelf(); } } else { stopSelf(); } } } This example illustrates how to use Service.stopSelf() to ensure that your Service doesn’t use any more resources than necessary. A good Android application needs to always release as many resources as possible, as soon as possible. 120 Part II: Getting the Most Out of Components Running in the Background A Service can be running when your application isn’t in the foreground, but this doesn’t mean that it won’t be executing any work on the main thread. Because all components’ lifecycle callbacks are executed on the application’s main thread, you need to make sure that any long-running operation you perform in your Service is moved to a new thread. (Refer to Chapter 2, where I explain how you can use a Handler or an AsyncTask to launch an operation on the main thread. In this section, I cover two additional methods for executing things on the main thread.) IntentService Using a Handler together with a Service has proven so efficient that Google implemented a utility class named IntentService that wraps the background thread handling of a Handler in a Service. All you need to do is extend the class, implement the onHandleIntent() method, and add the actions you want the Service to be able to receive, as illustrated here: public class MyIntentService extends IntentService { private static final String NAME = “MyIntentService”; public static final String ACTION_UPLOAD_PHOTO = “com.aptl.services.UPLOAD_PHOTO”; public static final String EXTRA_PHOTO = “bitmapPhoto”; public static final String ACTION_SEND_MESSAGE = “com.aptl.services.SEND_MESSAGE”; public static final String EXTRA_MESSAGE = “messageText”; public static final String EXTRA_RECIPIENT = “messageRecipient”; public MyIntentService() { super(NAME); // We don’t want intents redelivered // in case we’re shut down unexpectedly setIntentRedelivery(false); } /** * This method is executed on its own thread, one intent at a time... */ @Override protected void onHandleIntent(Intent intent) { String action = intent.getAction(); if(ACTION_SEND_MESSAGE.equals(action)) { String messageText = intent.getStringExtra(EXTRA_MESSAGE); String messageRecipient = intent.getStringExtra(EXTRA_RECIPIENT); sendMessage(messageRecipient, messageText); } else if(ACTION_UPLOAD_PHOTO.equals(action)) { Bitmap photo = intent.getParcelableExtra(EXTRA_PHOTO); uploadPhoto(photo); } } private void sendMessage(String messageRecipient, String messageText) { Chapter 6: Services and Background Tasks 121 // TODO Make network call... // TODO Send a broadcast that operation is completed } private void uploadPhoto(Bitmap photo) { // TODO Make network call... // TODO Send a broadcast that operation is completed } } The preceding example shows a class extending IntentService that handles two different actions, one for uploading a photo and one for sending a message. Each action needs to be added to the intent filter for the Service in the manifest as well. If you want to trigger an action, you simply call Context. startService() with an Intent carrying the specific action and extras. Multiple calls will result in them being queued up by the internal handler, so this class ensures that only one Intent is processed at any given time. A Service based on the IntentService will be kept in started state until no more operations are queued for processing. Parallel Execution The specialized Service class just described is very useful for most situations in which you simply want to spawn a background operation and don’t care too much about when it starts. If you send five Intents to an IntentService, they will be executed in sequential order, one at a time. This is usually a good practice but can sometimes cause problems. To ensure that each background operation you spawn is executed as soon as possible, you need some kind of parallel execution. Because the IntentService is built around a Handler that has only one thread, you need to use some other thread mechanism to handle this kind of situation. As I describe in Chapter 2, you can set up an AsyncTask to use an Executor for spawning instances in parallel. However, because AsyncTask is designed for operations running only a few seconds at most, you may need to do some more work if your operations are running for a significant amount of time. The following example shows the stub for a Service used for transcoding media to a new format (for instance, WAV to MP3). I left out the actual transcoding step here and focused on the use of the ExecutorService API for setting up parallel execution. In order to ensure that the Service is kept alive even if the application is not in the foreground, you call Service.startForeground() on the Service. Because Service. startForeground() and Service.stopForeground() don’t stack, you need to maintain an internal counter of how many active jobs you have and call Service.stopForeground() once the counter reaches 0 again. public class MediaTranscoder extends Service { private static final int NOTIFICATION_ID = 1001; public static final String ACTION_TRANSCODE_MEDIA = “com.aptl.services.TRANSCODE_MEDIA”; public static final String EXTRA_OUTPUT_TYPE = “outputType”; private ExecutorService mExecutorService; private int mRunningJobs = 0; private final Object mLock = new Object(); 122 Part II: Getting the Most Out of Components private boolean mIsForeground = false; public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); mExecutorService = Executors.newCachedThreadPool(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String action = intent.getAction(); if(ACTION_TRANSCODE_MEDIA.equals(action)) { String outputType = intent.getStringExtra(EXTRA_OUTPUT_TYPE); // Start new job and increase the running job counter synchronized (mLock) { TranscodeRunnable transcodeRunnable = new TranscodeRunnable(intent.getData(), outputType); mExecutorService.execute(transcodeRunnable); mRunningJobs++; startForegroundIfNeeded(); } } return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); mExecutorService.shutdownNow(); synchronized (mLock) { mRunningJobs = 0; stopForegroundIfAllDone(); } } public void startForegroundIfNeeded() { if(!mIsForeground) { Notification notification = buildNotification(); startForeground(NOTIFICATION_ID, notification); mIsForeGround = true; } } private Notification buildNotification() { Notification notification = null; // TODO Build the notification here... return notification; } Chapter 6: Services and Background Tasks 123 private void stopForegroundIfAllDone() { if(mRunningJobs == 0 && mIsForeground) { stopForeground(true); mIsForeground = false; } } private class TranscodeRunnable implements Runnable { private Uri mInData; private String mOutputType; private TranscodeRunnable(Uri inData, String outputType) { mInData = inData; mOutputType = outputType; } @Override public void run() { // TODO Perform transcoding here... // Decrease counter when we’re done... synchronized (mLock) { mRunningJobs--; stopForegroundIfAllDone(); } } } } Because most of the boilerplate code for dealing with threads is done in the ExecutorService API, this method for implementing parallel execution is recommended. Also, the ExecutorService shown in this example won’t consume resources when not in use and will keep a cache of threads to minimize the creation of new threads. Communicating with Services Once you know when to use a Service and how it’s executing, you need some way of communicating with it from your other component. You can communicate with a Service in two ways: by using the method Context.startService() or by using the Context.bindService() method. Context. startService()delivers the Intent to the method Service.onStartCommand() where you can trigger the background operation and later deliver the result back to the calling component through a broadcast or some other means. Context.bindService()is a way to retrieve a Binder that can be used to make synchronous method calls directly to the Service object. I start by describing how to communicate with a Service through Intents. Asynchronous Messaging with Intents The IntentService example shown earlier in this chapter provides an easy-to-use one-way communication between a component (usually an Activity) and the Service. But usually you want know the result of the operation you start, so you need some way for the Service to report back once it completes its task. You can 124 Part II: Getting the Most Out of Components do so several ways, but if you want to maintain the asynchronous behavior of the IntentService, the best approach is to send a broadcast, which is just as simple as starting an operation in your IntentService. You just need to implement a BroadcastReceiver that listens for the response. Figure 6-3 shows a simple diagram of this sort of communication. Figure 6-3 Diagram of asynchronous communication between an Activity, a Service, and a BroadcastReceiver The following code is the modified version of the uploadPhoto() method from the earlier example. Here you send only a simple broadcast without any extras, but you’re free to use this method to send back a more complex response as long as it can fit into an Intent. private void uploadPhoto(Bitmap photo) { // TODO Make network call... sendBroadcast(new Intent(BROADCAST_UPLOAD_COMPLETED)); } The advantage of this approach is that you have everything you need in Android and don’t need to build some complex message handling system between your components. You just declare the actions that represent the asynchronous messages and register them appropriately for each component. This approach works even if your Service resides in a different application or runs in a separate process. Chapter 6: Services and Background Tasks 125 The drawback to this solution is that you’re limited to what an Intent can carry. Also, you cannot use this solution for multiple, fast updates between the IntentService and your Activity, such as progress updates, because doing so will choke the system. If you need to do something like that, look at using a bound Service instead (see the next section). I go into the details of declaring and setting up BroadcastReceivers in Chapter 8. Locally Bound Services In an earlier example in this chapter, I showed how to bind to a Service in the same application using the local binder pattern. This solution is very useful when your Service provides interfaces that are too complicated to solve using only Intent messaging and where normal Java methods are easy to implement. Another reason for binding to a local Service is that you can provide a more complex way of doing callback from the Service back to the Activity. Because long-running operations still must be moved to a background thread in the Service, most of the calls to the Service should be asynchronous in their design. The actual call triggers a background operation and returns immediately. Once the operation is complete, the Service uses a callback interface to notify the Activity about the result. In the following example, I’ve modified the earlier example with a local Binder. I’ve added a callback interface and a class implementing AsyncTask for performing the hypothetical background operation. The Service returns a LocalBinder object in the onBind() method, from which the client can retrieve an object reference to the Service object and call the method doLongRunningOperation(). This method creates a new AsyncTask and executes it with the parameters sent from the client. During the run on the operation, the callback instance is called to notify the client of the progress and eventually of the result. public class MyLocalService extends Service { private static final int NOTIFICATION_ID = 1001; private LocalBinder mLocalBinder = new LocalBinder(); private Callback mCallback; public IBinder onBind(Intent intent) { return mLocalBinder; } public void doLongRunningOperation(MyComplexDataObject dataObject) { new MyAsyncTask().execute(dataObject); } public void setCallback(Callback callback) { mCallback = callback; } public class LocalBinder extends Binder { public MyLocalService getService() { return MyLocalService.this; } } 126 Part II: Getting the Most Out of Components public interface Callback { void onOperationProgress(int progress); void onOperationCompleted(MyComplexResult complexResult); } private final class MyAsyncTask extends AsyncTask { @Override protected void onPreExecute() { super.onPreExecute(); startForeground(NOTIFICATION_ID, buildNotification()); } @Override protected void onProgressUpdate(Integer... values) { if(mCallback != null && values.length > 0) { for (Integer value : values) { mCallback.onOperationProgress(value); } } } @Override protected MyComplexResult doInBackground(MyComplexDataObject... myComplexDataObjects) { MyComplexResult complexResult = new MyComplexResult(); // Actual operation left out for brevity... return complexResult; } @Override protected void onPostExecute(MyComplexResult myComplexResult) { if(mCallback != null ) { mCallback.onOperationCompleted(myComplexResult); } stopForeground(true); } @Override protected void onCancelled(MyComplexResult complexResult) { super.onCancelled(complexResult); stopForeground(true); } } private Notification buildNotification() { // Create a notification for the service.. return notification; } } Chapter 6: Services and Background Tasks 127 Also, the AsyncTask implemented will call startForeground() and stopForeground(), respectively to make sure the Service is kept alive until the long-running operation is finished, even if no clients are bound to the Service. (It’s outside the scope of this example to keep a count of how many times the method is called, as shown in the earlier example with parallel execution.) The following code shows how the updated Activity looks. The noticeable change comes from implementing the MyLocalService.Callback interface on the Activity. When you receive a reference to the Service in onServiceConnected(), you can set the callback to the Service object so that you receive notifications when a long-running operation is running. Here it becomes very important to remove the callback (that is, set it to null) when the user leaves the Activity and onPause() is called, or you will leak memory. public class MyActivity extends Activity implements ServiceConnection, MyLocalService.Callback { private MyLocalService mService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onResume() { super.onResume(); Intent bindIntent = new Intent(this, MyLocalService.class); bindService(bindIntent, this, BIND_AUTO_CREATE); } @Override protected void onPause() { super.onPause(); if (mService != null) { mService.setCallback(null); // Important to avoid memory leaks unbindService(this); } } // Callback method assigned to onClick for the button in the UI public void onTriggerLongRunningOperation(View view) { if(mService != null) { mService.doLongRunningOperation(new MyComplexDataObject()); } } @Override public void onOperationProgress(int progress) { // TODO Update user interface with progress.. } 128 Part II: Getting the Most Out of Components @Override public void onOperationCompleted(MyComplexResult complexResult) { // TODO Show result to user... } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mService = ((MyLocalService.LocalBinder) iBinder).getService(); mService.setCallback(this); // Once we have a reference to the service, we can update the UI and // enable buttons that should otherwise be disabled. findViewById(R.id.trigger_operation_button).setEnabled(true); } @Override public void onServiceDisconnected(ComponentName componentName) { // Disable the button as we are losing the // reference to the service. findViewById(R.id.trigger_operation_button).setEnabled(false); mService = null; } } Also, in the onServiceConnected() and onServiceDisconnected() methods, you can update the parts of the user interface that depend on the Service. In this case, you enable and disable the button that is used to trigger the long-running operation. If the user leaves the Activity (that is, presses Home or Back) before the operation is completed, the Service still keeps running because startForeground() was called. If the Activity resumes before a previously started operation is finished, it will receive the callbacks about the progress as soon as it is successfully bound to the Service. This behavior makes it easy to separate a long-running task from the user interface while still allowing your Activity to retrieve the current state once it resumes. If your Service maintains some kind of internal state, it’s a good practice to allow clients (like Activities) to retrieve the current state as well as subscribe (using a callback as just shown) to changes in the state because the state may have changed when an Activity resumes and binds to the Service. Summary The Service class is a powerful component that can easily become very complicated to use. You discovered two ways to start a Service and how the lifecycle is affected by the chosen method. You saw a few examples of how to implement and control your Service to make sure that it can be safely disconnected and reconnected with the user interface. When it comes to Services in Android, my best advice is to have one Service for each type of operation you want to perform that’s not related to the user interface. At times, you may start by implementing everything Chapter 6: Services and Background Tasks 129 within an Activity and then later discover that doing so was a mistake that now requires a lot of work to move all the operations to a Service in a less optimal way. I strongly recommend that you create Services from the start in your project to avoid this problem. It’s easier to move operations from a Service to an Activity than to do the opposite. Also, remember to be careful about how you start your Services. When using Context. startService(), you need to return the appropriate value from onStartCommand(). Returning the wrong value can have unforeseen consequences when the system eventually shuts down your Service because of lack of resources. Explicitly stopping your Service when it no longer has any operations to execute is a good practice. If you need bidirectional communication between your Service and an Activity, I recommend using the local binder pattern and using a callback interface that your Activity can implement. Another way is to use Intent messaging and let the Service send broadcasts when operations are done. The first approach is more powerful and will allow you to do frequent progress updates from the Service to your Activity, whereas the second method requires less code but isn’t as powerful. Finally, remember that the lifecycle callbacks, like onCreate(), onStartCommand() and onDestroy(), are all executing on the main thread of your application. This is the same main thread that your Activities and other components use. Whenever you perform something that could potentially block the thread for more than a few milliseconds, you should move it to a Handler or an AsyncTask. Further Resources Blogs Google’s changes to the Service API at http://android-developers.blogspot.se/2010/02/ service-api-changes-starting-with.html Dianne Hackborn at http://android-developers.blogspot.se/2010/04/multitasking- android-way.html Chapter 7 Android IPC Android has a powerful feature that’s capable of communicating between two different applications. You can set up this communication in many ways in your code, but there is one central mechanism behind the scenes that handles all inter-process communication, the Binder IPC (Inter-Process Communication). The Binder in Android has a long history. It was originally developed as the OpenBinder at Be Inc. for the Be Operating System (BeOS) under the leadership of Dianne Hackborn. It was ported and later rewritten for Android in order to support IPC for applications. Basically, the Binder provides the features for binding functions and data between one execution environment and another. Because each Android application runs in its own Dalvik VM, which is an isolated execution environment, the Binder is very suitable for this purpose. Back in 2009, there was a long debate in the Linux community about why Google chose to use the Binder for IPC rather than the existing solution in the Linux kernel named dbus. The simplest explanation is likely that Dianne Hackborn, one of the lead Android framework engineers, was also the one leading the development of the OpenBinder at Be Inc. When Android was first developed, this was their best choice for IPC, and today it’s an integral part of the Android system. The dbus mechanism from Linux is also used on many Android devices, specifically for communication with the Radio Interface Layer (RIL) and for Bluetooth up until Android 4.3. However, most IPC calls on Android go through the Binder. In addition to being used for communication between Android applications, the Binder is actually essential in order for an application to communicate with the Android system. When you retrieve a system Service using the Context.getSystemService() method, the Binder is working behind the scenes to provide your application with a wrapper for a Service object. The Binder isn’t just used by Services, it also handles all communication between Android components and the Android system. Normally, an Android application doesn’t have to contend with the low-level details of the Binder because the Android APIs provide nice wrappers that make it easy to perform IPC. In this chapter, I describe how the Binder works and provide a few examples that show you how to build remote APIs for other applications. The Binder Explained As I mentioned in the introduction, the Binder in Android was originally designed under the name “OpenBinder for BeOS” and not Linux, which is the kernel Android runs on. In earlier versions of Android, essentially the same code used for OpenBinder was used for the Linux kernel driver that implemented the Binder for Android. This was less than optimal because the architecture from BeOS is very different from the architecture found in Linux. In later versions of Android, Google rewrote the implementation so that now it’s better suited for the Linux kernel architecture. 132 Part II: Getting the Most Out of Components When two applications communicate using the Binder IPC, they’re using this kernel driver to relay messages (see Figure 7-1) between them. Besides the messaging function, the Binder provides additional functions such as identifying the remote caller (process ID and user ID) and notifying when a remote process dies (called link to death). Conceptual function callApplication 1 Binder Driver Linux Kernel Binder communication path Application 2 Figure 7-1 Simple diagram illustrating communication using the Binder IPC For example, the system uses these additional functions in the Binder when the system Service, which manages all windows in Android through the WindowManager, keeps a Binder reference to every application and is notified through a link-to-death notification when an application’s window closes. Communication using the Binder follows the client-server model. Clients use a client-side proxy to handle the communication with the kernel driver. On the server-side, the Binder framework maintains a number of Binder threads. The kernel driver delivers the messages from the client-side proxy to the receiving object using one of the Binder threads on the server-side. This is important to remember because when you receive calls to a Service through the Binder, they will not be executed on the main thread of your application. That way, a client to a remote Service cannot block the Service application’s main thread. You implement the Binder in Android by using the base class Binder and the interface IBinder. As I show in Chapter 6, a Service can return a class implementing the IBinder interface in the method Service. onBind(). When the Service publishes a remote API, you generally use an AIDL file to generate this IBinder class, but as I describe next, other methods are available as well. Binder Address Communicating over the Binder requires that the client know the address of the remote Binder object. However, the design of the Binder is such that only the implementation, like the Service you want to call, knows its address. You address on the Android API level by using Intent resolution. The client constructs an Intent object, using either an action String or a ComponentName and then uses that to initiate communication with the remote application. However, an Intent is only an abstraction of the actual Binder address and needs to be translated in order to set up the communication. Chapter 7: Android IPC 133 A special Binder node called ServiceManager that is running inside the Android system server manages all address resolution in Android. This is the only Binder node that has a globally known address. Because all components in Android use the Binder for communication, they need to register using the ServiceManager, which they reach through the well-known address (see Figure 7-2). 3. Service communication 1. addService() 1. getService() Service Manager Service Client Figure 7-2 Diagram showing service registration and lookup through the ServiceManager Clients that want to communicate with a Service or other component query the ServiceManager, implicitly through the Intent resolution, to receive the Binder address. Binder Transactions When one process sends data to another in Android, it’s called a transaction. You start transactions on the Binder by calling IBinder.transact() on the client, and the Service receives the call on the method Binder.onTransact(), as shown here: public String performCustomBinderTransaction(IBinder binder, String arg0, int arg1, float arg2) throws RemoteException { Parcel request = Parcel.obtain(); Parcel response = Parcel.obtain(); // Populate request data... request.writeString(arg0); request.writeInt(arg1); request.writeFloat(arg2); // Perform transaction binder.transact(IBinder.FIRST_CALL_TRANSACTION, request, response, 0); // Read the result from the response… String result = response.readString(); // Recycle the objects request.recycle(); response.recycle(); return result; } 134 Part II: Getting the Most Out of Components The method in the preceding example illustrates how from the client-side, once you have a valid IBinder reference, you can perform a custom Binder transaction toward the Service. I explain the Parcel objects in detail in the following example. They are used as simple data containers for the data you want to include in the transaction. public class CustomBinder extends Binder { @Override protected boolean onTransact(int code, Parcel request, Parcel response, int flags) throws RemoteException { // Read the data in the request String arg0 = request.readString(); int arg1 = request.readInt(); float arg2 = request.readFloat(); String result = buildResult(arg0, arg1, arg2); // Write the result to the response Parcel response.writeString(result); // Return true on success return true; } private String buildResult(String arg0, int arg1, float arg2) { String result = null; // TODO Build the result return result; } } If you implement a custom Binder object in your Service without using an AIDL, you need to implement the method Binder.onTransact() as just shown. Here you simply respond to the incoming transaction by populating the second Parcel object with the relevant data. The result is a synchronized two-way call through the Binder IPC. You can also perform a one-way call from the client by setting the flag in the IBinder.transact() call to FLAG_ONEWAY, in which case, you can leave the second Parcel argument as null. Doing so provides better performance for this call because it needs to marshal and unmarshal only one Parcel object. Using this low-level way of performing transactions between two applications is not recommended if you intend to publish an API for other developers to use. However, when you need fine-grained control of how data is sent between two applications, this can be an efficient method to use. I share it here to illustrate how the Binder works on its basic level. Most of the time, you’ll use either AIDL or a Messenger as described in the “Messenger” section, later in this chapter. Parcel A Binder transaction will usually carry some transaction data, as shown in the previous example. This data is called a parcel, and there is an API for developers, which allows you to create a parcel for most Java objects. Chapter 7: Android IPC 135 You can compare parcels in Android with serializable objects in Java SE. The difference is that you need to implement the marshaling and unmarshaling of objects yourself using the Parcelable interface. This interface defines two methods you need to implement for writing an object to a Parcel and also a static final Creator object that implements the code for reading the object from a Parcel, as shown here: public class CustomData implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public CustomData createFromParcel(Parcel parcel) { CustomData customData = new CustomData(); customData.mName = parcel.readString(); customData.mReferences = new ArrayList(); parcel.readStringList(customData.mReferences); customData.mCreated = new Date(parcel.readLong()); return customData; } @Override public CustomData[] newArray(int size) { return new CustomData[size]; } }; private String mName; private List mReferences; private Date mCreated; public CustomData() { mName = “”; // Defaults to empty string mReferences = new ArrayList(); mCreated = new Date(); // Defaults to now } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mName); parcel.writeStringList(mReferences); parcel.writeLong(mCreated.getTime()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; 136 Part II: Getting the Most Out of Components CustomData that = (CustomData) o; return mCreated.equals(that.mCreated) && mName.equals(that.mName); } @Override public int hashCode() { int result = mName.hashCode(); result = 31 * result + mCreated.hashCode(); return result; } } The preceding code shows an object that implements the Parcelable interface. Note the implementation of the CREATOR field and how the createFromParcel() method uses the Parcel.readStringList() method to read the entire List object without having to specify how long the list is (this is handled internally by the Parcel object). After you implement this interface, you can send objects of this class between applications through the Binder IPC. Link to Death Another feature of the Binder in Android is that it allows clients to be notified when a Service is terminated. As I mentioned earlier, this is called link to death, and it’s implemented through the Binder method IBinder. linkToDeath(). When a client receives an IBinder object in the onServiceConnected() method, the client can call linkToDeath() with a callback implementing the interface IBinder.DeathRecipient. Because Android applications can be killed by the system when it’s running low on resources (available RAM, and so on), it can be useful to register for these notifications in a client in case it wants to be notified when the remote side is terminated. The following code shows how to register for link to death once you receive a valid IBinder reference: public class LinkToDeathSample extends Service { private static final String TAG = “LinkToDeathSample”; // Service methods exclude for brevity... private void notifyRemoteServiceDeath(IBinder iBinder) { try { iBinder.linkToDeath(new MyLinkToDeathCallback(), 0); } catch (RemoteException e) { Log.e(TAG, “Error registering for link to death.”, e); } } class MyLinkToDeathCallback implements IBinder.DeathRecipient { @Override public void binderDied() { // TODO Handle death of remote binder... } } } Chapter 7: Android IPC 137 You can also check whether the process for the remote Binder is still alive by calling IBinder. pingBinder(). If that call returns true, the process is alive and ready. If you’re binding to a Service, this method is not necessary because you’ll always have the ServiceConnection.onServiceDisconnected() callback to notify you when you lose your connection. However, if you received a Binder object some other way, this method can be useful. Designing APIs Most applications will rarely need to implement an API for third-party applications because doing so is outside the scope of their features. However, it does become relevant with the types of applications that provide a plug- in mechanism. If you search for “plugin” at the Google Play Store, you’ll find tons of examples of these types of applications. If your application fits this category, you’ll probably benefit from preparing an API for third-party applications. An API for third-party applications can be either implemented as a Service or as a ContentProvider. In this section, I describe how to do so using a Service; I show how to use a ContentProvider in Chapter 9. When implementing an API, you need to consider a number of things. Do you need to handle concurrent requests, or is it enough to process one client request at a time? Will you publish only one or very few operations, or is it a more complex set of API methods that clients can use? The answer to these questions will determine the most appropriate method for implementing your remote API. Another detail to consider is whether you’ll be sharing this API with other developers or if it will be used only by your own applications (that is, only you will be publishing plug-ins). In the first case, consider building a library project that wraps the client-side implementation in an easy-to-use Java API. If you’re the only user of the API, it is probably safe to use either the AIDL or the Messenger directly as described in the next two sections. If it’s enough that your API is one-way, you’re probably fine with using an IntentService as I describe in Chapter 6. In that case, you just add the necessary permissions and make sure that the API is exported in the manifest. AIDL In software engineering, the term Interface Definition Language (IDL) has become the generic term for a specification language that describes the interface for a software component. In Android, the IDL is called Android Interface Definition Language (AIDL) and is written in text files with a Java-like syntax. However, you need to consider a number of differences between writing AIDL files and writing a Java interface. First, for all non-primitive parameters, you need to specify one of three directional types: in, out, or inout. The in type indicates that they are used only for input and that your client won’t see any changes that the Service does to this object. The out type indicates that the input object contains no relevant data but will be populated with data by the Service that’s relevant in the response from the method. The inout type is a combination of both types. It’s very important to use only the type that’s needed because there’s a cost associated with each type. 138 Part II: Getting the Most Out of Components Another thing to remember is that for all custom classes used in communication, you need to create an AIDL file that declares your class as a Parcelable. The following code snippet is an example of an AIDL file with the name CustomData.aidl. It should be placed in the same package as the Java class source file. package com.aptl.sampleapi; parcelable CustomData; Finally, all custom classes you need for your API must be imported in the AIDL file for the API, as shown here: package com.aptl.sampleapi; import com.aptl.sampleapi.CustomData; interface ApiInterfaceV1 { /** * Simple remote method for checking if a number is a prime. */ boolean isPrime(long value); /** * Retrieve all CustomData objects since timestamp. * Will get at most result.length objects. */ void getAllDataSince(long timestamp, out CustomData[] result); /** * Stores the CustomData object. */ void storeData(in CustomData data); } This is an example of an AIDL file with three methods. Note: Primitives don’t need a directional tag (they’re always called by value). Remember, after you’ve implemented a client, you cannot change or remove any methods that you’ve put in an AIDL. You can add new methods at the end of the file, but because of the way the AIDL compiler generates the identifier for each method, you cannot change any of the existing methods without breaking backward compatibility. When handling new versions of the API, the recommended way is to create a new AIDL file with the new or changed methods. Doing so allows you to maintain backward compatibility with older clients. As you can see by the name of the preceding AIDL file, you handle versioning of AIDL by appending V1 for the first version of the file. When you add new methods to the API, you create a file by ending with V2, and so on. This method for versioning is one of the drawbacks with using AIDL files. One way to manage this issue is to provide a Java wrapper around the AIDL and you publish this either as a library project or as a JAR file that developers can use. This way, a client won’t have to implement multiple AIDLs but can always download the latest version of your wrapper and be sure that it’s compatible. I show an example of how to create such a wrapper in section “Wrapping APIs with Library Projects,” later in this chapter. Chapter 7: Android IPC 139 When you have an AIDL file ready, you need to implement it on both the service-side and the client-side, as shown here: public class AidlService extends Service { private ArrayList mCustomDataCollection; @Override public void onCreate() { super.onCreate(); mCustomDataCollection = new ArrayList(); // TODO Populate the list with stored values... } public IBinder onBind(Intent intent) { return mBinder; } public static boolean isPrimeImpl(long number) { // Implementation left out for brevity... return false; } private void getDataSinceImpl(CustomData[] result, Date since) { int size = mCustomDataCollection.size(); int pos = 0; for(int i = 0; i < size && pos < result.length; i++) { CustomData storedValue = mCustomDataCollection.get(i); if(since.after(storedValue.getCreated())) { result[pos++] = storedValue; } } } private void storeDataImpl(CustomData data) { int size = mCustomDataCollection.size(); for (int i = 0; i < size; i++) { CustomData customData = mCustomDataCollection.get(i); if(customData.equals(data)) { mCustomDataCollection.set(i, data); return; } } mCustomDataCollection.add(data); } private final ApiInterfaceV1.Stub mBinder = new ApiInterfaceV1.Stub() { @Override public boolean isPrime(long value) throws RemoteException { return isPrimeImpl(value); } 140 Part II: Getting the Most Out of Components @Override public void getAllDataSince(long timestamp, CustomData[] result) throws RemoteException { getDataSinceImpl(result, new Date(timestamp)); } @Override public void storeData(CustomData data) throws RemoteException { storeDataImpl(data); } }; } The preceding example shows the implementation of the AIDL stub in the end on the Service. This object is also what is returned to clients that bind to the Service in the onBind() method. Note that each call to the API in the Service will be running on its own thread because the Binder provides a pool of threads on which it executes calls from clients. This means that a client cannot block the main thread of the Service it is calling when you’re using this method. The following Activity shows how to bind to a remote Service and retrieve the interface for ApiInterfaceV1. This is the preferred solution if you’re the sole user of the remote API and can manage the versioning on both sides (or on the same development team). public class MyApiClient extends Activity implements ServiceConnection { private ApiInterfaceV1 mService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onResume() { super.onResume(); bindService(new Intent(“com.aptl.sampleapi.AIDL_SERVICE”), this, BIND_AUTO_CREATE); } public void onCheckForPrime(View view) { EditText numberToCheck = (EditText) findViewById(R.id.number_ input); long number = Long.valueOf(numberToCheck.getText().toString()); boolean isPrime = mService.isPrime(number); String message = isPrime ? getString(R.string.number_is_prime, number) : getString(R.string.number_not_prime, number); Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } Chapter 7: Android IPC 141 @Override protected void onPause() { super.onPause(); unbindService(this); } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mService = ApiInterfaceV1.Stub.asInterface(iBinder); } @Override public void onServiceDisconnected(ComponentName componentName) { mService = null; } } Callbacks with AIDL Clients can also implement an AIDL that can be used as a callback interface by the Service, which is useful if you want to register clients to receive callbacks when something happens on the Service—for instance, when data is updated from an online server that the Service is communicating with. In the following example, you can see the new AIDL file for the callback interface, note the keyword oneway that tells the AIDL compiler that this interface is only a one-way communication. No response back to the caller, in this case the Service, is needed. This will give you a slight performance boost. package com.aptl.sampleapi; import com.aptl.sampleapi.CustomData; oneway interface AidlCallback { void onDataUpdated(in CustomData[] data); } Next, you create an instance of this interface in your client, shown as follows. In this case, you simply show a Toast when you receive a callback from the Service: private AidlCallback.Stub mAidlCallback = new AidlCallback.Stub() { @Override public void onDataUpdated(CustomData[] data) throws RemoteException { Toast.makeText(MyApiClient.this, “Data was updated!”, Toast.LENGTH_SHORT).show(); } }; In the AIDL for the Service shown earlier, you add one more line for registering the callback: void addCallback(in AidlCallback callback); 142 Part II: Getting the Most Out of Components Finally, you implement the addCallback() method on the Service. Here, you also use the linkToDeath() method to receive a notification in case the client Binder died. @Override public void addCallback(final AidlCallback callback) throws RemoteException { mCallbacks.add(callback); callback.asBinder().linkToDeath(new DeathRecipient() { @Override public void binderDied() { mCallbacks.remove(callback); } }, 0); } Normally, you should have both an addCallback() and a removeCallback() method, but I’m leaving that as an exercise for you to explore. The previous example shows how to create callback interfaces between applications. It also shows how you can transfer a Binder object between two applications without having to register it through the ServiceManager. Because only the client and the Service know the address for this Binder, it can effectively be used as a security mechanism when doing IPC. Messenger Another way of providing a remote interface is through the Messenger class. This class is useful when you have a Service where you don’t need to support concurrent operations to clients. The Messenger class uses a Handler to execute each incoming message, so all client calls will run on the same thread in serial order. You also get rid of the problems with AIDL files and can more easily provide an asynchronous message-based API for clients. Although not as powerful, this class can be more efficient at times because you’ll get much easier implementation, both for clients and Services. The following example shows how to use the Messenger class to provide an asynchronous API. The onBind() method returns the Binder object from the Messenger created in onCreate(). When the Messenger receives a message, it can reply to the client using a Messenger object stored in the replyTo field. public class MessengerService extends Service { private Handler mMessageHandler; private Messenger mMessenger; @Override public void onCreate() { super.onCreate(); HandlerThread handlerThread = new HandlerThread(“MessengerService”); Chapter 7: Android IPC 143 handlerThread.start(); mMessageHandler = new Handler(handlerThread.getLooper(), new MyHandlerCallback()); mMessenger = new Messenger(mMessageHandler); } public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } @Override public void onDestroy() { super.onDestroy(); mMessageHandler.getLooper().quit(); } private class MyHandlerCallback implements Handler.Callback { @Override public boolean handleMessage(Message message) { boolean delivered = false; switch (message.what) { case MessageAPI.SEND_TEXT_MSG: delivered = sendTextMessage((String) message.obj); break; case MessageAPI.SEND_PHOTO_MSG: delivered = sendPhotoMessage((Bitmap) message.obj); break; } Message reply = Message.obtain(null, MessageAPI.MESSAGE_DELIVERED_MSG, delivered); try { message.replyTo.send(reply); } catch (RemoteException e) { Log.e(“MessengerService”, “Error sending message reply!”, e); } return true; } } // Return true when delivered private boolean sendPhotoMessage(Bitmap photo) { // Implementation left out for brevity return true; } // Return true when delivered private boolean sendTextMessage(String textMessage) { // Implementation left out for brevity return true; } } 144 Part II: Getting the Most Out of Components The following example shows a client that first binds to the Service and then constructs a new Messenger object with the IBinder as a parameter. This now acts as a proxy for the Messenger running in the remote Service. When you send a message to the Service, you can also set the replyTo field of the Message object. public class MyMessengerClient extends Activity implements ServiceConnection { private ApiInterfaceV1 mService; private Messenger mRemoteMessenger; private Messenger mReplyMessenger; private Handler mReplyHandler; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); HandlerThread handlerThread = new HandlerThread(“ReplyMessenger”); handlerThread.start(); mReplyHandler = new Handler(handlerThread.getLooper(), new ReplyHandlerCallback()) mReplyMessenger = new Messenger(mReplyHandler); } @Override protected void onResume() { super.onResume(); bindService(new Intent(“com.aptl.sampleapi.MESSENGER_SERVICE”), this, BIND_AUTO_CREATE); } public void onSendTextPressed(View view) { String textMessage = ((EditText) findViewById(R.id.message_input)) .getText().toString(); Message message = Message.obtain(); message.what = MessageAPI.SEND_TEXT_MSG; message.obj = textMessage; message.replyTo = mReplyMessenger; try { mRemoteMessenger.send(message); } catch (RemoteException e) { // Remote service is dead... } } @Override protected void onPause() { super.onPause(); unbindService(this); } @Override protected void onDestroy() { super.onDestroy(); Chapter 7: Android IPC 145 mReplyHandler.getLooper().quit(); } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mRemoteMessenger = new Messenger(iBinder); } @Override public void onServiceDisconnected(ComponentName componentName) { mRemoteMessenger = null; } private class ReplyHandlerCallback implements Handler.Callback { @Override public boolean handleMessage(Message message) { switch (message.what) { case MessageAPI.MESSAGE_DELIVERED_MSG: // TODO Handle async reply from service break; } return true; } } } This method is very similar to using the IntentService as I describe in Chapter 6, but instead of working with Intent objects, here you’re utilizing the Message class used for triggering operations on a Handler, as I describe in Chapter 2. Also, using a Messenger provides a convenient way of implementing asynchronous communication without having to use BroadcastReceivers. Wrapping APIs with Library Projects Regardless of whether you use AIDL or the Messenger class to implement your remote API, it’s a good idea to extract all the API-specific classes and interfaces to a library project and create a pure Java wrapper for clients to use. Because you probably want to support your complex objects in your API, providing only an AIDL file for your API is usually not enough. You also need to provide these custom classes to clients. As I describe in Chapter 1, when it comes to distribution and versioning, setting up an Android library project for your API is a simple and efficient way of handling all the problems related to remote APIs. You can also package the compiled wrapper code into a JAR file that is easily distributed as a third-party library. I recommend using an Android library project, uploading it to an online version control service like GitHub, and letting other developers simply use that code to integrate with your application. The easiest way to set up a library project for your remote API is to move all AIDL files and Parcelable classes to a library project that you reference in the application that implements your remote API. However, if you have several AIDLs (new versions, client callbacks, and so on), it can easily become quite complicated, so it’s also a good practice to wrap everything in a more easy-to-use Java class, as shown here: 146 Part II: Getting the Most Out of Components public class ApiWrapper { private Context mContext; private ApiCallback mCallback; private MyServiceConnectionV1 mServiceConnection = new MyServiceConnectionV1(); private ApiInterfaceV1 mServiceV1; public void release() { mContext.unbindService(mServiceConnection); } public ApiWrapper(Context context, ApiCallback callback) { mContext = context; mCallback = callback; mContext.bindService(new Intent(“com.aptl.sampleapi.AIDL_ SERVICE”), mServiceConnection, Context.BIND_AUTO_CREATE); } public void getAllDataSince(long timestamp, CustomData[] result) { if (mServiceV1 != null) { try { mServiceV1.getAllDataSince(timestamp, result); } catch (RemoteException e) { // TODO Handle service error } } } void storeData(CustomData data) { if (mServiceV1 != null) { try { mServiceV1.storeData(data); } catch (RemoteException e) { // Handle service error } } } private class MyServiceConnectionV1 implements ServiceConnection { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mServiceV1 = ApiInterfaceV1.Stub.asInterface(iBinder); try { mServiceV1.setCallback(mAidlCallback); } catch (RemoteException e) { // Handle service error... } mCallback.onApiReady(ApiWrapper.this); } Chapter 7: Android IPC 147 @Override public void onServiceDisconnected(ComponentName componentName) { mServiceV1 = null; if(mCallback != null) { mCallback.onApiLost(); } } } private AidlCallback.Stub mAidlCallback = new AidlCallback.Stub() { @Override public void onDataUpdated(CustomData[] data) throws RemoteException { if(mCallback != null) { mCallback.onDataUpdated(data); } } }; public interface ApiCallback { void onApiReady(ApiWrapper apiWrapper); void onApiLost(); void onDataUpdated(CustomData[] data); } } The preceding code shows how to create a wrapper for the AIDL examples shown earlier in this chapter. This method creates a much easier interface for your Service to the client. You can even manage AIDL callbacks by wrapping them in ordinary Java interfaces as shown with the preceding ApiCallback. This method lets you use the standard Java approach for version-controlling your API. You can add the @ deprecated tag to methods, and you can add new methods to your wrapper that handle the versioning of the API behind the scenes. Clients will not have to worry about these details, and you can easily maintain backward compatibility. You can implement different versions of your API on the Service by returning different IBinder objects depending on the contents of the Intent used in Context.bindService(), as shown here: public IBinder onBind(Intent intent) { int apiVersionRequested = intent.getIntExtra(EXTRA_VERSION_TAG, 1); switch (apiVersionRequested) { case 1: return mBinderV1; case 2: return mBinderV2; case 3: return mBinderV3; default: return null; } } 148 Part II: Getting the Most Out of Components The preceding example shows how you can retrieve an int from the Intent to decide which version of the API to return. This method allows you to create new AIDL files for updates to your API. Your wrapper will now bind to each version and keep one local reference for every binding. Securing Remote APIs Security should always be a priority when you’re designing Android applications, regardless of what you’re doing. When providing APIs between applications security becomes even more important. (I go into security for Android applications in more detail in Chapter 12.) Luckily, securing your published Services, and other components, is quite easy, as shown here: This XML is an example of how the AndroidManifest.xml file might appear for a Service that you publish. The important areas are shown in bold. First, you need to set the attribute android:exported to true. The default value for this attribute depends on how you define the intent-filter for the Service. If you don’t include an intent-filter, the Service is only for internal use (addressed through its component name), and it won’t be exported. If you define an intent-filter, the Service is exported by default. I highly recommend that you always define this attribute and set the value according to your needs, whether or not it’s an exported Service. If you’re exporting a Service, the most important part is to set up permissions. I go into detail about defining permissions in Chapter 12, but the previous example shows the simplest form. You define the permission above the application tag and give it a protectionLevel. Next, you set the android:permission attribute for the Service to declare that clients for this Service must declare this permission in their manifest. Chapter 7: Android IPC 149 It’s usually enough to declare permissions as shown in the previous code block, but sometimes you need to go beyond Android’s permission management. In Chapter 12, I discuss more advanced methods for securing your application that also apply to APIs that you’ll publish. Summary In this chapter, you discovered how to use the Service component in Android to provide a remote API for other applications to use. You are now familiar with how the Binder IPC works in Android and the choices you have when it comes to implementing a remote API. As I discussed, AIDL is a powerful but complicated method that requires more consideration when designing. It allows you to do normal synchronous Java method calls across applications in different processes, but you need to carefully consider how to design your API and think about versioning. Also, using the Messenger class is an easy way to create an asynchronous remote API, but it’s also limited because all client calls will be running on a single thread, as opposed to the AIDL approach where you have one thread for every client. That said, the message-based approach will usually perform better than AIDL, so many times this approach is preferable. In addition, I recommended that you provide an Android library project that wraps your remote API in a more easy-to-use set of Java classes, especially if you will use the AIDL approach. Doing so also makes it easier for you to handle new versions of the API while maintaining backward-compatibility with older clients. Finally, pay extra attention to securing your remote API. Declare permissions properly and make sure that only the components that should be published have the android:exported flag set to true in the manifest. Further Resources Websites “Android Interprocess Communication” by Thorsten Schreiber at www.nds.rub.de/media/ attachments/files/2012/03/binder.pdf “Android IPC Mechanism” by Jim Huang at http://0xlab.org/~jserv/android-binder-ipc. pdf “Deep Dive into Android IPC/Binder Framework” by Aleksandar Gargenta at http://marakana. com/s/post/1340/Deep_Dive_Into_Binder_Presentation.htm Android Developers Blog. “Service API Changes Starting with Android 2.0” by Dianne Hackborn at http://android-developers.blogspot.se/2010/02/service-api-changes- starting-with.html A summary of using Binder by Dianne Hackborn at https://lkml.org/lkml/2009/6/25/3 Chapter 8 Mastering BroadcastReceivers and Configuration Changes An Android-based smartphone is a very powerful device with lots of different hardware components. Many of these components affect the state of the smartphone in different ways. The accelerometer detects the current physical orientation of the device (portrait or landscape), the Wi-Fi discovers new available networks and notifies the system when its connection to a network changes, the light-sensor controls the brightness of the screen, and the hardware buttons on the device trigger interrupts that generate some event in the system. At the same time, the Android system always tries to consume as little power as possible in order to make the battery last as long as possible. However, because Android applications have a relatively high degree of freedom as to how much they can control the device, it’s very important for you, as a developer, to react to the different events and changes to the device; otherwise, they could cause unnecessary power drain. By mastering the different system events and device changes, you can make your application more robust and work more smoothly with the overall system. A badly written application that eats up a user’s batteries will receive bad reviews, and ultimately users will seek better alternatives. Also, most of these events are not directly related to battery consumption or performance. However, you need to be aware of them in order to anticipate changes that will affect how your device is working. For instance, if the device loses its Wi-Fi connection and switches to the much slower EDGE connection, you may want to reduce the number of network calls or pick a smaller version of pictures you’re downloading. By watching for events indicating if the screen changes from on to off (or vice versa), you can make additional assumptions about a user’s activity and take appropriate actions based on that (for instance, screen off would probably mean that the user isn’t actively looking at the device). As an application developer, you can also send broadcast, either defined by the application as a new Intent action or by using some action defined by the Android APIs or third-party application. Doing so can be very useful when you want to dispatch events in the background, either within your own application or between two different applications. Although an application can listen to numerous events, I cover only a few of them in this chapter because there are so many defined by the Android platform. For more information about broadcast events, go to the official Android documentation site: http://d.android.com, and check out the relevant APIs. For instance, events related to telephony features are usually found in the android. telephony package. 152 Part II: Getting the Most Out of Components There are also different ways to listen for these events. Some are sent as broadcast Intents and received by BroadcastReceivers; others require you to implement some Java callback. In this chapter, I provide examples of these methods and how you can efficiently implement them. BroadcastReceivers The most common way that events are broadcast on Android is through Intent objects sent to BroadcastReceivers using the Context.sendBroadcast() methods. Many of the standard system events are defined as action strings and can be found in the API documentation for the Intent class. For instance, if your application needs to be notified whenever the user connects or disconnects the charger to the smartphone, two broadcast actions defined in the Intent class do that: ACTION_POWER_DISCONNECTED and ACTION_POWER_CONNECTED. The following code shows a simple BroadcastReceiver that receives an Intent object whenever the user connects or disconnects the power. In this method, the only thing you do is call Context.startService() to delegate the event to a service that performs the actual work. public class ChargerConnectedListener extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_POWER_CONNECTED.equals(action)) { context.startService( new Intent(MyService.ACTION_POWER_CONNECTED)); } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { context.startService( new Intent(MyService.ACTION_POWER_DISCONNECTED)); } } } The default method for implementing BroadcastReceivers is to declare them in the manifest. Consequently, it’s possible for the BroadcastReceiver to notify your service even though the user hasn’t started your application. This factor is especially useful for applications that should start on certain system events without user interaction. You could use the following approach to be notified whenever connectivity to Wi-Fi changes. For example, when the user comes home and connects her device to the home Wi-Fi, you could design your application so that it starts to sync data with other devices she has connected to the same network, without her having to manually trigger this action. Chapter 8: Mastering BroadcastReceivers and Configuration Changes 153 BroadcastReceivers can also be registered programmatically within Activities and Services. Some broadcast Intents can only be registered programmatically, and some only work if you declare them in your manifest. Check the official Android API documentation for details on each action. When you register a BroadcastReceiver programmatically, as shown in the following example, you must also unregister it in the matching callback. In the this example, the receiver is registered for the two actions in onResume() therefore, you unregister it in onPause(). public class MyActivity extends Activity { private ChargerConnectedListener mPowerConnectionReceiver; @Override protected void onResume() { super.onResume(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_POWER_CONNECTED); intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED); mPowerConnectionReceiver = new ChargerConnecedListener(); registerReceiver(mPowerConnectionReceiver, intentFilter); } @Override protected void onPause() { super.onPause(); unregisterReceiver(mPowerConnectionReceiver); } } You’ll usually want to programmatically register BroadcastReceivers when you’re only interested in the events while your application is running and active. This way, your application will consume fewer resources than if you declare them in your manifest and they start every time the event occurs. When your application needs to receive system events, always consider the difference between a BroadcastReceiver registered in the manifest or through Context.registerReceiver() so that you avoid using any more resources than necessary. Local BroadcastReceivers If you want to send and receive broadcast only within your own application’s process, consider using the LocalBroadcastManager instead of the more generic Context.sendBroadcast() method. This approach is more efficient because no cross-process management is included and you don’t have to consider the security issues normally involved with broadcasts. This class is not part of the standard APIs but is contained in the support APIs. The following code exemplifies how to send a local broadcast using the LocalBroadcastManager. public void sendLocalBroadcast(Intent broadcastIntent) { LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); localBroadcastManager.sendBroadcast(broadcastIntent); } 154 Part II: Getting the Most Out of Components To receive a local broadcast, you use the LocalBroadcastManager shown in the preceding code. The following example shows a simple Activity where you register and unregister for local broadcasts for a certain action. public class LocalBroadcastDemo extends Activity { public static final String LOCAL_BRODCAST_ACTION = “localBroadcast”; private BroadcastReceiver mLocalReceiver; @Override protected void onResume() { super.onResume(); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); IntentFilter intentFilter = new IntentFilter(LOCAL_BRODCAST_ ACTION); mLocalReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO Handle local broadcast... } }; localBroadcastManager.registerReceiver(mLocalReceiver, intentFilter); } @Override protected void onPause() { super.onPause(); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); localBroadcastManager.unregisterReceiver(mLocalReceiver); } } Local broadcast is a convenient way of broadcasting messages and states within your application. Local broadcast is more efficient than standard global broadcasts and is more secure because you can be certain that no data sent this way will leak outside your application. Remember to always unregister these local receivers in the same way you do normal receivers, or you may leak memory. Normal and Ordered Broadcasts Broadcasts are divided into two categories, normal and ordered. The normal broadcasts are sent to all receivers asynchronously and are received in an unspecified order, as shown in Figure 8-1. This method is more efficient but lacks some of the advanced features found for ordered broadcasts. No feedback can be sent to the broadcaster with normal broadcasts. An ordered broadcast is delivered to the registered receivers one at a time in a specified order (see Figure 8-2). You can control the order in which the broadcasts are received by setting the android:priority attribute for the relevant intent-filter tag in the manifest. Another feature of ordered broadcasts is that by using abortBroadcast(), setResultCode() and setResultData(), a receiver can set a result that is delivered back to the broadcaster or abort the broadcast so that the Intent won’t be propagated to the next receiver in the queue. Chapter 8: Mastering BroadcastReceivers and Configuration Changes 155 Broadcaster Receiver A Receiver B Receiver C Receiver D Figure 8-1 Diagram illustrating the asynchronous normal broadcasts to multiple receivers Broadcaster Receiver A Receiver B Receiver C Receiver D Figure 8-2 Diagram illustrating the sequential propagation of ordered broadcasts The following code shows the implementation of a receiver that expects an ordered broadcast. You start by checking that the broadcast is in fact ordered and then assign the result code, result data and any extras you want to deliver to the broadcaster. After the onReceive() method returns, the response will be sent back to the broadcaster automatically. public class OrderedReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if(isOrderedBroadcast() { setResultCode(Activity.RESULT_OK); setResultData(“simple response string”); // Get current response extras, or create new if null. Bundle resultExtras = getResultExtras(true); // Set our component name for the extras response... resultExtras.putParcelable(“componentName”, new ComponentName(context,getClass())); } } } In the following code, you see how to send an ordered broadcast and how responses are handled. You can register for responses by passing a BroadcastReceiver object to the method Context. sendBroadcast(). This receiver will then get a call to onReceive() for every receiver of the original ordered broadcast. public void sendOrderedBroadcastAndGetResponse() { Intent intent = new Intent(ACTION_ORDERED_MESSAGE); // The broadcast receiver that will handle responses BroadcastReceiver responseReceiver = new BroadcastReceiver() { 156 Part II: Getting the Most Out of Components @Override public void onReceive(Context context, Intent intent) { String resultData = getResultData(); Bundle resultExtras = getResultExtras(false); if (resultExtras != null) { ComponentName registeredComponent = resultExtras. getParcelable(“componentName”); } // TODO Handle response } }; sendOrderedBroadcast(intent, responseReceiver, null, RESULT_OK,null,null); } You rarely need to send an ordered broadcast in your own applications, but they can be helpful if you’ll be communicating with other applications (for instance, plug-ins can have a great use for this). In the Android system, the most common example of ordered broadcasts is when an application listens for incoming SMS messages, which is part of the hidden APIs. I describe this in further detail in Chapter 15. Sticky Broadcasts A variation of the normal broadcast is a sticky broadcast, which works a little bit differently. The difference is that the Intent sent with Context.sendStickyBroadcast() will “stay around” after the broadcast is complete, allowing future registrations for the matching Intent to receive the same broadcast as well. One example of such a broadcast is Intent.ACTION_BATTERY_CHANGED, which is used to indicate changes in the battery level for the device. Another example is Intent.ACTION_DOCK_EVENT, which indicates whether a device is placed in a dock. Check the Android API documentation for other examples of sticky broadcasts. The following code shows an example of a receiver that listens for battery changes. It will also notice whether the sticky broadcast is new or not. public class BatteryChangeListener extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(Intent.ACTION_BATTERY_CHANGED.equals(action)) { if(isInitialStickyBroadcast()) { // This is an old event from the “sticky” cache } else { // This is a new event that just occurred... } } } } Chapter 8: Mastering BroadcastReceivers and Configuration Changes 157 This method is especially useful for signaling system-wide states (such as battery status). In order for an application to send this type of broadcast, it must hold the permission android.permission. BROADCAST_STICKY and send sticky broadcasts using Context.sendStickyBroadcast(). Use sticky broadcasts with care from your own application, because it will put an additional load on the available system resources. Directed Broadcasts Another variation of the normal broadcasts is directed broadcasts. These broadcasts use a feature of the intent-filter with which you can explicitly specify the receiver by setting the ComponentName in the broadcasted Intent. This is the combination of the class- and package-name of the registered BroadcastReceiver, as shown in the following code : public void sendDirectedBroadcast(String packageName, String className, String action) { Intent intent = new Intent(action); intent.setComponent(new ComponentName(packageName, className)); sendBroadcast(directedBroadcastIntent); } The result of the previous code is that you send a normal broadcast that will be received only by the specified receiver, even if other receivers are registered for the same Intent action. Note: You must know both the package-name and the class-name of the receiver for this to work. The directed broadcast approach can be very useful for applications that provide plug-in functionality. When a plug-in is registered (installed), it can signal to the main application the relevant information for a directed broadcast. Enabling and Disabling Receivers When the broadcast you want to listen for is available only for receivers declared in the manifest, you can reduce the impact on the system’s load another way. With the PackageManager, you can enable and disable components in your application, which is helpful if you have a receiver that you want to be inactive unless the user performs a specific action (for instance, changes a setting). The two methods that follow illustrate how you can programmatically enable and disable a specific component based on its ComponentName. You just set android:enabled=”false” in the manifest as the default value for that component and later use the following code to change the value to true. public void enableBroadcastReceiver() { PackageManager packageManager = getPackageManager(); packageManager.setComponentEnabledSetting( new ComponentName(this, ChargerConnecedListener.class), 158 Part II: Getting the Most Out of Components PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } public void disableBroadcastReceiver() { PackageManager packageManager = getPackageManager(); packageManager.setComponentEnabledSetting( new ComponentName(this, ChargerConnecedListener.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } Notice the use of PackageManager.DONT_KILL_APP as the last parameter to setComponentEnabledSetting(). This will prevent the platform from killing the application, which is otherwise the default behavior when changing this property. You can use this method for enabling and disabling components for Activities as well (and also Services and ContentProviders). It’s an efficient way to toggle the visibility of your application’s icons in the launcher (also called, home-screen application tray). You could, for instance, show only the icon for the setup Activity after installation and later hide it using this method after the setup is complete. You can also use this method to show the icon for an Activity that shouldn’t be visible until the user completes the setup. System Broadcast Intents The Android API defines many different broadcast actions that relate to different system events. In earlier code examples in this chapter, I’ve shown a few of these, like changes in battery level or when the power to the devices was connect and disconnected. Because these events are spread out in different places relating to their respective function, finding a broadcast for a specific event can be difficult, even with the Android developer site’s search function. Maybe you don’t even know that a certain event exists, or perhaps you get too many irrelevant matches. Also, some very useful broadcast Intent actions aren’t publically specified in the API, and finding them requires some understanding of the hidden Android APIs. (I go into the details of the hidden Android APIs in Chapter 15.) In this section, you will find some of the most commonly used system events, along with some examples of when to use them. Several others are available, and the only way to find them is to know what you’re looking for and search the official Android APIs. A good place to start when looking for their definitions is in the Intent class. Auto-Starting Your Application One of the frequent questions Android application developers ask me is how they can make their applications start automatically. The short answer is that you can’t, not directly. However, you can register for certain events that eventually will be triggered and which you can use to start your application. There is also an event that is Chapter 8: Mastering BroadcastReceivers and Configuration Changes 159 sent to an application after it’s upgraded from an earlier version (usually after an update is downloaded and installed from Google Play). The following code shows the declaration of a receiver in the manifest that listens to the Intent.ACTION_ BOOT_COMPLETED and Intent.ACTION_MY_PACKAGE_REPLACED broadcasts. Note: The receiver is disabled by default. This is a good practice, especially when listening for Intent.ACTION_BOOT_ COMPLETED; otherwise your application will start every time the device is booting up, possibly waste your system’s resources. Enable receivers for these broadcasts only when doing so is necessary, such as after the user changes a setting in your application or enables certain features—for example, an alarm clock application in which the receiver is left disabled until the user schedules an alarm. Use the code from the earlier example for enabling (and disabling) this receiver as needed. User Presence and Screen State When a user locks a device (that is, presses the power button to turn off the device), the current Activity receives a call to onPause(), signifying that it has lost focus. Similarly, the Activity receives a call to onResume() when it regains focus after the lock screen is disabled. Normally, applications don’t need additional information, but what if you have a Service that needs to be notified every time the user unlocks the device or when the screen goes on or off? Luckily, there are broadcasts for these events as well, as shown in the following code block. Intent.ACTION_SCREEN_ON and Intent.ACTION_SCREEN_OFF are sent as the device screen goes on and off, respectively. Intent.ACTION_USER_PRESENT is sent when the user deactivates the lock screen. A simple diagram showing how these actions are broadcast is shown in Figure 8-3. 160 Part II: Getting the Most Out of Components Screen on and device unlocked Send Intent.ACTION_USER_PRESENT Send Intent.ACTION_SCREEN_OFF Send Intent.ACTION_SCREEN_ON Power button pressed Power button pressed User unlocks device Screen o and device locked Screen on and device locked Figure 8-3 A simple diagram illustrating when broadcasts are sent for the events when the screen goes on and off and when the user disables the lock screen Network and Connectivity Changes For many Android applications, one of the most important things to keep track of is the state of the network and what kind of connectivity the device currently has. It’s good practice to limit your application’s use of the network according to the available bandwidth. Most Android devices have two types of networks, cellular and Wi-Fi. If your application operates heavily on a network, you may want to defer transfers until the device is connected to a Wi-Fi; otherwise, you may incur considerable cost if you transfer data for users of mobile networks such as 3G or LTE. These events can also be useful for detecting when a user connects to a well-known Wi-Fi, like a corporate intranet, where it’s safe to transfer sensitive data. Connectivity and network broadcast actions are handled by different parts of the Android API. The action ConnectivityManager.CONNECTIVITY_ACTION is broadcast whenever a general change in network connectivity occurs, such as switching from Wi-Fi to mobile data. When this is received, the ConnectivityManager service can be retrieved using Context.getService() which lets you gain more detailed information about the current network. However, to get more fine-grained information about the current network, you also need to listen for broadcast actions from TelephonyManager (for cellular mobile data network events) and WifiManager (for Wi-Fi– related events). TelephonyManager lets you query the type of mobile data connection, and WifiManager gives you access to retrieval of the state of the Wi-Fi connection and the different IDs related to a Wi-Fi (SSID and BSSID). The following code is a simplified example of how to detect when a device enters a preconfigured “home” Wi-Fi, which is effective for applications configured to talk to servers or for media centers that are available only on a specific Wi-Fi. public class CheckForHomeWifi extends BroadcastReceiver { public static final String PREFS_HOME_WIFI_SSID = “homeSSID”; public void onReceive(Context context, Intent intent) { SharedPreferences preferences = Chapter 8: Mastering BroadcastReceivers and Configuration Changes 161 PreferenceManager.getDefaultSharedPreferences(context); String homeWifi = preferences.getString(PREFS_HOME_WIFI_SSID, null); if(homeWifi != null) { // Only check if home WiFi is set NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); if(networkInfo != null && networkInfo.getState().equals(NetworkInfo.State.CONNECTED)) { WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); if(wifiInfo != null && homeWifi.equals(wifiInfo.getSSID())) { // Success - We’re on out home WiFi! } else { // Fail - We’re on some other WiFi! } } } } } In this following example, you listen for changes from the ConnectivityManager and determine whether you’re on a mobile data network. If you receive mobile data, you make an additional check using the TelephonyManager to see if you’re on a 3G or LTE network. public class WhenOn3GorLTE extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION) { boolean noConnectivity = intent. getBooleanExtra(ConnectivityManager. EXTRA_NO_CONNECTIVITY, false); if(noConnectivity) { // No network at all.. :( } else { int networkType = intent. getIntExtra(ConnectivityManager. EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY); if(networkType == ConnectivityManager.TYPE_MOBILE) { checkfor3GorLte(context); } } } } 162 Part II: Getting the Most Out of Components private void checkfor3GorLte(Context context) { TelephonyManager telephonyManager = (TelephonyManager) context. getSystemService(Context.TELEPHONY_SERVICE); switch (telephonyManager.getNetworkType()) { case TelephonyManager.NETWORK_TYPE_HSDPA: case TelephonyManager.NETWORK_TYPE_HSPA: case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_HSUPA: case TelephonyManager.NETWORK_TYPE_LTE: // Yay - we got fast enough mobile data! :) break; default: // Slow mobile network - notify user... break; } } } Device Configuration Changes Whenever you turn an Android device from portrait to landscape orientation, an event is triggered by the system causing a configuration change. Several related triggers all cause this event, such as changing the UI mode on the device or when keyboard visibility changes. Configuration changes are a little tricky to manage on Android. For an Activity, the default behavior is to restart, which means that onPause(), onStop(), and onDestroy() are called and your Activity instance is lost, including all its data. To avoid having the Activity restart when a certain configuration change occurs, you can declare these events in the android:configChanges attribute of the Activity tag. As a result, the method Activity.onConfigurationChanged() is called with the new Configuration object, instead of your Activity restarting. Use this method only as a last resort— for instance, if you have a full-screen game or application with custom handling of changes in the device’s orientation. The standard way of handling configuration changes for an Activity is through the default behavior: Let the system restart it. This works well for most situations, but in some cases, you may want to avoid restarting your Activity because restarting it could have a negative impact on performance and user experience. For the Service, ContentProvider, and Application components, you can also use the onConfigurationChanged() method. The difference is the Activity class is called for all configuration changes without you having to add attributes in the manifest. Also, for these components, configuration changes will not force them to restart (which is good because otherwise Services would restart when you rotate your device). This allows your Service, or some other background component, to detect when the user rotates the device or when another application changes the UI mode. Chapter 8: Mastering BroadcastReceivers and Configuration Changes 163 Summary In this chapter, I introduced advanced concepts of the BroadcastReceivers that Android developers might not be familiar with. These receivers are powerful tools when it comes to reacting to system-wide events and also for communicating within your application, using the LocalBroadcastManager, or between multiple applications. I explained the difference between normal, ordered, sticky, and directed broadcasts and when and how to apply them. Next, I showed how to enable and disable receivers, and other components, in your code to ensure that they’re activated only when necessary. This powerful feature in Android should be used more often in applications to reduce their load on the overall system. Many different broadcast Intent actions are defined by the Android platform. I discussed a few of the most useful broadcasts and gave a few examples of when and how to use them. Most of them are documented in the official Android APIs, but some are part of the hidden APIs that I cover in more detail in Chapter 15. Finally, I covered how to detect configuration changes of the device, including screen orientation, keyboard state, and UI mode. By default, your Activity is restarted when a configuration change occurs, but you can override this using the android:configChanges attribute in the manifest. You can also listen for these changes by overriding the method onConfigurationChanged() in your Service components, which can help with receiving notifications about state changes in devices that lack a system broadcast. BroadcastReceivers and configuration changes are often overlooked in Android applications, but as a skilled developer, you can use them to create a smooth user experience and to react to changes that could otherwise cause unexpected behavior in an application. Further Resources Documentation For handling runtime configuration changes, go to http://developer.android.com/guide/ topics/resources/runtime-changes.html Chapter 9 Data Storage and Serialization Techniques This chapter is all about storing and representing data on a local device. Storing data is an important aspect of all software applications. Developers usually use the term persistence when talking about storing data, and serialization to describe how data is represented in its stored state. Without persistence, data would be able to keep its state only in RAM and would be lost after the relevant process finishes. Achieving persistence often involves compromises among performance, latency, size, and complexity. For instance, if data must be read fast, you’ll usually go with a slower than usual write operation. Serialization is all about how the data is structured, both in its persisted state as well as in-memory. In this chapter, I cover the two most common techniques used in Android applications for persistence and serialization,—namely, SQLite and SharedPreferences—as well as two alternative methods that also allow you to transport data over a network or between two devices. Storing data on a cloud-storage service such as Google Drive or Dropbox is beyond the scope of this chapter, but you can learn more about storing data on Google Drive in Chapter 19. Persistence Options for Android When you store persistent data on an Android device, the standard APIs provide two readymade methods for storing structured data, preference files and SQLite databases. Preference files are stored in an XML format and managed by the SharedPreferences class. SQLite databases have a more complex API and are usually wrapped in a ContentProvider component. The names of these methods indicate what they should be used for. You use SharedPreferences for settings, options, user preferences, and other simple values. In preference files, you don’t store arrays and tables of values or any binary data. Instead, data represented in Java as lists or arrays is more likely to go into an SQLite database through a ContentProvider. There are, of course, exceptions to these rules, so always consider the best choice for your application. Binary data, which is usually media such as image, video, or audio files, should not be stored directly in an SQLite database or a preference file. It’s usually better to store binary data as regular files, either in an app’s internal storage or on external public storage. However, in many cases, it may be a good idea to use a ContentProvider to handle the persistence of binary files as well; doing so provides convenient ways to deal with files and keep them in sync with the records in your database. 166 Part II: Getting the Most Out of Components Storing Data in Preference Files The files backing your SharedPreferences objects are regular XML files stored in the app’s data directory. The structure is quite simple because it allows only key/value pairs to be stored, but the Android APIs also provide a very convenient abstraction that allows you to read and write data in a type-safe way. The easiest way to create a SharedPreferences object is to use the PreferenceManager. getDefaultSharedPreferences() method, which gives you the default preference object for your application. Using this approach as your main storage for preferences is convenient because the framework manages the name of the file. However, if you have multiple preference files in your application, you’re better off using the Context.getSharedPreference() method, which allows you to freely name the file. If you want to create a preference file that is relevant to only one activity, you can use the Activity. getPreference() method, which gets its name from the Activity calling the method. The name for preference files created by PreferenceManager. getDefaultSharedPreferences() is formed by the package name with the suffix _ preferences—for instance, com.aaptl.code_preferences. Although you rarely need this name, it becomes important when you want to implement a backup agent for this file, as I describe in the section “Application Data Backup” near the end of this chapter. The types of values you can store in a preference file using the SharedPreferences class are int, float, long, boolean, String, and a Set of String objects (string arrays). The name of the key must always be a valid String, and the common practice is to use a dot notation for the keys in order to structure multiple keys into groups. For instance, if your preference file contains values that will be used to configure networking as well as user- interface settings, you can group these values by prefixing each key with the term network or ui. In this way, you can easily manage and avoid conflicting names. In the following example, you see how to structure preferences this way by using a prefix and defining the keys in a separate Java interface: public interface Constants { public static final String NETWORK_PREFIX = “network.”; public static final String UI_PREFIX = “ui.”; public static final String NETWORK_RETRY_COUNT = NETWORK_PREFIX + “retryCount”; public static final String NETWORK_CONNECTION_TIMEOUT = NETWORK_PREFIX + “connectionTimeout”; public static final String NETWORK_WIFI_ONLY = NETWORK_PREFIX + “wifiOnly”; public static final String UI_BACKGROUND_COLOR = UI_PREFIX + “backgroundColor”; public static final String UI_FOREGROUND_COLOR = UI_PREFIX + “foregroundColor”; public static final String UI_SORT_ORDER = UI_PREFIX + “sortOrder”; Chapter 9: Data Storage and Serialization Techniques 167 public static final int SORT_ORDER_NAME = 10; public static final int SORT_ORDER_AGE = 20; public static final int SORT_ORDER_CITY = 30; } The preceding method is the preferred way for accessing the preference values instead of hardcoding the key names for each access. Doing so removes the chance of misspelling the names of the keys, a common source of bugs. The following code is an example of using preferences together with a Constants class: public class MainActivity extends Activity { private void readUiPreferences() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); int defaultBackgroundColor = getResources(). getColor(R.color.default_background); int backgroundColor = preferences.getInt( Constants.UI_BACKGROUND_COLOR, defaultBackgroundColor); View view = findViewById(R.id.background_view); view.setBackgroundColor(backgroundColor); } } To change the values in a preference, you first retrieve an Editor instance, which provides the appropriate PUT method as well as methods for committing your changes. Prior to Android version 2.3 (API level 9), you committed changes using the commit() method, which did the writing to disk synchronously. However, in version 2.3, the Editor class provides an apply() method that performs the actual disk write operation asynchronously. Because you always want to avoid blocking operations in your main thread as much as possible, the apply() method is preferable to the old commit() method. This makes it safe to update SharedPreference on the main thread directly from a UI operation (for instance, in an onClick() method, as shown in the following example). public class MainActivity extends Activity { public void doToggleWifiOnlyPreference(View view) { SharedPreferences preferences = PreferenceManager. getDefaultSharedPreferences(this); boolean currentValue = preferences. getBoolean(Constants.NETWORK_WIFI_ONLY, false); preferences.edit() .putBoolean(Constants.NETWORK_WIFI_ONLY, !currentValue) .apply(); } } The preceding code shows where you use a click listener to toggle the preference value stored in Constants. NETWORK_WIFI_ONLY. If you were to use the old commit() method, the main thread could have been blocked, causing a degraded user experience. With apply(), you no longer have to worry about that issue. 168 Part II: Getting the Most Out of Components Every preference file has a single instance within the same process. So even though you retrieve a SharedPreference object from two different components (but in the same application) with the two objects having the same name, they share the same backing instance, so every change will immediately be reflected in both objects. In order to get a notification when a value is updated, you can register a callback listener that is triggered whenever apply() or commit() is called. The most common use for this is when you change a value in a preference from an Activity that should affect the behavior of a background service, as shown in this example: public class NetworkService extends IntentService implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String TAG = “NetworkService”; private boolean mWifiOnly; @Override public void onCreate() { super.onCreate(); SharedPreferences preferences = PreferenceManager .getDefaultSharedPreferences(this); preferences.registerOnSharedPreferenceChangeListener(this); mWifiOnly = preferences.getBoolean(Constants.NETWORK_WIFI_ONLY, false); } @Override public void onDestroy() { super.onDestroy(); SharedPreferences preferences = PreferenceManager .getDefaultSharedPreferences(this); preferences.unregisterOnSharedPreferenceChangeListener(this); } @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { if (Constants.NETWORK_WIFI_ONLY.equals(key)) { mWifiOnly = preferences .getBoolean(Constants.NETWORK_WIFI_ONLY, false); if(mWifiOnly) cancelNetworkOperationIfNecessary(); } } @Override protected void onHandleIntent(Intent intent) { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); Chapter 9: Data Storage and Serialization Techniques 169 int type = networkInfo.getType(); if (mWifiOnly && type != ConnectivityManager.TYPE_WIFI) { Log.d(TAG, “We should only perform network I/O over WiFi.”); return; } performNetworkOperation(intent); } } User Options and Settings UI In many applications, it’s common to provide a separate UI that allows users to change the options and settings of the application. Android provides a set of readymade Activity and Fragment classes that make creating such a UI easy—PreferenceActivity and PreferenceFragment. Start by creating an XML file, placed in the XML resources directory, which follows the PreferenceScreen syntax. This is a simple XML structure that specifies all the preferences you’ll allow users to change and also how they interact with the preferences. You can provide simple text fields for entering text strings, check boxes, and lists of choices. For each option, you can specify a title and description, and you can group a number of preferences together in different categories. You don’t need to deal with saving changed values because that’s managed by the PreferenceFragment. The instance of the SharedPreferences that a PreferenceFragment is working against is the same as you get from PreferenceManaget. getDefaultSharedPreferences(). The following code shows a PreferenceScreen XML for two user-configurable options relating to the previous examples. 170 Part II: Getting the Most Out of Components Next, you implement a PreferenceActivity, add a PreferenceFragment as its UI, and then call PreferenceFragment.addPreferencesFromResource() to specify the XML used for displaying the settings UI. The Android framework will generate a UI that follows the style and theme of your application (see Figure 9-1). Figure 9-1 The result of a simple PreferenceScreen In the following code, you specify which XML file to use from the resources. You also call PreferenceManager.setDefaultValues() so that the preference file is populated with the default values specified in the XML file. public class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false); addPreferencesFromResource(R.xml.preferences); } } Chapter 9: Data Storage and Serialization Techniques 171 The most common way to start this Activity is through an Intent where you specify the ComponentName, instead of using an action string. Also make sure that you set the android:exported flag to false in the manifest so that it can be started only within your application. High-Performance ContentProviders When you choose to store data in an SQLite database, I always recommend that you create a ContentProvider, even if you store data only for internal use. My reason is that Android provides several utility and UI-related classes that work on top of ContentProviders and make things much easier. Also, these classes provide an easy mechanism for notifying clients whenever an update occurs to the data, which makes it easier for developers to keep the list in the UI in sync with the actual content. When you create the tables for your database, be sure to consider its main purpose. Will the database mostly be read and displayed in a UI (for instance, a ListView), or are you designing a database that will have more write than read operations and that will happen in the background (for instance, an activity logger or a sports application tracking the user’s position during training)? Depending on the use, read performance may be more important than fast write operations, or vice versa. Android Database Design Design of a relational database is usually done through a process called database normalization. This process tries to minimize dependency and redundancy in a database using a number rule called normal form. There are a number of normal forms, but in most situations, only the first three are relevant. When a database design fulfills the first three normal forms, it’s considered normalized. In many cases, you may want to disregard the traditional database design and normalization rules. Unless you’ll be storing hundreds of thousands of records in your database, each containing a huge amount of text and maybe even binary data, you can generally use a much simpler database design. Consider an application that keeps track of tasks. Each task has a name, a date when it was created, a priority, a status, and an owner. If you were to make an optimal database design, it probably would look something like the one shown in Figure 9-2. Figure 9-2 A database with three tables where the task table has two foreign keys 172 Part II: Getting the Most Out of Components This design requires two foreign keys in the task table, which is perfectly okay, but it will make your application code more complex. The choice of statuses isn’t likely to change for this application, so the better option would be to remove the extra table and just interpret the column as a reference to a set of constants. The Owner field is at this moment a more complex matter. It may be okay just to store the name—in fact, doing so is okay for most occasions. If you need additional data for each person, you can rely on the ContactsProvider and store a reference to a contact in your table instead. The result is a much simpler table, as illustrated in Figure 9-3. Figure 9-3 A simplified version of the database for storing tasks Creating and Upgrading Databases I recommend that you always wrap your SQLite databases in Android using the ContentProvider component. In this way, you can manage all calls to the database from one place and also use several readymade utilities for working with databases. In this section, I give an example of a provider that stores tasks that can be given different priorities, status, and owners. I’ll start with the basics of creating the actual database. The following code shows the provider without the query methods (query(), insert(), update(), and delete()). I show some of these in the section “Database Transactions,” later in this chapter. The important thing here is the class MyDatabaseHelper that extends SQLiteOpenHelper. This class helps in opening SQLiteDatabase objects and in managing database upgrades. The onCreate() method for this class is called once when the application starts and the provider is accessed the first time—more specifically, the first time getReadableDatabase() or getWritableDatabase() is called. public class TaskProvider extends ContentProvider { public static final String AUTHORITY = “com.aptl.code.provider”; public static final int ALL_TASKS = 10; public static final int SINGLE_TASK = 20; public static final String TASK_TABLE = “task”; public static final String[] ALL_COLUMNS = new String[]{TaskColumns._ID, TaskColumns.NAME, TaskColumns.CREATED, TaskColumns.PRIORITY, TaskColumns.STATUS, TaskColumns.OWNER}; public static final String DATABASE_NAME = “TaskProvider”; public static final int DATABASE_VERSION = 2; public static final String TAG = “TaskProvider”; public static final String CREATE_SQL = “CREATE TABLE “ + TASK_TABLE + “ (“ + TaskColumns._ID + “ INTEGER PRIMARY KEY AUTOINCREMENT, “ + TaskColumns.NAME + “ TEXT NOT NULL, “ Chapter 9: Data Storage and Serialization Techniques 173 + TaskColumns.CREATED + “ INTEGER DEFAULT NOW, “ + TaskColumns.PRIORITY + “ INTEGER DEFAULT 0, “ + TaskColumns.STATUS + “ INTEGER DEFAULT 0, “ + TaskColumns.OWNER + “ TEXT);”; public static final String CREATED_INDEX_SQL = “CREATE INDEX “ + TaskColumns.CREATED + “_idx ON “ + TASK_TABLE + “ (“ + TaskColumns.CREATED + “ ASC);”; public static final String OWNER_INDEX_SQL = “CREATE INDEX “ + TaskColumns.OWNER + “_idx ON “ + TASK_TABLE + “ (“ + TaskColumns.CREATED + “ ASC);”; public static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); public MyDatabaseHelper mOpenHelper; static { mUriMatcher.addURI(AUTHORITY, “task”, ALL_TASKS); mUriMatcher.addURI(AUTHORITY, “task/#”, SINGLE_TASK); } @Override public boolean onCreate() { mOpenHelper = new MyDatabaseHelper(getContext()); return true; } // Query methods omitted for brevity... public interface TaskColumns extends BaseColumns { public static final String NAME = “name”; public static final String CREATED = “created”; public static final String PRIORITY = “priority”; public static final String STATUS = “status”; public static final String OWNER = “owner”; } private class MyDatabaseHelper extends SQLiteOpenHelper { public MyDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase database) { Log.d(TAG, “Create SQL : “ + CREATE_SQL); database.execSQL(CREATE_SQL); database.execSQL(CREATED_INDEX_SQL); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 174 Part II: Getting the Most Out of Components if (oldVersion < 2) { db.execSQL(“ALTER TABLE “ + TASK_TABLE + “ ADD COLUMN “ + TaskColumns.OWNER + “ TEXT”); db.execSQL(OWNER_INDEX_SQL); } } } } Every time the MyDatabaseHelper class is created, it compares the internal version number that the SQLite database currently has (stored in the internal table called android_metadata) with the one provided to the constructor of SQLiteOpenHelper. Thus you can perform database upgrades and add new columns to existing tables or perform any other SQL commands. This capability is efficient because it doesn’t require you to drop all tables and their content when you need to change the database. In the preceding example, the Owner column is part of the CREATE_SQL as well as the ALTER TABLE statement called in onUpgrade(). This is because there are two possible scenarios. In the first scenario, a new installation of the application will first call onCreate(), followed by onUpgrade(). Because the if statement in onUpgrade() is false, the ALTER TABLE statement won’t be called. However, for an upgrade of the application from an older version, onCreate() won’t be called, and the if statement in onUpgrade() is true, the new column will be created. By incrementally increasing the version number for your database and adding a new if statement for each alteration to the database, you have a simple yet powerful method for upgrading the database. In the code examples in this chapter I use String constants for declaring the SQL statements to make the examples more clear. A better alternative is to store them in text files in the raw application resources. This makes them easier to work with as well as simplifies testing. Implementing Query Methods When your database is queried (generally using ContentResolver.query()), the method ContentProvider.query() is called. Your implementation must take care of interpreting the incoming Uri to decide which query should be executed and check that all incoming parameters are safe. The following code shows the implementation of the query() method as well as two utility methods used for modifying the selection and selectionArgs parameters. Also, here you make a simple check to see if the incoming projection parameter is null, and if so you set it to the default value. The same check is made on the sortOrder parameter where you use the column named priority as the default for sorting. public static String[] fixSelectionArgs(String[] selectionArgs, String taskId) { if (selectionArgs == null) { selectionArgs = new String[]{taskId}; } else { String[] newSelectionArg = new String[selectionArgs.length + 1]; Chapter 9: Data Storage and Serialization Techniques 175 newSelectionArg[0] = taskId; System.arraycopy(selectionArgs, 0, newSelectionArg, 1, selectionArgs.length); } return selectionArgs; } public static String fixSelectionString(String selection) { selection = selection == null ? TaskColumns._ID + “ = ?” : TaskColumns._ID + “ = ? AND (“ + selection + “)”; return selection; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { projection = projection == null ? ALL_COLUMNS : projection; sortOrder = sortOrder == null ? TaskColumns.PRIORITY : sortOrder; SQLiteDatabase database = mOpenHelper.getReadableDatabase(); switch (mUriMatcher.match(uri)) { case ALL_TASKS: return database.query(TASK_TABLE, projection, selection, selectionArgs, null, null, sortOrder); case SINGLE_TASK: String taskId = uri.getLastPathSegment(); selection = fixSelectionString(selection); selectionArgs = fixSelectionArgs(selectionArgs, taskId); return database.query(TASK_TABLE, projection, selection, selectionArgs, null, null, sortOrder); default: throw new IllegalArgumentException(“Invalid Uri: “ + uri); } } The two utility methods for modifying the selection and selectionArgs parameters are used only when addressing a specific record in the database using an Uri. Note: You prepend (in the fixSelectionString() and fixSelectionArgs() methods) the ID column to the existing selection. This makes the query faster because comparison on the primary key column is always very fast and thus speeds up the entire query. When writing database queries, keep the simplest comparisons in the WHERE clause before the more complex ones. Doing so makes the query execute faster because it can determine much earlier whether the record should be included. 176 Part II: Getting the Most Out of Components Database Transactions When you execute a piece of SQL on an SQLite database, you always perform a transaction. Unless you specifically manage the transaction yourself, as I show next, it will be created for you automatically for that statement. Because most calls to a ContentProvider result in only one SQL statement, there’s’ little need for handling transactions manually in these cases. However, if your application will execute multiple SQL statements, such as inserting many new records in one batch, always manage your own transactions. The ContentProvider class provides two methods for transaction management, ContentProvider. bulkInsert() and ContentProvider.applyBatch(). In the following code, you see how to implement the bulkInsert() methods, which will insert multiple records in one single transaction. This approach is significantly faster than calling ContentProvider.insert() for every new piece of data you want to store. private Uri doInsert(Uri uri, ContentValues values, SQLiteDatabase database) { Uri result = null; switch (mUriMatcher.match(uri)) { case ALL_TASKS: long id = database.insert(TASK_TABLE, “”, values); if (id == -1) throw new SQLException(“Error inserting data!”); result = Uri.withAppendedPath(uri, String.valueOf(id)); } return result; } @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase database = mOpenHelper.getWritableDatabase(); Uri result = doInsert(uri, values, database); return result; } @Override public int bulkInsert(Uri uri, ContentValues[] contentValueses) { SQLiteDatabase database = mOpenHelper.getWritableDatabase(); int count = 0; try { database.beginTransaction(); for (ContentValues values : contentValueses) { Uri resultUri = doInsert(uri, values, database); if (resultUri != null) { count++; } else { count = 0; throw new SQLException(“Error in bulk insert”); } } database.setTransactionSuccessful(); } finally { database.endTransaction(); } return count; } Chapter 9: Data Storage and Serialization Techniques 177 The semantics for transactions are simple. You begin a new transaction by calling SQLiteDatabase. beginTransaction(). After you insert all the records successfully, you call SQLiteDatabase. setTransactionSuccessful() and then end the transaction with SQLiteException. endTransaction(). If something goes wrong in one of the insertions, you throw a SQLException, and all the previous insertions will be rolled back because the call to SQLiteDatabase. setTransactionSuccessful() was never called. I highly recommend that you implement this method for your own providers because it will increase the performance on insertions significantly. However, because this method works only for insert operations, you may need to implement another method for handling more complex operations. If you need to perform multiple update() or delete() statements within a single transaction, you must implement your own version of ContentProvider.applyBatch(). @Override public ContentProviderResult[] applyBatch(ArrayList operations) throws OperationApplicationException { SQLiteDatabase database = mOpenHelper.getWritableDatabase(); ContentProviderResult[] result = new ContentProviderResult[operations.size()]; try { database.beginTransaction(); for (int i = 0; i < operations.size(); i++) { ContentProviderOperation operation = operations.get(i); result[i] = operation.apply(this, result, i); } database.setTransactionSuccessful(); } finally { database.endTransaction(); } return result; } As with the buildInsert() method shown earlier, you begin a transaction, apply the operations, set the transaction successfully, and finally end the transaction. This API is designed for complex providers like the ContactsProvider, where there are many connected tables, each with its own Uri. Also, if you need to perform batch inserts to multiple tables, this API can still be useful in your own application. Storing Binary Data in ContentProvider Binary data includes anything that cannot be represented using the simple data types in Java, usually an image or some other media file, though it can be any type of file of a proprietary format. Working with such content can be tricky, but luckily the ContentProvider class provides a number of methods for dealing with this issue. Say that you want to store a JPG photo with each task in your database. First, you need to add another column to your task table with the name _data and the type TEXT, as shown here: db.execSQL(“ALTER TABLE “ + TASK_TABLE + “ ADD COLUMN _data TEXT”); 178 Part II: Getting the Most Out of Components The ContentProvider.openFileHelper() method uses this internally. You just store the path to the future file belonging to each record at each insertion. To do so, you modify the doInsert() method shown earlier. private Uri doInsert(Uri uri, ContentValues values, SQLiteDatabase database) { Uri result = null; switch (sUriMatcher.match(uri)) { case ALL_TASKS: long id = database.insert(TASK_TABLE, “”, values); if (id == -1) throw new SQLException(“Error inserting data: “ + values.toString()); result = Uri.withAppendedPath(uri, String.valueOf(id)); // Update row with _data field pointing at a file... File dataFile = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_ PICTURES); dataFile = new File(dataFile, FILE_PREFIX + id + FILE_SUFFIX); ContentValues valueForFile = new ContentValues(); valueForFile.put(“_data”, dataFile.getAbsolutePath()); update(result, values, null, null); } return result; } In this case, you write the JPG to the PICTURES directory on the external storage. Next, you override the method ContentProvider.openFile(), which will return a ParcelFileDescriptor to the client. You can then use that object for reading and writing directly to the file. @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { if(sUriMatcher.match(uri) == SINGLE_TASK) return openFileHelper(uri, mode); else return super.openFile(uri, mode); } Using openFileHelper()handles the actual opening of the file and passes it to the calling client. When you want to read a Bitmap for a certain record in your ContentProvider, you can simply use the same Uri as for the record, and the framework takes care of the rest, as shown here: public Bitmap readBitmapFromProvider(int taskId, ContentResolver resolver) throws FileNotFoundException { Uri uri = Uri.parse(“content://” + TaskProvider.AUTHORITY + “/” + TaskProvider.TASK_TABLE + “/” + taskId); return BitmapFactory.decodeStream(resolver.openInputStream(uri)); } Chapter 9: Data Storage and Serialization Techniques 179 Storing a file works similarly with ContentResolver.openOutputStream() with the correct Uri. This is especially useful when you want to display data from your ContentProvider in a ListView that contains both text and images, such as in the preceding example. Serializing Data for Persistence Using the ContentValues and Cursor classes when handling persistent data in your application is fine when working with your ContentProvider, but if you ever need to send the data over the Internet or share it with another device, you have a completely different challenge. To resolve this challenge, you need to transform your data to a format that the receiving end can work with and that is suitable for sending over the network. This technique is called serialization. Serialization is the concept of taking an object in memory and writing it to a file (or other output) such that the exact same memory representation can later be read (called deserialization). Android provides its internal serialization API with the Parcelable interface, but it isn’t designed or well-suited for persistent storage on files or for transferring over a network. In this section, I discuss two suitable formats for persisting complex data, JSON and Google Protocol Buffers. Both are well-supported by Android and have open-source implementations for most platforms, meaning that they’re’ well-suited for transporting over networks. JavaScript Object Notation JSON is an abbreviation for JavaScript Object Notation, and it’s a subset of the JavaScript standard. The official Android API has built-in support for reading and writing JSON data. This format is excellent for representing complex objects that don’t contain binary values. It has also become somewhat of a de-facto standard for sharing data with web services online. The following example shows a simple JSON array with three objects containing the same information that was stored in the TaskProvider in an earlier example. This format is excellent for sending tasks with an online web service or for sharing directly with a friend. [ { “name”: “Laundry”, “created”: 1370527168012, “priority”: 5, “owner”: “Erik”, “status”: 1 }, { “name”: “Groceries”, “created”: 1370476882046, “priority”: 3, “owner”: “Linda”, “status”: 2 }, 180 Part II: Getting the Most Out of Components { “name”: “Buy new sofa”, “created”: 1370326907735, “priority”: 2, “owner”: “Linda”, “status”: 1 } ] Reading JSON data from an InputStream is best done using the JsonReader API, as shown following: public JSONArray readTasksFromInputStream(InputStream stream) { InputStreamReader reader = new InputStreamReader(stream); JsonReader jsonReader = new JsonReader(reader); JSONArray jsonArray = new JSONArray(); try { jsonReader.beginArray(); while (jsonReader.hasNext()) { JSONObject jsonObject = readSingleTask(jsonReader); jsonArray.put(jsonObject); } jsonReader.endArray(); } catch (IOException e) { // Ignore for brevity } catch (JSONException e) { // Ignore for brevity } return jsonArray; } private JSONObject readSingleTask(JsonReader jsonReader) throws IOException, JSONException { JSONObject jsonObject = new JSONObject(); jsonReader.beginObject(); JsonToken token; do { String name = jsonReader.nextName(); if (“name”.equals(name)) { jsonObject.put(“name”, jsonReader.nextString()); } else if (“created”.equals(name)) { jsonObject.put(“created”, jsonReader.nextLong()); } else if (“owner”.equals(name)) { jsonObject.put(“owner”, jsonReader.nextString()); } else if (“priority”.equals(name)) { jsonObject.put(“priority”, jsonReader.nextInt()); } else if (“status”.equals(name)) { jsonObject.put(“status”, jsonReader.nextInt()); } Chapter 9: Data Storage and Serialization Techniques 181 token = jsonReader.peek(); } while (token != null && !token.equals(JsonToken.END_OBJECT)); jsonReader.endObject(); return jsonObject; } Although it’s also possible to read the entire contents of the InputStream to a String and then pass that to the constructor of JSONArray, the preceding method consumes less memory and will most likely be faster. Likewise, the JsonWriter class allows you to efficiently write JSON data to an OutputStream, as shown here: public void writeJsonToStream(JSONArray array, OutputStream stream) throws JSONException { OutputStreamWriter writer = new OutputStreamWriter(stream); JsonWriter jsonWriter = new JsonWriter(writer); int arrayLength = array.length(); jsonWriter.beginArray(); for(int i = 0; i < arrayLength; i++) { JSONObject object = array.getJSONObject(i); jsonWriter.beginObject(); jsonWriter.name(“name”). value(object.getString(“name”)); jsonWriter.name(“created”). value(object.getLong(“created”)); jsonWriter.name(“priority”). value(object.getInt(“priority”)); jsonWriter.name(“status”).f value(object.getInt(“status”)); jsonWriter.name(“owner”). value(object.getString(“owner”)); jsonWriter.endObject(); } jsonWriter.endArray(); jsonWriter.close(); } Advanced JSON Handling with Gson The JSONObject and JSONArray classes are convenient to work with as value objects in your code, but they have some limitations and usually consume more memory than necessary. Likewise, JsonReader and JsonWriter result in quite a lot of code if you have several different types of value objects. If you need more advanced serialization and deserialization of JSON data, you can find an excellent open-source library called Gson. You can find the documentation for Gson at https://code.google.com/p/google-gson. To include Gson in your Gradle configuration, add the following to the dependencies: compile ‘com. google.code.gson:gson:2.2.4’. 182 Part II: Getting the Most Out of Components This library allows you to convert a Plain Old Java Objects (POJOs) into JSON and back. All you need to do is to define your data as regular Java objects with getters and setters and include the Gson library in your project. The following class shows a simple Java object that represents a task: public class Task { private String mName; private String mOwner; private Status mStatus; private int mPriority; private Date mCreated; public Task() { } public Task(String name, String owner, Status status, int priority, Date created) { mName = name; mOwner = owner; mStatus = status; mPriority = priority; mCreated = created; } // Getters and setter omitted for brevity... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Task task = (Task) o; return mCreated.equals(task.mCreated) && mName.equals(task.mName); } @Override public int hashCode() { int result = mName.hashCode(); result = 31 * result + mCreated.hashCode(); return result; } public enum Status { CREATED, ASSIGNED, ONGOING, CANCELLED, COMPLETED } } Notice the use of Java enum for the status of the task. This object is much easier to work with in your application than the raw JSONObject. The following code shows how to read and write a Collection of Task objects. The serialized form is always valid JSON data, so this choice becomes very convenient when working toward web services that publish data in JSON format. When you’re also in control of the server-side code and it happens to be written in Java, you can easily share the same set of Java code between your server and Android application. Chapter 9: Data Storage and Serialization Techniques 183 public Collection readTasksFromStream(InputStream stream) { InputStreamReader reader = new InputStreamReader(stream); JsonReader jsonReader = new JsonReader(reader); Gson gson = new Gson(); Type type = new TypeToken>(){}.getType(); return gson.fromJson(jsonReader, type); } public void writeTasksToStream(Collection tasks, OutputStream outputStream) { OutputStreamWriter writer = new OutputStreamWriter(outputStream); JsonWriter jsonWriter = new JsonWriter(writer); Gson gson = new Gson(); Type type = new TypeToken>(){}.getType(); gson.toJson(tasks, type, jsonWriter); } To make it simpler to use your POJOs toward a ContentProvider, you can implement two methods that convert your objects to and from ContentValues and Cursor objects, as shown here: public ContentValues toContentValues() { ContentValues values = new ContentValues(); values.put(TaskProvider.TaskColumns.NAME, mName); values.put(TaskProvider.TaskColumns.OWNER, mOwner); values.put(TaskProvider.TaskColumns.STATUS, mStatus.ordinal()); values.put(TaskProvider.TaskColumns.PRIORITY, mPriority); values.put(TaskProvider.TaskColumns.CREATED, mCreated.getTime()); return values; } public static Task fromCursor(Cursor cursor) { Task task = new Task(); int nameColumnIdx = cursor.getColumnIndex(TaskProvider.TaskColumns.NAME); task.setName(cursor.getString(nameColumnIdx)); int ownerColumnIdx = cursor.getColumnIndex(TaskProvider.TaskColumns.OWNER); task.setOwner(cursor.getString(ownerColumnIdx)); int statusColumnIdx = cursor.getColumnIndex(TaskProvider.TaskColumns.STATUS); int statusValue = cursor.getInt(statusColumnIdx); for (Status status : Status.values()) { if(status.ordinal() == statusValue) { task.setStatus(status); } } int priorityColumnIdx = cursor.getColumnIndex(TaskProvider.TaskColumns.PRIORITY); task.setPriority(cursor.getInt(priorityColumnIdx)); int createdColumnIdx = cursor.getColumnIndex(TaskProvider.TaskColumns.CREATED); task.setCreated(new Date(cursor.getLong(createdColumnIdx))); return task; } 184 Part II: Getting the Most Out of Components If your application will have many different POJOs and tables, it may be easier to integrate an ORM (Object-Relational-Mapping) library to handle the serialization and deserialization between your SQLite database and Java objects. Two ORM libraries present a good option for Android today, greenDAO (http://greendao-orm.com) and OrmLight (http://ormlite.com). Google Protocol Buffers Google Protocol Buffer, or protobuf, is a way of encoding structured data in an efficient and extensible format. It differs from JSON in that it supports binary data mixed with simple data types and also has an advanced and extensible schema support. Protobuf has implementations for most software platforms, including a lite Java variant suitable for Android. You can find documentation, download links, and installation instructions for Google Protocol Buffers at https://developers.google.com/protocol-buffers. Remember that for Android you have to build the lite version of the tool, so you cannot use the one found in the central Maven repository. Executing “mvn package –P lite” in the Java source directory does this. Check the installation instructions for further details. Although JSON lets you read and write arbitrary data to a JSONObject, protobuf requires the use of a schema that defines the data you want to store. The schema defines a number of messages, where each message has a number of name-value pair fields. Each field can be either one of the supported built-in primitive data types, an enum, or another message. You can also specify whether a field is required or optional as well as some other parameters. Once your protobuf schema is complete, you use the protobuf tools to generate the Java code for your data. The generated Java classes can now be used for reading and writing protobuf data in a convenient way. The following code is a sample of a simple protobuf schema that defines the information similar to the TaskProvider shown earlier: package com.aptl.code.task; option optimize_for = LITE_RUNTIME; option java_package = “com.aptl.code.task”; option java_outer_classname = “TaskProtos”; message Task { enum Status { CREATED = 0; ONGOING = 1; CANCELLED = 2; COMPLETED = 3; } Chapter 9: Data Storage and Serialization Techniques 185 message Owner { required string name = 1; optional string email = 2; optional string phone = 3; } message Comment { required string author = 1; required uint32 timestamp = 2; required string content = 3; } required string name = 1; required uint64 created = 2; required int32 priority = 3; required Status status = 4; optional Owner owner = 5; repeated Comment comments = 6; } Note: Declarations in the beginning tell the protobuf tool what Java package and class name to use when generating the code. There is also an instruction to generate a lite version that is supported by Android. Deserializing a protobuf object from an InputStream is easy, as shown in the following example. The generated Java code gives you a number of functions for merging the content from byte arrays, ByteBuffer, and InputStream objects. public TaskProtos.Task readBrotoBufFromStream(InputStream inputStream) throws IOException { TaskProtos.Task task = TaskProtos.Task.newBuilder() .mergeFrom(inputStream).build(); Log.d(“ProtobufDemo”, “Read Task from stream: “ + task.getName() + “, “ + new Date(task.getCreated()) + “, “ + (task.hasOwner() ? task.getOwner().getName() : “no owner”) + “, “ + task.getStatus().name() + “, “ + task.getPriority() + task.getCommentsCount() + “ comments.”); return task; } In this example, you see how to retrieve the values in a protobuf object. Note: Protobuf objects are immutable. The only way to make a modification is to create a new builder from an existing object, set the new value, and generate a Task object that will replace the old one. This makes protobuf a bit harder to work with but forces you to use a better design when working with persistence of complex objects. 186 Part II: Getting the Most Out of Components The following method shows how to construct a new protobuf object for a task. You start by creating a new Builder for the specific object you want to construct and then you set the desired values and call Builder. build() to create your immutable protobuf object. public TaskProtos.Task buildTask(String name, Date created, String ownerName, String ownerEmail, String ownerPhone, TaskProtos.Task.Status status, int priority, List comments) { TaskProtos.Task.Builder builder = TaskProtos.Task.newBuilder(); builder.setName(name); builder.setCreated(created.getTime()); builder.setPriority(priority); builder.setStatus(status); if(ownerName != null) { TaskProtos.Task.Owner.Builder ownerBuilder = TaskProtos.Task.Owner.newBuilder(); ownerBuilder.setName(ownerName); if(ownerEmail != null) { ownerBuilder.setEmail(ownerEmail); } if(ownerPhone != null) { ownerBuilder.setPhone(ownerPhone); } builder.setOwner(ownerBuilder); } if (comments != null) { builder.addAllComments(comments); } return builder.build(); } When you need to write a protobuf object to a file or to a network stream, the API provides a set of methods for that. In the following code, you see an example where you can serialize (that is, write) a Task object to an OutputStream. public void writeTaskToStream(TaskProtos.Task task, OutputStream outputStream) throws IOException { task.writeTo(outputStream); } The main advantage of protobuf is that it consumes much less memory and is faster to read and write than JSON. Protobuf objects are also immutable, which is useful when you want to ensure that the values of the object remain the same during its lifetime. Chapter 9: Data Storage and Serialization Techniques 187 Application Data Backup After you resolve all your persistence needs for your application, consider whether your application will benefit from using the backup service for Android provided by Google. This service lets you back up your application’s persistence data and restore it if the user performs a factory reset or upgrades to a new Android device. Android provides a convenient API for managing backups, and it’s’ a feature that will be greatly appreciated by users if they lose their device. The backup is securely stored on a cloud and will be restored only to a device with the same Google ID. The following XML snippet is a piece from a typical AndroidManifest.xml file: ... To enable backup of your application’s data, you only need to specify the class name of your backup agent in the android:backupAgent attribute. This class deals with the backup and restoration of application data that you choose to back up for the user. The meta-data attribute in the preceding example specifies the API key that you get once you register your application on the Google backup service. You can find the site for registering your applications at https://developer.android.com/google/backup/signup.html. When you register and receive your API key, paste it into the android:value attribute, as just shown. Although the key is bound to your application’s package and cannot be used for other applications (that is, one key per application), you should take care not to share it publicly in any code you publish. The following class shows a simple backup agent used to back up and restore your default preferences file. Note: The filename for the preference file you get from PreferenceManager.getDefaultPreferences() is named _preferences. This is an undocumented detail that makes a big difference when it comes to backing up your preference files. public class MyBackupAgent extends BackupAgentHelper { public static final String PREFS_BACKUP_KEY = “prefsBackup”; @Override public void onCreate() { super.onCreate(); 188 Part II: Getting the Most Out of Components SharedPreferencesBackupHelper sharedPreferencesBackupHelper = new SharedPreferencesBackupHelper(this, getPackageName() + “_preferences”); addHelper(PREFS_BACKUP_KEY, sharedPreferencesBackupHelper); } } The BackupAgentHelper class automatically handles both backup and restoration of the preference files you choose to add to this helper. You can also add backup for other, regular files using the class FileBackupHelper. The backup agent provided for Android applications by Google is appropriate for small amounts of data. Although it would be technically possible to back up a SQLite database, you would probably be better off to first dump the contents of the database to a serialized format, compress the file, and perform your backup on that dump. In the Android SDK, you can find the command-line tool bmgr that allows you to force backup and restoration operations on your application. This is useful when you’re developing your applications because you can check that everything is working correctly. Summary Persisting data is central to most software development. The most common solution for Android applications is either through the SharedPreference class or by using a ContentProvider with an SQLite database. The SharedPreferences and ContentProvider API are both powerful and easy to use. Being able to get the most out of these APIs can make a huge difference in the overall user experience. However, when you need to transport data over the network, a number of options are available that aren’t part of the Android APIs. Serializing data with JSON, and more specifically using the Gson library, is a powerful technique that you should master. For the even more advanced situations where performance and size are crucial, using Google Protocol Buffers can help you solve your problems. Finally, consider the use of a backup agent in your application in order to provide users with a smooth experience when they switch to a new Android device. Use the backup agent to store user preferences and options that let them keep the same configuration between multiple devices. Further Resources Documentation Guide to Settings: http://developer.android.com/guide/topics/ui/settings.html Guide to Content Providers: http://developer.android.com/guide/topics/providers/ content providers.html Chapter 9: Data Storage and Serialization Techniques 189 Websites Full documentation and information about SQLite: www.sqlite.org Google Gson: https://code.google.com/p/google-gson Google Protocol Buffers—Tutorial for Java: https://developers.google.com/protocol- buffers/docs/javatutorial. Also see https://developers.google.com/protocol- buffers for additional information Two ORM frameworks suitable for Android are greeDAO (http://greendao-orm.com) and OrmLight (http://ormlite.com) Documentation for the Backup Agent: https://developer.android.com/google/backup/ signup.html Chapter 10 Writing Automated Tests If I had to pick one thing that developers of mobile apps often miss, it would be testing, despite the fact that high-quality software is the result of thorough testing. Consequently, this may be the most important chapter in this book. The traditional way of developing software has been to design the software architecture, implement the code, and finally test the code, maybe by using automated tests to some degree. However, when testing is the last thing done in the development cycle, often too little testing is done or the wrong things are tested. Test Driven Development (TDD) takes a different approach to software development, and its principles lead to high-quality software. With TDD, you start by defining your tests based on how your application will be used. Then you start implementing code that fulfills these tests, continuing until all the tests are satisfied. At this point, you can refactor your application code to optimize it for performance and to improve the overall design. Using TDD involves a number of tools and techniques. First, you need a unit-testing framework for writing the automated tests. This framework, which is included in the Android APIs, is the main focus of this chapter. Second, you need a continuous integration and build server. This server automatically builds your application and performs all the automated tests for every change in the code. Finally, you need a code-coverage tool that tells you how much of the application’s code is really being tested. Android Testing Principles On Android, you can divide tests into two categories: unit and instrumentation tests. Although within the field of TDD, there are other types of tests (for example, integration, functional, system, and component tests) in the context of Android, I’m focusing on unit and instrumentation tests. Unit testing works on a very fine-grained level, usually on individual methods and classes with all external dependencies removed. On the other hand, instrumentation tests focus on verifying the behavior of a component (Activity, Service, BroadcastReceiver or ContentProvider) in the overall system. The goal with unit testing is to verify that your methods behave as expected and that they can handle erroneous input without crashing. A framework called JUnit was developed for the Java programming language. JUnit is also part of the official Android APIs in the junit package along with the Android-specific testing framework under android.test. To do unit testing, you call a method in your application code and test its result for the expected output. This is called, asserting the result, and there is a ready-made utility class named Assert for this purpose. Each test you write should perform an assertion on the value, which in turn tells the testing framework whether the test passed. 192 Part II: Getting the Most Out of Components Each test is called a test case, and a set of test cases is called a test suite. Each test case starts with a setup where all dependencies are created and initialized. When the test case is complete, a teardown is performed to release the resources that were created during setup. Because the methods in your application code often have dependencies to the system or other components, you need a way to isolate a method when running a test. To do so, you use mock objects that simulate the behavior of the dependent objects. The Android API provides a set of mock classes that you can use in your tests. Mock objects are usually created in the setup of each test case and released during teardown. When using the new Android build system based on Gradle (which I describe in Chapter 1), the default place for all your test code is under /src/instrumentTest. The name of the default package for tests is the same as your application package name with .test appended. All of this is configurable in gradle.build. What to Test When you write your automated tests, test only your code. There’s no point in writing automated tests that verify system functions and services. For instance, you don’t need to write a unit test that verifies a button is pressed. Instead, write a test that verifies the button has an onClick listener and that the listener behaves as expected. Tests for user interfaces are some of the more complex tests you’ll write. However, again, keep in mind that you should test your own code, not the functionality of Android’s UI classes. For instance, you don’t need to write a test that verifies scrolling works in a ListView. Instead, focus on writing tests that verify the content of the ListView and that a click on a specific element performs the expected action. Because it’s hard to test large and complex methods, keep your tests as limited and focused as possible. Also, make sure you test only one thing at a time, which makes it easier to detect where a bug occurred and to do refactoring. It’s better to have many small test cases than a single large, complex test case for your Activity. This approach also affects how you write your code because writing small test cases in advance will result in better overall design in your code. Refactoring your code to extract smaller methods from a single large method is good practice. Use code coverage to detect which methods are covered by your tests and where you need to add additional test cases. Make sure you run all your test cases for every code commit. This is where a continuous integration server comes in handy. Be sure to configure your development environment so that it will trigger a new job on the build server for every commit. Basic Unit Testing When you test classes that don’t depend on a component lifecycle, do the simplest form of unit testing using the AndroidTestCase class. Doing so is useful when you can construct an object independently from any component or other part of the Android framework. Chapter 10: Writing Automated Tests 193 public class Util { public static int byteArrayToInt(byte[] bytes) { return bytes[3] & 0xFF | (bytes[2] & 0xFF) << 8 | (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24; } } Consider the preceding utility class with a method for converting four bytes to an integer. You don’t need to perform special setup or teardown for this test because the method takes simple inputs and is static. Only one testing method is needed for the method in the Util class. Running the test on a device generates a pass, and everything looks good. public class UtilTest extends AndroidTestCase { public void testBytesToIntConversion() { int result = Util.byteArrayToInt(new byte[] {(byte) 127, (byte) -1, (byte) -1, (byte) -1}); assertEquals(Integer.MAX_VALUE, result); result = Util.byteArrayToInt(new byte[] {(byte) 0, (byte) 0, (byte) 0, (byte) 0}); assertEquals(0, result); result = Util.byteArrayToInt(new byte[] {(byte) -128, (byte) 0, (byte) 0, (byte) 0}); assertEquals(Integer.MIN_VALUE, result); } } The preceding code verifies that the method gives the correct answer given a valid input, but it doesn’t test what happens when you give the method an invalid input. To do so, you add three test methods for the possible invalid input: for a null array, for an array that is too short, and for when an array is too long. Here is the code for the three test methods: public void testBytesToIntWithNull() { try { int result = Util.byteArrayToInt(null); } catch (IllegalArgumentException e) { return; } fail(); } public void testBytesToIntWithTooShortInput() { try { int result = Util.byteArrayToInt(new byte[] {1,2,3}); } catch (IllegalArgumentException e) { return; } fail(); } 194 Part II: Getting the Most Out of Components public void testBytesToIntWithTooLongInput() { try { int result = Util.byteArrayToInt(new byte[] {1,2,3,4,5,6,7,8,9}); } catch (IllegalArgumentException e) { return; } fail(); } You should expect to get an IllegalArgumentException when you have invalid input, or the test will fail. If you run the tests now, all three new test methods will fail, indicating that you need to fix your utility method. In the following code example I’ve modified the utility method to handle incorrect input. If you run the previous tests again, they should all pass. This is an example of TDD, although very simplified. public class Util { public static int byteArrayToInt(byte[] bytes) throws IllegalArgumentException { if(bytes == null || bytes.length != 4) { throw new IllegalArgumentException(); } return bytes[3] & 0xFF | (bytes[2] & 0xFF) << 8 | (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24; } } It’s a good practice to write the tests for your classes before you implement them completely so that you can verify that they work as expected. As shown here, you should not only check for correct behavior in normal cases but also test what happens when they receive a bad or unexpected input. Using this approach, you’ll greatly improve the quality of your code. Testing Activities Testing your user interfaces means testing all your Activity classes. You’ll generally use either ActivityUnitTestCase or ActivityInstrumentationTestCase2 when writing tests for your Activities. The first class provides a more isolated testing where you have only a minimal connection to the system infrastructure. This approach is helpful when you test methods in your Activity that in turn call methods that interact with the system (such as Context.startService()) and you want to limit the scope of your test. The following code shows a simple Activity that sets up a View to be displayed, along with the method startBackgroundJob() that will be used as a click listener in your view: public class MainActivity extends Activity { public static final String ACTION_START_BACKGROUND_JOB = “startBackgroundJob”; Chapter 10: Writing Automated Tests 195 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void startBackgroundJob(View view) { Intent backgroundJob = new Intent(ACTION_START_BACKGROUND_JOB); startService(backgroundJob); } } In the XML layout, you assign the click listener using the attribute android:onClick:
还剩433页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 3 人已下载

下载pdf

pdf贡献者

fff8be

贡献于2015-07-22

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf