android账号创建,管理,同步
1.AccountManager获取账户信息
1.1 AccountManager的获取
AccountManager mAccountManager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
或者
AccountManager accountManager = AccountManager.get(context);
1.2 账户的获取
首先需要权限
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
获取账户的方法
//Lists all accounts of any type registered on the device.获取所有账户
getAccounts()
//Lists all accounts of a particular type.获取指定类型的账户
getAccountsByType(String type)
2. 账户服务
要建立自己的账号信息,就要在manifest里声明一个关于账号的service
如davdroid的manifest文件中有如下代码
<service
android:name=".syncadapter.AccountAuthenticatorService"
android:exported="false" >
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/account_authenticator" />
</service>
其中account_authenticator如下
<account-authenticator
xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="bitfire.at.davdroid"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:smallIcon="@mipmap/ic_launcher"
/>
2.1 authenticatorservice的实现
这里实现的很简单,分为以下几个部分
- 实现一个自己的authenticator,并实现addAccount方法(这里其实是添加账户的操作)
- onCreate 的时候创建一个 Authenticator 对象,然后再 bindService 的时候,将 Authenticator 的 IBinder 传递回去,以供调用
- getAuthToken方法用来获得授权
public class AccountAuthenticatorService extends Service {
private AccountAuthenticator accountAuthenticator;
@Override
public void onCreate() {
accountAuthenticator = new AccountAuthenticator(this);
accountAuthenticator.toString();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("AccountAuthenticator", accountAuthenticator.toString());
}
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals(
android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT))
return accountAuthenticator.getIBinder();
return null;
}
private static class AccountAuthenticator extends
AbstractAccountAuthenticator {
final Context context;
public AccountAuthenticator(Context context) {
super(context);
this.context = context;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response,
Account account, Bundle options) throws NetworkErrorException {
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)
throws NetworkErrorException {
return null;
}
}
}
完成上面的步骤之后,在系统的设置里,就会出现这个类型的账户了
3 添加账号
首先需要权限
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
3.1 重写authenticator的addAccount方法
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
3.2 调用addAccountExplicitly
accountManager.addAccountExplicitly(account, config.password, userData)
这样就把一个账户添加进去了,其中account参数会制定账户类型和名称
4.账户信息的同步
4.1 自动同步
ContentResolver.setSyncAutomatically(account, authority, true); //自动同步
ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds);//设置同步时间间隔
4.2 手动同步
ContentResolver.setSyncAutomatically(account, authority, false); //手动同步
4.3 davdroid中的同步的调用实现
public Long getSyncInterval(@NonNull String authority) {
if (ContentResolver.getIsSyncable(account, authority) <= 0)
return null;
if (ContentResolver.getSyncAutomatically(account, authority)) {
List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(account, authority);
if (syncs.isEmpty())
return SYNC_INTERVAL_MANUALLY;
else
return syncs.get(0).period;
} else
return SYNC_INTERVAL_MANUALLY;
}
public void setSyncInterval(@NonNull String authority, long seconds) {
if (seconds == SYNC_INTERVAL_MANUALLY) {
//手动同步
ContentResolver.setSyncAutomatically(account, authority, false);
} else {//自动同步
ContentResolver.setSyncAutomatically(account, authority, true); //自动同步
ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds);//设置同步时间间隔
}
}
4.4 开发流程
- 创建SyncService并提供SyncAdapter的IBinder接口以便让系统调用
- 声明Sync服务, 并制定SyncAdapter的配置文件
- 生成账户启动Sync
4.4.1 创建SyncService
Sync服务一般工作在独立的进程, 并且可由操作系统调度, 当然要实现自己的Binder接口, 好在Google已经提供了非常简单的抽象类AbstractThreadedSyncAdapter来完成这件事情
public class SyncService extends Service{
private static final Object syncLock = new Object();
private static SyncAdapter syncAdapter = null;
@Override
public void onCreate() {
super.onCreate();
synchronized (syncLock) {
if (syncAdapter == null) {
syncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
class SyncAdapter extends AbstractThreadedSyncAdapter {
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
//do sync here
}
}
}
davdroid中syncservice的实现
public abstract class SyncAdapterService extends Service {
abstract protected AbstractThreadedSyncAdapter syncAdapter();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return syncAdapter().getSyncAdapterBinder();
}
public static abstract class SyncAdapter extends
AbstractThreadedSyncAdapter {
public SyncAdapter(Context context) {
super(context, false);
}
@Override
public void onPerformSync(Account account, Bundle extras,
String authority, ContentProviderClient provider,
SyncResult syncResult) {
App.log.info("Sync for " + authority + " has been initiated");
// required for dav4android (ServiceLoader)
Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
}
@Override
public void onSecurityException(Account account, Bundle extras,
String authority, SyncResult syncResult) {
App.log.log(Level.WARNING,
"Security exception when opening content provider for "
+ authority);
syncResult.databaseError = true;
Intent intent = new Intent(getContext(), PermissionsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Notification notify = new NotificationCompat.Builder(getContext())
.setSmallIcon(R.drawable.ic_error_light)
.setLargeIcon(App.getLauncherBitmap(getContext()))
.setContentTitle( getContext().getString(R.string.sync_error_permissions))
.setContentText( getContext().getString(R.string.sync_error_permissions_text))
.setContentIntent(
PendingIntent.getActivity(getContext(), 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT))
.setCategory(NotificationCompat.CATEGORY_ERROR).build();
NotificationManagerCompat nm = NotificationManagerCompat
.from(getContext());
nm.notify(Constants.NOTIFICATION_PERMISSIONS, notify);
}
protected boolean checkSyncConditions(@NonNull AccountSettings settings) {
if (settings.getSyncWifiOnly()) {
ConnectivityManager cm = (ConnectivityManager) getContext()
.getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo network = cm.getActiveNetworkInfo();
if (network == null) {
App.log.info("No network available, stopping");
return false;
}
if (network.getType() != ConnectivityManager.TYPE_WIFI
|| !network.isConnected()) {
App.log.info("Not on connected WiFi, stopping");
return false;
}
String onlySSID = settings.getSyncWifiOnlySSID();
if (onlySSID != null) {
onlySSID = "\"" + onlySSID + "\"";
WifiManager wifi = (WifiManager) getContext()
.getSystemService(WIFI_SERVICE);
WifiInfo info = wifi.getConnectionInfo();
if (info == null || !onlySSID.equals(info.getSSID())) {
App.log.info("Connected to wrong WiFi network ("
+ info.getSSID() + ", required: " + onlySSID
+ "), ignoring");
return false;
}
}
}
return true;
}
}
}
日历的同步操作
同步数据的实际操作在onPerformSync方法中进行
public class CalendarsSyncAdapterService extends SyncAdapterService {
@Override
protected AbstractThreadedSyncAdapter syncAdapter() {
return new SyncAdapter(this);
}
private static class SyncAdapter extends SyncAdapterService.SyncAdapter {
public SyncAdapter(Context context) {
super(context);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Log.e("SyncAdapter", "init ...");
super.onPerformSync(account, extras, authority, provider, syncResult);
try {
AccountSettings settings = new AccountSettings(getContext(), account);
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
return;
updateLocalCalendars(provider, account, settings);
for (LocalCalendar calendar : (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) {
// App.log.info("Synchronizing calendar #" + calendar.getId() + ", URL: " + calendar.getName());
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, settings, extras, authority, syncResult, calendar);
syncManager.performSync();
}
} catch (CalendarStorageException e) {
App.log.log(Level.SEVERE, "Couldn't enumerate local calendars", e);
syncResult.databaseError = true;
} catch (InvalidAccountException e) {
App.log.log(Level.SEVERE, "Couldn't get account settings", e);
}
App.log.info("Calendar sync complete");
}
private void updateLocalCalendars(ContentProviderClient provider, Account account, AccountSettings settings) throws CalendarStorageException {
SQLiteOpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
try {
// enumerate remote and local calendars
SQLiteDatabase db = dbHelper.getReadableDatabase();
Long service = getService(db, account);
Map<String, CollectionInfo> remote = remoteCalendars(db, service);
LocalCalendar[] local = (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, null, null);
boolean updateColors = settings.getManageCalendarColors();
// delete obsolete local calendar
for (LocalCalendar calendar : local) {
String url = calendar.getName();
if (!remote.containsKey(url)) {
App.log.fine("Deleting obsolete local calendar " + url);
calendar.delete();
} else {
// remote CollectionInfo found for this local collection, update data
CollectionInfo info = remote.get(url);
App.log.fine("Updating local calendar " + url + " with " + info);
calendar.update(info, updateColors);
// we already have a local calendar for this remote collection, don't take into consideration anymore
remote.remove(url);
}
}
// create new local calendars
for (String url : remote.keySet()) {
CollectionInfo info = remote.get(url);
App.log.info("Adding local calendar list " + info);
LocalCalendar.create(account, provider, info);
}
} finally {
dbHelper.close();
}
}
@Nullable
Long getService(@NonNull SQLiteDatabase db, @NonNull Account account) {
@Cleanup Cursor c = db.query(Services._TABLE, new String[] { Services.ID },
Services.ACCOUNT_NAME + "=? AND " + Services.SERVICE + "=?", new String[] { account.name, Services.SERVICE_CALDAV }, null, null, null);
if (c.moveToNext())
return c.getLong(0);
else
return null;
}
@NonNull
private Map<String, CollectionInfo> remoteCalendars(@NonNull SQLiteDatabase db, Long service) {
Map<String, CollectionInfo> collections = new LinkedHashMap<>();
if (service != null) {
@Cleanup Cursor cursor = db.query(Collections._TABLE, null,
Collections.SERVICE_ID + "=? AND " + Collections.SUPPORTS_VEVENT + "!=0 AND " + Collections.SYNC,
new String[] { String.valueOf(service) }, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
CollectionInfo info = CollectionInfo.fromDB(values);
collections.put(info.url, info);
}
}
return collections;
}
}
}
4.4.2 声明Sync服务, 并制定SyncAdapter的配置文件
此服务需能交给操作系统使唤, 那么声明也需要加入一些表示
- 必须使用android:exported="true"
- 而且需要指定过滤器android.content.SyncAdapter,这样系统启动的时候可以注册到
用SyncAdapterCache类来将这些信息放到/data/system/registered_services/android.content.SyncAdapter.xml
- 还需要指定syncadapter的配置文件
<service
android:name=".syncadapter.CalendarsSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService" >
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_calendars" />
</service>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="bitfire.at.davdroid"
android:contentAuthority="com.android.calendar"
android:allowParallelSyncs="true"
android:supportsUploading="true"
android:isAlwaysSyncable="true"
android:userVisible="true" />
userVisible决定了是否在系统Settings的Account里面可以看到更新记录, 并且控制开启与关闭
supportsUploading决定在Provider notifyChanged 的时候, syncToNetwork是否起作用, 如果前者为true, 那么后者为true的时候可以触发SyncAdapter onPerformSync的回调
总结
1.创建账户类型xml/authenticator.xml
2.创建一个service用来给account framework调用,service中拿到authticator的binder
3.在manifest中配置service(注意service的intentfilter和metadata等配置)
4.通过accountmanager的addaccount就可以添加账户
5.创建provider定义好authrity
6.创建syncadapter和使用syncadapter的service(service中有syncadapter的binder对象)
7.xml中配置这个syncadapter
8.在manifest中配置这个service("android.content.SyncAdapter")
9.激活同步功能
1-3创建账户类型 4增加账户 5-9同步账户
参考:
http://blog.csdn.net/kifile/article/category/1402671/1
http://talentprince.github.io/blog/2015/01/26/android-zhang-hao-yu-tong-bu-xi-tong-part-one/
http://www.apkbus.com/thread-14570-1-1.html
http://skyseraph.com/2016/06/19/Android/%E4%B8%80%E7%A7%8D%E6%8F%90%E9%AB%98Android%E5%BA%94%E7%94%A8%E8%BF%9B%E7%A8%8B%E5%AD%98%E6%B4%BB%E7%8E%87%E6%96%B0%E6%96%B9%E6%B3%95/
https://shyling.com/posts/android-app-persistent.html
http://blog.csdn.net/crazyman2010/article/details/52101446