在应用中,很多app都有登陆注册功能,这样可以更好的管理个人信息,很多时候人们会使用sharepreference保存账户信息,把他经过加密写入文件中,这样既方便有简单。但是这样真的好吗?当服务器数据更新时,当一个应用具有多个账号时候,管理起来很不方便,并且安全性也不可靠。在Android2.0中加入了一个新的包android.accounts,该包功能十分强大,主要包括了集中式的账户管理API,用以安全地存储和访问认证的令牌和密码,可以在同一个设备中管理同一应用的多个不同账号,能够自动批量的同步服务器更新账户,甚至可以和不同服务器进行数据同步和安全认证。现在进入主题,这篇文章将从账户的管理和账户的更新两大方面进行展开介绍。涉及到如下核心类:

  • AccountManager
  • AbstractAccountAuthenticator
  • AuthenticatorService
  • AccountAuthenticatorActivity
  • AbstractThreadedSyncAdapter
  • SyncService

账户管理

一.AccountManager

AccountManager是一个账户管理类,来实现账户的管理,常用的方法如下:

  • addAccount() :添加一个帐户。
  • addAccountExplicitly(): 直接添加一个帐户到AccountManager。
  • getAccounts():获取所有帐户。
  • getAccountsByType(String package):获取指定的账号
  • removeAccount():删除帐户

二.AbstractAccountAuthenticator

