探索 Android的SSAID(Android ID) 的奥秘

本篇文章基 android-11.0.0_r17 编写

我们在做App开发的时候,通常会有获取唯一标示的需求,在这里Android提供了ANDROID_ID的方式来满足大家需求


String androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);

我可以通过Settings下的Secure内部类下的getString方法来获取,并且传入了一个 ContentResolver 对象以及系统提供的 Settings.Secure.ANDROID_ID 常量值,看到这里大家不妨猜一下,我想这里肯定是通过IPC的方式访问Settings应用,然后根据自身的应用信息来去生成一个ID,我们继续往下


public static String getString(ContentResolver resolver, String name) {

    return getStringForUser(resolver, name, resolver.getUserId());

}

public static String getStringForUser(ContentResolver resolver, String name,

        int userHandle) {

    if (MOVED_TO_GLOBAL.contains(name)) {

        Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"

                + " to android.provider.Settings.Global.");

        return Global.getStringForUser(resolver, name, userHandle);

    }

    if (MOVED_TO_LOCK_SETTINGS.contains(name)) {

        synchronized (Secure.class) {

            if (sLockSettings == null) {

                sLockSettings = ILockSettings.Stub.asInterface(

                        (IBinder) ServiceManager.getService("lock_settings"));

                sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID;

            }

        }

        if (sLockSettings != null && !sIsSystemProcess) {

            // No context; use the ActivityThread's context as an approximation for

            // determining the target API level.

            Application application = ActivityThread.currentApplication();

            boolean isPreMnc = application != null

                    && application.getApplicationInfo() != null

                    && application.getApplicationInfo().targetSdkVersion

                    <= VERSION_CODES.LOLLIPOP_MR1;

            if (isPreMnc) {

                try {

                    return sLockSettings.getString(name, "0", userHandle);

                } catch (RemoteException re) {

                    // Fall through

                }

            } else {

                throw new SecurityException("Settings.Secure." + name

                        + " is deprecated and no longer accessible."

                        + " See API documentation for potential replacements.");

            }

        }

    }

    return sNameValueCache.getStringForUser(resolver, name, userHandle);

}

首先判断两个List里是否存在name,这里如果name=ANDROID_ID的话,两个if都不会命中,会直接从最后行来获取,这里可能是缓存,我们先进去看看具体逻辑


@UnsupportedAppUsage

