Android Settings中账户图片冲突问题的分析
问题描述:在设置--添加账号菜单多出Exchange重复账号 总共3个exchange账号条目,且功能相同。概率性问题,恢复出厂设置之后,可能就会出现(1/8)且有时候还会出现两个相同的exchange账号。
1、问题分析:
1)/Settings/src/com/android/settings/accounts/ChooseAccountActivity.java
出现异常时,settings模块的log中 关于pref.type的信息如下:
Line 19710: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.exchange hide=false
Line 19712: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.email hide=false
Line 19714: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google.android.gm.exchange hide=false
Line 19716: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google hide=false
Line 19718: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.email.imap hide=false
Line 19720: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google.android.gm.legacyimap hide=false
Line 19722: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google.android.gm.pop3 hide=false
Line 19724: 01-01 07:00:19.389 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.email.pop3 hide=false
可以看出并没有重复的账户类型,那么,当pref.type正常时,为什么会出现账户图标出错的问题,发现整个preference的图标,名称的获取是从下面这行代码中得到的,然后就从/Settings/src/com/android/settings/accounts/ChooseAccountActivity.java
出发跟踪了一下这部分代码。
mAuthDescs = AccountManager.get(this).getAuthenticatorTypesAsUser(mUserHandle.getIdentifier());
**2)/frameworks/base/core/java/android/accounts/AccountManager.java**
public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
try {
return mService.getAuthenticatorTypes(userId);
} catch (RemoteException e) {
// will never happen
throw new RuntimeException(e);
}
}
在这个方法中,查看了mService的类型,如下:
private final IAccountManager mService;
根据IPC机制,我们跳转到AccountManagerService这个类。
3)frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java
在getAuthenticatorTypes()方法中,主要为下面这句:
return getAuthenticatorTypesInternal(userId);
然后跳转到getAuthenticatorTypesInternal()方法。
private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId) {
Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
AuthenticatorDescription[] types =
new AuthenticatorDescription[authenticatorCollection.size()];
int i = 0;
for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
: authenticatorCollection) {
types[i] = authenticator.type;
i++;
}
return types;
}
然后我们可以推断出,其资源的加载还在这行代码中:
authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
又因为:
private final IAccountAuthenticatorCache mAuthenticatorCache;
跳转至AccountAuthenticatorCache类。
4)/frameworks/base/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
然后发现这个类,并没有getAllServices()方法,因为其继承自 RegisteredServicesCache类,所有转去 RegisteredServicesCache类查看其getAllServices()方法。
5)/frameworks/base/core/java/android/content/pm/RegisteredServicesCache.java
/**
* @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
* registered authenticators.
*/
public Collection<ServiceInfo<V>> getAllServices(int userId) {
synchronized (mServicesLock) {
// Find user and lazily populate cache
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services == null) {
generateServicesMap(null, userId);
}
return Collections.unmodifiableCollection(
new ArrayList<ServiceInfo<V>>(user.services.values()));
}
}
根据其方法解释,我们大体可以理解它的具体功能,然后我们进入generateServicesMap(null, userId)方法。
发现在其中有这样一句:
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
其实现为:
protected List<ResolveInfo> queryIntentServices(int userId) {
final PackageManager pm = mContext.getPackageManager();
return pm.queryIntentServicesAsUser(
new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
}
根据名称,我们不难猜出,它是一个根据特定intent,userid查询相关操作的service,并将该service的<meta-data>数据读出。
那么我们就得看一下这个mInterfaceName是什么呢?
搜索了一下,发现它是在RegisteredServicesCache类初始化的时候被赋值的,因为RegisteredServicesCache类在这个分析链中是被AccountAuthenticatorCache继承的,然后我们就得回到AccountAuthenticatorCache类了,看看它的初始化
6)/frameworks/base/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
AccountAuthenticatorCache的初始化如下:
public AccountAuthenticatorCache(Context context) {
super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
AccountManager.AUTHENTICATOR_META_DATA_NAME,
AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
}
然后我们回到一开始的AccountManager类,发现在其中有这样几行代码:
public static final String ACTION_AUTHENTICATOR_INTENT =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_META_DATA_NAME =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
然后结合new Intent(mInterfaceName) =new Intent("android.accounts.AccountAuthenticator")。
又到了展现曾工工具强大的地方了,我们搜索了一下
android.accounts.AccountAuthenticator
,然后发现在/packages/apps/Email/AndroidManifest.xml
中,注册了八个存在这样<action>
的service
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
然后看了看他们的<meta-data>数据。
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_pop3"
/>
点开/packages/apps/Email/res/xml/authenticator_pop3.xml
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_pop3"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/ic_notification_mail_24dp"
android:label="@string/pop3_name"
android:accountPreferences="@xml/account_preferences"
/>
对比其图标,账户类型等信息,确实是Settings模块中account的图标和账户。
7)/frameworks/base/core/java/android/content/pm/RegisteredServicesCache.java
然后就接着回到了generateServicesMap()方法中的。
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
发现之后,就是这样的一个循环:
for (ResolveInfo resolveInfo : resolveInfos) {
try {
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
}
serviceInfos.add(info);
} catch (XmlPullParserException|IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
于是接着看parseServiceInfo()方法,发现在其中有这样一个关键方法;
V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
si.packageName, attrs);
因为AccountAuthenticatorCache类中,有对这个方法进行重写,具体内容如下:
public AuthenticatorDescription parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs) {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AccountAuthenticator);
try {
final String accountType =
sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
final int labelId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_label, 0);
final int iconId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_icon, 0);
final int smallIconId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
final int prefId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
final boolean customTokens = sa.getBoolean(
com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
if (TextUtils.isEmpty(accountType)) {
return null;
}
android.util.Log.d("AccountAuthenticatorCache", "accountType:" + accountType+",packageName:" + packageName+",labelId:" +labelId+",iconId:" +iconId+",smallIconId:" +smallIconId+",prefId:" + prefId+",customTokens:" +customTokens);
return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
smallIconId, prefId, customTokens);
} finally {
sa.recycle();
}
}
其中需要解释的是这个数组:
public TypedArray obtainAttributes (AttributeSet set, int[] attrs)(说明此函数)
说明:返回一个由AttributeSet获得的一系列的基本的属性值,不需要用用一个主题或者/和样式资源执行样式。
参数:
set:现在检索的属性值;
attrs:制定的检索的属性值
public void recycle()
返回先前检索的数组,稍后再用。
那么具体的表现可以联想一下这两部分:
<1>
1)public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
2) /packages/apps/Email/res/xml/authenticator_pop3.xml中的account-authenticator标签中间的内容。
<2>
<declare-styleable name="AccountAuthenticator">
<!-- The account type this authenticator handles. -->
<attr name="accountType" format="string"/>
<!-- The user-visible name of the authenticator. -->
<attr name="label"/>
<!-- The icon of the authenticator. -->
<attr name="icon"/>
<!-- Smaller icon of the authenticator. -->
<attr name="smallIcon" format="reference"/>
<!-- A preferences.xml file for authenticator-specific settings. -->
<attr name="accountPreferences" format="reference"/>
<!-- Account handles its own token storage and permissions.
Default to false
-->
<attr name="customTokens" format="boolean"/>
</declare-styleable>
分析到此,基本可以结束,由于没有全局去看,基本是按照一个链条在走,所以还有是有一些疑惑的地方,如果大家在看的时候有什么新的见解,可以告诉我一块研究研究。
2、总结:
**1)AccountAuthenticatorCache是Android平台中账户验证服务(Account AuthenticatorService,AAS)的管理中心。而AAS则由应用程序通过在AndroidManifest.xml中输出符合指定要求的Service信息而来。
**
2)RegisteredServicesCache是一个模板类,专门用于管理系统中指定Service的信息收集和更新,而具体是哪些Service由RegisteredServicesCache构造时的参数指定。而AccountAuthenticatorCache从RegisteredServicesCache<AuthenticatorDescription>派生。专门负责关于账户部分的Service的信息收集和更新。
3)在修改了framework部分的代码,之后,想在超级服务器上进行本地验证,可以讲修改的模块单独编译,比如编译service模块,可以用:
source build.sh 项目名 services -j32
修改了framework/base下的文件,可以进入framework/base目录下使用:mm
命令编译
编译完成之后,进入out/target/project/特定名称/system/framework/目录下,查看其时间,将所有编译影响到的文件都在手机中进行替换即可。