使用系统的账号中心

继上篇《App保活攻防仗》提到的通过账户同步来实现进程保活手段,于是顺藤摸瓜,好好的学一下系统账号构建和同步的流程。
大家在平时使用 Android 手机的时候,都会发现有些应用(例如 qq,微信,淘宝)为自己创建了账号系统,并且能够在设置页面看到他,可是当自己希望为自己的软件写一个账号系统的时候总是不知从何入手,现在我们就从头开始,一步一步打造属于自己应用的账号系统。

具体操作:

1、获取账号信息
  • (1)首先申明读取账户的权限,如下:
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>

注意,targetSdkVersion 23及以上需要runtime permissions!!否则,返回NULL。
- (2)获取账号管理类:

首先获取系统的账号管理类AccountManager,通过

AccountManager mAccountManager = (AccountManager) getSystemService(ACCOUNT_SERVICE); 

或者

ccountManager accountManager = AccountManager.get(context);  
  • (3)获取账户信息:
accountManager.getAccounts();  //获取所有账户信息
accountManager.getAccountsByType("com.isinta");//获取特定的账户信息  
2、建立自己的账号服务

我们已经知道了如何获取 Android 本身现有的账号信息,现在我们就开始着手建立属于自己的账号系统吧。

(1)声明一个关于账号的服务
<service  
android:name="com.isinta.app.account.AccountService"  
android:enabled="true"  
android:exported="true">  
    <intent-filter>  
       <actio android:name="android.accounts.AccountAuthenticator"/>  
    </intent-filter>  
    <meta-data 
        android:name="android.accounts.AccountAuthenticator"  
        android:resource="@xml/authenticator"/>  
</service>  

在上面的代码中,我们通过设置 intent-filter 告知系统,我们当前应用中有一个账号服务,至于具
体的账号信息则放在 meta-data 中的 android:resource 文件中提供, 该文件为authenticator.xml,放置路径为 res/xml,内容如下:

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

在这里,我们就可以向系统提供相关的账户信息,用于在 Android Setting 目录下显示对应的账号信息。例如这里,我们就定义了当前的账户类型为”com.isinta”,在账户系统中显示的标签为@string/app_name 对应的 String 对象,显示的 icon 为ic_launcher。

(2)具体来实现我们自定义的账号服务
public class AccountService extends Service {
    private Authenticator authenticator;
    public static final String ACCOUNT_TYPE = "com.isinta.app.accounaccounttype";
    public static final String TOKEN_TYPE = "com.isinta.app.tokentype";

    @Override
    public void onCreate() {
        authenticator = new Authenticator(this);
    }
    @Override
    public IBinder onBind(Intent intent) {
        return authenticator.getIBinder();
    }

    //Android 为我们提供了一个叫做 AbstractAccountAuthenticator 的抽象类
    //Android 需要从我们自定义的 Service 中获取一个 AbstractAccountAuthenticator 对象,然后再调用对象中的方法来实现账号系统的具体操作
    class Authenticator extends AbstractAccountAuthenticator {
        private Context context;
        private AccountManager accountManager;

        public Authenticator(Context context) {
            super(context);
            this.context = context;
            accountManager = AccountManager.get(context);

        }

        @Override
        public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                     String s) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
                throws NetworkErrorException {
            final Bundle bundle = new Bundle();
            //only one account permitted
            if(accountManager.getAccountsByType(ACCOUNT_TYPE).length > 0) {
                final Intent intent = new Intent(context, FailActivity.class);
                intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
                bundle.putParcelable(AccountManager.KEY_INTENT, intent);
                return bundle;
            }
            final Intent intent = new Intent(context, AuthenticatorActivity.class);
            intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
            bundle.putParcelable(AccountManager.KEY_INTENT, intent);
            return bundle;
        }

