前言

在公司的项目中,使用 AccountManager 统一管理//获取帐号列表/或对应帐号类型的某个帐号

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 权限。

如果想适配 Android 8.0,你可以通过 newChooseAccountIntent 来申请权限,接着通过 AccountManager.getAccounts() 即可以正常获取权限。


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 的权限(android6.0及以上是运行时权限,需动态申请)

第二种情况:

有权限 Manifest.permission.GET_ACCOUNTS_PRIVILEGED,只有priv/app目录下的app声明之后才会授予此权限 (不管targetSdkVersion<26,还是>=26,有此权限,都有getAccountsXXX的权限 )

第三种情况:

和注册此帐号类型的authenticator app签名一致(同第二种情况,与targetSdkVersion无关,只要签名一致,即可在8.0的机器上有权限调用getAccountsXXX)

第四种情况:

caller app有权限Manifest.permission.READ_CONTACTS,该accountType的authenticator app要有Manifest.permission.WRITE_CONTACTS(这两个都是dangerous permission,需要动态申请)
根据Requesting Permissions才发现,read contacts,write contacts和get account这三个权限是属于同一个权限组的

官方推荐适配方案

如果上面四个条件你都不满足,AccountManager还提供里另外两个接口:

/**
* 返回用户选择授予获取帐号的弹窗Intent
*/
static public Intent newChooseAccountIntent(Account selectedAccount,
ArrayList<Account> allowableAccounts,
String[] allowableAccountTypes,
String descriptionOverrideText,
String addAccountAuthTokenType,
String[] addAccountRequiredFeatures,
Bundle addAccountOptions)
/**
* 将某个帐号对特定包名可见性(允许/拒绝)
* 只有和account的authenticator app签名一致才能调用此接口
*/
public boolean setAccountVisibility(Account account, String packageName, @AccountVisibility int visibility)
/**
* 此外,android8.0还追加下面接口,与setAccountVisibility接口相同
* 在登录成功,向AccountManager数据库中添加帐号时添加对特定包名的可见性
* 名义上,只有authenticator app才可以调用此接口
*/
public boolean addAccountExplicitly(Account account, String password, Bundle extras, Map<String, Integer> visibility)

上述接口要么是用户来选择授权同意,要么是authenticator app给予授权,具体来说android 8.0更加加强了用户的隐私数据安全性

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 方法里面调用 getAccountsByTypeAsUser 方法,而在 getAccountsByTypeAsUser 方法里面,有调用 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 最终会调用到 AccountManagerService 的相应方法

public class AccountManagerServiceextends IAccountManager.Stubimplements RegisteredServicesCacheListener<AuthenticatorDescription>

接下来,我们一起来看一下 AccountManagerService 的 getAccountsAsUser 方法。 getAccountsAsUser 方法里面很简单,只是调用了 getAccountsAsUserForPackage 方法去获取结果。

@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 方法里面,经过一系列的判断,最终又会调用到 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 方法里面,又会调用 getAccountsFromCache 去获取结果

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 是否为空,最终都会调用到 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 里面,又会调用 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 权限 // CallerApp 拥有  Manifest.permission.READ_CONTACTS ,authenticator APP 拥有 Manifest.permission.WRITE_CONTACTS// 是否拥有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 权限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 权限
  • CallerApp 拥有 Manifest.permission.READ_CONTACTS ,authenticator APP 拥有 Manifest.permission.WRITE_CONTACTS
  • 拥有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 权限
// target  sdk < 26boolean preO = isPreOApplication(packageName);// 签名是否一致// target  sdk < 26  小于 26 ,并且拥有 Manifest.permission.GET_ACCOUNTS 权限 // CallerApp 拥有  Manifest.permission.READ_CONTACTS ,authenticator APP 拥有 Manifest.permission.WRITE_CONTACTS// 是否拥有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 权限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;}}

题外话

写这篇博客的主要目的其实不是为了讨论解决方案,而是为了记录一下跟踪 Framework 代码的一些历程吧。很多时候,我们知道了解决方案之后,往往没有往更深一层去了解,为什么这种解决方案是有效的。

或许刚开始跟踪源码的过程,是有点痛苦,也很想放弃,但如果当你坚持下来,找到解决方案的时候,你会莫名地发现有一种成就感。

参考博客:
android 8.0 —AccountManager之行为变更

转载请注明原博客地址

