Android账号管理系统详解
在应用中,很多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账号管理系统详解相关推荐
- Android Studio 插件开发详解一:入门练手
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112003 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...
- Android 虚拟分区详解(一) 参考资料推荐
文章目录 0. 导读 1. Android 官方 VAB 文档 1.1 公开文档 1.2 半公开文档 2. Device Mapper 文档 2.1 device mapper 文档 2.2 dmse ...
- C#利用ASP.NET?Core开发学生管理系统详解
文章来源: 学习通http://www.bdgxy.com/ 普学网http://www.boxinghulanban.cn/ 智学网http://www.jaxp.net/ 表格制作excel教程h ...
- 《Android游戏开发详解》——第1章,第1.6节函数(在Java中称为“方法”更好)...
本节书摘来自异步社区<Android游戏开发详解>一书中的第1章,第1.6节函数(在Java中称为"方法"更好),作者 [美]Jonathan S. Harbour,更 ...
- JMessage Android 端开发详解
JMessage Android 端开发详解 目前越来越多的应用会需要集成即时通讯功能,这里就为大家详细讲一下如何通过集成 JMessage 来为你的 App 增加即时通讯功能. 首先,一个最基础的 ...
- 《Java和Android开发实战详解》——2.5节良好的Java程序代码编写风格
本节书摘来自异步社区<Java和Android开发实战详解>一书中的第2章,第2.5节良好的Java程序代码编写风格,作者 陈会安,更多章节内容可以访问云栖社区"异步社区&quo ...
- Android事件流程详解
Android事件流程详解 网络上有不少博客讲述了android的事件分发机制和处理流程机制,但是看过千遍,总还是觉得有些迷迷糊糊,因此特地抽出一天事件来亲测下,向像我一样的广大入门程序员详细讲述an ...
- Android Studio 插件开发详解二:工具类
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112856 本文出自[赵彦军的博客] 在插件开发过程中,我们按照开发一个正式的项 ...
- 《Android游戏开发详解》一2.16 区分类和对象
本节书摘来异步社区<Android游戏开发详解>一书中的第2章,第2.16节,作者: [美]Jonathan S. Harbour 译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社 ...
- Android Framework系统服务详解
Android Framework系统服务详解 操作环境 系统:Linux (Ubuntu 12.04) 平台:高通 Android版本:5.1 PS: 符号...为省略N条代码 一.大致原理分析 A ...
最新文章
- 安卓手机可以连接斑马系统吗_Zebra斑马 StageNow 安卓系统移动设备快速部署工具...
- 在 NetBeans IDE 中设计 Swing GUI
- 币安布局去中心化交易所,原来是因为这三个原因!
- 【TensorFlow-windows】keras接口——卷积手写数字识别,模型保存和调用
- nodejs mysql 返回值_带有Mysql数据库返回值的Nodejs
- java hashset 实现_HashSet实现原理分析(Java源码剖析)
- 词法分析与语法分析简介
- 数学与泛型编程:高效编程的奥秘pdf_Java 泛型与类型擦除
- SharePoint 2010 文档管理系列
- FFmpeg[11] - ffmpeg去除水印(图片和文字)
- 正式学习Linux的第一节课
- ONLYOFFICE 如何连接集成到 Wordpress 上
- VS2022 安装 .NET Framework 4.0的方法
- 微信域名防封技术,我们应该如何解决屏蔽拦截
- android云测如何使用教程,iTestin使用教程-Testin云测.PDF
- AG7120与AG7220做HDMI信号延长放大器驱动方案讲解|AG7120与AG7220设计HDMI信号延长放大器电路参考
- java技术及ssh框架和jsp技术的介绍 外文文献及翻译_java技术及ssh框架和jsp技术的介绍 外文文献及翻译.doc...
- 架构师小跟班:推荐46个非常经典的Linux面试题
- 技术VC的优势以及技术VC是如何生存的
- 黑鹰红客基地VIP美工教程系列
热门文章
- 对数正态分布(Log-Normal Distribution)
- kali无线安全分析工具
- laravel mysql 事务_laravel框架中的MySQL事务处理 阿星小栈
- 炒鸡酷,IT互联网程序员就业新前景:看极客是怎么靠两个披萨影响世界
- 如何将多个excel表格合并成一个_如何把两个excel表合并成一个
- 计算机系统课程 笔记总结 CSAPP第四章 处理器体系结构(4.1-4.3)
- 病毒分析--WannaCry分析--1
- 一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如6=1+2+3.编程找出1000以内的所有完数。
- 优矿-python计算上证50之间的相关系数
- 硬盘重装系统:电脑本地硬盘重装系统步骤