public String getStringForUser(ContentResolver cr, String name, final int userHandle) {

    //首先判断当前应用是不是属于当前登录用户

    final boolean isSelf = (userHandle == UserHandle.myUserId());

    int currentGeneration = -1;

    if (isSelf) {

        synchronized (NameValueCache.this) {

            if (mGenerationTracker != null) {

                if (mGenerationTracker.isGenerationChanged()) {

                    if (DEBUG) {

                        Log.i(TAG, "Generation changed for type:"

                                + mUri.getPath() + " in package:"

                                + cr.getPackageName() +" and user:" + userHandle);

                    }

                    mValues.clear();

                } else if (mValues.containsKey(name)) {

                    return mValues.get(name);

                }

                if (mGenerationTracker != null) {

                    currentGeneration = mGenerationTracker.getCurrentGeneration();

                }

            }

        }

    } else {

        if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle

                + " by user " + UserHandle.myUserId() + " so skipping cache");

    }

    IContentProvider cp = mProviderHolder.getProvider(cr);

    // Try the fast path first, not using query().  If this

    // fails (alternate Settings provider that doesn't support

    // this interface?) then we fall back to the query/table

    // interface.

    if (mCallGetCommand != null) {

        try {

            Bundle args = null;

            if (!isSelf) {

                args = new Bundle();

                args.putInt(CALL_METHOD_USER_KEY, userHandle);

            }

            boolean needsGenerationTracker = false;

            synchronized (NameValueCache.this) {

                if (isSelf && mGenerationTracker == null) {

                    needsGenerationTracker = true;

                    if (args == null) {

                        args = new Bundle();

                    }

                    args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);

                    if (DEBUG) {

                        Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()

                                + " in package:" + cr.getPackageName() +" and user:"

                                + userHandle);

                    }

                }

            }

            Bundle b;

            // If we're in system server and in a binder transaction we need to clear the

            // calling uid. This works around code in system server that did not call

            // clearCallingIdentity, previously this wasn't needed because reading settings

            // did not do permission checking but thats no longer the case.

            // Long term this should be removed and callers should properly call

            // clearCallingIdentity or use a ContentResolver from the caller as needed.

            if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {

                final long token = Binder.clearCallingIdentity();

                try {

                    b = cp.call(cr.getPackageName(), cr.getAttributionTag(),

                            mProviderHolder.mUri.getAuthority(), mCallGetCommand, name,

                            args);

                } finally {

                    Binder.restoreCallingIdentity(token);

                }

            } else {

                b = cp.call(cr.getPackageName(), cr.getAttributionTag(),

                        mProviderHolder.mUri.getAuthority(), mCallGetCommand, name, args);

            }

            if (b != null) {

                String value = b.getString(Settings.NameValueTable.VALUE);

                // Don't update our cache for reads of other users' data

                if (isSelf) {

                    synchronized (NameValueCache.this) {

                        if (needsGenerationTracker) {

                            MemoryIntArray array = b.getParcelable(

                                    CALL_METHOD_TRACK_GENERATION_KEY);

                            final int index = b.getInt(

                                    CALL_METHOD_GENERATION_INDEX_KEY, -1);

                            if (array != null && index >= 0) {

                                final int generation = b.getInt(

                                        CALL_METHOD_GENERATION_KEY, 0);

                                if (DEBUG) {

                                    Log.i(TAG, "Received generation tracker for type:"

                                            + mUri.getPath() + " in package:"

                                            + cr.getPackageName() + " and user:"

                                            + userHandle + " with index:" + index);

                                }

                                if (mGenerationTracker != null) {

                                    mGenerationTracker.destroy();

                                }

                                mGenerationTracker = new GenerationTracker(array, index,

                                        generation, () -> {

                                    synchronized (NameValueCache.this) {

                                        Log.e(TAG, "Error accessing generation"

                                                + " tracker - removing");

                                        if (mGenerationTracker != null) {

                                            GenerationTracker generationTracker =

                                                    mGenerationTracker;

                                            mGenerationTracker = null;

                                            generationTracker.destroy();

                                            mValues.clear();

                                        }

                                    }

                                });

                                currentGeneration = generation;

                            }

                        }

                        if (mGenerationTracker != null && currentGeneration ==

                                mGenerationTracker.getCurrentGeneration()) {

                            mValues.put(name, value);

                        }

                    }

                } else {

                    if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle

                            + " by " + UserHandle.myUserId()

                            + " so not updating cache");

                }

                return value;

            }

            // If the response Bundle is null, we fall through

            // to the query interface below.

        } catch (RemoteException e) {

            // Not supported by the remote side?  Fall through

            // to query().

        }

    }

    Cursor c = null;

    try {

        Bundle queryArgs = ContentResolver.createSqlQueryBundle(

                NAME_EQ_PLACEHOLDER, new String[]{name}, null);

        // Same workaround as above.

        if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {

            final long token = Binder.clearCallingIdentity();

            try {

                c = cp.query(cr.getPackageName(), cr.getAttributionTag(), mUri,

                        SELECT_VALUE_PROJECTION, queryArgs, null);

            } finally {

                Binder.restoreCallingIdentity(token);

            }

        } else {

            c = cp.query(cr.getPackageName(), cr.getAttributionTag(), mUri,

                    SELECT_VALUE_PROJECTION, queryArgs, null);

        }

        if (c == null) {

            Log.w(TAG, "Can't get key " + name + " from " + mUri);

            return null;

        }

        String value = c.moveToNext() ? c.getString(0) : null;

        synchronized (NameValueCache.this) {

            if (mGenerationTracker != null

                    && currentGeneration == mGenerationTracker.getCurrentGeneration()) {

                mValues.put(name, value);

            }

        }

        if (LOCAL_LOGV) {

            Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +

                    name + " = " + (value == null ? "(null)" : value));

        }

        return value;

    } catch (RemoteException e) {

        Log.w(TAG, "Can't get key " + name + " from " + mUri, e);

        return null;  // Return null, but don't cache it.

    } finally {

        if (c != null) c.close();

    }

}

