美文网首页
Android Settings中账户图片冲突问题的分析

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

作者: minhelloworld | 来源:发表于2020-02-29 17:16 被阅读0次

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

    相关文章

      网友评论

          本文标题:Android Settings中账户图片冲突问题的分析

          本文链接:https://www.haomeiwen.com/subject/zchmhhtx.html