再次理解Android账号管理体系

2023-09-14 21:56:20

目录

✅ 0. 需求

📂 1. 前言

🔱 2. 使用

2.1 账户体系前提

2.2 创建账户服务

2.3 操作账户-增删改查

💠 3. 源码流程


✅ 0. 需求

        试想,自己去实现一个账号管理体系,该如何做呢?

        ——————————

        最近有遇到这样一个需求:AR眼镜只支持同时与一个手机绑定,即:AR眼镜已与手机绑定过的话,不再支持与其他手机绑定;如要绑定其他手机,需要先解绑当前手机才能重新绑定。

        理解一下需求,即是:眼镜端需要账号管理体系,主要用来存储已绑定过手机的token。

        AR眼镜绑定手机时序图,如下所示:


 

📂 1. 前言

        我们知道,Android的账号管理体系是用来管理用户在Android设备上的身份验证和授权的系统,包括了对账号的创建、授权、修改和删除等操作的管理。

        那么,我们为什么要使用Android的账号管理体系?

        ——————————

        尽管,我们可以自己使用SP、MMKV、文件或数据库等方式来存储、更新、删除账户、密码或AuthToken;但其实涉及到跨进程通信,实现起来其实是稍显麻烦的;并且对于数据安全,信息加密这块的可靠性也有待商榷。

        另外,从本文第一张图可见,我们在架构设计中规划的账号体系服务,是寄生于Android启动时system_server开启的服务,然后通过binder方式,提供给其他进程使用。

        然而,在技术预研时发现,强大的Android早已想到了这点,在Android 2.0开始就加入了新包android.accounts,为我们准备好了这样一个服务ACCOUNT_SERVICE。

        而且,该包功能已十分强大,我们可以直接拿来使用,功能主要包括:

  1. 集中式的账户管理API,可以安全地存储和访问认证的令牌和密码;
  2. 可以在同一个设备中管理同一应用的多个不同账号,能够自动批量的同步服务器更新账户,甚至可以和不同服务器进行数据同步和安全认证;
  3. 把账户的验证过程、AuthToken的获取过程分离出来,降低程序的耦合性;
  4. 并且会在”设置”应用中添加一个账户入口;
  5. 方便应用间账号共享。

🔱 2. 使用

2.1 账户体系前提

        1)账号体系共享前提:使用相同的签名

        2)权限申明:

<uses-permission android:name="android.permission.GET_ACCOUNTS" />

2.2 创建账户服务

        步骤一:定义一个action为android.accounts.AccountAuthenticator的Intent的Service,并在meta-data的resource属性指定该Account基本显示信息的xml文件authenticator,模版代码如下:

<service
    android:name=".portal.feature.account.service.AccountService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator" />
    </intent-filter>
    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator" />
</service>

        步骤二:在res下的xml文件夹中新建authenticator.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.agg.account"
    android:icon="@drawable/ic_test"
    android:label="AggAccount"
    android:smallIcon="@drawable/ic_test" />

        :android:accountType表示的是Account类型,它必须是唯一的,一般是包名。

        步骤三:创建继承自AbstractAccountAuthenticator的Authenticator文件,如下:

class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {

    override fun editProperties(
        response: AccountAuthenticatorResponse?, accountType: String?
    ): Bundle? {
        return null
    }

    override fun addAccount(
        response: AccountAuthenticatorResponse?,
        accountType: String?,
        authTokenType: String?,
        requiredFeatures: Array<out String>?,
        options: Bundle?
    ): Bundle? {
        return null
    }

    override fun getAuthToken(
        response: AccountAuthenticatorResponse?,
        account: Account,
        authTokenType: String?,
        options: Bundle?
    ): Bundle? {
        return null
    }

    override fun confirmCredentials(
        response: AccountAuthenticatorResponse?, account: Account?, options: Bundle?
    ): Bundle? {
        return null
    }

    override fun getAuthTokenLabel(authTokenType: String?): String {
        return ""
    }