别看这个方法这么多代码,但是其实真正核心的逻辑只有两行,得到SettingsProvider的ContentProvider

然后在调用call方法来查询数据,返回一个Bundle数据,我们的AndroidID就在这个Bundle中,我们先去看这个Provider的实现


IContentProvider cp = mProviderHolder.getProvider(cr);

b = cp.call(cr.getPackageName(), cr.getAttributionTag(),

                mProviderHolder.mUri.getAuthority(), mCallGetCommand, name, args);

我们来到了SettingsProvider这个项目下,找到对应的Provider,代码如下


@Override

public Bundle call(String method, String name, Bundle args) {

    final int requestingUserId = getRequestingUserId(args);

    switch (method) {

        case Settings.CALL_METHOD_GET_CONFIG: {

            Setting setting = getConfigSetting(name);

            return packageValueForCallResult(setting, isTrackingGeneration(args));

        }

        case Settings.CALL_METHOD_GET_GLOBAL: {

            Setting setting = getGlobalSetting(name);

            return packageValueForCallResult(setting, isTrackingGeneration(args));

        }

        case Settings.CALL_METHOD_GET_SECURE: {

            Setting setting = getSecureSetting(name, requestingUserId,

                    /*enableOverride=*/ true);

            return packageValueForCallResult(setting, isTrackingGeneration(args));

        }

        case Settings.CALL_METHOD_GET_SYSTEM: {

            Setting setting = getSystemSetting(name, requestingUserId);

            return packageValueForCallResult(setting, isTrackingGeneration(args));

        }

        case Settings.CALL_METHOD_PUT_CONFIG: {

            String value = getSettingValue(args);

            final boolean makeDefault = getSettingMakeDefault(args);

            insertConfigSetting(name, value, makeDefault);

            break;

        }

        case Settings.CALL_METHOD_PUT_GLOBAL: {

            String value = getSettingValue(args);

            String tag = getSettingTag(args);

            final boolean makeDefault = getSettingMakeDefault(args);

            final boolean overrideableByRestore = getSettingOverrideableByRestore(args);

            insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,

                    overrideableByRestore);

            break;

        }

        case Settings.CALL_METHOD_PUT_SECURE: {

            String value = getSettingValue(args);

            String tag = getSettingTag(args);

            final boolean makeDefault = getSettingMakeDefault(args);

            final boolean overrideableByRestore = getSettingOverrideableByRestore(args);

            insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false,

                    overrideableByRestore);

            break;

        }

        case Settings.CALL_METHOD_PUT_SYSTEM: {

            String value = getSettingValue(args);

            boolean overrideableByRestore = getSettingOverrideableByRestore(args);

            insertSystemSetting(name, value, requestingUserId, overrideableByRestore);

            break;

        }

        case Settings.CALL_METHOD_SET_ALL_CONFIG: {

            String prefix = getSettingPrefix(args);

            Map<String, String> flags = getSettingFlags(args);

            Bundle result = new Bundle();

            result.putBoolean(Settings.KEY_CONFIG_SET_RETURN,

                    setAllConfigSettings(prefix, flags));

            return result;

        }

        case Settings.CALL_METHOD_RESET_CONFIG: {

            final int mode = getResetModeEnforcingPermission(args);

            String prefix = getSettingPrefix(args);

            resetConfigSetting(mode, prefix);

            break;

        }

        case Settings.CALL_METHOD_RESET_GLOBAL: {

            final int mode = getResetModeEnforcingPermission(args);

            String tag = getSettingTag(args);

            resetGlobalSetting(requestingUserId, mode, tag);

            break;

        }

        case Settings.CALL_METHOD_RESET_SECURE: {

            final int mode = getResetModeEnforcingPermission(args);

            String tag = getSettingTag(args);

            resetSecureSetting(requestingUserId, mode, tag);

            break;

        }

        case Settings.CALL_METHOD_DELETE_CONFIG: {

            int rows  = deleteConfigSetting(name) ? 1 : 0;

            Bundle result = new Bundle();

            result.putInt(RESULT_ROWS_DELETED, rows);

            return result;

        }

        case Settings.CALL_METHOD_DELETE_GLOBAL: {

            int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0;

            Bundle result = new Bundle();

            result.putInt(RESULT_ROWS_DELETED, rows);

            return result;

        }

        case Settings.CALL_METHOD_DELETE_SECURE: {

            int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;

            Bundle result = new Bundle();

            result.putInt(RESULT_ROWS_DELETED, rows);

            return result;

        }

        case Settings.CALL_METHOD_DELETE_SYSTEM: {

            int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;

            Bundle result = new Bundle();

            result.putInt(RESULT_ROWS_DELETED, rows);

            return result;

        }

        case Settings.CALL_METHOD_LIST_CONFIG: {

            String prefix = getSettingPrefix(args);

            Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),

                    isTrackingGeneration(args));

            reportDeviceConfigAccess(prefix);

            return result;

        }

        case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: {

            RemoteCallback callback = args.getParcelable(

                    Settings.CALL_METHOD_MONITOR_CALLBACK_KEY);

            setMonitorCallback(callback);

            break;

        }

        case Settings.CALL_METHOD_LIST_GLOBAL: {

            Bundle result = new Bundle();

            result.putStringArrayList(RESULT_SETTINGS_LIST,

                    buildSettingsList(getAllGlobalSettings(null)));

            return result;

        }

        case Settings.CALL_METHOD_LIST_SECURE: {

            Bundle result = new Bundle();

            result.putStringArrayList(RESULT_SETTINGS_LIST,

                    buildSettingsList(getAllSecureSettings(requestingUserId, null)));

            return result;

        }

        case Settings.CALL_METHOD_LIST_SYSTEM: {

            Bundle result = new Bundle();

            result.putStringArrayList(RESULT_SETTINGS_LIST,

                    buildSettingsList(getAllSystemSettings(requestingUserId, null)));

            return result;

        }

        default: {

            Slog.w(LOG_TAG, "call() with invalid method: " + method);

        } break;

    }

    return null;

}

