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

本篇文章基 android-11.0.0_r17 编写

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

我们先看一下如何获取ANDROID_ID,然后逐渐深入系统,去查看ANDROID_ID的生成规则,帮助大家更好的理解和选择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);
}

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

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

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

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

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

上一篇:Fiddler手机抓包


下一篇:GLFWError #65542 Happen, WGL: The driver does not appear to support OpenGL 问题解决