Category Archives: G_Tips

[Android Dev] Gmail OAuth

自从几个月前Gmail更新之后,第三方app就不能再通过ContentResolver去获取gmail的内容和状态了。因此很多Gmail Unread/Reminder就没办法用以前的方式工作了。我的app里Gmail Reminder的功能也因此暂时取消。
最近终于有时间继续写code,google了一番之后发现目前最可靠的方法就是用OAuth来访问Gmail。

不过在Android实现OAuth还是有一点tricky的,在这边写下来,作为开发笔记,说不定也能帮到一些人 🙂
p.s. 得感谢Google Search和Stackoverflow,从里面抄了不少code.

计划分成2部分: 1) OAuth认证; 2) 通过Token访问Gmail并计算unread count
具体的细节可以直接访问开源项目 https://code.google.com/p/minemessagevibrator/

一)OAuth: 主要使用signpost库实现。(https://code.google.com/p/oauth-signpost/)

1. 是一个处理OAuth以及callback的Activity. 这个Activity负责打开oauth的网站,让用户认证完成之后得到callback,并得到和保存token.
说明:
i) android:scheme和android:host是为了回调的时候响应自定义的URI: “mine-activity://mine-vibration/”
ii) singleTask是为了回调的时候能得到onNewIntent()消息

<activity android:name=".oauth.MineOAuthAccessActivity" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mine-activity" android:host="mine-vibration"/>
</intent-filter>
</activity>

2. 准备signpost的OAuthProvider, 其中consumerKey和consumerSecret是应用的key和secret,熟悉Twitter OAuth的人肯定知道。在Google上可以用”anonymous”

mConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
mProvider = new CommonsHttpOAuthProvider(
"https://www.google.com/accounts/OAuthGetRequestToken?scope="
+ URLEncoder.encode("https://mail.google.com/", "utf-8"),
"https://www.google.com/accounts/OAuthGetAccessToken",
"https://www.google.com/accounts/OAuthAuthorizeToken?hd=default");
mCallbackUrl = "mine-activity://mine-vibration/";

3. 获得request token的URL,很简单。但是注意这一步需要网络连接。

String authUrl = mProvider.retrieveRequestToken(mConsumer, mCallbackUrl);

4. 打开浏览器让用户进行认证,也很简单。

startActivity(new Intent("android.intent.action.VIEW", Uri.parse(ret)).
setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP |
Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_FROM_BACKGROUND));

5. 当用户认证完,网站会重定向到callback URL,就是 “mine-activity://mine-vibration/”,在Android里,会启动MineOAuthAccessActivity并调用onNewIntent(),我们要做的事情是从回调的参数里获得verifier,并获取access token & secret。这样OAuth的认证过程就结束了。之后app可以通过access token去访问gmail。

Uri uri = intent.getData();
String verifier = uri.getQueryParameter("oauth_verifier");
mProvider.retrieveAccessToken(mConsumer, verifier);
mConsumer.getToken();    // AccessToken
mConsumer.getTokenSecret();    // Token Secret

6. 在小内存的机器上(比如说俺的Milestone),启动浏览器之后基本上原来的Activity就会被kill掉,而且process也很可能被杀掉,这样当callback回来的时候,会发现原来的变量都没了。所以我们必须保存OAuth相关的变量,这样可以在启动浏览器之前保存,在重启Activity之后恢复。可以通过java的Serialize object来实现。
保存:

FileOutputStream fos = context.openFileOutput("xxx", // saved file name
Context.MODE_PRIVATE);
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(xxx);    // the object to save
os.close();

读取:

FileInputStream fis;
fis = context.openFileInput("xxx");  // the file name
ObjectInputStream is = new ObjectInputStream(fis);
xxx= (xxx) is.readObject();    // the object to load
is.close();

但是signpost的类虽然是Serializable的,但是其中有个不知道算不算bug的问题,其中CommonsHttpOAuthProvider里的httpClient被声明成transient的,所以这个变量是不能被serialize的。因此得额外加一句:

mProvider.setHttpClient(new DefaultHttpClient());

Done!

btw, 通过Token访问Gmail并计算unread count会有另外一篇blog来介绍。

Share