这里呢,我们只关心 CALL_METHOD_GET_SECURE 。为了代码的完整性,我保留了所有代码。在上面的逻辑上可以看到是先是通过getSecureSetting方法获取了Setting对象,然后调用packageValueForCallResult方法传入Setting对象,我们先看getSecureSetting的方法实现


private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {

    if (DEBUG) {

        Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");

    }

    // Resolve the userId on whose behalf the call is made.

    final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);

    // Ensure the caller can access the setting.

    enforceSettingReadable(name, SETTINGS_TYPE_SECURE, UserHandle.getCallingUserId());

    // Determine the owning user as some profile settings are cloned from the parent.

    final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);

    if (!isSecureSettingAccessible(name, callingUserId, owningUserId)) {

        // This caller is not permitted to access this setting. Pretend the setting doesn't

        // exist.

        SettingsState settings = mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_SECURE,

                owningUserId);

        return settings != null ? settings.getNullSetting() : null;

    }

    // As of Android O, the SSAID is read from an app-specific entry in table

    // SETTINGS_FILE_SSAID, unless accessed by a system process.

    if (isNewSsaidSetting(name)) {

        PackageInfo callingPkg = getCallingPackageInfo(owningUserId);

        synchronized (mLock) {

            return getSsaidSettingLocked(callingPkg, owningUserId);

        }

    }

    if (enableOverride) {

        if (Secure.LOCATION_MODE.equals(name)) {

            final Setting overridden = getLocationModeSetting(owningUserId);

            if (overridden != null) {

                return overridden;

            }

        }

    }

    // Not the SSAID; do a straight lookup

    synchronized (mLock) {

        return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE,

                owningUserId, name);

    }

}