    override fun updateCredentials(
        response: AccountAuthenticatorResponse?,
        account: Account?,
        authTokenType: String?,
        options: Bundle?
    ): Bundle? {
        return null
    }

    override fun hasFeatures(
        response: AccountAuthenticatorResponse?, account: Account?, features: Array<out String>?
    ): Bundle? {
        return null
    }

}

        步骤四:创建帐户Service,并在Service的onBind中调AbstractAccountAuthenticator的getIBinder()返回其用于远程调用的IBinder,如下所示:

class AccountService : Service() {

    private var authenticator: Authenticator? = null

    override fun onCreate() {
        super.onCreate()
        authenticator = Authenticator(this)
    }

    override fun onBind(intent: Intent?): IBinder? {
        return authenticator?.iBinder
    }

}

        :运行起来程序后,在“设置”应用-“帐户”-“添加帐户”列表中就已可以发现自己的app了。

2.3 操作账户-增删改查

        1)获取所有账号

    /**
     * 获取所有账号
     */
    fun getAllAccount(context: Context) {
        val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
        Log.e(TAG, "getAllAccount: size = ${accountManager.accounts.size}")
        for (account in accountManager.accounts) {
            Log.e(TAG, "getAllAccount: $account")
        }
    }

         2)添加账号

    /**
     * 添加账号
     */
    fun addAccount(
        context: Context, account: Account, password: String, authToken: String
    ) {
        val bundle = Bundle()
        bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
        bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)
        bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken)
        bundle.putString(AccountManager.KEY_PASSWORD, password)
        AccountManager.get(context).addAccountExplicitly(account, password, bundle)
    }

         3)更新某个账号

    /**
     * 更新某个账号
     */
    fun updateAccount(
        context: Context, account: Account, password: String = "", authToken: String = ""
    ) {
        var updatePassword = password
        var updateAuthToken = authToken
        AccountManager.get(context).apply {
            if (updatePassword.isEmpty()) {
                updatePassword = getUserData(account, AccountManager.KEY_PASSWORD)
            }
            if (updateAuthToken.isEmpty()) {
                updateAuthToken = getUserData(account, AccountManager.KEY_AUTHTOKEN)
            }
            removeAccountExplicitly(account)
        }
        addAccount(context, account, updatePassword, updateAuthToken)
    }

         4)删除某个账号

    /**
     * 删除某个账号
     */
    fun delAccount(context: Context, account: Account) {
        val isRemoveSuccess = AccountManager.get(context).removeAccountExplicitly(account)
        Log.e(TAG, "delAllAccount: isRemoveSuccess = $isRemoveSuccess, $account")
    }

          5)删除所有账号

    /**
     * 删除所有账号
     */
    fun delAllAccount(context: Context) {
        val accountManager = AccountManager.get(context)
        for (account in accountManager.accounts) {
            val isRemoveSuccess = accountManager.removeAccountExplicitly(account)
            Log.e(TAG, "delAllAccount: isRemoveSuccess = $isRemoveSuccess, $account")
        }
    }

💠 3. 源码流程

  • 从系统启动system_server进程,进而启动ACCOUNT_SERVICE服务开始;
  • AccountManager是一个面向应用程序开发的组件,它提供了一套对应于IAccountManager协议的应用程序接口;
  • 这组接口通过Binder机制与系统服务AccountManagerService进行通信,协作完成帐号相关的操作;
  • 同时AccountManager接收authenticators提供的回调,以便在帐号操作完成之后向调用此帐号服务的业务返回对应的接口,同时触发这个业务对结果的处理。

更多推荐

线性代数的本质(六)——线性空间

文章目录线性空间线性空间子空间坐标与同构线性变换与矩阵基变换与坐标变换线性空间线性空间Grant:普适的代价是抽象。仔细分析就会发现,关于向量空间的一切概念及有关定理都不依赖于向量的具体表现形式(有序数组),也不依赖于向量加法、数乘的具体计算式,而只依赖于如下两点:向量的加法与数乘运算封闭;加法、数乘满足八条运算法则。

EMMC模块电路的PCB设计建议