AbstractAccountAuthenticator的实现类用户账户添加,登陆接口认证操作,他是一个抽象类,需要写出他的实现类。在实现类中需要复写他的抽象方法,一共7个。如下:
    1.editProperties:返回一个Bundle,其中包含可用于编辑属性的活动的Intent,如果无返回,可以直接返回null,或者抛出常,例如new UnsupportedOperationException()。
    2.addAccount:添加指定的accountType的帐户。(重要)
    当用户在添加账户页面选择账户进行添加或者调用accountManager.addAccount 的时候,AbstractAccountAuthenticator中的addAccount 方法会被默认调用,因此需要重写addAccount 方法。
    3.confirmCredentials:检查用户是否知道帐户的凭据。连接服务器进行身份校验。如果无校验可以返回返回null,或者抛出常。
    4.getAuthToken:获得一个账户authtoken。(重要)
    当执行AccountAuthenticatorActivity中的mAccountManager.blockingGetAuthToken(account,Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE);时调用该方法。如果之前成功获取过AuthenToken会缓存,之后不会在调用getAuthenToken()方法,除非调用invalidateAuthenToken()。
    5.getAuthTokenLabel:向认证者询问给定的authTokenType的本地化标签。如果无,可以直接返回null,或者抛出异常。
    6.updateCredentials:更新帐户的本地存储的凭据。如果无更新,可以直接返回null,或者抛出异常。
    7.hasFeatures:检查帐户是否支持所有指定的验证器特定功能。如果不需要可以直接返回null,或者抛出异常。
    AccountManager和Authenticator的方法相对应,比如,AccountManager的addAccount()方法会调用Authenticator的addAccount()方法,Authenticator的方法会返回一个Bundle给AccountManager处理。
    接下来介绍两个比较重要的方法:addAccount,getAuthToken

    @Overridepublic Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s1,String[] strings, Bundle bundle) throws NetworkErrorException {/***  这里是跳转到新的页面让用户添加账户,还可以直接添加账户,方法如下*  Account account = new Account(String name, String type);*  accountManager.addAccountExplicitly(account,password,userdata);*/Intent intent = new Intent(ctx, RegisterActivty.class);intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);Bundle b = new Bundle();bundle.putParcelable(AccountManager.KEY_INTENT, intent);return b;}
    @Overridepublic Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle options) throws NetworkErrorException {//可以请求服务器获取token,这里为了简单直接返回Bundle bundle;if (!s.equals(ConstantsGlobal.AUTH_TOKEN_TYPE)) {// 通过blockingGetAuthToken方法传来的Constants.AUTHTOKEN_TYPE进行比较bundle = new Bundle();bundle.putInt(AccountManager.KEY_ERROR_CODE, 1);bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authToken");return bundle;}AccountManager am = AccountManager.get(ctx);String psw = am.getPassword(account);if (!TextUtils.isEmpty(psw)) {//根据服务器接口根据账户密码获取authToken// String authToken = NetworkUtilities.authenticate(account.name, psw);//这里为了测试使用随机数Random random = new Random();bundle = new Bundle();String authToken = random.nextLong() + "";//如果已经到服务器验证过账号并保存到AccountManager中,并且返回if (!TextUtils.isEmpty(authToken)) {Bundle result = new Bundle();//result.putString(AccountManager.KEY_AUTHTOKEN, authToken);bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);//不返回name和type会报错“the type and name should not be empty”bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);return bundle;}}//如果没有到服务器验证过账号并保存到AccountManager中,则重新倒添加账号页面中验证。bundle = new Bundle();Intent intent = new Intent(ctx, AuthenticatorActivity.class);bundle.putParcelable(AccountManager.KEY_INTENT, intent);intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);return bundle;}

上面两个方法每一步的标注非常清楚了,这里不在详细叙述。

三.AuthenticatorService

1.为Authenticator创建Service

Authenticator说完了,那么他在什么地方使用呢?接下来介绍AuthenticatorService,Authenticator就在AuthenticatorService里面使用,AuthenticatorService他是一个服务继承自Server。组需要在onBind方法中返回Authenticator的IBinder对象。

    @Overridepublic IBinder onBind(Intent intent) {return new Authenticator(getApplicationContext()).getIBinder();}

2.清单文件配置:

    <service android:name=".accountmanager.AuthenticatorService"><intent-filter><action android:name="android.accounts.AccountAuthenticator" /></intent-filter><meta-dataandroid:name="android.accounts.AccountAuthenticator"android:resource="@xml/authenticator" /></service>

其中<action android:name="android.accounts.AccountAuthenticator" />必须配置。

3.添加Metadata组件,在res/xml/目录下声明组件

    <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"android:accountType="com.account.accountmanagerdemo"android:icon="@mipmap/ic_launcher"android:smallIcon="@mipmap/ic_launcher"android:label="@string/app_name"/>

注意:ccountType很重要,用来唯一标识Authenticator,AccountManager的方法中有accountType的参数需要和此
处保持一致。一般为包名称,否则创建不成功。

四.AccountAuthenticatorActivity

在前期准备写好之后,写下来需要在activity里面写入登陆,注册等相关功能了,这时候需要用到了AccountAuthenticatorActivity他是实现一个用于帮助实现一个AbstractAccountAuthenticator的Activity的基类。如果AbstractAccountAuthenticator需要用一个Activity去处理请求,就可以使用一个扩展的AccountAuthenticatorActivity来实现账户管理的Activity,这里创建AuthenticatorActivity类让他继承自AccountAuthenticatorActivity,作为操作账户的界面,可以进行账户的注册,登陆查询,更新等。

1.注册,核心代码如下:

    //在这里调用接口访问服务器进行注册。这里为了方便,不进行网络操作String mAccount = account.getText().toString().trim();String mPwd = pwd.getText().toString().trim();if (TextUtils.isEmpty(mAccount) || TextUtils.isEmpty(mPwd)) {return;}//如果注册成功,则执行以下代码,否则重新注册,这里默认注册成功Account account = new Account(mAccount, ConstantsGlobal.ACCOUNT_TYPE);AccountManager am = AccountManager.get(RegisterActivty.this);am.addAccountExplicitly(account, mPwd, null);

2.登陆,核心代码如下:

    //请求接口,从服务器获取数据token;mAccount为登陆的账户名称Account account = new Account(mAccount, ConstantsGlobal.ACCOUNT_TYPE);AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {@Overridepublic void run(AccountManagerFuture<Bundle> future) {try {//获取tokenString token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN);Intent intent = new Intent(AuthenticatorActivity.this, MainActivity.class);Account account1 = new Account(future.getResult().getString(AccountManager.KEY_ACCOUNT_NAME),future.getResult().getString(AccountManager.KEY_ACCOUNT_TYPE));intent.putExtra(ConstantsGlobal.KEY_ACCOUNT, account1);startActivity(intent);} catch (Exception e) {e.printStackTrace();}}};accountManager.getAuthToken(account, ConstantsGlobal.AUTH_TOKEN_TYPE, null, AuthenticatorActivity.this, callback, null);

3.退出登陆

    AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {@Overridepublic void run(AccountManagerFuture<Bundle> future) {try {String token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN);accountManager.invalidateAuthToken(ConstantsGlobal.ACCOUNT_TYPE, token);finish();} catch (Exception e) {e.printStackTrace();}}};//account为要退出登录的登陆名称;this为当前页面的ActivityaccountManager.getAuthToken(account, ConstantsGlobal.AUTH_TOKEN_TYPE, null, this, callback, null);