private PackageInfo getCallingPackageInfo(int userId) {

    try {

        return mPackageManager.getPackageInfo(getCallingPackage(),

                PackageManager.GET_SIGNATURES, userId);

    } catch (RemoteException e) {

        throw new IllegalStateException("Package " + getCallingPackage() + " doesn't exist");

    }

}

我们的查询Setting的这个操作,会通过 isNewSsaidSetting(name) 这个判断来处理,然后他调用getCallingPackageInfo来查询应用签名信息,最后把存有应用签名信息的PackageInfo传入getSsaidSettingLocked方法中。我们继续查看


private Setting getSsaidSettingLocked(PackageInfo callingPkg, int owningUserId) {

    // Get uid of caller (key) used to store ssaid value

    // 这个name是uid,是应用ID

    String name = Integer.toString(

            UserHandle.getUid(owningUserId, UserHandle.getAppId(Binder.getCallingUid())));

    if (DEBUG) {

        Slog.v(LOG_TAG, "getSsaidSettingLocked(" + name + "," + owningUserId + ")");

    }

    // Retrieve the ssaid from the table if present.

    // 先通过mSettings缓存来获取对应的Setting对象,到这里如果Settings不是空的,其实就已经查出来ssaid了

    final Setting ssaid = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID, owningUserId,name);

    // If the app is an Instant App use its stored SSAID instead of our own.

    // 获取免安装应用的ssaid,咱们正常安装的apk都是返回空值,具体操作在PackageManagerService下的getInstantAppAndroidId方法,感兴趣的同学可以去自己看一下,这里不延伸了

    final String instantSsaid;

    final long token = Binder.clearCallingIdentity();

    try {

        instantSsaid = mPackageManager.getInstantAppAndroidId(callingPkg.packageName,

                owningUserId);

    } catch (RemoteException e) {

        Slog.e(LOG_TAG, "Failed to get Instant App Android ID", e);

        return null;

    } finally {

        Binder.restoreCallingIdentity(token);

    }

    final SettingsState ssaidSettings = mSettingsRegistry.getSettingsLocked(

            SETTINGS_TYPE_SSAID, owningUserId);

    if (instantSsaid != null) {

        // Use the stored value if it is still valid.

        if (ssaid != null && instantSsaid.equals(ssaid.getValue())) {

            return mascaradeSsaidSetting(ssaidSettings, ssaid);

        }

        // The value has changed, update the stored value.

        final boolean success = ssaidSettings.insertSettingLocked(name, instantSsaid, null,

                true, callingPkg.packageName);

        if (!success) {

            throw new IllegalStateException("Failed to update instant app android id");

        }

        Setting setting = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID,

                owningUserId, name);

        return mascaradeSsaidSetting(ssaidSettings, setting);

    }

    // Lazy initialize ssaid if not yet present in ssaid table.

    // 这里会做一个检测,当我们的App首次安装的时候,缓存的ssaid列表里是没有Setting对象的

    if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) {

        Setting setting = mSettingsRegistry.generateSsaidLocked(callingPkg, owningUserId);

        return mascaradeSsaidSetting(ssaidSettings, setting);

    }

    return mascaradeSsaidSetting(ssaidSettings, ssaid);

}

private Setting mascaradeSsaidSetting(SettingsState settingsState, Setting ssaidSetting) {

    // SSAID settings are located in a dedicated table for internal bookkeeping

    // but for the world they reside in the secure table, so adjust the key here.

    // We have a special name when looking it up but want the world to see it as

    // "android_id".

    if (ssaidSetting != null) {

        return settingsState.new Setting(ssaidSetting) {

            @Override

            public int getKey() {

                final int userId = getUserIdFromKey(super.getKey());

                return makeKey(SETTINGS_TYPE_SECURE, userId);

            }

            @Override

            public String getName() {

                return Settings.Secure.ANDROID_ID;

            }

        };

    }

    return null;

}

private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {

    if (!trackingGeneration) {

        if (setting == null || setting.isNull()) {

            return NULL_SETTING_BUNDLE;

        }

        return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue());

    }

    Bundle result = new Bundle();

    result.putString(Settings.NameValueTable.VALUE,

            !setting.isNull() ? setting.getValue() : null);

    mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());

    return result;

}

