Android Settings中账户图片冲突问题的分析

2020-02-29  本文已影响0人  minhelloworld

问题描述:在设置--添加账号菜单多出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/目录下,查看其时间,将所有编译影响到的文件都在手机中进行替换即可。

上一篇下一篇

猜你喜欢

热点阅读