AsyncTask
或者 IntentService
.
AbstractAccountAuthenticator
的文档。
AbstractAccountAuthenticator
的类,通过返回null或是抛出异常来模拟实现需要的方法。
/* * Implement AbstractAccountAuthenticator and stub out all * of its methods */ public class Authenticator extends AbstractAccountAuthenticator { // Simple constructor public Authenticator(Context context) { super(context); } // Editing properties is not supported @Override public Bundle editProperties( AccountAuthenticatorResponse r, String s) { throw new UnsupportedOperationException(); } // Don't add additional accounts @Override public Bundle addAccount( AccountAuthenticatorResponse r, String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException { return null; } // Ignore attempts to confirm credentials @Override public Bundle confirmCredentials( AccountAuthenticatorResponse r, Account account, Bundle bundle) throws NetworkErrorException { return null; } // Getting an authentication token is not supported @Override public Bundle getAuthToken( AccountAuthenticatorResponse r, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } // Getting a label for the auth token is not supported @Override public String getAuthTokenLabel(String s) { throw new UnsupportedOperationException(); } // Updating user credentials is not supported @Override public Bundle updateCredentials( AccountAuthenticatorResponse r, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } // Checking features for the account is not supported @Override public Bundle hasFeatures( AccountAuthenticatorResponse r, Account account, String[] strings) throws NetworkErrorException { throw new UnsupportedOperationException(); } }
/** * A bound Service that instantiates the authenticator * when started. */ public class AuthenticatorService extends Service { ... // Instance field that stores the authenticator object private Authenticator mAuthenticator; @Override public void onCreate() { // Create a new authenticator object mAuthenticator = new Authenticator(this); } /* * When the system binds to this Service to make the RPC call * return the authenticator's IBinder. */ @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } }
/res/xml/
目录中。名字自定义,一般叫做 authenticator.xml
。
元素:
android:accountType
android:icon
res/xml/syncadapter.xml
设置了 android:userVisible="true”
属性将Sync adapter对用户可见,那必须要提供一个图标资源。它将显示在“设置”应用的“账号”一项中。
android:smallIcon
android:icon。
android:label
xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="example.com"
android:icon="@drawable/ic_launcher"
android:smallIcon="@drawable/ic_launcher"
android:label="@string/app_name"/>
下声明一个
子元素如下:
android:name="com.example.android.syncadapter.AuthenticatorService">
android:name="android.accounts.AccountAuthenticator"/>
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
设置了通过action android.accounts.AccountAuthenticator
启动的filter。这个action是由系统发送的。当被触发时,系统会启动你之前提供的封装了你的athtenticator的 AuthenticatorService
。
声明了authenticator的元数据。通过 android:name
属性将meta-data与认证框架关联。 android:resource
指定刚才创建的元数据文件的名字。
ContentProvider
类 ,实现那些必要的方法。如下面这段代码:
/* * Define an implementation of ContentProvider that stubs out * all methods */ public class StubProvider extends ContentProvider { /* * Always return true, indicating that the * provider loaded correctly. */ @Override public boolean onCreate() { return true; } /* * Return an empty String for MIME type */ @Override public String getType() { return new String(); } /* * query() always returns no results * */ @Override public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } /* * insert() always returns null (no URI) */ @Override public Uri insert(Uri uri, ContentValues values) { return null; } /* * delete() always returns "no rows affected" (0) */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } /* * update() always returns "no rows affected" (0) */ public int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
属性,它具有以下属性:
android:name="com.example.android.datasync.provider.StubProvider"
android:authorities="com.example.android.datasync.provider"
android:exported="false"
android:syncable="true"
setIsSyncable()
。这个值决定了同步框架可以与Provider传输数据,但是也仅在你明确调用的时候才传输。( 译者注:SyncAdapter有类似属性:isAlwaysSyncable )。
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.network.sync.BasicSyncAdapter"
android:versionCode="1"
android:versionName="1.0" >
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
...
android:name="com.example.android.datasync.provider.StubProvider"
android:authorities="com.example.android.datasync.provider"
android:exported="false"
android:syncable="true"/>
...
AbstractThreadedSyncAdapter 基类并且实现其构造方法。在
构造方法中做一些创建
SyncAdapter时需要的
设置
,类似于在
Activity.onCreate()
设置一个Activity。例如,如果你使用ContentProvider存储数据的,在构造方法中就去获取一下
ContentResolver
的实例。因为在Android3.0版本中新加了一个支持 parallelSyncs
参数的构造方法,为保持兼容需要创建两种形式的构造方法。
AbstractThreadedSyncAdapter
及其构造方法。
/** * Handle the transfer of data between a server and an * app, using the Android sync adapter framework. */ public class SyncAdapter extends AbstractThreadedSyncAdapter { ... // Global variables // Define a variable to contain a content resolver instance ContentResolver mContentResolver; /** * Set up the sync adapter */ public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ mContentResolver = context.getContentResolver(); } ... /** * Set up the sync adapter. This form of the * constructor maintains compatibility with Android 3.0 * and later platform versions */ public SyncAdapter( Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ mContentResolver = context.getContentResolver(); ... }
onPerformSync()
方法。
onPerformSync()
时会传入以下参数:
Account
对象,如果你的服务器不需要账号,直接无视就可以。Bundle
对象ContentProviderClient
,它是ContentProvider的一个轻量接口,具有与ContentResolver
相同的功能。如果是用ContentProvider保存的数据,你可以用这个对象连接到ContentProvider,否则无视就好。SyncResult
对象,可以用来将同步的结果传给同步框架。onPerformSync()
的总体结构:
/* * Specify the code you want to run in the sync adapter. The entire * sync adapter runs in a background thread, so you don't have to set * up your own background processing. */ @Override public void onPerformSync( Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { /* * Put the data transfer code here. */ ... }
onPerformSync()
是根据你的同步需求和服务器连接协议定制的,通常是下面几个通用的步骤:
onPerformSync()
放在后台线程,因此不需要自己设置后台运行。
onPerformSync()
中。将网络操作集中处理可以降低频繁的发起终止网络造成的功耗。要了解如何高效的完成网络访问,可以参阅 Transferring Data Without Draining the Battery,其中讲了几个网络传输任务,可以添加到数据传输的代码中。
onPerformSync()
方法。
onCreate()
方法中以单实例的形式实例化SyncAdapter,这样也会将SyncAdapter的实例化延迟到framework首次传输数据要创建Service的时候执行。实例化的过程要保证线程安全,以免同步框架会将多次同步响应添加到队列中。
package com.example.android.syncadapter;
/**
* Define a Service that returns an IBinder
for the
* sync adapter class, allowing the sync adapter framework to call
* onPerformSync().
*/
public class SyncService extends Service {
// Storage for an instance of the sync adapter
private static SyncAdapter sSyncAdapter = null;
// Object to use as a thread-safe lock
private static final Object sSyncAdapterLock = new Object();
/*
* Instantiate the sync adapter object.
*/
@Override
public void onCreate() {
/*
* Create the sync adapter as a singleton.
* Set the sync adapter as syncable
* Disallow parallel syncs
*/
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
/**
* Return an object that allows the system to invoke
* the sync adapter.
*
*/
@Override
public IBinder onBind(Intent intent) {
/*
* Get the object that allows external processes
* to call onPerformSync(). The object is created
* in the base class code when the SyncAdapter
* constructors call super()
*/
return sSyncAdapter.getSyncAdapterBinder();
}
}
注意: 更详细的示例代码可以参阅示例工程BasicSyncAdapter.zip.
addAccountExplicitly()
方法,添加一个具有账号类型的虚拟账号。最好是在打开应用页面时的 onCreate()
中调用这个方法。下面是示例:
public class MainActivity extends FragmentActivity { ... ... // Constants // The authority for the sync adapter's content provider public static final String AUTHORITY = "com.example.android.datasync.provider" // An account type, in the form of a domain name public static final String ACCOUNT_TYPE = "example.com"; // The account name public static final String ACCOUNT = "dummyaccount"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Create the dummy account mAccount = CreateSyncAccount(this); ... } ... /** * Create a new dummy account for the sync adapter * * @param context The application context */ public static Account CreateSyncAccount(Context context) { // Create the account type and default account Account newAccount = new Account( ACCOUNT, ACCOUNT_TYPE); // Get an instance of the Android account manager AccountManager accountManager = (AccountManager) context.getSystemService( ACCOUNT_SERVICE); /* * 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 yourelement in the manifest, * then call context.setIsSyncable(account, AUTHORITY, 1) * here. */ } else { /* * The account exists or some other error occurred. Log this, report it, * or handle it internally. */ } } ... }
/res/xml/
目录下,名字自定义,但一般叫做 syncadapter.xml
。
,它具有以下属性:
元素的android:authorities
属性值。ACCOUNT_TYPE
常量还有authenticator的元数据文件中定义的保持一致。notifyChange(android.net.Uri, android.database.ContentObserver, boolean)
方法发起的同步会带仅上传的SYNC_EXTRAS_UPLOAD标志位)requestSync()
发起。了解更多请参看Running a Sync Adapter;(译者注:Provider中有isSyncable属性,功能类似)xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.example.android.datasync.provider"
android:accountType="com.android.example.datasync"
android:userVisible="false"
android:supportsUploading="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"/>
元素里添加以下子元素:
android.permission.INTERNET
android.permission.INTERNET
getIsSyncable()
方法时就需要这个权限。android.permission.WRITE_SYNC_SETTINGS
addPeriodicSync()
方法时就需要这个权限。但调用requestSync()
发起同步并不需要这个权限。android.permission.AUTHENTICATE_ACCOUNTS
... android:name="android.permission.INTERNET"/> android:name="android.permission.READ_SYNC_SETTINGS"/> android:name="android.permission.WRITE_SYNC_SETTINGS"/> android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> ...
中添加下面的子元素:
android:name="com.example.android.datasync.SyncService" android:exported="true" android:process=":sync"> android:name="android.content.SyncAdapter"/> android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" />
元素设置了一个由Action 为 android.content.SyncAdapter
的Intent触发的过滤器,系统要运行SyncAdapter时会发送这个Intent。当过滤器被触发时,系统会创建绑定用的Service,本例中即 SyncService
。
android:exported="true"
的属性允许出你的应用之外的进程来访问这个Service。
android:process=":sync"
告诉系统在名为 sync
的全局共享的进程中运行这个Service。如果你的应用中有多个SyncAdapter,他们可以共享这个进程,可以降低一些消耗。
元素规定了我们之前创建SyncAdapter元数据XML文件。 android:name
属性说明了这个元数据是给同步框架的。 android:resource
元素指定了元数据文件的名字。
notifyChange(android.net.Uri, android.database.ContentObserver, boolean)
方法);如果是伪造的ContentProvider,那可能要麻烦一些。BroadcastReceiver,然后应用调用
ContentResolver.requestSync()
发起同步。谷歌云消息(
Google Cloud Messaging )提供了发送这种消息的服务端和客户端的组件。使用GCM比轮询服务器更可靠更有效率,轮询需要
保持
一个一直
活动的
Service
,而GCM使用仅在消息到达时才被激活的 BroadcastReceiver
;即使服务端没有变更,轮询还是要定期的消耗电量,而GCM仅在需要的时候才会发送消息。
requestSync()
发起同步:
public class GcmBroadcastReceiver extends BroadcastReceiver { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider" // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Incoming Intent key for extended data public static final String KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"; ... @Override public void onReceive(Context context, Intent intent) { // Get a GCM object instance GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); // Get the type of GCM message String messageType = gcm.getMessageType(intent); /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType) && intent.getBooleanExtra(KEY_SYNC_REQUEST)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(ACCOUNT, AUTHORITY, null); ... } ... } ... }
requestSync()
告诉框架运行sync adapter。
onChange()
永远不会被调用到。这时,你需要自己提供检测数据变化的机制,然后在数据变化时调用requestSync()
;
ContentObserver
类并且实现其中的 onChange()
方法。 在 onChange()
中调用 requestSync()
来运行SyncAdapter。
registerContentObserver(),在
调用时还需要传入需要监听的URI。ContentProvider框架会将这个URI与传入 ContentResolver
的方法(例如 ContentResolver.insert()
)的URI进行比较。如果匹配成功,那你的 ContentObserver.onChange()
会被调用。
requestSync()
的 ContentObserver:
public class MainActivity extends FragmentActivity { ... // Constants // Content provider scheme public static final String SCHEME = "content://"; // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Path for the content provider table public static final String TABLE_PATH = "data_table"; // Account public static final String ACCOUNT = "default_account"; // Global variables // A content URI for the content provider's data table Uri mUri; // A content resolver for accessing the provider ContentResolver mResolver; ... public class TableObserver extends ContentObserver { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ @Override public void onChange(boolean selfChange) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null); } /* * Define a method that's called when data in the * observed content provider changes. */ @Override public void onChange(boolean selfChange, Uri changeUri) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. ContentResolver.requestSync(ACCOUNT, AUTHORITY, null); } ... } ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver object for your app mResolver = getContentResolver(); // Construct a URI that points to the content provider data table mUri = new Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build(); /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ TableObserver observer = new TableObserver(false); /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(mUri, true, observer); ... } ... }
ContentResolver
。通过调用setSyncAutomatically()
,可以设置ContentResolve
在收到消息时自动发起同步。
setSyncAutomatically()
不会禁用addPeriodicSync()
,因此你的SyncAdapter可能会被频繁的重复触发。因此如果要定期的触发同步,就应该禁用掉 setSyncAutomatically()
。
ContentResolver
通过网络消息触发同步:
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account public static final String ACCOUNT = "default_account"; // Global variables // A content resolver for accessing the provider ContentResolver mResolver; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver for your app mResolver = getContentResolver(); // Turn on automatic syncing for the default account and authority mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true); ... } ... }
addPeriodicSync()
可以设置一个固定的时间间隔,在经过这段时间后发起同步。由于同步框架也负责其他SyncAdapter的执行,为了最大化电池续航,这个同步的间隔时间可能会有几秒的误差。而且,在没有网络的时候,框架也不会发起同步。
addPeriodicSync()
并不能在一天的某个特定时间点发起同步,要实现这一点需要使用重复的闹钟作触发。关于重复闹钟更详细的信息在的 AlarmManager
参考文档中有描述。如果你是调用的 setInexactRepeating()
设置触发时间,那么时间要设置成随机的,这样能保证将不同设备的同步时间错开。
addPeriodicSync()
并不会禁用 setSyncAutomatically()
,因此可能会有多个同步在短时间内发生。而且,只有少数的Flag可以在 addPeriodicSync()
中使用,其他不允许使用的Flag可以参考 addPeriodicSync()
的文档。
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account public static final String ACCOUNT = "default_account"; // Sync interval constants public static final long SECONDS_PER_MINUTE = 60L; public static final long SYNC_INTERVAL_IN_MINUTES = 60L; public static final long SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE; // Global variables // A content resolver for accessing the provider ContentResolver mResolver; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver for your app mResolver = getContentResolver(); /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( ACCOUNT, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL); ... } ... }
ContentResolver.requestSync()
。
SYNC_EXTRAS_MANUAL
SYNC_EXTRAS_EXPEDITED
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider" // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... /* * Create the dummy account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = CreateSyncAccount(this); ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ public void onRefreshButtonClick(View v) { ... // Pass the settings flags by inserting them in a bundle Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_EXPEDITED, true); /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle); }