![image](http://habr.habrastorage.org/post_images/658/bc2/339/658bc233994d253f42e3767aac2db2b5.png)
На дворе 2014 год, доля Android JellyBean перевалила за 60%, появились новые тренды в дизайне. В общем случилось много всего интересного. Но синхронизация данных с сервером осталось неотъемлемой частью большинства приложения. Существует много способов реализации ее в приложении. Android предоставляет нам SyncAdapter Framework, который позволяет автоматизировать и координировать этот процесс и предоставляет множество плюшек в довесок.
Account
Для начала нам потребуется собственный аккаунт на устройстве. Сначала, я думаю, стоит ответить на вопрос, зачем? Действительно, зачем?
Краткое резюме преимуществ:
- Поддержка фоновых механизмов вроде SyncAdapter
- Стандартизация способа авторизации
- Поддержка различных токенов (прав доступа)
- Шаринг аккаунта с разграничением привилегий (возможность использовать один аккаунт для различных приложения, как это делает Google)
Шаги для получения плюшек:
1) Создание Authenticator’а
2) Создание Activity для логина
3) Создание сервиса для общения с нашим аккаунтом
AccountManager — управляет аккаунтами устройства. Приложения запрашивают авторизационные токены именно у него.
AbstractAccountAuthenticator — компонент для работы с определенным типом аккаунта. Вся механика по работе с аккаунтом (авторизация, разграничение прав) осуществляется здесь. Может быть общим для различных приложений. AccountManager работает именно с ним.
AccountAuthenticatorActivity — базовый класс активити для авторизации/создания аккаунта. Вызывается AccountManager’ом в случае необходимости идентифицировать аккаунт (токен отсутствует или протух).
Как это все работает, можно посмотреть на диаграмме из документации
Когда нам понадобился токен мы работаем с методом AccountManager’а — getAuthToken. Стоит заметить что это асинхронный метод и его можно безопасно вызывать из UI потока. Существует также синхронная версия этого метода — blockingGetAuthToken. К диаграмме еще вернемся.
Создание Authenticator’а
Для создания собственного Authenticator’а нам необходимо расширить AbstractAccountAuthenticator и реализовать несколько его методов (7 если быть точным). Но для нас, на данный момент, представляют интерес всего 2.
@Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { final Intent intent = new Intent(mContext, NewAccountActivity.class); intent.putExtra(NewAccountActivity.EXTRA_TOKEN_TYPE, accountType); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); final Bundle bundle = new Bundle(); if (options != null) { bundle.putAll(options); } bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; }
Метод как видно из названия вызывается при попытке добавить новый аккаунт. Все что мы должны в нем сделать это вернуть Intent который должен запустить наше Activity. Чтобы иметь возможность добавить аккаунт из приложения нам потребуются соответствующие разрешения.
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" />
@Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { final Bundle result = new Bundle(); final AccountManager am = AccountManager.get(mContext.getApplicationContext()); String authToken = am.peekAuthToken(account, authTokenType); if (TextUtils.isEmpty(authToken)) { final String password = am.getPassword(account); if (!TextUtils.isEmpty(password)) { authToken = AuthTokenLoader.signIn(mContext, account.name, password); } } if (!TextUtils.isEmpty(authToken)) { result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); } else { final Intent intent = new Intent(mContext, LoginActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra(LoginActivity.EXTRA_TOKEN_TYPE, authTokenType); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); } return result; }
Что же происходит в момент вызова этого метода: пытаемся получить текущий токен методом peekAuthToken, если токен существует, можем добавить проверку на валидность (напомню что это асинхронный метод, так что можем ломиться на сервер) и возвращем результат. Если токена нет и/или сервер нам не отдал его, мы возвращаем тот же интент что и в методе addAccount. В этом случае пользователя выбьет на экран авторизации.
Создание Activity авторизации
Наше активити должно наследоваться от AccountAuthenticatorActivity (строго говоря, не должно а может: в AccountAuthenticatorActivity 20 строчек вспомогательного кода, который можно написать руками в любом другом активити). У нас будет самое простое активити с полями логин/пароль и кнопкой войти. В целом, в AccountManager’е можно сохранять произвольную информацию о профиле пользователя. Отвечать за получение токена будет AuthTokenLoader, но можно использовать любой понравившийся механизм. Задача-то простая — получить от сервера токен.
public void onTokenReceived(Account account, String password, String token) { final AccountManager am = AccountManager.get(this); final Bundle result = new Bundle(); if (am.addAccountExplicitly(account, password, new Bundle())) { result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_AUTHTOKEN, token); am.setAuthToken(account, account.type, token); } else { result.putString(AccountManager.KEY_ERROR_MESSAGE, getString(R.string.account_already_exists)); } setAccountAuthenticatorResult(result); setResult(RESULT_OK); finish(); }
Данный метод вызывается, когда токен от сервера получен (а это говорит о валидности аккаунта) и, соответственно, можно добавить аккаунт на устройство. setAccountAuthenticatorResult — метод для передачи результата обратно в AccountManager.
Сервис для интергации в систему
Сервис позволит системе и другим приложениям связываться с нашим Authenticator’ом. Код сервиса максимально прост:
public class GitHubAuthenticatorService extends Service { private GitHubAuthenticator mAuthenticator; @Override public void onCreate() { super.onCreate(); mAuthenticator = new GitHubAuthenticator(getApplicationContext()); } @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } }
Все что он делает это возвращает IBinder нашего Authenticator’a. Причем, метод getIBinder уже реализован в AbstractAccountAuthenticator. Осталось только прописать наш сервис в манифесте приложения.
<service android:name=".account.GitHubAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/github_authenticator" /> </service>
Осталась совсем маленькая деталь: вы могли заметить такую строчку
android:resource="@xml/github_authenticator"
Это метафайл, который описывает наш Authenticator. Его необходимо создать в папке res/xml. В нем мы указываем иконку нашего аккаунта, его название и тип. В самом простом случае, он выглядит так:
<?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="com.github.elegion" android:icon="@drawable/ic_github" android:label="@string/github" android:smallIcon="@drawable/ic_github" />
Вот, в целом, все. После этих хитрых манипуляций мы получили возможность создавать свой аккаунт на устройстве. При всей кажущейся сложности, этот процесс на самом деле сводится к реализации 2-х методов, создания xml метафайла и описания сервиса в манифесте. Остальные методы Authenticator’а необходимы для шаринга нашего аккаунта во внешний мир с разделением привилегий, о чем мы поговорим в следующих статьях.
P.S. В качестве бонуса: у AccountManager’а есть метод setUserData(final Account account, final String key, final String value) который по сути предоставляет нам возможность хранения любой информации в формате key-value. Это то о чем я говорил немного выше. Это еще одна плюшка в довесок к остальным — возможность хранить профиль пользователя без необходимости создания/использования внутренних хранилищ.
Исходники проекта можно взять тут
ссылка на оригинал статьи http://habrahabr.ru/company/e-Legion/blog/206210/
Добавить комментарий