到此为止,账号登陆的相关功能介绍结束,接下来看看账号的更新功能。

账户更新

一.SyncService

账号更新功能需要一个服务来提供,让SyncService继承自Service来实现账号更新功能,和上面所说的AuthenticatorService一样需要在onBind方法中返回一个IBinder,这里需要借助于SyncAdapter,关于SyncAdapter稍后在做描述,通过syncAdapter.getSyncAdapterBinder()方法就可以获取到IBinder对象。

    @Overridepublic IBinder onBind(Intent intent) {return syncAdapter.getSyncAdapterBinder();}

需要注意的是实例化syncAdapter的过程要保证线程安全,以免同步框架会将多次同步响应添加到队列中。清单文件中配置如下:

    <serviceandroid:name=".accountrefresh.SyncService"android:exported="true"><intent-filter><action android:name="android.content.SyncAdapter" /></intent-filter><meta-dataandroid:name="android.content.SyncAdapter"android:resource="@xml/syncadapter" /><meta-dataandroid:name="android.provider.CONTACTS_STRUCTURE"android:resource="@xml/contacts" /></service>

为了便于数据的传输和更新,在这里使用provider。一个适配器只能同步一个Authority,若想使一个账户同步多个Authority,可以向系统注册多个绑定同一账户的sync-adapter,syncadapter配置如下:

    <?xml version="1.0" encoding="utf-8"?><sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"android:accountType="com.account.accountmanagerdemo"android:allowParallelSyncs="false"android:contentAuthority="com.android.contacts"android:isAlwaysSyncable="true"android:supportsUploading="false"android:userVisible="false" ></sync-adapter>

1.accountType账号类型,需要与前面在代码段中的ACCOUNT_TYPE 常量还有authenticator的元数据文件中定义的保持一致。
    2.allowParallelSyncs是否支持上传到云端,否则仅支持下载。
    3.userVisible是否支持在设置中可见。
    4.设置是否允许SyncAdapter多实例同时运行。
    5.指定同步框架是否可以在任意时间运行你的SyncAdapter。

二.AccountContentProvider