        @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                         Account account, Bundle bundle)
                throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String s, Bundle bundle)
                throws NetworkErrorException {
            //get one existing token
            String authToken = accountManager.peekAuthToken(account, TOKEN_TYPE);

            //if not, might be expired, register again
            if (TextUtils.isEmpty(authToken)) {
                final String password = accountManager.getPassword(account);
                if (password != null) {
                    try {
                        //get new token
                        authToken = account.name + password;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            //without password, need to sign again
            if (!TextUtils.isEmpty(authToken)) {
                final Bundle result = new Bundle();
                result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
                result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
                result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
                return result;
            }

            //no account data at all, need to do a sign
            final Intent intent = new Intent(context, AuthenticatorActivity.class);
            intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
            intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_NAME, account.name);
            final Bundle b = new Bundle();
            b.putParcelable(AccountManager.KEY_INTENT, intent);
            return b;
        }

        @Override
        public String getAuthTokenLabel(String s) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                        Account account, String s, Bundle bundle)
                throws NetworkErrorException {
            throw new UnsupportedOperationException();
        }

        @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                  Account account, String[] strings)
                throws NetworkErrorException {
            throw new UnsupportedOperationException();
        }
    }
}

当你完成上面的步骤之后,你就会发现,在你的设置页面点击添加账户时就会出现你自定义的账户了!

(3)为我们的应用账户中心添加具体账户咯。

通过上面的步骤,我们已经能够在添加账户的界面看到属于我们自己的账户类别了,但是你会发现当你点击它们的时候,没有任何作用,那么我们应该怎么在设备上完成添加账户的操作呢?
- A: 加入添加账户的权限

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

- B: 添加账户
当用户在添加账户页面选择账户进行添加或者调用accountManager.addAccount 的时候,系统会默认调用 AbstractAccountAuthenticator 中的 addAccount 方法,因此你需要重写 addAccount 方法,直接添加默认账户,或者跳转到某个页面,让用户填写用户信息,然后添加账户。或者,使用 addAccountExplicitly 直接添加账户。

```
Account account = new Account("isinta","com.isinta");  
    accountManager.addAccountExplicitly(account,password,userdata);  
```

添加成功后,你就会发现在设置页面出现你所建立的账户,点击进去,会发现账户名什么的也已经成功设置。
(4)设计我们应用账号中心的界面

在账号信息页面,并不意味着只能暂时简单的账户信息,在这里,你可以根据你的需求自定义账号页面效果。你需要首先在authencator.xml(内容见之前的文章)中声明一下你需要使用的界面效果:

android:accountPreferences="@xml/account_preferences"  
3、同步账号数据

账户信息同步其实主要来说有两种方式,一种是自动同步,一种是手动同步。从名字上大家就能够看出两者的区别,前者是我们设定一个固定时间间隔,让 android 系统帮我们自动同步数据,后者则是在应用中调用某个方法直接告诉设备,通知系统同步数据。
- (1)自动同步:在这里需要留意的是,虽然前者有设置一个固定时间间隔,但是 android 会尽量将所有同步数据的时间都安排在一起,以减少唤醒设备的次数,因此你可能发现虽然你设置了一个固定的间隔时间,但是到了那个时间点,系统其实并没有按时同步数据。设置自动同步的代码如下:

ContentResolver.setSyncAutomatically(account1, "com.isinta.provider", true);  
               ContentResolver.addPeriodicSync(account1, "com.isinta.provider", bundle, 10);
  • (2)手动同步:在我看来手动同步其实是很有作用的,因为当你对账户信息做出更改之后,你不能完全依赖系统的同步机制,他的同步时间可能会在很久之后,因此,你需要手动调用账户同步接口,令系统同步数据。手动同步的代码如下:
ContentResolver.requestSync(account1, "com.isinta.provider", bundle);  
  • (3) 处理同步数据时的异常
    同步数据时难免会出现问题,例如网络中断,或者是账号密码验证失败。对于这两种情况而言,前者不需要太过留意,只要之后能够同步正确数据即可,但是后者则需要你通知系统中断当前的账号服务,避免出现账号安全问题。

参考文章:
http://yarin.blog.51cto.com/1130898/479032

详细啊!!
http://blog.udinic.com/2013/04/24/write-your-own-android-authenticator/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值