Upload
charlie-burningham
View
220
Download
1
Embed Size (px)
Citation preview
Best Practices for Accessing Google APIs on AndroidYaniv InbarMay 10, 2011
Speaker Feedback: goo.gl/EUnAoTwitter Hashtags: #io2011 #GoogleAPIs #Android
Google APIs Client Library for Java (goo.gl/ZrqPu)
Now in Beta
4
APIs
OAuth 2.0
ClientLogin
OAuth 1.0
PhotosVideos
XMLJSON
Auth
New Google
APIs
Google DataAPIs
DataFormats
AnyRESTAPIs
Platforms AndroidGoogle
App Engine
Java 5 (SE, EE)
OUTLINE
5
APIs
OAuth 2.0
ClientLogin
OAuth 1.0
PhotosVideos
XMLJSON
Auth
New Google
APIs
Google DataAPIs
DataFormats
AnyRESTAPIs
Platforms AndroidGoogle
App Engine
Java 5 (SE, EE)
OUTLINE
6
PlatformsJava 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
7
HelloBuzz Examplepublic class HelloBuzz {
public static void main(String[] args) throws IOException {
Buzz buzz = new Buzz(new NetHttpTransport(), 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);
}
}
Java 5 (SE, EE)
8
HelloBuzzServlet Examplepublic class HelloBuzzServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain");
PrintWriter writer = resp.getWriter();
Buzz buzz = new Buzz(new UrlFetchTransport(), new JacksonFactory());
ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute();
if (feed.items != null)
for (Activity activity : feed.items)
writer.println(activity.buzzObject.content);
}
}
Google App
Engine
9
HelloBuzzActivity Examplepublic class HelloBuzzActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Buzz buzz = new Buzz(new NetHttpTransport(), 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(new ArrayAdapter<Spanned>(this,
android.R.layout.simple_expandable_list_item_1, activities));
} catch (IOException e) {}
}
}
Android
10
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
11
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:
Platforms
void initializeRequestFactory() {
requestFactory = transport.createRequestFactory(new HttpRequestInitializer() {
public void initialize(HttpRequest request) {
GoogleHeaders headers = new GoogleHeaders();
headers.setApplicationName("Google-CalendarSample/1.0");
headers.gdataVersion = "2”;
request.headers = headers;
request.enableGZipContent = true;
}
});
}
12
Java Platforms – HTTP Trouble-Shooting Tip
• Enable logging of HTTP requests and responses:
– If you also need to see Authorization header, use Level.ALL instead
• On Android, you also need to:
Logger.getLogger("com.google.api.client.http").setLevel(Level.CONFIG);
adb shell setprop log.tag.HttpTransport DEBUG
Platforms
13
AndroidAndroid: 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)
14
HelloBuzzAsyncActivity Example (part 1 of 2)public class HelloBuzzActivity extends ListActivity {
final Buzz buzz = new Buzz(new NetHttpTransport(), new AndroidJsonFactory());
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new LoadActivities().execute();
}
class LoadActivities extends AsyncTask<Void, Void, ActivityFeed> {
final ProgressDialog dialog = new ProgressDialog(HelloBuzzActivity.this);
@Override
protected void onPreExecute() {
dialog.setMessage("Loading Activities...");
dialog.show();
}
@Override
protected ActivityFeed doInBackground(Void... params) {
try {
return buzz.activities.list("112550920411899420633", "@public").execute();
} catch (IOException e) { return null; } }
Android
15
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(new ArrayAdapter<Spanned>(HelloBuzzActivity.this,
android.R.layout.simple_expandable_list_item_1, activities));
}
}
}
Android
16
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
17
Android – Shrink Application with ProGuardAndroid
Full ProGuard0
500
1000
1500
2000
2500
2300
123
Application Size (KBs)
Based on HelloBuzzActivity sample (95% savings)
18
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>;
}
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
# Needed by Guava
-dontwarn sun.misc.Unsafe
Android
19
APIs
OAuth 2.0
ClientLogin
OAuth 1.0
PhotosVideos
XMLJSON
Auth
New Google
APIs
Google DataAPIs
DataFormats
AnyRESTAPIs
Platforms AndroidGoogle
App Engine
Java 5 (SE, EE)
OUTLINE
20
Google Data APIs (“GData”)
• Old Decentralized Architecture
• XML– Some have read-only JSON-C
• ClientLogin, OAuth 1.0, and OAuth 2.0
• Examples:
Google DataAPIs
YouTube Data API
Google Calendar Data API
Blogger Data API
Google Contacts Data API
21
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
Google DataAPIs
22
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:
New Google
APIs
Buzz API
Latitude API
Search API for Shopping
23
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
24
Google APIs – Explorer (goo.gl/Ccni0) New Google
APIs• Web-based Google APIs exploration tool
25
OAuth 2.0 – Google apis console (goo.gl/UyAZB) New Google
APIs
26
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:
New Google
APIs
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();
27
APIs
OAuth 2.0
ClientLogin
OAuth 1.0
PhotosVideos
XMLJSON
Auth
New Google
APIs
Google DataAPIs
DataFormats
AnyRESTAPIs
Platforms AndroidGoogle
App Engine
Java 5 (SE, EE)
OUTLINE
28
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
29
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 GenericJson when you need to preserve arbitrary fields
• Important when using PUT to update an entry to avoid dropping data
JSON
30
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 DateTime updated;
@Key public String id;
@Key public ActivityObject object;
}
public class ActivityObject {
@Key public String content;
}
JSON
Java
JSON
31
JSON – Gzip and Partial ResponseBased on HelloBuzz sample (95% savings)
Full Gzip Gzip & Partial0
1000
2000
3000
4000
5000
6000
7000
8000
7036
1289286
Response Size (bytes)
JSON
32
JSON Example – Partial with the Generated Libraries
• Before:
• After:
• Or for more data:
Buzz.Activities.List request = buzz.activities.list("112550920411899420633", "@public");request.fields = "items/object/content";ActivityFeed feed = request.execute();
JSON
ActivityFeed feed = buzz.activities.list("112550920411899420633", "@public").execute();
Buzz.Activities.List request = buzz.activities.list("112550920411899420633", "@public");request.fields = "items(object/content,updated,id)";ActivityFeed feed = request.execute();
33
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
}
JSON
34
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 DateTime updated;
@Key public Title title;
@Key(“gCal:timezone”) public TimeZone timeZone;
}
public class Title {
@Key(“@type”) public String type;
@Key(“text()”) public String value;
}
public class TimeZone {
@Key(“@value”) public String value;
}
XML
Java
XML
35
XML – Tips
• Must declare XML namespaces:
• Partial response fields mask is automatically computed URL query parameter:
XML
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 {...}
<F extends Feed> F executeGetFeed(CalendarUrl url, Class<F> feedClass) throws IOException { url.fields = GoogleAtom.getFieldsFor(feedClass); HttpRequest request = requestFactory.buildGetRequest(url); return request.execute().parseAs(feedClass);}
36
Media
• Download Media (GET):– HttpResponse.getContent() returns InputStream
– HttpResponse.contentType is the media type
• Upload Media (POST or PUT):– Use InputStreamContent for uploading an input stream
• type is the content type
• length is the length of the media content (if known)
• inputStream is the media content input stream
PhotosVideos
37
MediaPhotosVideos
public class PostPhotoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(); Intent intent = getIntent(); Bundle extras = intent.getExtras(); InputStreamContent content = new InputStreamContent(); ContentResolver contentResolver = 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(new GenericUrl( "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(); }}
38
APIs
OAuth 2.0
ClientLogin
OAuth 1.0
PhotosVideos
XMLJSON
Auth
New Google
APIs
Google DataAPIs
DataFormats
AnyRESTAPIs
Platforms AndroidGoogle
App Engine
Java 5 (SE, EE)
OUTLINE
39
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
Auth
40
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
ClientLogin
41
ClientLogin for Calendar ExampleClientLogin
42
ClientLogin for Calendar Example (1 of 3)public class HelloCalendarActivity extends ListActivity {
GoogleAccountManager accountManager;
HttpRequestFactory requestFactory;
SharedPreferences settings;
String accountName;
String authToken;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestFactory = transport.createRequestFactory(new HttpRequestInitializer() {
public void initialize(HttpRequest request) {
GoogleHeaders headers = new GoogleHeaders();
headers.gdataVersion = "2";
headers.setGoogleLogin(authToken);
request.headers = headers;
}
});
ClientLogin
43
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();
}
}
}
ClientLogin
44
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);
}
ClientLogin
45
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
46
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
47
OAuth 2.0 for Buzz ExampleOAuth
2.0
48
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
49
OAuth 2.0 for Buzz Example (1 of 4)public class HelloBuzzActivity extends ListActivity {
private static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/buzz";
GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(null);
Buzz buzz;
Account account;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
buzz = new Buzz(new NetHttpTransport(), accessProtectedResource, new AndroidJsonFactory());
buzz.accessKey = "ABCdef123_9q";
...
if (account != null && accessProtectedResource.getAccessToken() == null) {
getAuthToken(account);
}
}
}
OAuth 2.0
50
OAuth 2.0 for Buzz Example (2 of 4) @Override
protected ActivityFeed doInBackground(Void... params) {
try {
// execute HTTP requests
} catch (HttpResponseException e) {
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);
}
}
OAuth 2.0
51
OAuth 2.0 for Buzz Example (3 of 4) private static final String AUTH_TOKEN_TYPE = "oauth:https://www.googleapis.com/auth/buzz";
private void getAuthToken(Account account) {
AccountManager.get(this).getAuthToken(account, AUTH_TOKEN_TYPE, true, new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bundle = future.getResult();
if (bundle.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, REQUEST_AUTHENTICATE);
} else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
setAuthToken(bundle.getString(AccountManager.KEY_AUTHTOKEN));
new LoadActivities().execute();
}
} catch (Exception e) {
}
}
}, null);
}
OAuth 2.0
52
OAuth 2.0 for Buzz Example (4 of 4) public static final int REQUEST_AUTHENTICATE = 0;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_AUTHENTICATE:
if (resultCode == RESULT_OK) {
gotAuthToken(account);
} else {
// user denied
}
break;
}
}
OAuth 2.0
53
APIs
OAuth 2.0
ClientLogin
OAuth 1.0
PhotosVideos
XMLJSON
Auth
New Google
APIs
Google DataAPIs
DataFormats
AnyRESTAPIs
Platforms AndroidGoogle
App Engine
Java 5 (SE, EE)
OUTLINE
54
Conclusion
• Google APIs Client Library for Java (goo.gl/ZrqPu)– Now in Beta!
– Supports all Google APIs
• Generated libraries for Google APIs on new infrastructure
• Supports JSON, XML, and media
• Supports ClientLogin and OAuth 2.0
– Android developer’s guide (goo.gl/Wk5An)
– Questions/Comments:
• Support page (goo.gl/PCNUx)
• Discussion on the Google Group (goo.gl/ZOMtp)
Questions?
Speaker Feedback: goo.gl/EUnAoTwitter Hashtags: #io2011 #GoogleAPIs #Android