开发你自己的Android 授权管理器 (AccountManager的使用以及应用场景)
??http://www.devtf.cn/?p=1125
- 原文鏈接 :?Write your own Android Authenticator
- 原文作者 :?UDI COHEN
- 譯文出自 :?開發(fā)技術前線 www.devtf.cn。未經(jīng)允許,不得轉載!
- 譯者 :?kevinhong
- 校對者:?desmond1121
- 狀態(tài) : 完成
18個月之前,我在開發(fā)Any.DD同步系統(tǒng)時,打算使用安卓提供的AccountManager?API實現(xiàn)認證的相關功能并存儲用戶秘鑰。當我用它來訪問Google賬號時,一切都非常簡單,所以我想Any.DD就用這個API來做吧。確實,使用SyncAdapter進行同步操作進展很順利,看起來的確是一個完美的方案。但它的問題也隨之出現(xiàn)——沒有好的文檔,開發(fā)者社區(qū)也沒能提供太多可供參考的經(jīng)驗,而我們也沒有太多時間來研究這個“無人之地“引發(fā)的各種問題。所以當時決定使用其他方案。
但是,今非昔比…
因為一個最近著手的項目,我最近又開始研究相關功能,我突然發(fā)現(xiàn)這方面知識的豐富程度有了巨大的提升。包括Android.com上的更好的文檔,外面的教程(?教程1?教程2?)也逐漸豐富了起來。讓我們了解到聲名狼藉的?AccountManager?的神秘之處。坊間傳聞的用來創(chuàng)建個人賬戶的方式,我?guī)缀跞甲x了。
但是,好像還是缺點什么。
我感覺整個流程并非盡知,有些部分并沒有足夠清楚。所以我決定用我的方法調(diào)查它——就像我平時想要了解一件事情所用的方法一樣——用“杰克鮑爾“的方式。我發(fā)表了這篇深入調(diào)查后的結論性文章,包含了所有這個服務所能提供的功能和一些我覺得重要的需要發(fā)掘的細節(jié)。后面,我還會貼出一篇關于“SyncAdapter“的文章,所以如果讀者感興趣,我建議讀者通過RSS或Twitter來訂閱(我的博客)。我還是比較了解這方面的諸多細節(jié),不僅僅是教程提供的簡單功能。但如果我遺漏了什么,請在評論中指出。
為什么選擇 Account Manager?
為什么?
為什么不是寫一個簡單的登錄表單,實現(xiàn)一個提交按鈕,發(fā)送(post)所有信息到服務器,然后服務器返回一個鑒權令牌(auth token)?原因在于有很多(與用戶鑒權相關的)附加功能和小細節(jié)你未必能考慮周全。這些容易被開發(fā)者忽略的小細節(jié)可能導致用戶重新登錄,或者被“100000個用戶才會出現(xiàn)一次,無所謂“的聲音 忽略掉。用戶如果在另外一個客戶端修改密碼該如何處理?auth-token的過期判斷呢?是要運行一個沒有用戶交互UI的后臺服務嗎?想要用戶登錄一次,相關APP就可以自動登錄的便利嗎?(就像Google的APP那樣)
讀這篇文章之前或許讓你感覺有些東西太復雜,但其實不是。對于絕大多數(shù)應用場景來說, Account Manager都簡化了登錄過程。而且我也給你提供了代碼樣例,還有什么理由不用呢?
好吧,讓我們來看看(使用?AccountManager?)都有哪些好處:
好處:標準的用戶鑒權方式;為開發(fā)者簡化了登錄的流程;處理訪問拒絕的場景;可以為一個賬戶處理不同類型的訪問令牌(如:只讀、全權限);輕松的在不同程序間共享令牌;有如Sync Adapter這樣的后臺處理的良好支持;并且,在手機的Setting界面中有一個很酷的入口:
看,媽媽,設置屏幕上有我的”名紙”!
缺陷:需要學習它!但是,嗨,這不就是你讀此文的目的嗎?
要實現(xiàn)這些功能,需要下面幾步:
1. 創(chuàng)建?Authenticator?,這是所有操作的核心
2. 創(chuàng)建若干?Activity?,用戶可以在其中輸入驗證需要的信息
3. 創(chuàng)建?Service?,通過?Service?我們可以與?Authenticator?進行交互
首先,(來看)一些概念。
Authenti..啥?
授權令牌 (Authentication Token,?auth-token?) – 是由服務器提供的一個臨時的訪問令牌。所有需要識別用戶的請求,在發(fā)送到服務器時都要帶著這個令牌。在這篇文章中,我們使用?OAuth2?,它也是現(xiàn)在最為流行的方法。
授權服務器– 用來管理所有用戶的服務器。它將會為登錄到服務器的用戶生成授權令牌(auth-token),并且校驗所有的用戶請求(是否合法)。授權令牌有時間限制,過期后將時效。
AccountManager– 管理設備上的所有賬戶,也是這項功能的核心。App從?AccountManager?獲得auth-token,它也將決定是否該打開登錄、創(chuàng)建用戶的?Activity?,或者從之前的請求中返回一個已經(jīng)存儲好的auth-token。?AccountManager?了解不同場景下該調(diào)用何種操作。
AccountAuthenticator– 是一個為具體賬戶類型提供鑒權處理過程的組件。?AccountManager?查找合適的?AccountAuthenticator?,與其通信,并根據(jù)賬戶類型執(zhí)行所有動作。?AccountAuthenticator?知道哪個?Activity?用來讓用戶輸入登錄信息,也知道服務器上次返回的auth-token在哪里存儲。在一個賬戶類型下,多個不同的服務也會共用同一個?AccountAuthenticator?。比如Google的AccountAuthenticator?為GMail提供認證服務,也為其他的Google程序,如:Google Calendar和Google Drive提供授權服務。
AccountAuthenticatorActivity– “登錄/創(chuàng)建用戶“?Activity?的基類,當用戶需要認證的時候,?authenticato?r調(diào)用會這個?Activity?。這個?Activity?負責用戶登錄或用戶創(chuàng)建過程,并將auth-token返回給
authenticator?。
當你的App需要auth-token時,只需調(diào)用?AccountManager#getAuthToken()?。?AccountManager?將負責一切必須的步驟直到給你拿到auth-token。Google提供了一個流程圖展現(xiàn)了整個過程:
這里獲得更多如何使用OAuth2進行認證的文章。
現(xiàn)在,我們已經(jīng)了解了基礎知識。現(xiàn)在來看看如何建立一個自有賬戶類型的authenticator。
建立我們自己的Authenticator
如前文所述, Account Authenticator 由?AccountManager?管理并滿足賬戶相關的所有任務:存儲auth-token;展現(xiàn)賬戶登錄屏幕;處理服務器的用戶登錄。
建立我們自己的?Authenticator?需要繼承Abstract?AccountAuthenticator?并實現(xiàn)一些方法。我們現(xiàn)在來關注其中兩個主要方法:
addAccount
當用戶打算登錄并在一個設備上新建賬戶時,會調(diào)用這個方法。
我們需要返回一個Bundle,其中包含一個會啟動我們自己的_?AccountAuthenticatorActivity?(稍后解釋)的Intent,這個方法在app通過調(diào)用?AccountManager#addAccount()?(需要特殊權限)時被調(diào)用。或者在手機設置 中,用戶點擊“添加新用戶“時被調(diào)用,即:
例如:
@Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {final Intent intent = new Intent(mContext, AuthenticatorActivity.class);intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);final Bundle bundle = new Bundle();bundle.putParcelable(AccountManager.KEY_INTENT, intent);return bundle; } @Override publicBundleaddAccount(AccountAuthenticatorResponseresponse,StringaccountType,StringauthTokenType,String[]requiredFeatures,Bundleoptions)throwsNetworkErrorException{ finalIntentintent=newIntent(mContext,AuthenticatorActivity.class); intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE,accountType); intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE,authTokenType); intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT,true); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,response); finalBundlebundle=newBundle(); bundle.putParcelable(AccountManager.KEY_INTENT,intent); returnbundle; }getAuthToken
如上面的流程圖所示,getAuthToken可以獲取存儲在設備上的已經(jīng)登陸成功用戶的auth-token。如果auth-token不存在,將會提示用戶登錄。在成功登陸之后,請求auth-token的app會“長等待“此token。為了避免此情況,我們應該通過?AccountManager#peekAuthToken()?來檢查?AccountManager?是否已經(jīng)存在一個有效的auth-token。如果沒有,我們應該返回與?addAccount()?相同的結果。
<br />@Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {// Extract the username and password from the Account Manager, and ask// the server for an appropriate AuthToken.final AccountManager am = AccountManager.get(mContext);String authToken = am.peekAuthToken(account, authTokenType);// Lets give another try to authenticate the userif (TextUtils.isEmpty(authToken)) {final String password = am.getPassword(account);if (password != null) {authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);}}// If we get an authToken - we return itif (!TextUtils.isEmpty(authToken)) {final Bundle result = new Bundle();result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);result.putString(AccountManager.KEY_AUTHTOKEN, authToken);return result;}// If we get here, then we couldn't access the user's password - so we// need to re-prompt them for their credentials. We do that by creating// an intent to display our AuthenticatorActivity.final Intent intent = new Intent(mContext, AuthenticatorActivity.class);intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);final Bundle bundle = new Bundle();bundle.putParcelable(AccountManager.KEY_INTENT, intent);return bundle; } <br/>@Override publicBundlegetAuthToken(AccountAuthenticatorResponseresponse,Accountaccount,StringauthTokenType,Bundleoptions)throwsNetworkErrorException{ // Extract the username and password from the Account Manager, and ask // the server for an appropriate AuthToken. finalAccountManageram=AccountManager.get(mContext); StringauthToken=am.peekAuthToken(account,authTokenType); // Lets give another try to authenticate the user if(TextUtils.isEmpty(authToken)){ finalStringpassword=am.getPassword(account); if(password!=null){ authToken=sServerAuthenticate.userSignIn(account.name,password,authTokenType); } } // If we get an authToken - we return it if(!TextUtils.isEmpty(authToken)){ finalBundleresult=newBundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME,account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE,account.type); result.putString(AccountManager.KEY_AUTHTOKEN,authToken); returnresult; } // If we get here, then we couldn't access the user's password - so we // need to re-prompt them for their credentials. We do that by creating // an intent to display our AuthenticatorActivity. finalIntentintent=newIntent(mContext,AuthenticatorActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,response); intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE,account.type); intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE,authTokenType); finalBundlebundle=newBundle(); bundle.putParcelable(AccountManager.KEY_INTENT,intent); returnbundle; }如果我們通過此方法獲得的auth-token已經(jīng)無效了,比如過期了或者用戶從其他客戶端修改了密碼。我們應該調(diào)用?AccountManager#invalidateAuthToken()?使當前存儲在?AccountManager?的auth-token失效,并調(diào)用?getAuthToken()?再次請求auth-token。再次調(diào)用?getAuthToken()?時會嘗試使用之前存儲的密碼進行登陸,如果失敗,用戶將必須再次輸入登陸信息。
所以,用戶要在哪輸入驗證信息?這就是?AccountAuthenticatorActivity?了。
創(chuàng)建Activity
AccountAuthenticatorActivity?是整個過程中唯一直接與用戶交互的?Activity?。
Authenticator?首先調(diào)用這個?Activity?,此?Activity?將展現(xiàn)一個用戶登錄表單,發(fā)送到服務器鑒權用戶,并將結果傳給?authenticator?。我們繼承?AccountAuthenticatorActivity?,不僅要實現(xiàn)常規(guī)Activity的功能,還要實現(xiàn)?setAccountAuthenticatorResult()?方法。此方法負責將鑒權過程的結果發(fā)送給?Authenticator。此方法也為我們省掉了與?Authenticator?直接交互。
我在我的?Activity?中構建了一個簡單的用戶名/密碼表單。你可以使用Android官方網(wǎng)站上建議使用的登錄?Activity?模版,提交時,我進行了以下操作:
public void submit() {final String userName = ((TextView) findViewById(R.id.accountName)).getText().toString();final String userPass = ((TextView) findViewById(R.id.accountPassword)).getText().toString();new AsyncTask<Void, Void, Intent>() {@Overrideprotected Intent doInBackground(Void... params) {String authtoken = sServerAuthenticate.userSignIn(userName, userPass, mAuthTokenType);final Intent res = new Intent();res.putExtra(AccountManager.KEY_ACCOUNT_NAME, userName);res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);res.putExtra(AccountManager.KEY_AUTHTOKEN, authtoken);res.putExtra(PARAM_USER_PASS, userPass);return res;}@Overrideprotected void onPostExecute(Intent intent) {finishLogin(intent);}}.execute(); } publicvoidsubmit(){ finalStringuserName=((TextView)findViewById(R.id.accountName)).getText().toString(); finalStringuserPass=((TextView)findViewById(R.id.accountPassword)).getText().toString(); newAsyncTask<Void,Void,Intent>(){ @Override protectedIntentdoInBackground(Void...params){ Stringauthtoken=sServerAuthenticate.userSignIn(userName,userPass,mAuthTokenType); finalIntentres=newIntent(); res.putExtra(AccountManager.KEY_ACCOUNT_NAME,userName); res.putExtra(AccountManager.KEY_ACCOUNT_TYPE,ACCOUNT_TYPE); res.putExtra(AccountManager.KEY_AUTHTOKEN,authtoken); res.putExtra(PARAM_USER_PASS,userPass); returnres; } @Override protectedvoidonPostExecute(Intentintent){ finishLogin(intent); } }.execute(); }sServerAuthenticate?是與服務器進行認證的接口,我實現(xiàn)了了其中例如?userSignIn?(用戶登錄)和?userSignUp?(用戶注冊)的方法,這些方法會在登錄成功時獲得服務器返回的auth-token。
mAuthTokenType?是我從服務器請求的令牌的類型。我可以讓服務器給我不同的令牌例如只讀或全訪問,或者在相同的賬戶下的不同服務。一個好的列子是Google‘賬號,它的令牌類型包括:“_Manage your calendars”(管理日歷), “Manage your _tasks”(管理任務), “View your calendars”(查看日歷)等等。在這個列子中,我不會為不同類型的令牌區(qū)分不同的操作。
完成后,調(diào)用 finishLogin():
private void finishLogin(Intent intent) {String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);String accountPassword = intent.getStringExtra(PARAM_USER_PASS);final Account account = new Account(accountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) {String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);String authtokenType = mAuthTokenType;// Creating the account on the device and setting the auth token we got// (Not setting the auth token will cause another call to the server to authenticate the user)mAccountManager.addAccountExplicitly(account, accountPassword, null);mAccountManager.setAuthToken(account, authtokenType, authtoken);} else {mAccountManager.setPassword(account, accountPassword);}setAccountAuthenticatorResult(intent.getExtras());setResult(RESULT_OK, intent);finish(); } privatevoidfinishLogin(Intentintent){ StringaccountName=intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); StringaccountPassword=intent.getStringExtra(PARAM_USER_PASS); finalAccountaccount=newAccount(accountName,intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE)); if(getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT,false)){ Stringauthtoken=intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); StringauthtokenType=mAuthTokenType; // Creating the account on the device and setting the auth token we got // (Not setting the auth token will cause another call to the server to authenticate the user) mAccountManager.addAccountExplicitly(account,accountPassword,null); mAccountManager.setAuthToken(account,authtokenType,authtoken); }else{ mAccountManager.setPassword(account,accountPassword); } setAccountAuthenticatorResult(intent.getExtras()); setResult(RESULT_OK,intent); finish(); }通過上面的方法我們獲得了一個全新的auth-token,具體細節(jié)如下:
在這個案例中,?AccountManager?已經(jīng)存在了一條記錄,它是一個已經(jīng)失效了的auth-token。新的auth-token會替代原有的,此時你不需要做任何操作。但如果用戶的密碼已經(jīng)修改,你需要向?AccountManager?更新用戶密碼,就像上面代碼中實現(xiàn)的那樣。
添加了一個新的賬戶到設備 —— 這是有技巧的部分。當新賬戶創(chuàng)建時,auth-token并沒有立刻保存到?AccountManager?,你需要顯示的設置auth-token。這也是我在添加完賬戶之后,又明確的設置auth-token的原因。如果設置失敗了,?AccountManager?將會再到服務器執(zhí)行獲取auth-token的過程,這時getAuthToken會被調(diào)用,并再次進行用戶鑒權。
注意:addAccountExplicitly() 的第三個參數(shù),是用戶數(shù)據(jù)Bundle,它可以用在?AccountManager?保存其他認證信息的同時存儲一些自定義信息,比如你的服務的API Key。當然這些信息也可以通過?setUserData()?設置。
在Activity完成登陸之后,我們已經(jīng)為?AccountManager?建立了我們的賬戶。最后調(diào)用?setAccountAuthenticatorResult()?將信息傳回給?Authenticator?。
現(xiàn)在一切流程準備就緒,那誰來啟動這個過程呢?(其他應用)如何來訪問它?我們需要讓我們的?Authenticator?對其他想使用它的應用可用,因此我們需要讓?Authenticator?在后臺運行(還可以調(diào)用登錄屏幕),使用?Service?是一個明顯的選擇。
創(chuàng)建Service
Service非常簡單。
我們要做的,是讓其他的進程與我們的服務綁定,并于?Authenticator?交互。幸運的是,?Authenticator?的父類?AbstractAccountAuthenticator?提供了?getIBinder()?方法,該方法返回了一個?IBInder?實現(xiàn)。我們的服務需要在?onBind()方法中調(diào)用它。這個基本的實現(xiàn)保證了其他進程請求?Authenticator?時進行適當?shù)牟僮?#xff08;原文為:“調(diào)用合適的方法“。譯者注)。如果讀者想了解其中的細節(jié),可以看看Transport?,一個?AbstractAccountAuthenticator?的內(nèi)部類,并了解關于AIDL——進程間通信的一些知識。
現(xiàn)在我們的服務是這樣的:
public class UdinicAuthenticatorService extends Service {@Overridepublic IBinder onBind(Intent intent) {UdinicAuthenticator authenticator = new UdinicAuthenticator(this);return authenticator.getIBinder();} } publicclassUdinicAuthenticatorServiceextendsService{ @Override publicIBinderonBind(Intentintent){ UdinicAuthenticatorauthenticator=newUdinicAuthenticator(this); returnauthenticator.getIBinder(); } }..and on the manifest we need to add our service with the
在manifest文件中,需要對Service聲明
<service android:name=".authentication.UdinicAuthenticatorService"><intent-filter><action android:name="android.accounts.AccountAuthenticator" /></intent-filter><meta-data android:name="android.accounts.AccountAuthenticator"android:resource="@xml/authenticator" /> </service> <serviceandroid:name=".authentication.UdinicAuthenticatorService"> <intent-filter> <actionandroid:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-dataandroid:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/> </service>很簡單,是吧?
作為資源引用的?authenticator.xml?用來定義?Authenticator?用到的一些屬性。
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"android:accountType="com.udinic.auth_example"android:icon="@drawable/ic_udinic"android:smallIcon="@drawable/ic_udinic"android:label="@string/label"android:accountPreferences="@xml/prefs"/> <account-authenticatorxmlns:android="http://schemas.android.com/apk/res/android"android:accountType="com.udinic.auth_example"android:icon="@drawable/ic_udinic"android:smallIcon="@drawable/ic_udinic"android:label="@string/label"android:accountPreferences="@xml/prefs"/>讓我們來解釋一下:
-
accountType(賬戶類型)是一個獨一無二的名字,用來識別我們的賬戶類別。當其他app要通過我們的應用進行鑒權操作時。它需要明確知道這個名字來使用?AccountManager?。
-
icon and smallIcon(圖標和小圖標)是在設備的Setting畫面中,賬戶條目顯示的圖標。在賬戶確認畫面(稍后會解釋),它也會出現(xiàn)。
-
label(標簽)是在設備Setting畫面中顯示的代表我們賬戶的的字符串。
-
accountPreferences(賬戶偏好)是一個偏好XML的引用。它將在通過設備的Setting畫面中訪問賬戶偏好時展現(xiàn),它允許用戶更好的控制莊戶。作為例子,你可以看看Google及Dropbox的賬戶偏好畫面,里面包含了一些可供調(diào)整的項。我的例子如下:
你需要了解的其他特性
在我調(diào)查的過程中,我發(fā)現(xiàn)了一些有意思的場景。為了讓你使用相關API時不致想破頭,我把它分享出來。
“為一個設備上不存在的賬戶請求auth-token,將會導致未定義的失敗。“
在我這,這個“未定義的失敗“雖熱調(diào)用了登錄畫面,但當我輸入認證信息后卻沒有任何反應,所以你得注意一下。
先進先服務 – 如果你復制了authenticator的相關代碼到你的兩個應用,二者具備相同的邏輯,但在每個應用中修改了自己的登錄畫面。在這種情況下,無論哪個應用需要請求auth-token,都會調(diào)用先安裝的app的authenticator。如果你卸載了第一個應用,第二個應用的authenticator才會起作用(因為它是唯一有個了)。克服這個問題的一個技巧,是將不同的登陸頁放到同一個authenticator中,并在調(diào)用?addAccount()?時,把各自的設計需求通過addAccountOptions 的參數(shù)傳過去。
為了安全,小心共享 – 如果你打算獲取其他應用的?authenticator?生成的auth-token,并且該應用與你的應用具有不同的簽名秘鑰,用戶必須顯示的同意這個操作。用戶將會看到如下畫面:
“Full access to..”字符串是通過我們的?Authenticator?的_getAuthTokenLabel()?方法獲得。你可以為每一個auth-token類型指定一個標簽,以便在類似的場景下對用戶更加友好。
1, 存儲密碼 –?AccountManager?并沒有使用加密方法存儲密碼。所有的密碼都明文存儲。你不能對其他的?Authenticator?調(diào)用?peekAuthToken()?方法(獲得其他?Authenticator?的auth-token)(會出現(xiàn)“caller uid X is different than the authenticator’s uid”錯誤),但root權限或adb命令卻可以得到。在示例代碼中,存儲明文密碼是為了讓已經(jīng)過期的token自動登錄更方便。在大多數(shù)場合中,我選擇更加安全的方式,但有的時候,為用戶的錯誤犧牲便利性是不值得的——如果有人獲得了root權限并能在設備上執(zhí)行adb命令,那與獲得用戶的“最高分“比起來,他可以做到更大的危害。
接下來?
現(xiàn)在,你已經(jīng)對這個很棒的服務比較熟悉了。你可以在Google Play上?下載我開發(fā)的樣例程序?。它會在你的設備創(chuàng)建“Udinic account”類型的賬戶,驗證則會通過Parse.com賬戶來進行。樣例應用提供了下面幾項功能:
getAuthToken按鈕首先會查詢設備上是否有“Udinic”類型的賬戶。如果有,它通過調(diào)用?AccountManager#getAuthToken()?來返回token。如果有多個token,它將彈出一個對話框讓用戶選擇打算使用哪一個。
getAuthTokenByFeatures調(diào)用了?AccountManager?提供的一個很棒很方便的同名方法,它會為你完成所有的工作。它也會查詢?AccountManager?中是否包含目標類型“Udinic”。它的行為遵循以下步驟:
- 沒有賬戶時: 調(diào)用?_ addAccount()_?讓用戶創(chuàng)建一個賬戶。此后,自動調(diào)用?getAuthToken()?獲取token。
- 存在賬戶時:獲取auth-token。
- 有兩個賬戶或更多時:創(chuàng)建一個賬戶選擇對話框,并返回用戶所選賬戶的token。
如果你打算讓token失效,你可以用?invalidateAuthToken?按鈕。注意:Udinic authenticator知道如何恢復失效token,就像之前示例代碼中所展示的那樣,通過?getAuthToken()?方法。那意味著,在讓token失效后,?getAuthToken?按鈕仍然可以返回token,但只在它向服務器請求成功之后。你可以在LogCat中通過查看網(wǎng)絡狀態(tài)來確認。刪除帳戶只能通過設備的Setting菜單。
你可以在?Github?上下載相關源代碼。里面包含了2個樣例應用,所以你可以嘗試下2個應用之間共享相同的?Authenticator?。比如:使用不同的簽名密鑰打包程序,一個應用請求由另一個應用創(chuàng)建的auth-token。你也可以嘗試創(chuàng)建一個?authenticator的apklib(apk庫),并在不同的應用中重用它。如果你有意見或建議,別猶豫直接在文章后面指出或在Github上提出PR吧。
總結
以上是生活随笔為你收集整理的开发你自己的Android 授权管理器 (AccountManager的使用以及应用场景)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: scardsvr 智能卡修复工具原理及代
- 下一篇: sublime 正则跨行匹配