Android 8.0(Android O) AccountManager 行为变更相关推荐

  1. 该功能仅支持Android5.0,Android 5.0 android:elevation适用于View,但不适用于Button?

    在SDK Manager的Android 5.0示例中,有ElevationBasic示例.它显示了两个View对象:一个圆形和一个正方形.该圈子已android:elevation设置为30dp: ...

  2. 怎么更新android 10.0,Android 10.0(Q OS)系统升级计划Androi

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Android 10.0(Q OS)系统升级计划 Android 10.0 系统升级计划: 系列 型号 升级计划 Galaxy S10 SM-G9730 ...

  3. android studio8.0,Android Studio错误:(8,0)未找到ID为’android’的插件

    我在OS X(10.9.3)上安装了 Android Studio(0.6.1),使用Brew(brew install gradle)安装了Gradle 1.1.但是,我无法得到我的第一个Hello ...

  4. nexus5 android 7.0,Android 7.0 Nougat正式版刷机教程—nexus5X

    Android 7.0正式版发布已经有一段时间,手上有nexus设备的童鞋不知道更新了最新系统没有,反正我的nexus 5x 6.0的系统是始终没有收到7.0的OTA升级推送,无奈,只有自己动手,丰衣 ...

  5. edge android 6.0,Android 6.0.1让Galaxy S6 Edge的曲面屏真正有了用武之地

    很多Android用户此刻大概都在期盼Android 6.0棉花糖系统更新,不过大部分人有这种期盼只不过是有点必须升级到新系统的强迫症.而三星则认为,需要针对自家产品,在新系统的更新上做点实在的事情. ...

  6. android 8.0如何编译,ubuntu16.0编译Android 8.0 Android O记录

    一.必要的系统环境 硬盘200G,内存4G+,交换分区(swap)4G+(可装完系统后添加,见后面错误处理部分) 二.下载源码,国内可以在清华镜像站下载 网上有很多教程,这里就不再累述了 三.然后安装 ...

  7. Google nexus 6p android 8.0——android 6.0 两部曲

    Google nexus 6p android8.1降级至android6.0两步曲 一.解锁的安装使用(两个锁:oem和bootloader,需要人为地unlock) 1.电脑端的操作部分 使用最新 ...

  8. htc+820+android+5.0,Android L附体!全球首款64位手机图赏

    Desire 820如约而至,而且是顶着全球首款64位八核手机的光环.如果你对它感兴趣,不妨来看看真机图赏. 从真机图上看,Desire 820的外形与816很相似,也继承了诸多HTC One M8上 ...

  9. s4+android+6.0,Android 4.3固件泄露 国际版S4可尝鲜

    Android 4.3固件泄露 国际版S4可尝鲜 2013年06月30日 02:55作者:杨霏霏编辑:杨霏霏文章出处:泡泡网原创 分享 泡泡网手机频道6月29日 近日有外媒曝光了原生版GALAXY S ...

  10. MTK Android 9.0(Android P) + kernel-4.9 默认关闭DM Verity 和secure boot 解决无法adb remount的问题

    在android P版本上执行adb remount会提示以下错误: remount of the / superblock failed: Permission denied remount fai ...

最新文章

  1. 暴力 + 贪心 --- Codeforces 558C : Amr and Chemistry
  2. LD(Levenshtein distance)莱文斯坦距离----编辑距离
  3. 数据结构-队列之循环队列
  4. [jQuery] jQuery是通过哪个方法和Sizzle选择器结合的?
  5. jQuery水印插件 - Watermark 和 FormWatermark
  6. java之yield(),sleep(),wait()区别详解-备忘笔记
  7. ASCII码表对照图
  8. 1.2 微信小程序开发-用户登录页面设计
  9. boost电路输出电流公式_BOOST电路参数计算公式
  10. 计算机相关的外文参考文献,计算机英文参考文献
  11. 计算机制造商logo,如何更改系统oem制造商logo等信息
  12. 专业办公套件(Office 2019)for Mac
  13. 第二阶段javaweb-day01-mqsql基础
  14. block标签、inline标签、inline-block标签的特点
  15. NDK-r25交叉编译qemu-7.0.0 第66步报错
  16. Android App屏幕旋转要点
  17. 人工智能安全(二)—攻击
  18. 参考文献格式字号字体_论文格式要求及字体大小
  19. 中国第一代程序员盘点
  20. CodeM美团2018初赛A轮 题目一

热门文章

  1. Java中的测不准原理
  2. MATLAB实现已知DH参数的正运动方程求解
  3. 电脑耳机插入外放解决办法
  4. 播放失败,请在设置中切换输出设备(电脑插入耳机还是外放的问题)
  5. keras中VGG19预训练模型的使用
  6. CISSP第5/8知识点错题集
  7. python的self.boardx -= 5 什么意思_python小白求帮助
  8. 项目一:SORM基本框架之基本思路
  9. 使用U盘安装 mac os
  10. 求生之路服务器正在强制此文件的一致性,求生之路2和朋友联机错误代码的解决办法...