One of the highlights of the Android 2.0 SDK is that you can write custom sync providers to integrate with the system contacts, calendars, etc. The only problem is that there’s very little documentation on how it all fits together. And worse, if you mess up in certain places, the Android system will crash and reboot! Always up for a challenge, I’ve navigated through the sparse documentation, vague mailing list posts, and the Android source code itself to build a sync provider for our Last.fm app. Want to know how to build your own? Read on!
Account Authenticators
The first piece of the puzzle is called an Account Authenticator, which defines how the user’s account will appear in the “Accounts & Sync” settings. Implementing an Account Authenticator requires 3 pieces: a service that returns a subclass of AbstractAccountAuthenticator from the onBind method, an activity to prompt the user to enter their credentials, and an xml file describing how your account should look when displayed to the user. You’ll also need to add the android.permission.AUTHENTICATE_ACCOUNTS permission to your AndroidManifest.xml.
The Service
The authenticator service is expected to return a subclass of AbstractAccountAuthenticator from the onBind method — if you don’t, Android will crash and reboot when you try to add a new account to the system. The only method in AbstractAccountAuthenticator we really need to implement is addAccount, which returns an Intent that the system will use to display the login dialog to the user. The implementation below will launch our app’s main launcher activity with an action of “fm.last.android.sync.LOGIN” and an extra containing the AccountAuthenticatorResponse object we use to pass data back to the system after the user has logged in.
AccountAuthenticatorService.java
import fm.last.android.LastFm;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
/**
* Authenticator service that returns a subclass of AbstractAccountAuthenticator in onBind()
*/
public class AccountAuthenticatorService extends Service {
private static final String TAG = "AccountAuthenticatorService";
private static AccountAuthenticatorImpl sAccountAuthenticator = null;
public AccountAuthenticatorService() {
super();
}
public IBinder onBind(Intent intent) {
IBinder ret = null;
if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT))
ret = getAuthenticator().getIBinder();
return ret;
}
private AccountAuthenticatorImpl getAuthenticator() {
if (sAccountAuthenticator == null)
sAccountAuthenticator = new AccountAuthenticatorImpl(this);
return sAccountAuthenticator;
}
private static class AccountAuthenticatorImpl extends AbstractAccountAuthenticator {
private Context mContext;
public AccountAuthenticatorImpl(Context context) {
super(context);
mContext = context;
}
/*
* The user has requested to add a new account to the system. We return an intent that will launch our login screen if the user has not logged in yet,
* otherwise our activity will just pass the user's credentials on to the account manager.
*/
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
Bundle reply = new Bundle();
Intent i = new Intent(mContext, LastFm.class);
i.setAction("fm.last.android.sync.LOGIN");
i.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
reply.putParcelable(AccountManager.KEY_INTENT, i);
return reply;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) {
return null;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) {
return null;
}
}
}
The account authenticator service should be defined in your AndroidManifest.xml, with a meta-data tag referencing an xml definition file, as follows:
Snippet from AndroidManifest.xml
<service android:name="AccountAuthenticatorService"
android:exported="true" android:process=":auth">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
The Activity
If you don’t already have a login screen, there’s a convenience class AccountAuthenticatorActivity you can subclass that will pass your response back to the authentication manager for you, however if you already have a login activity in place you may find it easier to just pass the data back yourself, as I have done here. When the user has successfully been authenticated, we create an Account object for the user’s credentials. An account has an account name, such as the username or email address, and an account type, which you will define in your xml file next. You may find it easier to store your account type in strings.xml and use getString() to fetch it, as it is used in multiple places.
Snippet from the Last.fm login activity
Account account = new Account(username, getString(R.string.ACCOUNT_TYPE)));
AccountManager am = AccountManager.get(this);
boolean accountCreated = am.addAccountExplicitly(account, password, null);
Bundle extras = getIntent.getExtras();
if (extras != null) {
if (accountCreated) { //Pass the new account back to the account manager
AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, username);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, getString(R.string.ACCOUNT_TYPE));
response.onResult(result);
}
finish();
}
The XML definition file
The account xml file defines what the user will see when they’re interacting with your account. It contains a user-readable name, the system account type you’re defining, various icons, and a reference to an xml file containing PreferenceScreens the user will see when modifying your account.
authenticator.xml
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="fm.last.android.account"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/app_name"
android:accountPreferences="@xml/account_preferences"/>
account_preferences.xml
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="General Settings" />
<PreferenceScreen
android:key="account_settings"
android:title="Account Settings"
android:summary="Sync frequency, notifications, etc.">
<intent
android:action="fm.last.android.activity.Preferences.ACCOUNT_SETUP"
android:targetPackage="fm.last.android"
android:targetClass="fm.last.android.activity.Preferences" />
</PreferenceScreen>
</PreferenceScreen>
Putting it all together
Now we’re ready for testing! The Android accounts setting screen doesn’t handle exceptions very well — if something goes wrong, your device will reboot! A better way to test is to launch the emulator, run the “Dev Tools” app, and pick “AccountsTester”.
You should see your new account type in the list, along with the built-in “Corporate” account type. Go ahead and select your account type from the drop-down list, and then press the “Add” button, and you should be presented with your login activity. After authenticating, your account should appear in a list below the buttons. At this point, it should be safe to use the system “Accounts & Sync” settings screen to remove or modify your account.
Ready to fill in that section below “Data & synchronization”? Let’s move on to part 2!
The source code for the implementation referenced here is available in my Last.fm github project under the terms of the GNU General Public License. A standalone sample project is also available here under the terms of the Apache License 2.0. Google has also released their own sample sync provider on the Android developer portal that’s a bit more complete than mine.
分享到:
相关推荐
Writing An Interpreter In Go 英文mobi 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
在本文中,我们将深入探讨“Writing an Eclipse Plug-in”这一主题,这是一系列教程,旨在指导开发者逐步创建属于自己的Eclipse插件。Eclipse插件是扩展Eclipse集成开发环境(IDE)功能的重要方式,允许用户自定义...
1. Go语言编程:文档标题提到了“writing an interpreter in go”,这表明本书是关于使用Go语言来编写解释器的教程。Go语言以其简洁、高效和并发处理能力强而著称,它允许开发者快速构建出性能优越的应用程序。解释...
一个简单的开源Android工具类库,提供许多常用的类帮助我们开发程序。 AndroidCommon 一个简单的开源Android工具类库,提供许多常用的类帮助我们开发程序。 These are the Android Common Utils. Class ...
" Ball - Writing An Interpreter In Go" 本资源主要讲述如何使用 Go 语言编写一个解释器,作者 Thorsten Ball 通过一步步的指导 Teaching 读者如何从头开始创建一个 Monkey 编程语言的解释器。 在本书中,作者...
项目中的`writing-an-interpreter-main`可能包含了实现解释器的完整代码库,读者可以通过阅读和学习这些代码来深入了解Rust解释器的实现细节。 总之,《在Go Book中编写解释器的Rust实现》提供了一个深入学习编程...
How to build an interpreter for a C-like programming language from scratch What a lexer, a parser and an Abstract Syntax Tree (AST) are and how to build your own What closures are and how and why they...
Chapter 1 : AIR Installing the Adobe Development Tools Installing the AIR Runtime on an Android Device What Is in the AIR SDK New ActionScript Libraries AIR on the Desktop Versus AIR on Android Mobile...
Movie Screenshot Movie 依赖的官方支持包 com.android.support:appcompat-v7:23.4.0 ...Licensed under the Apache ...Unless required by applicable law or agreed to in writing, software distributed under the
An example of writing an input method for a software keyboard. Spinner A simple application that serves as an application-under-test for the SpinnerTest sample application. SpinnerTest An example ...
CODE系列 2-1 Writing-Solid-Code.part1
An Analog Electronics Companion: Basic Circuit Design for Engineers and Scientists Publisher: Cambridge University Press | ISBN: 0521687802 | edition 2007 | PDF | 668 pages | 11,21 mb Engineers and ...
《Writing_Solid_Code》这本书是指导开发者如何编写稳健的C语言程序的经典之作。它深入探讨了C编程中的关键概念和最佳实践,旨在帮助程序员构建出可靠、高效且易于维护的软件系统。书中涵盖了一系列重要的知识点,...
Chapter 1 : AIR Installing the Adobe Development Tools Installing the AIR Runtime on an Android Device What Is in the AIR SDK New ActionScript Libraries AIR on the Desktop Versus AIR on Android Mobile...
1. "Writing+An+Interpreter+In+Go+B01N2T1VD2.epub" - 这是书籍的epub版本,可以导入到各种支持该格式的电子阅读器上。 2. "Writing+An+Interpreter+In+Go+B01N2T1VD2.mobi" - 这是书籍的mobi版本,适用于Amazon ...
Chapter 1 : AIR Installing the Adobe Development Tools Installing the AIR Runtime on an Android Device What Is in the AIR SDK New ActionScript Libraries AIR on the Desktop Versus AIR on Android Mobile...
Chapter 1 : AIR Installing the Adobe Development Tools Installing the AIR Runtime on an Android Device What Is in the AIR SDK New ActionScript Libraries AIR on the Desktop Versus AIR on Android Mobile...
在《Writing An Interpreter In Go》这本书中,作者Thorsten Ball引领读者一起构建一个名为Monkey的编程语言的解释器。本书采用逐步的方式,从零代码开始,直到构建出一个完全功能的Monkey编程语言解释器。书中的...