使用Google帐号登录Android应用

需求

对于一个应用来说,帐号管理真是一件麻烦事,所以通过OAuth之类的使用第三方帐号登录是常见的做法。关于这个实现,在Web端有很多现成的解决方案。我之前发过的《RESTful客户端库:RestClient 》也实现了一些(当时提供了饭否、foursquare和google,现在又增加了twitter)。

那么在移动端要怎么做?

Android系统内置了Google帐号(不要跟我谈被阉过的国行版,我们要放眼国际不是么,而且以此类推同样应该可以实现用别的帐号登录),如果能直接用这个登录岂不是方便?

当然,Google提供了这样的功能。

基本流程

整个登录流程大体是这样:

* 应用在登录界面上显示当前手机中已经登录的所有Google帐号(可能不止一个)
* 用户在界面上选择一个帐号
* 程序向系统请求这个帐号验证(附带参数说明需要用这个帐号干什么,比如登录的话只需要取得用户的基本信息)
* Android在界面上弹出提示(显示什么应用请求什么帐号的什么权限)
* 用户选择同意
* Android向Google服务器提交请求
* Google服务器通过验证后返回一个token
* Android将token返回给应用程序
* 应用程序用这个Token登录应用服务端
* 应用服务端用这个token向Google服务器验证(调用取得用户基本信息的API)
* 成功登录

看上去很复杂,但大部分都是标准操作或是系统内置,对于用户来说很简单:

* 登录应用时选择一个帐号
* 确认
* 等待登录成功

根本不需要输入帐号密码什么的。

功能实现

Android SDK里提供了AccountManager用于实现这一功能。

首先要取得当前手机里已登录的所有Google帐号:

private Account[] mAccounts;

// …

    @Override     public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAccounts = AccountManager.get(this.getActivity()).getAccountsByType("com.google");         for (Account a: mAccounts) {            // a.name is account name         } // … }

说明:上面是以登录页面为一个fragment为例的,所以用了 this.getAcitivity() ,如果登录页面为一个activity,这里用this即可。"com.google"表示只取Google帐号,过滤掉其它帐号。

然后把取得的mAccounts列表显示到界面上,比如用一个ListView,这个视个人爱好,就不具体作代码举例了。

接下来,在用户点击一个帐号以后,需要依次启动两个异步任务:一个用于取得Google的token,一个用这个token登录应用后端。

在启动异步任务之前,在ListView的 onItemClick 事件里取得用户选择的帐号名,然后根据帐号名取得Account对象:

Account a = getAccountByName(name);
    AccountManagerFuture<bundle> amf = AccountManager.get(
	mActivity).getAuthToken(a,
		"oauth2:https://www.googleapis.com/auth/userinfo.email",
		null, mActivity, null, null);

说明:getAccountByName就是个循环,根据name从mAccounts里找到匹配的Account对象。mActivity为当前的Activity,如果是在Fragment里,记得用this.getActivity。

需 要注意的是其中的scope参数,就是例子中的这个"oauth2:https://www.googleapis.com/auth /userinfo.email"。表示用oauth2验证,权限为读取用户基本信息中的email地址信息,更多资料参见 Google API 文档(注意,不是Android开发文档)。

之后启动第一个异步任务:

@Override
protected String doInBackground(Object... params) {
	try {
		return mFuture.getResult(30, TimeUnit.SECONDS).getString("authtoken");
	} catch (OperationCanceledException e) {
		// ...
	} catch (AuthenticatorException e) {
		// ...
	} catch (IOException e) {
		// ...
	}
	return null;
}

说明:AsyncTask的其它部分省略,其中mFuture就是上面取得的AccountManagerFuture<bundle>对象。返回的结果是一个Bundle,所以要调用getString方法取得authtoken。

因 为future.getResult方法是Android系统通过网络向Google服务器请求验证,其间Android还会向用户弹出提示请求的内容, 需要用户确认。所以这个方法的执行时间会比较长,如果不放到异步任务里执行的话,会把界面阻塞住,而且SDK对此也有限制(不放异步任务会报错)。这里只 是举例,所以设置了30秒的超时,实际应该设置得更长一些,给用户留点判断时间,甚至可以不带参数,无限时等待。

取得token以后的事情就是移动应用与应用后端之间的事情。通常就是一个REST请求把token提交给后端,由后端去验证(并取得用户登录身份)。

后端的实现可以用我之前那个RestClient库去调用Google API,取得用户的登录email,然后处理用户的登录操作。具体实现就是一般的第三方登录方式实现,这里略过不提。

推送到[go4pro.org]