到这里其实就已经知道整个查询过程了。我在梳理一下整个查询过程。

1.通过Settings.Secure.getString来发起查询请求

2.Settings内部会通过SettingsProvider的方式去查询,此时已经通过IPC的方式查询了,处理流已经到了SettingsProvider进程内

3.SettingsProvider会通过uid进行查询对应的Setting也就是ssaid对象,然后在最后会判断是否为空,因为我们应用首次安装的时候,这个ssaid是空值,系统会生成一遍ssaid,然后在返回封装有ssaid的Setting对象

4.最后调用packageValueForCallResult方法,把Setting对象转化为Bundle对象并用于进程传递

接下来看一下生成过程 generateSsaidLocked


public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) {

    // Read the user's key from the ssaid table.

    // 获取当前用户维度的setting对象

    Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);

    if (userKeySetting == null || userKeySetting.isNull()

            || userKeySetting.getValue() == null) {

        // Lazy initialize and store the user key.

        generateUserKeyLocked(userId);

        userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);

        if (userKeySetting == null || userKeySetting.isNull()

                || userKeySetting.getValue() == null) {

            throw new IllegalStateException("User key not accessible");

        }

    }

    final String userKey = userKeySetting.getValue();

    if (userKey == null || userKey.length() % 2 != 0) {

        throw new IllegalStateException("User key invalid");

    }

    // Convert the user's key back to a byte array.

    final byte[] keyBytes = HexEncoding.decode(userKey);

    // Validate that the key is of expected length.

    // Keys are currently 32 bytes, but were once 16 bytes during Android O development.

    if (keyBytes.length != 16 && keyBytes.length != 32) {

        throw new IllegalStateException("User key invalid");

    }

    //用 userKey 初始化 HmacSHA256 算法

    final Mac m;

    try {

        m = Mac.getInstance("HmacSHA256");

        m.init(new SecretKeySpec(keyBytes, m.getAlgorithm()));

    } catch (NoSuchAlgorithmException e) {

        throw new IllegalStateException("HmacSHA256 is not available", e);

    } catch (InvalidKeyException e) {

        throw new IllegalStateException("Key is corrupted", e);

    }

    // Mac each of the developer signatures.

    // 通过 callingPkg 对象获取调用应用的签名信息,然后进行 HmacSHA256 加密

    for (int i = 0; i < callingPkg.signatures.length; i++) {

        byte[] sig = callingPkg.signatures[i].toByteArray();

        m.update(getLengthPrefix(sig), 0, 4);

        m.update(sig);

    }

    // Convert result to a string for storage in settings table. Only want first 64 bits.

    // 截取前16位字符作为当前应用的AndroidId

    final String ssaid = HexEncoding.encodeToString(m.doFinal(), false /* upperCase */)

            .substring(0, 16);

    // Save the ssaid in the ssaid table.

    final String uid = Integer.toString(callingPkg.applicationInfo.uid);

    final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);

    //插入到缓存中

    final boolean success = ssaidSettings.insertSettingLocked(uid, ssaid, null, true,

        callingPkg.packageName);

    if (!success) {

        throw new IllegalStateException("Ssaid settings not accessible");

    }

    return getSettingLocked(SETTINGS_TYPE_SSAID, userId, uid);

}

其中生成过程也很清楚了,是基于当前用户的随机串(会存储到本地文件,只有初次生成是随机串),加上当前应用的签名信息,采用HmacSHA256的加密方式得到的加密串截取前16位生成出来,所以只要应用签名不会变,手机登录的用户不会变的情况下,ANDROID_ID就不会变

有些同学可能还不太明白userKey是什么东西。我简单描述下

userKey 是在设备的系统首次启动的时候,根据当前的userId生成的一个随机串,然后存储到 /data/system/users/0/settings_global.xml 目录下,这个只有在恢复出厂设置、变更用户、刷机的时候才会发生变化

所以当下8.0以上如果想要区分设备ID,SSAID是当下最好的选择,毕竟Google大法好

posted @ 2021-04-06 19:29  柯壮  阅读(2097)  评论(0编辑  收藏  举报