1.账户更新数据的存储获取可以与ContentProvider框架协作,来存储更新数据,不仅是方便使用SyncAdapter,而且它也具有更好的安全性。这里可以使用一个虚拟的ContentProvider,可以全部返回null或者0。

    public class AccountContentProvider extends ContentProvider {@Overridepublic boolean onCreate() {return false;}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {return null;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {return null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {return 0;}}

2.清单文件配置

    <provideandroid:name=".accountrefresh.AccountContentProvider"android:authorities="com.account.accountmanagerdemo.accountrefresh.provider"android:exported="false"android:syncable="true">

(1)authorities用来唯一指定一个ContentProvider的URI authority。这个值最好设置为“包名 + .provider”。
      (2)指定实现ContentProvider的完整类名。
      (3)设置外部应用是否可以访问。因为我们的Provider并不需要别的应用访问,所以设置为”false”。这个值并不影响同步框架的访问。android:exported="false"。
      (4)设置是否可同步。如果设置为true 就不需要在代码中再调用setIsSyncable() 。这个值决定了同步框架可以与Provider传输数据,但是也仅在你明确调用的时候才传输。android:syncable="true"。

三.AbstractThreadedSyncAdapter

AbstractThreadedSyncAdapter是一个抽象类,用于账户的同步操作,它是对 Account的内容进行同步操作的适配器。当他收到同步请求时,产生一个线程来进行Account指定内容的同步处理。同步框架用的ContentPrivder框架协作,同时需要SyncAdapter做支持,否则崩溃这里,定义一个类SyncAdapter,让他继承自AbstractThreadedSyncAdapter。
    1.原理
    SyncAdapter不会自动做数据传输,它只是封装你的代码,以便框架可以在后台调用,而不需要你的应用介入。同步框架准备要同步应用数据的时候,它会调用SyncAdapter中实现的onPerformSync()方法。应该通过定期任务或是根据一些事件的结果来运行SyncAdapter。比如,隔一段时间或在每天某个特殊的时间运行,或是在本地数据变化后运行。
    2.调用时机
     (1)服务端数据变化时
        服务端数据变化时,根据服务端发送的消息运行。这样可以避免轮询服务器影响性能和功耗。
     (2)本地数据变化时
        本地数据变化后同步可以将本地变化的数据发送到服务端,适合用来确保服务端数据最新。如果数据真的是用ContentProvider保存的,那这方式是很容易实现的(译者注:在ContentProvider中使用ContentResolver的 notifyChange(android.net.Uri, android.database.ContentObserver, boolean)方法);如果是伪造的ContentProvider,那可能要麻烦一些。
     (3)系统发送网络消息时
        当系统发出保持TCP/IP连接开启的网络消息时发起,这个网络消息是网络框架的一部分。这是自动同步的一种方式,可以考虑和基于时间间隔同步结合起来使用。
     (4)固定时间间隔
        自定义一个固定的时间间隔,或者是每天的某个时间点发起。
     (5)即时发起
        由用户手动操作发起。但是,为了有更好的体验,最好还是以自动同步为主,这样可以降低电池和网络资源的消耗。
      3.更新
      更新调用ContentResolver的requestSync(Account account, String authority, Bundle extras)方法进行数据更新。强烈建议使用自动刷新账号以及SyncAdapter中其他刷新方式,为了测试期间,这里举例手动刷新,在AuthenticatorActivity界面调用手动调用刷新:

       private void refreshAccount() {Bundle bundle = new Bundle();bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);ContentResolver.requestSync(CreateSyncAccount(), ConstantsGlobal.AUTHORITY, bundle);}
        private static final String oldAccountName = "中央电视台";//此数据一般是账号变更或者服务器返回新账号public Account CreateSyncAccount() {// 创建账户类型和默认账户名称Account newAccount = new Account(oldAccountName, ConstantsGlobal.ACCOUNT_TYPE);/** Add the account and account type, no password or user data* If successful, return the Account object, otherwise report an error.*/if (accountManager.addAccountExplicitly(newAccount, null, null)) {/** If you don't set android:syncable="true" in* in your <provider> element in the manifest,* then call context.setIsSyncable(account, AUTHORITY, 1)* here.*/Log.e("AuthenticatorActivity", "账户更新成功");Toast.makeText(this,"更新成功,请重新点击查看账户列表",Toast.LENGTH_SHORT).show();} else {/** The account exists or some other error occurred. Log this, report it,* or handle it internally.*/Log.e("AuthenticatorActivity", "账户更新失败,或者此账户已经存在");Toast.makeText(this,"更新成功,数据没有变化,请查看列表",Toast.LENGTH_SHORT).show();}return newAccount;}

4.onPerformSync方法

    @Overridepublic void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {}

4.1 参数含义:
    (1)与本次触发事件关联的Account对象,如果你的服务器不需要账号,直接无视就可以。
    (2)包含一些标志位的Bundle对象。
    (3)系统中ContentProvider的authority,一般是你自己应用中的ContentProvider对应的authority。
    (4)authority对应的ContentProviderClient,它是ContentProvider的一个轻量接口,具有与ContentResolver相同的功能。如果是用ContentProvider保存的数据,你可以用这个对象连接到ContentProvider,否则无视就好。
    (5)SyncResult对象,可以用来将同步的结果传给同步框架。
    4.2 操作步骤
     (1)连接服务器
     虽然同步开始时你可以认为网络是通畅的,但是同步框架并不会自动帮你连接服务器
     (2)下载上传数据
     SyncAdapter不会自动做数据传输。如果你要从服务端取数据存到本地,那你必须提供请求、下载、插入数据的代码。同样,如果需要上传数据,也要读数据、发送数据请求。除此之外,还需要处理数据传输中发生的网络错误。
     (3)处理数据冲突
     SyncAdapter不会自动处理服务端和本地的数据冲突。而且,也不会检测本地和服务端的数据哪一个更新。你必须自己提供算法处理这种场景。
     (4)清理
     在传输结束后关闭与服务器的链接,清理临时文件和缓存。
     注意: 同步框架自动将onPerformSync()放在后台线程,因此不需要自己设置后台运。

需要添加权限如下:

    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/><uses-permission android:name="android.permission.GET_ACCOUNTS"/><uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/><uses-permission android:name="android.permission.USE_CREDENTIALS"/><uses-permission android:name="android.permission.INTERNET"></uses-permission><uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>

最后附上源码地址:
    http://download.csdn.net/download/yoonerloop/10130204

运行效果如下:

Android账号管理系统详解相关推荐

  1. Android Studio 插件开发详解一:入门练手

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112003 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  2. Android 虚拟分区详解(一) 参考资料推荐

    文章目录 0. 导读 1. Android 官方 VAB 文档 1.1 公开文档 1.2 半公开文档 2. Device Mapper 文档 2.1 device mapper 文档 2.2 dmse ...

  3. C#利用ASP.NET?Core开发学生管理系统详解

    文章来源: 学习通http://www.bdgxy.com/ 普学网http://www.boxinghulanban.cn/ 智学网http://www.jaxp.net/ 表格制作excel教程h ...

  4. 《Android游戏开发详解》——第1章,第1.6节函数(在Java中称为“方法”更好)...

    本节书摘来自异步社区<Android游戏开发详解>一书中的第1章,第1.6节函数(在Java中称为"方法"更好),作者 [美]Jonathan S. Harbour,更 ...

  5. JMessage Android 端开发详解

    JMessage Android 端开发详解 目前越来越多的应用会需要集成即时通讯功能,这里就为大家详细讲一下如何通过集成 JMessage 来为你的 App 增加即时通讯功能. 首先,一个最基础的 ...

  6. 《Java和Android开发实战详解》——2.5节良好的Java程序代码编写风格

    本节书摘来自异步社区<Java和Android开发实战详解>一书中的第2章,第2.5节良好的Java程序代码编写风格,作者 陈会安,更多章节内容可以访问云栖社区"异步社区&quo ...

  7. Android事件流程详解

    Android事件流程详解 网络上有不少博客讲述了android的事件分发机制和处理流程机制,但是看过千遍,总还是觉得有些迷迷糊糊,因此特地抽出一天事件来亲测下,向像我一样的广大入门程序员详细讲述an ...

  8. Android Studio 插件开发详解二:工具类

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112856 本文出自[赵彦军的博客] 在插件开发过程中,我们按照开发一个正式的项 ...

  9. 《Android游戏开发详解》一2.16 区分类和对象

    本节书摘来异步社区<Android游戏开发详解>一书中的第2章,第2.16节,作者: [美]Jonathan S. Harbour 译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社 ...

  10. Android Framework系统服务详解

    Android Framework系统服务详解 操作环境 系统:Linux (Ubuntu 12.04) 平台:高通 Android版本:5.1 PS: 符号...为省略N条代码 一.大致原理分析 A ...

最新文章

  1. 安卓手机可以连接斑马系统吗_Zebra斑马 StageNow 安卓系统移动设备快速部署工具...
  2. 在 NetBeans IDE 中设计 Swing GUI
  3. 币安布局去中心化交易所,原来是因为这三个原因!
  4. 【TensorFlow-windows】keras接口——卷积手写数字识别,模型保存和调用
  5. nodejs mysql 返回值_带有Mysql数据库返回值的Nodejs
  6. java hashset 实现_HashSet实现原理分析(Java源码剖析)
  7. 词法分析与语法分析简介
  8. 数学与泛型编程:高效编程的奥秘pdf_Java 泛型与类型擦除
  9. SharePoint 2010 文档管理系列
  10. FFmpeg[11] - ffmpeg去除水印(图片和文字)
  11. 正式学习Linux的第一节课
  12. ONLYOFFICE 如何连接集成到 Wordpress 上
  13. VS2022 安装 .NET Framework 4.0的方法
  14. 微信域名防封技术,我们应该如何解决屏蔽拦截
  15. android云测如何使用教程,iTestin使用教程-Testin云测.PDF
  16. AG7120与AG7220做HDMI信号延长放大器驱动方案讲解|AG7120与AG7220设计HDMI信号延长放大器电路参考
  17. java技术及ssh框架和jsp技术的介绍 外文文献及翻译_java技术及ssh框架和jsp技术的介绍 外文文献及翻译.doc...
  18. 架构师小跟班:推荐46个非常经典的Linux面试题
  19. 技术VC的优势以及技术VC是如何生存的
  20. 黑鹰红客基地VIP美工教程系列

热门文章

  1. 对数正态分布(Log-Normal Distribution)
  2. kali无线安全分析工具
  3. laravel mysql 事务_laravel框架中的MySQL事务处理 阿星小栈
  4. 炒鸡酷,IT互联网程序员就业新前景:看极客是怎么靠两个披萨影响世界
  5. 如何将多个excel表格合并成一个_如何把两个excel表合并成一个
  6. 计算机系统课程 笔记总结 CSAPP第四章 处理器体系结构(4.1-4.3)
  7. 病毒分析--WannaCry分析--1
  8. 一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如6=1+2+3.编程找出1000以内的所有完数。
  9. 优矿-python计算上证50之间的相关系数
  10. 硬盘重装系统:电脑本地硬盘重装系统步骤