560 likes | 737 Views
Best Practices for Accessing Google APIs on Android. Yaniv Inbar May 10, 2011 Speaker Feedback: goo .gl/ EUnAo Twitter Hashtags : #io2011 # GoogleAPIs #Android. Google APIs Client Library for Java ( goo.gl/ZrqPu ). Now in Beta. New Google APIs. Android. Google Data APIs.
E N D
Best Practices for Accessing Google APIs on Android • YanivInbar • May 10, 2011 • Speaker Feedback: goo.gl/EUnAo • Twitter Hashtags: #io2011 #GoogleAPIs #Android
Google APIs Client Library for Java (goo.gl/ZrqPu) • Now in Beta
New Google APIs Android Google Data APIs Java 5 (SE, EE) Any REST APIs JSON XML Photos Videos Google App Engine OAuth 2.0 Client Login OAuth 1.0 Platforms APIs Data Formats Auth OUTLINE
New Google APIs Android Google Data APIs Java 5 (SE, EE) Any REST APIs JSON XML Photos Videos Google App Engine OAuth 2.0 Client Login OAuth 1.0 Platforms APIs Data Formats Auth OUTLINE
Platforms Java Platforms – Any Java Environment • Platform neutral • Works (mostly) the same in any Java environment • Pluggable • Pluggable HTTP library • Pluggable data format • Pluggable streaming JSON library • Pluggable streaming XML library • Pluggable Authentication
Java 5 (SE, EE) HelloBuzz Example public class HelloBuzz { public static void main(String[] args) throws IOException { Buzz buzz = new Buzz(newNetHttpTransport(), new JacksonFactory()); ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute(); if (feed.items != null) for (Activity activity : feed.items) System.out.println(activity.buzzObject.content); } }
Google App Engine HelloBuzzServlet Example public class HelloBuzzServletextends HttpServlet { @Override protected void doGet(HttpServletRequestreq, HttpServletResponseresp) throws IOException { resp.setContentType("text/plain"); PrintWriter writer = resp.getWriter(); Buzz buzz = new Buzz(newUrlFetchTransport(), new JacksonFactory()); ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute(); if (feed.items!= null) for (Activity activity : feed.items) writer.println(activity.buzzObject.content); } }
Android HelloBuzzActivity Example public class HelloBuzzActivityextends ListActivity { @Override public void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); Buzz buzz = new Buzz(newNetHttpTransport(), new AndroidJsonFactory()); List<Spanned> activities = new ArrayList<Spanned>(); try { ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute(); if (feed.items!= null) for (Activity activity : feed.items) activities.add(Html.fromHtml(activity.buzzObject.content)); setListAdapter(newArrayAdapter<Spanned>(this, android.R.layout.simple_expandable_list_item_1, activities)); } catch (IOExceptione) {} } }
Platforms Java Platforms – Pluggable HTTP library • UrlFetchTransport • Google App Engine only • NetHttpTransport • Based on java.net.HttpURLConnection • Included in Java & Android SDK • Preferred choice on Android since Gingerbread • ApacheTransport • Based on Apache HTTP Client • Included in Android SDK • Preferred choice for older Android SDK’s up to FroYo • AndroidHttp.newCompatibleTransport() • Picks NetHttpTransport or ApacheTransport based on SDK Level
Platforms Java Platforms – HTTP Request Factory • Library provides request factory abstraction to provide hooks to: • Initialize all requests • Run code before executing a request • Retry unsuccessful requests • Example for Google Calendar: • void initializeRequestFactory() { • requestFactory = transport.createRequestFactory(newHttpRequestInitializer() { • public void initialize(HttpRequest request) { • GoogleHeaders headers = new GoogleHeaders(); • headers.setApplicationName("Google-CalendarSample/1.0"); • headers.gdataVersion= "2”; • request.headers= headers; • request.enableGZipContent= true; • } • }); • }
Platforms Java Platforms – HTTP Trouble-Shooting Tip • Enable logging of HTTP requests and responses: • If you also need to see Authorization header, use Level.ALLinstead • On Android, you also need to: • Logger.getLogger("com.google.api.client.http").setLevel(Level.CONFIG); adb shell setproplog.tag.HttpTransport DEBUG
Android Android: using AsyncTask • Activities run in the main UI thread • No HTTP in main thread • Otherwise Android may shut down the application (“… is not responding”) • Simplest solution is to use AsyncTask • Sessions from Google I/O • [2010] Beginner's Guide to Android (goo.gl/qhaZg)
Android HelloBuzzAsyncActivity Example (part 1 of 2) public class HelloBuzzActivityextends ListActivity { final Buzz buzz = new Buzz(newNetHttpTransport(), new AndroidJsonFactory()); @Override public void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); new LoadActivities().execute(); } class LoadActivitiesextends AsyncTask<Void, Void, ActivityFeed> { final ProgressDialogdialog = new ProgressDialog(HelloBuzzActivity.this); @Override protected void onPreExecute() { dialog.setMessage("Loading Activities..."); dialog.show(); } @Override protected ActivityFeeddoInBackground(Void... params) { try { return buzz.activities.list("112550920411899420633", "@public").execute(); } catch (IOExceptione) { return null; } }
Android HelloBuzzAsyncActivity Example (part 2 of 2) @Override protected void onPostExecute(ActivityFeed feed) { dialog.dismiss(); if (feed == null) return; List<Spanned> activities = new ArrayList<Spanned>(); if (feed.items!= null) for (Activity a : feed.items) activities.add(Html.fromHtml(a.buzzObject.content)); setListAdapter(newArrayAdapter<Spanned>(HelloBuzzActivity.this, android.R.layout.simple_expandable_list_item_1, activities)); } } }
Android Android – Using a background Service • Background service makes HTTP requests to API • Store result in a SQLite database • Activity lifecycle • Send async request to background service • If alive when response arrives, process immediately • Else, onCreate check for updated data • Sessions from Google I/O • [2010] Developing Android REST client applications (goo.gl/R15we)
Android Android – Shrink Application with ProGuard Based on HelloBuzzActivity sample (95% savings)
Android Android – Setting Up ProGuard (goo.gl/x1hit) • Eclipse New Project Wizard generates default.properties and proguard.cfg • Add to default.properties: • Add to proguard.cfg: proguard.config=proguard.cfg # Needed by google-api-client to keep generic types and @Key annotations accessed via reflection -keepclassmembers class * { @com.google.api.client.util.Key <fields>; } -keepattributesSignature,RuntimeVisibleAnnotations,AnnotationDefault # Needed by Guava -dontwarnsun.misc.Unsafe
New Google APIs Android Google Data APIs Java 5 (SE, EE) Any REST APIs JSON XML Photos Videos Google App Engine OAuth 2.0 Client Login OAuth 1.0 Platforms APIs Data Formats Auth OUTLINE
Google Data APIs Google Data APIs (“GData”) • Old Decentralized Architecture • XML • Some have read-only JSON-C • ClientLogin, OAuth 1.0, and OAuth 2.0 • Examples: YouTube Data API Google Calendar Data API Blogger Data API Google Contacts Data API
Google Data APIs Google Data APIs (“GData”) – Java Library • Google Data Java Client Library • Nice XML data model • But only works with Google Data APIs • Nothing else is supported • Doesn’t support Android • May be fixed in Ice Cream Sandwich SDK? • Still maintained and not deprecated • But we’ve stopped developing it 2 years ago
New Google APIs Google APIs • New Centralized Architecture • JSON • OAuth 1.0 and OAuth 2.0 • Google I/O sessions • Google I/O ’10 “How Google Builds APIs” (goo.gl/i1WfR) • Google I/O ‘11 “Life of a Google API Developer” (goo.gl/VKJ0q) • Examples: Buzz API Latitude API Search API for Shopping
New Google APIs Google APIs - Discovery Service (goo.gl/iHUN6) • Announcing V1 of the Discovery Service today • Directory of supported APIs • Discovery document for each API • A list of API resource schemas based on JSON Schema. • E.g. “ActivitiesFeed” • A list of API methods and available parameters for each method. • E.g. “buzz.activities.list” • A list of available OAuth 2.0 scopes. • E.g. “https://www.googleapis.com/auth/buzz” • Google I/O ’11 session “Building Custom Client Libraries for Google APIs” (goo.gl/b5P6b)
New Google APIs Google APIs – Explorer (goo.gl/Ccni0) • Web-based Google APIs exploration tool
New Google APIs OAuth 2.0 – Google apis console (goo.gl/UyAZB)
New Google APIs Google APIs – Generated Libraries (goo.gl/avR14) • Announcing generated Java libraries based on the Discovery API • Get an activity feed: • Insert an activity: • Delete an activity: • ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute(); • Activity activity = new Activity(); • activity.buzzObject = new ActivityObject(); • activity.buzzObject.content = "Posted using Google API Client Library for Java " • + "(http://code.google.com/p/google-api-java-client/)"; • Activity result = buzz.activities.insert("@me", activity).execute(); • buzz.activities.delete("@me", "@self", activity.id).execute();
New Google APIs Android Google Data APIs Java 5 (SE, EE) Any REST APIs JSON XML Photos Videos Google App Engine OAuth 2.0 Client Login OAuth 1.0 Platforms APIs Data Formats Auth OUTLINE
JSON JSON – Streaming parser/serializer library • Pluggable choice of streaming library • Avoid memory overhead of a full parser like JSONObject • Stream parsing faster than “full” data models • JacksonFactory • Fastest. Based on popular Jackson library (goo.gl/Z0yZF) • GsonFactory • Fast, and smaller than Jackson. • Based on Google GSON library (goo.gl/xBO5C) • AndroidJsonFactory • Same as GSON, but built in to Honeycomb (SDK 3.0) in package android.util (goo.gl/uJHiR)
JSON JSON – Data Model • Rich Java Object to JSON mapping • Similar to functionality provided by Jackson and Google GSON • Examples • Java String/Number/Boolean maps to JSON string/number/boolean • Java enums can be used for JSON string • Java Arrays and Collection can be used for JSON arrays • Map or Java objects can be used for JSON objects • Full richness of Java supported, including generic types (e.g. List<Activity>) • Extend GenericJsonwhen you need to preserve arbitrary fields • Important when using PUT to update an entry to avoid dropping data
JSON JSON – Data Model Example { "updated":"2010-05-20T23:08:07.471Z", "id":"tag:google.com,2010:buzz:z12nu3oa1r25gr3wp04cd3zp2zvafrjrlso0k", "object": { "content":"Presenting live at Google I/O!” } } public class Activity { @Key public DateTimeupdated; @Key public String id; @Keypublic ActivityObjectobject; } public class ActivityObject { @Keypublic String content; } JSON Java
JSON JSON – Gzip and Partial Response Based on HelloBuzz sample (95% savings)
JSON JSON Example – Partial with the Generated Libraries • Before: • After: • Or for more data: • ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute(); • Buzz.Activities.List request = buzz.activities.list("112550920411899420633", "@public"); • request.fields= "items/object/content"; • ActivityFeed feed = request.execute(); • Buzz.Activities.List request = buzz.activities.list("112550920411899420633", "@public"); • request.fields= "items(object/content,updated,id)"; • ActivityFeed feed = request.execute();
JSON JSON: streaming entries HttpResponse response = buzz.activities.list("112550920411899420633", "@public").executeUnparsed(); InputStream content = response.getContent(); JsonParser parser = jsonFactory.createJsonParser(content); parser.nextToken(); parser.skipToKey("data"); parser.skipToKey("items"); while (parser.nextToken() == JsonToken.START_OBJECT) { Activity activity = parser.parse(Activity.class, null); // process activity }
XML XML – Data Model <entry xmlns='http://www.w3.org/2005/Atom’xmlns:gCal='http://schemas.google.com/gCal/2005'> <id>http://www.google.com/calendar/feeds/default/calendars/abc123%40group.calendar.google.com</id> <updated>2011-05-09T23:59:51.000Z</updated> <title type='text'>abc 2</title> <gCal:timezone value='UTC'/> </entry> public class CalendarEntry { @Key public String id; @Key public DateTimeupdated; @Key public Title title; @Key(“gCal:timezone”)public TimeZonetimeZone; } public class Title { @Key(“@type”)public String type; @Key(“text()”)public String value; } public class TimeZone { @Key(“@value”)public String value; } XML Java
XML XML – Tips • Must declare XML namespaces: • Partial response fields mask is automatically computed URL query parameter: • public static final XmlNamespaceDictionary DICTIONARY = new XmlNamespaceDictionary() • .set("", "http://www.w3.org/2005/Atom") // default namespace • .set("gCal", "http://schemas.google.com/gCal/2005"); • public class CalendarUrl extends GoogleUrl { • ... • } • <Fextends Feed> FexecuteGetFeed(CalendarUrlurl, Class<F> feedClass) throws IOException { • url.fields= GoogleAtom.getFieldsFor(feedClass); • HttpRequest request = requestFactory.buildGetRequest(url); • return request.execute().parseAs(feedClass); • }
Photos Videos Media • Download Media (GET): • HttpResponse.getContent() returns InputStream • HttpResponse.contentTypeis the media type • Upload Media (POST or PUT): • Use InputStreamContentfor uploading an input stream • type is the content type • length is the length of the media content (if known) • inputStreamis the media content input stream
Photos Videos Media • public class PostPhotoActivityextends Activity { • @Override • public void onCreate(BundlesavedInstanceState) { • super.onCreate(savedInstanceState); • HttpRequestFactoryrequestFactory = new NetHttpTransport().createRequestFactory(); • Intent intent = getIntent(); • Bundle extras = intent.getExtras(); • InputStreamContent content = new InputStreamContent(); • ContentResolvercontentResolver = getContentResolver(); • Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM); • content.inputStream= contentResolver.openInputStream(uri); • Cursor cursor = contentResolver.query(uri, null, null, null, null); • cursor.moveToFirst(); • content.type= intent.getType(); • content.length= cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.SIZE)); • HttpRequest request = requestFactory.buildPostRequest(newGenericUrl( • "https://picasaweb.google.com/data/feed/api/user/default/albumid/default"), content); • GoogleHeaders headers = new GoogleHeaders(); • request.headers= headers; • String fileName = cursor.getString(cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME)); • headers.setSlugFromFileName(fileName); • request.execute().ignore(); • } • }
New Google APIs Android Google Data APIs Java 5 (SE, EE) Any REST APIs JSON XML Photos Videos Google App Engine OAuth 2.0 Client Login OAuth 1.0 Platforms APIs Data Formats Auth OUTLINE
Auth AccountManager • User accounts centrally controlled on Android • Including synchronization • AccountManager: abstraction to store auth token per account • Need to know the “account type” and “auth token type” • Google Accounts controlled by AccountManager • Account type always “com.google” • Auth token type depends on authentication method and Google API • Tip: library provides a handy GoogleAccountManager • Don’t try to bypass AccountManager and handle authentication yourself
Client Login ClientLogin (goo.gl/WkGFX) • Username/password authentication for Google Data APIs • Not supported by new Google APIs, like Buzz and Latitude! • Request permission from user to have complete read/write access to a Google service • Auth token type is “service name” • For example “cl” for Google Calendar
Client Login ClientLogin for Calendar Example
Client Login ClientLogin for Calendar Example (1 of 3) public class HelloCalendarActivityextends ListActivity { GoogleAccountManageraccountManager; HttpRequestFactoryrequestFactory; SharedPreferencessettings; String accountName; String authToken; @Override public void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestFactory= transport.createRequestFactory(newHttpRequestInitializer() { public void initialize(HttpRequest request) { GoogleHeaders headers = new GoogleHeaders(); headers.gdataVersion= "2"; headers.setGoogleLogin(authToken); request.headers= headers; } });
Client Login ClientLogin for Calendar Example (2 of 3) accountManager = new GoogleAccountManager(this); settings = this.getSharedPreferences("prefs", 0); authToken= settings.getString("authToken", null); accountName= settings.getString("accountName", null); Account account = accountManager.getAccountByName(accountName); if (account == null) { chooseAccount(); } else { new LoadCalendars().execute(); } } }
Client Login ClientLogin for Calendar Example (3 of 3) private static final String AUTH_TOKEN_TYPE = "cl"; private void chooseAccount() { AccountManager.get(this).getAuthTokenByFeatures("com.google", AUTH_TOKEN_TYPE, null, this, null, null, new AccountManagerCallback<Bundle>() { public void run(AccountManagerFuture<Bundle> future) { try { Bundle bundle = future.getResult(); setAccountName(bundle.getString(AccountManager.KEY_ACCOUNT_NAME)); setAuthToken(bundle.getString(AccountManager.KEY_AUTHTOKEN)); new LoadCalendars().execute(); } catch (Exception e) { } } }, null); }
OAuth 2.0 OAuth 2.0 (goo.gl/CdEGm) • Latest OAuth standard, supported by (almost) all Google APIs • Including the Google Data APIs • Request a more fine-grained “scope” of access • Example for Google Buzz: • https://www.googleapis.com/auth/buzz for read/write access to Buzz data • https://www.googleapis.com/auth/buzz.readonly for read access to Buzz data • https://www.googleapis.com/auth/photos for read access to Buzz photos • Auth tokens are temporary and expire in 1 hour • Check for 401 error response code and go through auth flow again
OAuth 2.0 OAuth 2.0 – Getting an Auth Token • Sorry, don’t have an ideal story here yet. • Use auth token type of “oauth2:” plus space-separated scopes • For example: oauth2:https://www.googleapis.com/auth/buzz • Code example is exactly the same, just change the AUTH_TOKEN_TYPE
OAuth 2.0 OAuth 2.0 for Buzz Example
OAuth 2.0 OAuth 2.0 – Issues • Issue #1: User Interface shows the auth token type, not a comprehensible message about what permission is being granted • Quick-and-dirty: you can use “Google Buzz”as an alias for “oauth2:https://www.googleapis.com/auth/buzz”. We will try to set up more of these aliases. • Issue #2: Anonymous unregistered quota only gives you zero or near-zero for new Google APIs like Buzz • Solution: register on the Google apis console and get an “API key” or “access key” from the API Access tab under “Simple API Access” • Make sure you flip the switch to ON for the API you need in the “Services” tab • Free registered quota for Buzz for example 1,000,000 queries/day, so you only pay for usage above that level
OAuth 2.0 OAuth 2.0 for Buzz Example (1 of 4) public class HelloBuzzActivityextends ListActivity { private static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/buzz"; GoogleAccessProtectedResourceaccessProtectedResource= new GoogleAccessProtectedResource(null); Buzz buzz; Account account; @Override public void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); buzz = new Buzz(newNetHttpTransport(), accessProtectedResource, new AndroidJsonFactory()); buzz.accessKey= "ABCdef123_9q"; ... if (account != null && accessProtectedResource.getAccessToken() == null) { getAuthToken(account); } } }
OAuth 2.0 OAuth 2.0 for Buzz Example (2 of 4) @Override protected ActivityFeeddoInBackground(Void... params) { try { // execute HTTP requests } catch (HttpResponseExceptione) { if (e.response.statusCode== 401) { accountManager.invalidateAuthToken(accessProtectedResource.getAccessToken()); accessProtectedResource.setAccessToken(null); SharedPreferences.Editor editor2 = settings.edit(); editor2.remove(PREF_AUTH_TOKEN); editor2.commit(); getAuthToken(account); } }