解读某OAuth 2.0的开源示例android-oauth-app
2016-05-08 18:50
302 查看
解读某OAuth 2.0的开源示例android-oauth-app
OpenContextApps的android-oauth-app是学习OAuth认证时候找到的一个开源代码. 是4年前的一个Android OAuth 2.0 Demo Application. 认证的地址都已经失效了, 但是还是值得一看的.项目的github地址: https://github.com/OpenConextApps/android-oauth-app
简介:
This mobile application is able to connect to web service secured with OAuth 2.0.
这个app能够使用OAuth 2.0进行web service安全认证.
OAuth Properties 关于OAuth属性的配置文件
The properties file located at res/raw/demo.properties contains the OAuth configuration parameters: 在res/raw/demo.properties中有OAuth的配置参数.authorize_url=https://frko.surfnetlabs.nl/workshop/php-oauth/authorize.php authorize_response_type=code authorize_grant_type=authorization_code authorize_client_id=oauth-mobile-app authorize_scope=grades authorize_redirect_uri=oauth-mobile-app://callback token_url=https://frko.surfnetlabs.nl/workshop/php-oauth/token.php token_grant_type=authorization_code webservice_url=https://frko.surfnetlabs.nl/workshop/php-oauth-grades-rs/api.php/grades/@me
You can modify them for instance to use your own environment. 你可以根据你自己的情况修改它们.
下面开始分析过程:
分析manifest文件
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".StartActivity" android:label="@string/title_activity_start" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SchemeCaptureActivity" android:exported="true" android:label="@string/title_activity_scheme_capture" android:permission="android.permission.INTERNET" > <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="oauth-mobile-app" /> </intent-filter> </activity> </application>
只有两个activity. 注意到SchemeCaptureActivity中可以单独为Activity设置网络访问权限. 另外注意到intent-filter的设置
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> //指定该Activity能被浏览器安全调用 <data android:scheme="oauth-mobile-app" /> </intent-filter>
这里表示android:scheme指定接受Uri的Scheme为oauth-mobile-app, BROWSABLE的意思就是浏览器在特定条件下可以打开你的activity.
整个流程是StartActivity打开浏览器, 然后浏览器在特定的条件下打开SchemeCaptureActivity.
分析StartActivity类
/** * The Home Activity to start the application with. The current settings can be * shown here. With the start button the Authentication flow will be started. * The scheme of the redirect_url will be catch by The other Activity * (SchemeCaptureActivity). * 程序的主Activity. 将显示当前设置. 点击开始按钮将开始认证流程. redirect_url将会被SchemeCaptureActivity捕获. * * @author jknoops @ iprofs.nl */ public class StartActivity extends Activity { private AuthenticationDbService service; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.v("demo.SActivity", "Starting Demo Application"); setContentView(R.layout.activity_start); Log.d("demo.SActivity", "Loading properties"); service = AuthenticationDbService.getInstance(this); //加载属性值 Log.d("demo.SActivity", "Initialiing the screen."); EditText editTextUrl = (EditText) findViewById(R.id.editText_autorize_url); editTextUrl.setText(service.getAuthorize_url()); //设置认证地址 EditText editTextResponseType = (EditText) findViewById(R.id.editText_response_type); editTextResponseType.setText(service.getAuthorize_response_type()); //是指响应类型 EditText editTextClientId = (EditText) findViewById(R.id.editText_client_id); editTextClientId.setText(service.getAuthorize_client_id());//设置认证客户端id EditText editTextRedirectUri = (EditText) findViewById(R.id.editText_redirect_uri); editTextRedirectUri.setText(service.getAuthorize_redirect_uri()); //设置重定向uri Button startButton = (Button) findViewById(R.id.button_start); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { //拼接OAuth认证地址 StringBuilder sb = new StringBuilder(); // basic authorize sb.append(service.getAuthorize_url()); // response type sb.append("?"); sb.append("response_type="); sb.append(service.getAuthorize_response_type()); // client_id sb.append("&"); sb.append("client_id="); sb.append(service.getAuthorize_client_id()); // scope sb.append("&"); sb.append("scope="); sb.append(service.getAuthorize_scope()); // redirect sb.append("&"); sb.append("redirect_uri="); sb.append(service.getAuthorize_redirect_uri()); String url = sb.toString(); Log.d("demo.SActivity", "Starting (Starting class) with url = " + url); //拼接之后的url为https://frko.surfnetlabs.nl/workshop/php-oauth/authorize.php?response_type=code&client_id=oauth-mobile-app&scope=grades&redirect_uri=oauth-mobile-app://callback Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(i); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_start, menu); return true; } }
StartActivity从AuthenticationDbService对象中获得了很多数据. 这些数据是从properties中或则从网络请求中获得的数据, 然后保存在SP文件中.
分析AuthenticationDbService类
/** * A Helper class for storing/retrieving some data. The tokens are stored in a * Preferences file. The properties are loaded from the properties file. * 存储/获取一些数据的帮助类. token值被存储在sp文件中. 属性值保存在properties文件中. */ public class AuthenticationDbService { /** * the local AuthenticationDbService. * 本地验证数据服务对象 */ private static AuthenticationDbService _instance; private static Context _context; //声明Oauth认证中要使用的参数 private static final String ACCESS_TOKEN = "access_token"; //访问的token private static final String REFRESH_TOKEN = "refresh_token"; //刷新的token private static final String TOKEN_TYPE = "token_type"; //token类型 private static final String EXPIRES_IN = "expires_in"; //过期时长 private static final String SCOPE = "scope"; //范围 private static final String EXPIRES_IN_LONG = "expires_in_long"; //过期时间的毫秒值 public static final String RESPONSE_TYPE_TOKEN = "token"; //响应类型token public static final String RESPONSE_TYPE_CODE = "code"; //响应类型code /** * The properties from demo.properties are loaded inside this Properties. * 从demo.properties中获得属性值 */ private Properties demoProperties; /** * The values for the tokens are stored inside the preferences file. * 这些token值保存在sp中 */ private SharedPreferences mPrefs; /** * Static getInstance() method for Singleton pattern. * 单例模式 * * @return the AuthenticationDbService */ public static AuthenticationDbService getInstance(Context context) { if (_instance == null) { _context = context; _instance = new AuthenticationDbService(); } return _instance; } /** * Private constructor * 私有化构造方法,从sp中读取配置信息 */ private AuthenticationDbService() { mPrefs = _context.getSharedPreferences("OpenConext.demo", Context.MODE_PRIVATE); loadProperties(); //加载属性值 } /** * Loading properties from the file system. * 从属性文件中获得属性 */ private void loadProperties() { Resources resources = _context.getResources(); // Read from the /res/raw directory //从 /res/raw目录读取配置信息 try { //openRawResource()读取raw中文件为流的形式 InputStream rawResource = resources.openRawResource(R.raw.demo); demoProperties = new Properties(); demoProperties.load(rawResource); //将值到流中 Log.d("demo.DbService", "The properties are now loaded"); Log.v("demo.DbService", "properties: " + demoProperties); } catch (NotFoundException e) { Log.e("demo.DbService.error", "Did not find raw resource: " + e); } catch (IOException e) { Log.e("demo.DbService.error", "Failed to open demo property file"); } } /** * Retrieving the refresh token from the local storage on the device. * 获得refresh_token */ public String getRefreshToken() { return mPrefs.getString(REFRESH_TOKEN, null); } /** * Storing the refresh token into the local storage on the device. * 将refresh_token保存到sp中 */ public void setRefreshToken(final String token) { Editor editor = mPrefs.edit(); editor.putString(REFRESH_TOKEN, token); editor.commit(); } /** * Retrieving the access token from the local storage on the device. If * there is a value "expires in" is stored, there will be checked if the * access token is still valid. When there is no "expires in" or the access * token is still valid, the access token will be returned. Otherwise an * empty access token will be returned. * <p/> * 从本地获得access_token. 如果存储了过期时间信息, 那么将要检查access_token的有效性. * 当没有过期,或者access_token仍然有效, 将会返回access_token. 否则将会返回空值. */ public String getAccessToken() { //获得过期时间 long expires_in_long = mPrefs.getLong(EXPIRES_IN_LONG, -1); //如果sp不包含expires_in键值, 则直接获得access_token if (!mPrefs.contains(EXPIRES_IN)) { return mPrefs.getString(ACCESS_TOKEN, null); } //如果没有获得过期时间则access_token为空 if (expires_in_long == -1) { return ""; } //获得当前时间的毫秒值 long now_long = new Date().getTime(); //判断过期时间是否大于当前时间 if (expires_in_long > now_long) { Log.v("access_token", "Expires in " + (expires_in_long - now_long) + " milliseconds"); //没有过期则返回access_token return mPrefs.getString(ACCESS_TOKEN, null); } Log.v("access_token", "Overtime " + (now_long - expires_in_long) + " milliseconds"); return ""; } /** * 设置access_token */ public void setAccessToken(final String token) { Editor editor = mPrefs.edit(); editor.putString(ACCESS_TOKEN, token); editor.commit(); } /** * 获得token_type */ public String getTokenType() { return mPrefs.getString(TOKEN_TYPE, null); } /** * 设置token_type */ public void setTokenType(final String type) { Editor editor = mPrefs.edit(); editor.putString(TOKEN_TYPE, type); editor.commit(); } /** * 获得expires_in过期时长 */ public Integer getExpiresIn() { return mPrefs.getInt(EXPIRES_IN, -1); } /** * 设置expires_in过期时长,和expires_in_long过期时间的毫秒值 * 注意:expires_in和expires_in_long总是一起保存的或修改的 */ public void setExpiresIn(final int expiresIn) { //获得当期时间的毫秒值 Date nowDate = new Date(); long nowLong = nowDate.getTime(); //计算expires值 long expiresLong = nowLong + (1000l * expiresIn); //写入sp中 Editor editor = mPrefs.edit(); editor.putLong(EXPIRES_IN_LONG, expiresLong); editor.putInt(EXPIRES_IN, expiresIn); editor.commit(); } /** * 获取scope */ public String getScope() { return mPrefs.getString(SCOPE, null); } /** * 设置scope */ public void setScope(final String scope) { Editor editor = mPrefs.edit(); editor.putString(SCOPE, scope); editor.commit(); } public String getAuthorize_client_secret() { return demoProperties.getProperty("authorize_client_secret", null); } /** * 获得认证地址 */ public String getAuthorize_url() { return demoProperties.getProperty("authorize_url"); } public String getAuthorize_response_type() { return demoProperties.getProperty("authorize_response_type"); } public String getAuthorize_grant_type() { return demoProperties.getProperty("authorize_grant_type"); } public String getAuthorize_client_id() { return demoProperties.getProperty("authorize_client_id"); } public String getAuthorize_scope() { return demoProperties.getProperty("authorize_scope"); } /** * 获得重定向地址 */ public String getAuthorize_redirect_uri() { return demoProperties.getProperty("authorize_redirect_uri"); } public String getToken_url() { return demoProperties.getProperty("token_url"); } public String getToken_grant_type() { return demoProperties.getProperty("token_grant_type"); } public String getWebservice_url() { return demoProperties.getProperty("webservice_url"); } }
整个本地数据存储服务类AuthenticationDbService都很简单. 我们接下来看看能够被浏览器开启的SchemeCaptureActivity类. 整个类代码很长, 需要一点点耐心.
分析SchemeCaptureActivity类
中间留空的一块为EditText, 用来显示一些错误信息和获得的数据.
/** * The Scheme Capture Activity will catch the configured scheme of the * redirect_url. The logic of retrieving the refresh/access token will also be * done in the class. With a valid access token the configured webservices will * be requested to get the data. * <p/> * SchemeCaptureActivity将捕获redirect_url配置的scheme. 该类会获取refresh_token和access_token. * 如果webservice请求时携带一个有效的access_token,将能服务器获得数据 * * @author jknoops @ iprofs.nl */ public class SchemeCaptureActivity extends Activity { Map<String, String> fragments = new HashMap<String, String>(); //使用map保存查询参数 private EditText et; //响应返回类型是否是code private boolean isResponseTypeIsCode; private AuthenticationDbService service = AuthenticationDbService .getInstance(this); //获得数据响应类型码的任务 private RetrieveDataResponseTypeCodeTask retrieveDataResponseTypeCodeTask; //获得refresh_token和access_token的任务 private RetrieveRefreshAndAccessTokenTask retrieveRefreshAndAccessTokenTask; //获得access_token的任务 private RetrieveAccessTokenTask retrieveAccessTokenTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_schemecapture); //响应类型是code吗 isResponseTypeIsCode = AuthenticationDbService.RESPONSE_TYPE_CODE .equals(service.getAuthorize_response_type()); Button refreshButton = (Button) findViewById(R.id.button_refresh); refreshButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { //刷新数据 refreshData(); } }); //获得传过来的url Uri data = getIntent().getData(); Log.v("demo.SCActivity", "Uri = " + data.toString()); //响应类型是否为code if (isResponseTypeIsCode) { //如果返回的是code,则携带code获得请求参数 retrieveQueryParamatersWithResponseTypeCode(data); } else { //如果返回的是token,则携带token获得请求参数 retrieveQueryParametersWithResponseTypeToken(data); } et = (EditText) findViewById(R.id.editText_output); et.setTextSize(10); refreshData(); //刷新数据 } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_start, menu); return true; } /** * Retrieve the parameters from the response data. This method should only * be used when the response type is "token". When the response type is * "token" the response is inside the fragment. The data from the fragment * will be stored in the local fragments Dictionary. * <p/> * 从响应数据中获得参数. 该方法仅仅在响应类型是token的情况下使用. * * @param data - the data to split into key/values */ private void retrieveQueryParametersWithResponseTypeToken(Uri data) { //从Uri中获得fragment String fragment = data.getFragment(); Log.v("demo.SCActivity", "Fragement (Token) = " + fragment); //按照&切割fragment String[] pairs = fragment.split("&"); Log.v("demo.SCActivity", "Pairs (Token) = " + pairs.toString()); int i = 0; String key = ""; String value = ""; StringBuilder sb = new StringBuilder(); //将fragment保存为key-value形式 while (pairs.length > i) { int j = 0; String[] part = pairs[i].split("="); while (part.length > j) { String p = part[j]; if (j == 0) { key = p; sb.append(key + " = "); } else if (j == 1) { value = p; fragments.put(key, value); sb.append(value + "\n"); } Log.v("demo.SCActivity", "[" + i + "," + j + "] = " + p); j++; } i++; } //从fragment中存储token storeTokenFromFragments(); } /** * Retrieve the parameters from the response data. This method should only * be used when the response type is "code". When the response type is * "code" the response is in the queryparameters. The data from the * queryparameters will be stored in the local fragments Dictionary. * 从响应数据中获得参数. 该方法仅仅被使用在响应类型为code时. 查询参数将会被存储在本地的fragment目录中. * * @param data - the data to split into key/values */ private void retrieveQueryParamatersWithResponseTypeCode(Uri data) { //从Uri中获得查询参数 String queryParameters = data.getQuery(); Log.v("demo.SCActivity", "Queryparameters (Code) = " + queryParameters); //切割查询参数 String[] pairs = queryParameters.split("&"); Log.v("demo.SCActivity", "Pairs (Code) = " + pairs.toString()); int i = 0; String key = ""; String value = ""; StringBuilder sb = new StringBuilder(); //将查询参数转化为key-value形式 while (pairs.length > i) { int j = 0; String[] part = pairs[i].split("="); while (part.length > j) { String p = part[j]; if (j == 0) { key = p; sb.append(key + " = "); } else if (j == 1) { value = p; fragments.put(key, value); sb.append(value + "\n"); } Log.v("demo.SCActivity", "[" + i + "," + j + "] = " + p); j++; } i++; } } /** * A task for retrieving the access and (optional) refresh token. The task * will be created when needed. The task will be executed in a separate * Thread. This method should only be used when the response type is "code". * The authorization code is needed to retrieve the requested tokens. This * code should be available in the local dictionary fragments. * 使用code获取refresh_token和access_token. 该方法仅仅被用于响应类型为code时. * 需要使用refresh_code才能获得authorization code. */ private void retrieveRefreshAndAccessTokenWithResponseTypeCode() { if (retrieveRefreshAndAccessTokenTask == null) { retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask(); } Log.d("TASK-1", retrieveRefreshAndAccessTokenTask.toString()); //获取之前的任务状态, 如果为完成,则执行新的异步任务 if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.FINISHED) { retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask(); Log.d("TASK-1-execute1", "RetrieveRefreshAndAccessTokenTask"); retrieveRefreshAndAccessTokenTask.execute(); } else if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.RUNNING) { //如果还有任务正在进行,则什么都不做,等待上次任务的结束 // log Log.d("TASK-1-wait", "RetrieveRefreshAndAccessTokenTask"); this.logUI("Please wait..."); } else { //默认为执行新的异步任务 Log.d("TASK-1-execute2", "RetrieveRefreshAndAccessTokenTask"); retrieveRefreshAndAccessTokenTask.execute(); } } /** * A task for retrieving the access token with a current refresh token. The * task will be created when needed. The task will be executed in a separate * Thread. This method should only be used when the response type is "code". * The refresh token should be available in the local DBService * 该方法逻辑和上面的一样. 使用code获得access_token */ private void retrieveAccessTokenWithResponseTypeCode() { Log.v("demo.OpenConext", "retrieveAccessTokenWithResponseTypeCode"); if (retrieveAccessTokenTask == null) { retrieveAccessTokenTask = new RetrieveAccessTokenTask(); } Log.d("TASK-2", retrieveAccessTokenTask.toString()); if (retrieveAccessTokenTask.getStatus() == Status.FINISHED) { retrieveAccessTokenTask = new RetrieveAccessTokenTask(); Log.d("TASK-2-execute1", "RetrieveAccessTokenTask"); retrieveAccessTokenTask.execute(); } else if (retrieveAccessTokenTask.getStatus() == Status.RUNNING) { // log Log.d("TASK-2-wait", "RetrieveAccessTokenTask"); this.logUI("Please wait..."); } else { Log.d("TASK-2-execute2", "RetrieveAccessTokenTask"); retrieveAccessTokenTask.execute(); } } /** * A task for retrieving the data with a current access token. The task will * be created when needed. The task will be executed in a separate Thread. * This method should only be used when the response type is "code". The * access token should be available in the local DBService * 该方法逻辑和上面的一样. 使用code,access_token获取数据 */ private void retrieveDataWithAccessTokenWithResponseTypeCode() { if (retrieveDataResponseTypeCodeTask == null) { retrieveDataResponseTypeCodeTask = new RetrieveDataResponseTypeCodeTask(); } Log.d("TASK-3", retrieveDataResponseTypeCodeTask.toString()); if (retrieveDataResponseTypeCodeTask.getStatus() == Status.FINISHED) { retrieveDataResponseTypeCodeTask = new RetrieveDataResponseTypeCodeTask(); Log.d("TASK-3-execute1", "RetrieveDataResponseTypeCodeTask"); retrieveDataResponseTypeCodeTask.execute(); } else if (retrieveDataResponseTypeCodeTask.getStatus() == Status.RUNNING) { // log Log.d("TASK-3-wait", "RetrieveDataResponseTypeCodeTask"); this.logUI("Please wait..."); } else { Log.d("TASK-3-execute2", "RetrieveDataResponseTypeCodeTask"); retrieveDataResponseTypeCodeTask.execute(); } } //界面显示日志状态 private void logUI(String text) { Log.v("text-added", text); if (et.getText() == null) { et.setText("" + text); } else { et.setText("" + et.getText() + "\n" + text); } } /** * A task for retrieving the data with a current access token. The task will * be created when needed. The task will be executed in a separate Thread. * This method should only be used when the response type is "token". The * access token should be available in the local DBService * 使用token, access_token获得数据. * 该方法仅仅被使用在响应类型为token时. */ private void retrieveDataWithAccessTokenWithResponseTypeToken() { //从内存中获得access_token的值 String access_token = fragments.get("access_token"); Log.v("demo.OpenConext", "access_token=" + access_token); //获得webService地址 String url = service.getWebservice_url(); Log.v("demo.OpenConext", "url=" + url); BufferedReader in = null; try { URL jsonURL = new URL(url); Log.v("demo.OpenConext", jsonURL.toString()); HttpsURLConnection tc = (HttpsURLConnection) jsonURL .openConnection(); //https连接,添加请求属性Authorization为access_token tc.addRequestProperty("Authorization", "Bearer " + access_token); Log.v("demo.OpenConext", tc.toString()); InputStreamReader isr = new InputStreamReader(tc.getInputStream()); in = new BufferedReader(isr, 256); //拼接响应数据 StringBuilder sb = new StringBuilder(); sb.append(et.getText()); sb.append("\nLenght="); sb.append(tc.getContentLength()); sb.append("\nType="); sb.append(tc.getContentType()); sb.append("\nCode="); sb.append(tc.getResponseCode()); Log.v("demo.OpenConext", "" + tc.getResponseCode()); sb.append("\nMessage="); sb.append(tc.getResponseMessage()); //遍历所有的响应头字段 for (String key : tc.getHeaderFields().keySet()) { Log.v("demo.OpenConext", "key=" + key + " and size=" + tc.getHeaderField(key).length()); } //读取输出 String output = ""; if ((output = in.readLine()) != null) { sb.append("\n"); sb.append(output); Log.v("demo.OpenConext", "output=" + output); } else { sb.append("\n"); sb.append(output); Log.v("demo.OpenConext", "output=" + output); } et.setText(sb.toString()); tc.disconnect(); } catch (Exception e) { Log.e("demo.OpenConext.error", "retrieveDataWithAccessTokenWithResponseTypeToken", e); doAuthentication(); //执行认证 } finally { try { in.close(); } catch (IOException e) { Log.e("demo.OpenConext.error", "retrieveDataWithAccessTokenWithResponseTypeToken", e); doAuthentication(); //执行认证 } } } /** * The default flow for retrieving data. If needed, retrieve refresh and * access token from the authorization code. If needed, retrieve new access * token from the refresh token. If needed, retrieve new authorization code. * retrieve the data. * 获得数据的默认流程. * 如果有必要,可以使用authorization code中获得refresh_token和access_token. * 如果有必要,可以使用refresh_token中获得新的access_token. * 如果有必要,可以获得新的authorization code */ private void refreshData() { //禁用连接池重用 disableConnectionReuseIfNecessary(); //如果是code类型 if (isResponseTypeIsCode) { Log.v("demo.OpenConext", "refreshdata: responseType = code"); String accessToken = service.getAccessToken(); //先从本地获得access_token if (accessToken == null || "".equalsIgnoreCase(accessToken)) { //如果access_token为空 if (service.getRefreshToken() == null || "".equalsIgnoreCase(service.getRefreshToken())) { //如果refresh_token也为空 //使用code获得refresh_token,access_token retrieveRefreshAndAccessTokenWithResponseTypeCode(); } else { //如果refresh_token不为空,则直接使用code获得access_token retrieveAccessTokenWithResponseTypeCode(); } } else { //如果不为空,则直接使用code和access_token获得数据 retrieveDataWithAccessTokenWithResponseTypeCode(); } } else { //使用token和access_token获得数据 retrieveDataWithAccessTokenWithResponseTypeToken(); } } /** * For use with android 2.2 (FROYO) the property http.keepAlive needs to be * set on false. After this the connection pooling won't be used. * <p/> * 使用android 2.2(FROYO)系统, http头keepAlive需要设置为false. 设置之后http连接池将不会被使用. */ private static void disableConnectionReuseIfNecessary() { // HTTP connection reuse which was buggy pre-froyo // 安卓2.2之前http连接重试的的一个bug if (Build.VERSION.SDK_INT == 8) { System.setProperty("http.keepAlive", "false"); } } /** * The properties from the tokenString will be stored in the local * DBService. The access token and the token type are required. */ private void storeTokens(String tokenString) throws JSONException { JSONObject jo = new JSONObject(tokenString); if ((!jo.has("access_token")) && (!jo.has("token_type"))) { // Nothing to store return; } service.setAccessToken(jo.getString("access_token")); service.setTokenType(jo.getString("token_type")); if (jo.has("refresh_token")) { service.setRefreshToken(jo.getString("refresh_token")); } if (jo.has("expires_in")) { service.setExpiresIn(jo.getInt("expires_in")); } if (jo.has("scope")) { service.setScope(jo.getString("scope")); } } /** * The properties from the local fragments will be stored in the DBService. * 将access_token和token_type保存到本地sp文件 */ private void storeTokenFromFragments() { //保存access_token到sp中 if (fragments.containsKey("access_token")) { service.setAccessToken(fragments.get("access_token")); } else { service.setAccessToken(""); } //保存token_type到sp中 if (fragments.containsKey("token_type")) { service.setTokenType(fragments.get("toke_type")); } else { service.setTokenType(""); } } /** * When the user is required to authenticate again, the refresh and access * tokens will be removed from the DBService. The right URL will be made * from the authentication properties. A activity will be activated to * execute the created URL. Because the of the configured scheme * configuration, this application will capture the response from the OAuth * server. * 当用户再次请求认证,本地存储的refresh_token和access_token将会被删除. * 将从认证properties中获得正确的URL. 因为配置的scheme信息,一个activity将被激活用来执行创建的url. 该应用将捕捉到OAuth认证服务器的响应. */ public void doAuthentication() { //请求access_token和refresh_token service.setAccessToken(""); service.setRefreshToken(""); //拼接认证的url StringBuilder sb = new StringBuilder(); // basic authorize sb.append(service.getAuthorize_url()); //获得认证地址 // response type sb.append("?"); sb.append("response_type="); //请求类型 sb.append(service.getAuthorize_response_type()); // client_id sb.append("&"); sb.append("client_id="); //客户端id sb.append(service.getAuthorize_client_id()); // scope sb.append("&"); sb.append("scope="); //范围 sb.append(service.getAuthorize_scope()); // redirect sb.append("&"); sb.append("redirect_uri="); //重定向地址 sb.append(service.getAuthorize_redirect_uri()); String url = sb.toString(); Log.v("demo.OpenConext", "Starting (Scheme Class) with url = " + url); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(i); } /** * A AsyncTask for retrieving the data from the configured webservice with a * access token. This task should only be used when the response type is * "code". If needed the access token will be retrieved again. If the * postExecute the data retrieved from the webservice will be put in a * component on the screen. * 使用access_token获得数据. * 如果有必要access_token将会被重新获得.如果能够获取数据,将显示在屏幕中 * * @author jknoops @ iprofs.nl */ private class RetrieveDataResponseTypeCodeTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String result = ""; HttpURLConnection conn = null; try { URL webserviceUrl = new URL(service.getWebservice_url()); conn = (HttpURLConnection) webserviceUrl.openConnection(); if (service.getTokenType().equalsIgnoreCase("bearer")) { conn.setRequestProperty("Authorization", "Bearer " + service.getAccessToken()); } Log.d("demo.OpenConext", conn.getRequestProperties().toString()); Log.v("demo.OpenConext", conn.toString()); InputStreamReader isr = new InputStreamReader( conn.getInputStream()); BufferedReader in = new BufferedReader(isr, 256); String response = ""; StringBuilder sb_output = new StringBuilder(); while ((response = in.readLine()) != null) { Log.v("demo.OpenConext", "response=" + response); sb_output.append(response); } sb_output.append("\n"); result = sb_output.toString(); } catch (MalformedURLException e) { Log.e("demo.OpenConext.error", "retrieveDataWithAccessTokenWithResponseTypeCode", e); } catch (IOException e) { try { Log.d("demo.OpenConext.error", "" + conn.getResponseCode() + " " + conn.getResponseMessage()); int responseCode = conn.getResponseCode(); if (responseCode == 401) { // token invalid // token无效 StringBuilder sb_output = new StringBuilder(); sb_output.append("\n"); sb_output.append("Oops the token is invalid, let me try again!\n"); result = sb_output.toString(); //获取access_token retrieveAccessTokenWithResponseTypeCode(); } else { // something else // 如果是其他错误 StringBuilder sb_output = new StringBuilder(); sb_output.append("\n"); sb_output.append("Oops something happend!\n"); sb_output.append("HTTP response code = " + responseCode + "\n"); sb_output.append("HTTP response msg = " + conn.getResponseMessage() + "\n"); result = sb_output.toString(); } } catch (IOException e1) { Log.e("demo.OpenConext.error", "RetrieveDataResponseTypeCodeTask", e); } } return result; } @Override protected void onPostExecute(String result) { Log.d("DEBUG-RetrieveDataResponseTypeCodeTask", "onPostExecute = " + result); logUI(result); } } /** * A asynctask for retrieving the access and (optional) refresh token from * the configured OAuth server. This task should only be used when the * response type is "code". The authorization code can only be used once. * After using the authorization code, the code will be removed from the * local DBService. If the postExecute the tokens will be stored and the * task for retrieving the data will be executed. * <p/> * 从OAuth Server获得refresh_token和access_token的任务. * 该任务仅仅被用在响应类型为code时. * authorization code只能被使用一次. * 使用了authorization code之后, authorization code将从本地移除. * 在postExecute()中将存储token的值. * 并执行获得数据的方法 * * @author jknoops @ iprofs.nl */ private class RetrieveRefreshAndAccessTokenTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String result = ""; if (fragments.containsKey("code")) { String code = fragments.get("code"); Log.v("demo.OpenConext", "code=" + code); //获得token_url String url = service.getToken_url(); URL tokenUrl; try { tokenUrl = new URL(url); //使用post表单提交 HttpsURLConnection conn = (HttpsURLConnection) tokenUrl .openConnection(); //将参数进行URL编码 String param = "grant_type=" + URLEncoder.encode( service.getAuthorize_grant_type(), "UTF-8") + "&code=" + URLEncoder.encode(code, "UTF-8") + "&redirect_uri=" + URLEncoder.encode( service.getAuthorize_redirect_uri(), "UTF-8") + "&client_id=" + URLEncoder.encode( service.getAuthorize_client_id(), "UTF-8"); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setFixedLengthStreamingMode(param.getBytes().length); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // send the POST out // 发送post数据 PrintWriter out = new PrintWriter(conn.getOutputStream()); out.print(param); out.close(); // build the string to store the response text from the // server String response = ""; Log.d("DEBUG", "" + conn.getResponseCode()); Log.d("DEBUG", "" + conn.getResponseMessage()); // start listening to the stream Scanner inStream = new Scanner(conn.getInputStream()); // process the stream and store it in StringBuilder // 处理流,并将数据存入StringBuilder中 while (inStream.hasNextLine()) { response += (inStream.nextLine()); } Log.v("demo.OpenConext", response); result = response; } catch (MalformedURLException e) { Log.e("demo.OpenConext.error", "retrieveRefreshAndAccessTokenWithResponseTypeCode", e); } catch (IOException e) { Log.e("demo.OpenConext.error", "retrieveRefreshAndAccessTokenWithResponseTypeCode", e); } fragments.remove("code"); //code只能被使用一次 } return result; } @Override protected void onPostExecute(String result) { if (result != null && !"".equals(result)) { try { storeTokens(result); //存储token //使用code,access_token获得数据 retrieveDataWithAccessTokenWithResponseTypeCode(); } catch (JSONException e) { doAuthentication(); //重新执行认证 } } else { doAuthentication(); //重写执行认证 } } } /** * A AsyncTask for retrieving the access token from the configured OAuth * server with a refresh token. This task should only be used when the * response type is "code". The refresh token can only be used multiple * times. If the postExecute the tokens will be stored and the task for * retrieving the data will be executed. * 使用refresh_token获得access_token. * 该任务仅仅被使用在响应类型为code时 * refresh_token可以被使用多次. * 执行postExecute()时,将保存token值,并执行获得数据的方法. * * @author jknoops @ iprofs.nl */ private class RetrieveAccessTokenTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String result = ""; //获得token_url String url = service.getToken_url(); URL tokenUrl; HttpsURLConnection conn = null; try { tokenUrl = new URL(url); conn = (HttpsURLConnection) tokenUrl.openConnection(); //通过refresh_token获得access_token,通过post表单提交 String param = "grant_type=" + URLEncoder.encode("refresh_token", "UTF-8") + "&refresh_token=" + URLEncoder.encode(service.getRefreshToken(), "UTF-8"); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setFixedLengthStreamingMode(param.getBytes().length); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // send the POST out PrintWriter out = new PrintWriter(conn.getOutputStream()); out.print(param); out.close(); // build the string to store the response text from the server String response = ""; // start listening to the stream Scanner inStream = new Scanner(conn.getInputStream()); // process the stream and store it in StringBuilder while (inStream.hasNextLine()) { response += (inStream.nextLine()); } Log.v("demo.OpenConext", response); result = response; } catch (MalformedURLException e) { Log.e("demo.OpenConext.error", "retrieveAccessTokenWithResponseTypeCode", e); } catch (IOException e) { try { Log.d("demo.OpenConext.error", "" + conn.getResponseCode() + " " + conn.getResponseMessage()); int responseCode = conn.getResponseCode(); if (responseCode == 400) { Log.v("demo.SCActivity.error", "Bad request, authenticate again. Refresh token is not valid anymore."); return ""; } else { Log.v("demo.SCActivity.error", "Bad request, authenticate again. Refresh token is not valid anymore."); // something else StringBuilder sb_output = new StringBuilder(); sb_output.append("Oops something happend!\n"); sb_output.append("HTTP response code = " + responseCode + "\n"); sb_output.append("HTTP response msg = " + conn.getResponseMessage() + "\n"); Log.v("demo.SCActivity.error", sb_output.toString()); return ""; } } catch (IOException e1) { Log.e("demo.OpenConext.error", "RetrieveDataResponseTypeCodeTask", e); } } return result; } @Override protected void onPostExecute(String result) { if (result != null && !"".equals(result)) { logUI("Retrieved new Token(s)"); try { storeTokens(result); //存储token retrieveDataWithAccessTokenWithResponseTypeCode(); //通过access_token获得数据 } catch (JSONException e) { doAuthentication(); //重新执行认证 } } else { doAuthentication(); //重新执行认证 } } } }
800多行的代码. 其实很多流程都是类似的. 下面来进行分解.
分析onCreate()方法中重要的代码
//响应类型是否为code if (isResponseTypeIsCode) { //如果返回的是code,则携带code获得请求参数 retrieveQueryParamatersWithResponseTypeCode(data); } else { //如果返回的是token,则携带token获得请求参数 retrieveQueryParametersWithResponseTypeToken(data); } //................ refreshData(); //刷新数据
分析retrieveQueryParamatersWithResponseTypeCode()方法
/** * Retrieve the parameters from the response data. This method should only * be used when the response type is "code". When the response type is * "code" the response is in the queryparameters. The data from the * queryparameters will be stored in the local fragments Dictionary. * 从响应数据中获得参数. 该方法仅仅被使用在响应类型为code时. 查询参数将会被存储在fragments的map中 */ private void retrieveQueryParamatersWithResponseTypeCode(Uri data) { //从Uri中获得查询参数 String queryParameters = data.getQuery(); //切割查询参数 String[] pairs = queryParameters.split("&"); int i = 0; String key = ""; String value = ""; //将查询参数转化为key-value形式,保存在fragments的map中 StringBuilder sb = new StringBuilder(); while (pairs.length > i) { int j = 0; String[] part = pairs[i].split("="); while (part.length > j) { String p = part[j]; if (j == 0) { key = p; sb.append(key + " = "); } else if (j == 1) { value = p; fragments.put(key, value); sb.append(value + "\n"); } j++; } i++; } }
而retrieveQueryParametersWithResponseTypeToken()和retrieveQueryParamatersWithResponseTypeCode()基本相同, 只是最后将获得的token保存到了本地.
分析retrieveQueryParamatersWithResponseTypeToken()方法
/** * Retrieve the parameters from the response data. This method should only * be used when the response type is "token". When the response type is * "token" the response is inside the fragment. The data from the fragment * will be stored in the local fragments Dictionary. * <p/> * 从响应数据中获得参数. 该方法仅仅在响应类型是token的情况下使用. */ private void retrieveQueryParametersWithResponseTypeToken(Uri data) { //从Uri中获得fragment String fragment = data.getFragment(); //按照&切割fragment String[] pairs = fragment.split("&"); int i = 0; String key = ""; String value = ""; StringBuilder sb = new StringBuilder(); //将fragment保存为key-value形式 while (pairs.length > i) { int j = 0; String[] part = pairs[i].split("="); while (part.length > j) { String p = part[j]; if (j == 0) { key = p; sb.append(key + " = "); } else if (j == 1) { value = p; fragments.put(key, value); sb.append(value + "\n"); } j++; } i++; } //将fragments中的token保存到本地 storeTokenFromFragments(); }
这里需要说明的是, 这里的fragment不是我们平常使用的那个fragment, 而是Uri的组成部分之一.
Uri的组成部分
[scheme:][//authority][path][?query][#fragment]
详细可以参考这篇文章: Uri详解之——Uri结构与代码提取
看看如何将token相关的信息保存到本地的storeTokenFromFragments()方法
分析storeTokenFromFragments()方法
/** * The properties from the local fragments will be stored in the DBService. * 将access_token和token_type保存到本地sp文件 */ private void storeTokenFromFragments() { //保存access_token到sp中 if (fragments.containsKey("access_token")) { service.setAccessToken(fragments.get("access_token")); } else { service.setAccessToken(""); } //保存token_type到sp中 if (fragments.containsKey("token_type")) { service.setTokenType(fragments.get("toke_type")); } else { service.setTokenType(""); } }
接下来解析refreshData()方法. 这个方法是OAuth认证流程的一个重要的方法.
分析refreshData()方法
/** * The default flow for retrieving data. If needed, retrieve refresh and * access token from the authorization code. If needed, retrieve new access * token from the refresh token. If needed, retrieve new authorization code. * retrieve the data. * 获得数据的默认流程. * 如果有必要,可以使用authorization code中获得refresh_token和access_token. * 如果有必要,可以使用refresh_token中获得新的access_token. * 如果有必要,可以获得新的authorization code */ private void refreshData() { //禁用连接池重用 disableConnectionReuseIfNecessary(); //如果是code类型 if (isResponseTypeIsCode) { String accessToken = service.getAccessToken(); //先从本地获得access_token if (accessToken == null || "".equalsIgnoreCase(accessToken)) { //如果access_token为空 if (service.getRefreshToken() == null || "".equalsIgnoreCase(service.getRefreshToken())) { //如果refresh_token也为空 //使用code获得refresh_token,access_token retrieveRefreshAndAccessTokenWithResponseTypeCode(); } else { //如果refresh_token不为空,则直接使用code获得access_token retrieveAccessTokenWithResponseTypeCode(); } } else { //如果不为空,则直接使用code和access_token获得数据 retrieveDataWithAccessTokenWithResponseTypeCode(); } } else { //使用token和access_token获得数据 retrieveDataWithAccessTokenWithResponseTypeToken(); } }
首先还是看看 disableConnectionReuseIfNecessary();//禁用连接池重用. 从注释中可以看出android 2.2之前版本的HTTP连接池有一个bug, 这里就是禁用连接池重用.
分析disableConnectionReuseIfNecessary()方法
/** * For use with android 2.2 (FROYO) the property http.keepAlive needs to be * set on false. After this the connection pooling won't be used. * <p/> * 使用android 2.2(FROYO)系统, http头keepAlive需要设置为false. 设置之后http连接池将不会被使用. */ private static void disableConnectionReuseIfNecessary() { // HTTP connection reuse which was buggy pre-froyo // 安卓2.2之前http连接重试的的一个bug if (Build.VERSION.SDK_INT == 8) { System.setProperty("http.keepAlive", "false"); } }
其中主要调用的方法有
retrieveRefreshAndAccessTokenWithResponseTypeCode(),
retrieveAccessTokenWithResponseTypeCode(),
retrieveDataWithAccessTokenWithResponseTypeCode(),
这三个方法的代码都很类似. 我们只要看一个就好了.
分析retrieveRefreshAndAccessTokenWithResponseTypeCode()方法
/** * A task for retrieving the access and (optional) refresh token. The task * will be created when needed. The task will be executed in a separate * Thread. This method should only be used when the response type is "code". * The authorization code is needed to retrieve the requested tokens. This * code should be available in the local dictionary fragments. * 使用code获取refresh_token和access_token. 该方法仅仅被用于响应类型为code时. * 需要使用refresh_code才能获得authorization code. */ private void retrieveRefreshAndAccessTokenWithResponseTypeCode() { if (retrieveRefreshAndAccessTokenTask == null) { retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask(); } //获取之前的任务状态, 如果为完成,则执行新的异步任务 if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.FINISHED) { retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask(); retrieveRefreshAndAccessTokenTask.execute(); } else if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.RUNNING) { //如果还有任务正在进行,则什么都不做,等待上次任务的结束 this.logUI("Please wait..."); } else { //默认为执行新的异步任务 retrieveRefreshAndAccessTokenTask.execute(); } }
从这些方法可以看出这里使用的是AsyncTask()进行网络请求的. 那么重点也就是这些Task了.
1.RetrieveRefreshAndAccessTokenTask类//如果没有refresh_token和access_token, 则执行RetrieveRefreshAndAccessTokenTask获取.
2.RetrieveAccessTokenTask类 //如果有refresh_token但是没有access_token, 则执行RetrieveAccessTokenTask获取.
3.RetrieveDataResponseTypeCodeTask类 //如果有access_token且响应类型为code, 则执行RetrieveDataResponseTypeCodeTask获取.
4.剩下的一种情况: 如果响应类型为token,则直接调用retrieveDataWithAccessTokenWithResponseTypeToken()获取
分析RetrieveRefreshAndAccessTokenTask类
/** * A asynctask for retrieving the access and (optional) refresh token from * the configured OAuth server. This task should only be used when the * response type is "code". The authorization code can only be used once. * After using the authorization code, the code will be removed from the * local DBService. If the postExecute the tokens will be stored and the * task for retrieving the data will be executed. * <p/> * 从OAuth Server获得refresh_token和access_token的任务. * 该任务仅仅被用在响应类型为code时. * authorization code只能被使用一次. * 使用了authorization code之后, authorization code将从本地移除. * 在postExecute()中将存储token的值. * 并执行获得数据的方法 */ private class RetrieveRefreshAndAccessTokenTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String result = ""; if (fragments.containsKey("code")) { String code = fragments.get("code"); //获得token_url String url = service.getToken_url(); URL tokenUrl; try { tokenUrl = new URL(url); //使用post表单提交 HttpsURLConnection conn = (HttpsURLConnection) tokenUrl .openConnection(); //将参数进行URL编码 String param = "grant_type=" + URLEncoder.encode( service.getAuthorize_grant_type(), "UTF-8") + "&code=" + URLEncoder.encode(code, "UTF-8") + "&redirect_uri=" + URLEncoder.encode( service.getAuthorize_redirect_uri(), "UTF-8") + "&client_id=" + URLEncoder.encode( service.getAuthorize_client_id(), "UTF-8"); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setFixedLengthStreamingMode(param.getBytes().length); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // send the POST out // 发送post数据 PrintWriter out = new PrintWriter(conn.getOutputStream()); out.print(param); out.close(); // build the string to store the response text from the // server String response = ""; Log.d("DEBUG", "" + conn.getResponseCode()); Log.d("DEBUG", "" + conn.getResponseMessage()); // start listening to the stream Scanner inStream = new Scanner(conn.getInputStream()); // process the stream and store it in StringBuilder // 处理流,并将数据存入StringBuilder中 while (inStream.hasNextLine()) { response += (inStream.nextLine()); } result = response; } catch (MalformedURLException e) { Log.e("demo.OpenConext.error", "retrieveRefreshAndAccessTokenWithResponseTypeCode", e); } catch (IOException e) { Log.e("demo.OpenConext.error", "retrieveRefreshAndAccessTokenWithResponseTypeCode", e); } fragments.remove("code"); //code只能被使用一次 } return result; } @Override protected void onPostExecute(String result) { if (result != null && !"".equals(result)) { try { storeTokens(result); //存储token //使用code,access_token获得数据 retrieveDataWithAccessTokenWithResponseTypeCode(); } catch (JSONException e) { doAuthentication(); //重新执行认证 } } else { doAuthentication(); //重新执行认证 } } }
使用POST请求携带code提交. 请求成功后code失效, 将获得的tokens信息保存到本地. 再通过access_token获得数据. 如果没有获得tokens信息, 则重写执行OAuth认证.
保存token信息的方法, 很简单.
分析storeTokens()方法
/** * The properties from the tokenString will be stored in the local * DBService. The access token and the token type are required. * 将token信息保存到本地 */ private void storeTokens(String tokenString) throws JSONException { JSONObject jo = new JSONObject(tokenString); if ((!jo.has("access_token")) && (!jo.has("token_type"))) { // Nothing to store return; } service.setAccessToken(jo.getString("access_token")); service.setTokenType(jo.getString("token_type")); if (jo.has("refresh_token")) { service.setRefreshToken(jo.getString("refresh_token")); } if (jo.has("expires_in")) { service.setExpiresIn(jo.getInt("expires_in")); } if (jo.has("scope")) { service.setScope(jo.getString("scope")); } }
再来看看重新执行认证的方法doAuthentication().
分析doAuthentication()方法
/** * When the user is required to authenticate again, the refresh and access * tokens will be removed from the DBService. The right URL will be made * from the authentication properties. A activity will be activated to * execute the created URL. Because the of the configured scheme * configuration, this application will capture the response from the OAuth * server. * 当用户再次请求认证,本地存储的refresh_token和access_token将会被删除. * 将从认证配置文件properties中获得正确的URL. 因为配置的scheme信息,一个activity将被激活用来执行创建的url. 该应用将捕捉到OAuth认证服务器的响应. */ public void doAuthentication() { //请求access_token和refresh_token service.setAccessToken(""); service.setRefreshToken(""); //拼接认证的url StringBuilder sb = new StringBuilder(); // basic authorize sb.append(service.getAuthorize_url()); //获得认证地址 // response type sb.append("?"); sb.append("response_type="); //请求类型 sb.append(service.getAuthorize_response_type()); // client_id sb.append("&"); sb.append("client_id="); //客户端id sb.append(service.getAuthorize_client_id()); // scope sb.append("&"); sb.append("scope="); //范围 sb.append(service.getAuthorize_scope()); // redirect sb.append("&"); sb.append("redirect_uri="); //重定向地址 sb.append(service.getAuthorize_redirect_uri()); String url = sb.toString(); Log.v("demo.OpenConext", "Starting (Scheme Class) with url = " + url); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(i); }
这个和StartActivity中的请求验证的代码就是一样的.
分析RetrieveAccessTokenTask类
再来看RetrieveAccessTokenTask, 其实和上面的RetrieveRefreshAndAccessTokenTask流程上没有什么区别. 只是携带的post参数不同而已./** * A AsyncTask for retrieving the access token from the configured OAuth * server with a refresh token. This task should only be used when the * response type is "code". The refresh token can only be used multiple * times. If the postExecute the tokens will be stored and the task for * retrieving the data will be executed. * 使用refresh_token获得access_token. * 该任务仅仅被使用在响应类型为code时 * refresh_token可以被使用多次. * 执行postExecute()时,将保存token值,并执行获得数据的方法. */ private class RetrieveAccessTokenTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String result = ""; //获得token_url String url = service.getToken_url(); URL tokenUrl; HttpsURLConnection conn = null; try { tokenUrl = new URL(url); conn = (HttpsURLConnection) tokenUrl.openConnection(); //通过refresh_token获得access_token,通过post表单提交 String param = "grant_type=" + URLEncoder.encode("refresh_token", "UTF-8") + "&refresh_token=" + URLEncoder.encode(service.getRefreshToken(), "UTF-8"); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setFixedLengthStreamingMode(param.getBytes().length); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // send the POST out PrintWriter out = new PrintWriter(conn.getOutputStream()); out.print(param); out.close(); // build the string to store the response text from the server String response = ""; // start listening to the stream Scanner inStream = new Scanner(conn.getInputStream()); // process the stream and store it in StringBuilder while (inStream.hasNextLine()) { response += (inStream.nextLine()); } Log.v("demo.OpenConext", response); result = response; } catch (MalformedURLException e) { Log.e("demo.OpenConext.error", "retrieveAccessTokenWithResponseTypeCode", e); } catch (IOException e) { try { Log.d("demo.OpenConext.error", "" + conn.getResponseCode() + " " + conn.getResponseMessage()); int responseCode = conn.getResponseCode(); if (responseCode == 400) { Log.v("demo.SCActivity.error", "Bad request, authenticate again. Refresh token is not valid anymore."); return ""; } else { Log.v("demo.SCActivity.error", "Bad request, authenticate again. Refresh token is not valid anymore."); // something else StringBuilder sb_output = new StringBuilder(); sb_output.append("Oops something happend!\n"); sb_output.append("HTTP response code = " + responseCode + "\n"); sb_output.append("HTTP response msg = " + conn.getResponseMessage() + "\n"); Log.v("demo.SCActivity.error", sb_output.toString()); return ""; } } catch (IOException e1) { Log.e("demo.OpenConext.error", "RetrieveDataResponseTypeCodeTask", e); } } return result; } @Override protected void onPostExecute(String result) { if (result != null && !"".equals(result)) { logUI("Retrieved new Token(s)"); try { storeTokens(result); //存储token信息到本地 retrieveDataWithAccessTokenWithResponseTypeCode(); //通过access_token获得数据 } catch (JSONException e) { doAuthentication(); //重新执行认证 } } else { doAuthentication(); //重新执行认证 } } }
再来看看RetrieveDataResponseTypeCodeTask.
分析RetrieveDataResponseTypeCodeTask类
/** * A AsyncTask for retrieving the data from the configured webservice with a * access token. This task should only be used when the response type is * "code". If needed the access token will be retrieved again. If the * postExecute the data retrieved from the webservice will be put in a * component on the screen. * 使用access_token获得数据. * 如果有必要access_token将会被重新获得.如果能够获取数据,将显示在屏幕中 */ private class RetrieveDataResponseTypeCodeTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String result = ""; HttpURLConnection conn = null; try { URL webserviceUrl = new URL(service.getWebservice_url()); conn = (HttpURLConnection) webserviceUrl.openConnection(); //携带access_token if (service.getTokenType().equalsIgnoreCase("bearer")) { conn.setRequestProperty("Authorization", "Bearer " + service.getAccessToken()); } InputStreamReader isr = new InputStreamReader(conn.getInputStream()); BufferedReader in = new BufferedReader(isr, 256); String response = ""; StringBuilder sb_output = new StringBuilder(); while ((response = in.readLine()) != null) { sb_output.append(response); } sb_output.append("\n"); result = sb_output.toString(); } catch (MalformedURLException e) { Log.e("demo.OpenConext.error", "retrieveDataWithAccessTokenWithResponseTypeCode", e); } catch (IOException e) { try { Log.d("demo.OpenConext.error", "" + conn.getResponseCode() + " " + conn.getResponseMessage()); int responseCode = conn.getResponseCode(); if (responseCode == 401) { // token invalid token无效 StringBuilder sb_output = new StringBuilder(); sb_output.append("\n"); sb_output.append("Oops the token is invalid, let me try again!\n"); result = sb_output.toString(); //使用该access_token不能获得数据的话,那么将重新获取access_token retrieveAccessTokenWithResponseTypeCode(); } else { // something else // 如果是其他错误 StringBuilder sb_output = new StringBuilder(); sb_output.append("\n"); sb_output.append("Oops something happend!\n"); sb_output.append("HTTP response code = " + responseCode + "\n"); sb_output.append("HTTP response msg = " + conn.getResponseMessage() + "\n"); result = sb_output.toString(); } } catch (IOException e1) { Log.e("demo.OpenConext.error", "RetrieveDataResponseTypeCodeTask", e); } } return result; } @Override protected void onPostExecute(String result) { Log.d("DEBUG-RetrieveDataResponseTypeCodeTask", "onPostExecute = " + result); logUI(result); } }
最后看看retrieveDataWithAccessTokenWithResponseTypeToken(), 这个方法名长的有点变态了.
分析retrieveDataWithAccessTokenWithResponseTypeToken()方法
/** * A task for retrieving the data with a current access token. The task will * be created when needed. The task will be executed in a separate Thread. * This method should only be used when the response type is "token". The * access token should be available in the local DBService * 使用token, access_token获得数据. * 该方法仅仅被使用在响应类型为token时. */ private void retrieveDataWithAccessTokenWithResponseTypeToken() { //从fragments的map中获得access_token的值 String access_token = fragments.get("access_token"); //获得webService地址 String url = service.getWebservice_url(); BufferedReader in = null; try { URL jsonURL = new URL(url); HttpsURLConnection tc = (HttpsURLConnection) jsonURL .openConnection(); //https连接,添加请求属性Authorization为access_token tc.addRequestProperty("Authorization", "Bearer " + access_token); Log.v("demo.OpenConext", tc.toString()); InputStreamReader isr = new InputStreamReader(tc.getInputStream()); in = new BufferedReader(isr, 256); //拼接响应数据 StringBuilder sb = new StringBuilder(); sb.append(et.getText()); sb.append("\nLenght="); sb.append(tc.getContentLength()); sb.append("\nType="); sb.append(tc.getContentType()); sb.append("\nCode="); sb.append(tc.getResponseCode()); Log.v("demo.OpenConext", "" + tc.getResponseCode()); sb.append("\nMessage="); sb.append(tc.getResponseMessage()); //遍历所有的响应头字段 for (String key : tc.getHeaderFields().keySet()) { Log.v("demo.OpenConext", "key=" + key + " and size=" + tc.getHeaderField(key).length()); } //读取输出 String output = ""; if ((output = in.readLine()) != null) { sb.append("\n"); sb.append(output); Log.v("demo.OpenConext", "output=" + output); } else { sb.append("\n"); sb.append(output); Log.v("demo.OpenConext", "output=" + output); } et.setText(sb.toString()); tc.disconnect(); } catch (Exception e) { Log.e("demo.OpenConext.error", "retrieveDataWithAccessTokenWithResponseTypeToken", e); doAuthentication(); //执行认证 } finally { try { in.close(); } catch (IOException e) { Log.e("demo.OpenConext.error", "retrieveDataWithAccessTokenWithResponseTypeToken", e); doAuthentication(); //执行认证 } } }
基本上整个流程都已经分析完毕了. 虽然代码有点多, 但是不难, 不是吗?
相关文章推荐
- Android之使用GSON解析JSON数据
- Android 定位服务(Location-Based Services)
- Cocos2dx 3.10 apk打包命令
- AndroidStudio中导入jar包方法(超详细)
- [android] 手机卫士自定义吐司
- Android studio启动时报错
- .net下4款不错的微信SDK
- 仿进入GameCenter的五个气球效果
- android中Context的应用总结
- Android实现查询数据库,把数据库内容显示到屏幕
- 文件输入输出
- Android DiskLruCache框架解析,硬盘加载图片到缓存
- couldn't load cocos2dcpp from loader dalvik.system.pathclassloader
- Unity跑酷游戏的无尽关卡是如何生成的?
- android ignore="MergeRootFrame" 有什么用
- Android获取keystore文件的信息
- 史上最详细的iOS之事件的传递和响应机制
- Android-日志打印工具类
- Android性能优化之内存优化
- Android Volley完全解析(四),带你从源码的角度理解Volley