EMMC电路简介EMMC(EmbeddedMultiMediaCard)是MMC协会订立、主要针对手机或平板电脑等产品的内嵌式存储器标准规格。EMMC在封装中集成了一个控制器,提供标准接口并管理闪存。原理电路8位数据信号如图8-38所示,地址、控制信号如图8-39所示,电源信号如图8-40所示。RK3588EMMC控制

深入理解Java单例模式和优化多线程任务处理

目录饿汉模式懒汉模式单线程版多线程版双重检查锁定阻塞队列单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例,并提供一个全局访问点。饿汉模式类加载的同时,创建实例。classSingleton{privatestaticfinalSingletoninstance=newSingleton();//将构

《Web安全基础》07. 反序列化漏洞

web1:基本概念1.1:序列化&反序列化1.2:反序列化漏洞1.3:POP链2:PHP反序列化2.1:序列化&反序列化2.2:魔术方法3:JAVA反序列化3.1:序列化&反序列化3.2:反射机制3.3:相关资源本系列侧重方法论,各工具只是实现目标的载体。命令与工具只做简单介绍,其使用另见《安全工具录》。靶场参考:pi

利用Pycharm将python程序打包为exe文件(亲测可用)

最近做了一个关于py的小项目,对利用Pycharm将python文件打包为exe文件不是很熟悉,故学习记录之。目录一、下载pyinstaller库二、打开Pycharm进行打包(不更改图标)三、打开Pycharm进行打包(更改图标)一、下载pyinstaller库1.点击win+r,输入cmd打开控制管理器2.输入pi

[maven] scopes & 管理 & profile & 测试覆盖率

[maven]scopes&管理&profile&测试覆盖率这里将一些其他的特性和测试覆盖率(主要是jacoco)scopesmaven的scope主要就是用来限制和管理依赖的传递性,简单的说就是,每一个scope都有其对应的特性,并且会决定依赖包在打包和运行时是否会被使用这里主要谈论的差别是compileclassp

Secrets of RLHF in Large Language Models Part I: PPO

本文是LLM系列文章,针对《SecretsofRLHFinLargeLanguageModelsPartI:PPO》的翻译。大型语言模型中RLHF的秘密(上):PPO摘要1引言2相关工作3人类反馈的强化学习4有益和无害的奖励模型5PPO的探索6评估和讨论局限性摘要大型语言模型(LLM)为通用人工智能的发展制定了蓝图。它

DC/DC开关电源学习笔记(七)低压大电流DC/DC变换技术

低压大电流DC/DC变换技术1.无暂态要求的低压大电流DC/DC变换技术2.负载极其快速变化的低压大电流DC/DC变换技术2.1非隔离型VRM2.2隔离型VRM低压大电流高功率DC/DC变换技术,已从前些年的3.3V降至现在的1.0V左右,电流目前已可达到几十安至几百安。同时,电源的输出指标,如纹波、精度、效率、过冲、

sql 脚本 WITH 作用

WITH是SQL中的一个关键字,用于创建临时表达式,也称为公共表表达式(CommonTableExpression,CTE)。它可以在查询中定义一个临时的命名结果集,并可以在后续的查询中引用该结果集。WITH的主要作用有两个:提高可读性:通过使用WITH关键字,可以将复杂的查询逻辑分解为多个简单的部分,并使用有意义的名

Sqilte3初步教程

文章目录安装创建数据库创建和删除表插入行数据安装Windows下安装,首先到下载页面,下载Windows安装软件,一般是sqlite-dll-win32-*.zipsqlite-tools-win32-*.zip下载之后将其内容解压到同一个文件夹下,我把它们都放在了D:\CS\sqlite目录下,然后将这个目录添加到环

【Springboot】Springboot如何优雅停机?K8S中Pod如何优雅停机?

什么是优雅停机:就是对应用进程发送停止指令之后,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等就是对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响,可以继续完成已有请求的处理,但是停止接受新请求本质上是JVM即将关闭前执行的一些额外的处理代码可以避免

热文推荐