Android 8.0(Android O) AccountManager 行为变更
前言
在公司的項目中,使用 AccountManager 統(tǒng)一管理//獲取帳號列表/或?qū)獛ぬ栴愋偷哪硞€帳號
AccountManager#getAccounts()/getAccountsByType(String accountType) 獲取不到 AccountManager 里面的信息。
第一時間的反應是去查詢官方 Android O 的行為變更,果然,Android O 對 AccountManager 做出了相應的修改。
官方文檔地址
Account access and discoverability
In Android 8.0 (API level 26), apps can no longer get access to user accounts unless the authenticator owns the accounts or the user grants that access. The GET_ACCOUNTS permission is no longer sufficient. To be granted access to an account, apps should either use AccountManager.newChooseAccountIntent() or an authenticator-specific method. After getting access to accounts, an app can can call AccountManager.getAccounts() to access them.
Android 8.0 deprecates LOGIN_ACCOUNTS_CHANGED_ACTION. Apps should instead use addOnAccountsUpdatedListener() to get updates about accounts during runtime.
For information about new APIs and methods added for account access and discoverability, see Account Access and Discoverability in the New APIs section of this document.
簡單來說,如果你的應用
在 Android 8.0 中,如果你的 target sdk 大于 25,通過 AccountManager.getAccounts() 無法獲取 Account 的相關信息,即使你的 App 擁有 GET_ACCOUNTS permission 權(quán)限。
如果想適配 Android 8.0,你可以通過 newChooseAccountIntent 來申請權(quán)限,接著通過 AccountManager.getAccounts() 即可以正常獲取權(quán)限。
Android O AccountManager 適配方案
以下的解決方案來自
android 8.0 —AccountManager之行為變更
- Target API level below O and have deprecated GET_ACCOUNTS permission.
- Have GET_ACCOUNTS_PRIVILEGED permission.
- Have the same signature as authenticator.
- Have READ_CONTACTS permission and account type may be associated with contacts data - (verified by WRITE_CONTACTS permission check for the authenticator).
第一種情況:
targetSdkVersion<26,判斷邏輯和8.0之前的判斷邏輯是一樣的,會檢查 Manifest.permission.GET_ACCOUNTS 的權(quán)限(android6.0及以上是運行時權(quán)限,需動態(tài)申請)
第二種情況:
有權(quán)限 Manifest.permission.GET_ACCOUNTS_PRIVILEGED,只有priv/app目錄下的app聲明之后才會授予此權(quán)限 (不管targetSdkVersion<26,還是>=26,有此權(quán)限,都有getAccountsXXX的權(quán)限 )
第三種情況:
和注冊此帳號類型的authenticator app簽名一致(同第二種情況,與targetSdkVersion無關,只要簽名一致,即可在8.0的機器上有權(quán)限調(diào)用getAccountsXXX)
第四種情況:
caller app有權(quán)限Manifest.permission.READ_CONTACTS,該accountType的authenticator app要有Manifest.permission.WRITE_CONTACTS(這兩個都是dangerous permission,需要動態(tài)申請)
根據(jù)Requesting Permissions才發(fā)現(xiàn),read contacts,write contacts和get account這三個權(quán)限是屬于同一個權(quán)限組的
官方推薦適配方案
如果上面四個條件你都不滿足,AccountManager還提供里另外兩個接口:
/** * 返回用戶選擇授予獲取帳號的彈窗Intent */ static public Intent newChooseAccountIntent(Account selectedAccount, ArrayList<Account> allowableAccounts, String[] allowableAccountTypes, String descriptionOverrideText, String addAccountAuthTokenType, String[] addAccountRequiredFeatures, Bundle addAccountOptions) /** * 將某個帳號對特定包名可見性(允許/拒絕) * 只有和account的authenticator app簽名一致才能調(diào)用此接口 */ public boolean setAccountVisibility(Account account, String packageName, @AccountVisibility int visibility) /** * 此外,android8.0還追加下面接口,與setAccountVisibility接口相同 * 在登錄成功,向AccountManager數(shù)據(jù)庫中添加帳號時添加對特定包名的可見性 * 名義上,只有authenticator app才可以調(diào)用此接口 */ public boolean addAccountExplicitly(Account account, String password, Bundle extras, Map<String, Integer> visibility)上述接口要么是用戶來選擇授權(quán)同意,要么是authenticator app給予授權(quán),具體來說android 8.0更加加強了用戶的隱私數(shù)據(jù)安全性
newChooseAccountIntent顯示給用戶的彈窗樣式如下:
從源碼的角度分析這幾種解決方案
上面說了幾種解決方案,為什么這幾種方案會有效果呢?
下面讓我們一起從源碼的角度來解讀。
@NonNull public Account[] getAccountsByType(String type) {return getAccountsByTypeAsUser(type, Process.myUserHandle()); }/** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */ @NonNull public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {try {return mService.getAccountsAsUser(type, userHandle.getIdentifier(),mContext.getOpPackageName());} catch (RemoteException e) {throw e.rethrowFromSystemServer();} }getAccountsByType 方法里面調(diào)用 getAccountsByTypeAsUser 方法,而在 getAccountsByTypeAsUser 方法里面,有調(diào)用 mService 的 getAccountsAsUser 方法。
那這個 mService 是什么東東呢?
private final IAccountManager mService;可以看到其實是一個 AIDl,里面有若干方法
interface IAccountManager {Account[] getAccounts(String accountType, String opPackageName);Account[] getAccountsForPackage(String packageName, int uid, String opPackageName);Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features,String opPackageName);void getAccountByTypeAndFeatures(in IAccountManagerResponse response, String accountType,in String[] features, String opPackageName);void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,in String[] features, String opPackageName);// 省略了若干方法}而這個 AIDL 最終會調(diào)用到 AccountManagerService 的相應方法
public class AccountManagerServiceextends IAccountManager.Stubimplements RegisteredServicesCacheListener<AuthenticatorDescription>接下來,我們一起來看一下 AccountManagerService 的 getAccountsAsUser 方法。 getAccountsAsUser 方法里面很簡單,只是調(diào)用了 getAccountsAsUserForPackage 方法去獲取結(jié)果。
@Override @NonNull public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {int callingUid = Binder.getCallingUid();mAppOpsManager.checkPackage(callingUid, opPackageName);return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,opPackageName, false /* includeUserManagedNotVisible */); }@NonNull private Account[] getAccountsAsUserForPackage(String type,int userId,String callingPackage,int packageUid,String opPackageName,boolean includeUserManagedNotVisible) {---- // 省略若干方法long identityToken = clearCallingIdentity();try {UserAccounts accounts = getUserAccounts(userId);return getAccountsInternal(accounts,callingUid,opPackageName,visibleAccountTypes,includeUserManagedNotVisible);} finally {restoreCallingIdentity(identityToken);} }在 getAccountsAsUserForPackage 方法里面,經(jīng)過一系列的判斷,最終又會調(diào)用到 getAccountsInternal 方法。
@NonNull private Account[] getAccountsInternal(UserAccounts userAccounts,int callingUid,String callingPackage,List<String> visibleAccountTypes,boolean includeUserManagedNotVisible) {ArrayList<Account> visibleAccounts = new ArrayList<>();for (String visibleType : visibleAccountTypes) {Account[] accountsForType = getAccountsFromCache(userAccounts, visibleType, callingUid, callingPackage,includeUserManagedNotVisible);if (accountsForType != null) {visibleAccounts.addAll(Arrays.asList(accountsForType));}}Account[] result = new Account[visibleAccounts.size()];for (int i = 0; i < visibleAccounts.size(); i++) {result[i] = visibleAccounts.get(i);}return result; }在 getAccountsInternal 方法里面,又會調(diào)用 getAccountsFromCache 去獲取結(jié)果
protected Account[] getAccountsFromCache(UserAccounts userAccounts, String accountType,int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) {Preconditions.checkState(!Thread.holdsLock(userAccounts.cacheLock),"Method should not be called with cacheLock");if (accountType != null) {Account[] accounts;synchronized (userAccounts.cacheLock) {accounts = userAccounts.accountCache.get(accountType);}if (accounts == null) {return EMPTY_ACCOUNT_ARRAY;} else {return filterAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),callingUid, callingPackage, includeManagedNotVisible);}} else {int totalLength = 0;Account[] accountsArray;synchronized (userAccounts.cacheLock) {for (Account[] accounts : userAccounts.accountCache.values()) {totalLength += accounts.length;}if (totalLength == 0) {return EMPTY_ACCOUNT_ARRAY;}accountsArray = new Account[totalLength];totalLength = 0;for (Account[] accountsOfType : userAccounts.accountCache.values()) {System.arraycopy(accountsOfType, 0, accountsArray, totalLength,accountsOfType.length);totalLength += accountsOfType.length;}}return filterAccounts(userAccounts, accountsArray, callingUid, callingPackage,includeManagedNotVisible);} }而在 getAccountsFromCache 里面,不管 accountType 是否為空,最終都會調(diào)用到 filterAccounts 方法。
@NonNull private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,@Nullable String callingPackage, boolean includeManagedNotVisible) {String visibilityFilterPackage = callingPackage;if (visibilityFilterPackage == null) {visibilityFilterPackage = getPackageNameForUid(callingUid);}Map<Account, Integer> firstPass = new LinkedHashMap<>();for (Account account : unfiltered) {int visibility = resolveAccountVisibility(account, visibilityFilterPackage, accounts);if ((visibility == AccountManager.VISIBILITY_VISIBLE|| visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE)|| (includeManagedNotVisible&& (visibility== AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE))) {firstPass.put(account, visibility);}}Map<Account, Integer> secondPass =filterSharedAccounts(accounts, firstPass, callingUid, callingPackage);Account[] filtered = new Account[secondPass.size()];filtered = secondPass.keySet().toArray(filtered);return filtered; }在 filterAccounts 里面,又會調(diào)用 resolveAccountVisibility 去判斷我們的 CallerApp 是否可以訪問我們的 Account。
private Integer resolveAccountVisibility(Account account, @NonNull String packageName,UserAccounts accounts) {Preconditions.checkNotNull(packageName, "packageName cannot be null");int uid = -1;try {long identityToken = clearCallingIdentity();try {uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);} finally {restoreCallingIdentity(identityToken);}} catch (NameNotFoundException e) {Log.d(TAG, "Package not found " + e.getMessage());return AccountManager.VISIBILITY_NOT_VISIBLE;}// System visibility can not be restricted.if (UserHandle.isSameApp(uid, Process.SYSTEM_UID)) {return AccountManager.VISIBILITY_VISIBLE;}int signatureCheckResult =checkPackageSignature(account.type, uid, accounts.userId);// Authenticator can not restrict visibility to itself.if (signatureCheckResult == SIGNATURE_CHECK_UID_MATCH) {return AccountManager.VISIBILITY_VISIBLE; // Authenticator can always see the account}// Return stored value if it was set.int visibility = getAccountVisibilityFromCache(account, packageName, accounts);if (AccountManager.VISIBILITY_UNDEFINED != visibility) {return visibility;}boolean isPrivileged = isPermittedForPackage(packageName, uid, accounts.userId,Manifest.permission.GET_ACCOUNTS_PRIVILEGED);// Device/Profile owner gets visibility by default.if (isProfileOwner(uid)) {return AccountManager.VISIBILITY_VISIBLE;}// target sdk < 26boolean preO = isPreOApplication(packageName);// 簽名是否一致// target sdk < 26 小于 26 ,并且擁有 Manifest.permission.GET_ACCOUNTS 權(quán)限 // CallerApp 擁有 Manifest.permission.READ_CONTACTS ,authenticator APP 擁有 Manifest.permission.WRITE_CONTACTS// 是否擁有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 權(quán)限if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH)|| (preO && checkGetAccountsPermission(packageName, uid, accounts.userId))|| (checkReadContactsPermission(packageName, uid, accounts.userId)&& accountTypeManagesContacts(account.type, accounts.userId))|| isPrivileged) {// Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature// match.visibility = getAccountVisibilityFromCache(account,AccountManager.PACKAGE_NAME_KEY_LEGACY_VISIBLE, accounts);if (AccountManager.VISIBILITY_UNDEFINED == visibility) {visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;}} else {visibility = getAccountVisibilityFromCache(account,AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, accounts);if (AccountManager.VISIBILITY_UNDEFINED == visibility) {visibility = AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;}}return visibility; }我們主要看這一段就好,從這一段我們可以看到滿足下列其中一個條件,即可獲取得到相關的 Account 信息。
- 簽名一致
- target sdk < 26 小于 26 ,并且擁有 Manifest.permission.GET_ACCOUNTS 權(quán)限
- CallerApp 擁有 Manifest.permission.READ_CONTACTS ,authenticator APP 擁有 Manifest.permission.WRITE_CONTACTS
- 擁有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 權(quán)限
題外話
寫這篇博客的主要目的其實不是為了討論解決方案,而是為了記錄一下跟蹤 Framework 代碼的一些歷程吧。很多時候,我們知道了解決方案之后,往往沒有往更深一層去了解,為什么這種解決方案是有效的。
或許剛開始跟蹤源碼的過程,是有點痛苦,也很想放棄,但如果當你堅持下來,找到解決方案的時候,你會莫名地發(fā)現(xiàn)有一種成就感。
參考博客:
android 8.0 —AccountManager之行為變更
轉(zhuǎn)載請注明原博客地址
總結(jié)
以上是生活随笔為你收集整理的Android 8.0(Android O) AccountManager 行为变更的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python爬虫(云打码平台)
- 下一篇: legacy引导gpt分区_安装win1