创建同步适配器

注意:我们建议使用 WorkManager 作为大多数后台处理用例的推荐解决方案。请参阅 后台处理指南,了解哪种解决方案最适合您。

应用中的同步适配器组件可封装负责传输数据的任务的代码 在设备和服务器之间传输数据基于您在 您的应用,则同步适配器框架会运行同步适配器组件中的代码。要将 同步适配器组件添加到应用中,则需要添加以下部分:

同步适配器类。
用于将数据传输代码封装到与同步适配器兼容的接口中的类 框架。
绑定 Service
可让同步适配器框架运行同步适配器中的代码的组件 类。
同步适配器 XML 元数据文件。
包含同步适配器相关信息的文件。框架会将此文件读取 了解如何加载和安排数据传输。
应用清单中的声明。
此 XML 声明绑定服务并指向特定于同步适配器的元数据。

本课将介绍如何定义这些元素。

创建同步适配器类

在本课程的这一部分中,您将学习如何创建同步适配器类,用于封装 数据传输代码。创建该类包括扩展同步适配器基类,定义 类构造函数,并实现在其中定义数据传输的方法 任务。

扩展同步适配器基类

要创建同步适配器组件,首先需要扩展 AbstractThreadedSyncAdapter 并编写其构造函数。使用 构造函数来运行设置任务。 就像您使用 Activity.onCreate() 设置 活动。例如,如果您的应用使用内容提供程序来存储数据,则应使用构造函数 以获取 ContentResolver 实例。由于 Android 平台版本 3.0 中添加了构造函数,以支持 parallelSyncs 参数,您需要创建两种形式的构造函数以保持兼容性。

注意:同步适配器框架旨在与同步适配器配合使用 这些组件属于单例实例介绍了如何实例化同步适配器组件 将在本单元后面的部分 将同步适配器绑定到框架

以下示例展示了如何实现 AbstractThreadedSyncAdapter 及其构造函数:

Kotlin

/**
 * Handle the transfer of data between a server and an
 * app, using the Android sync adapter framework.
 */
class SyncAdapter @JvmOverloads constructor(
        context: Context,
        autoInitialize: Boolean,
        /**
         * Using a default argument along with @JvmOverloads
         * generates constructor for both method signatures to maintain compatibility
         * with Android 3.0 and later platform versions
         */
        allowParallelSyncs: Boolean = false,
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        val mContentResolver: ContentResolver = context.contentResolver
) : AbstractThreadedSyncAdapter(context, autoInitialize, allowParallelSyncs) {
    ...
}

Java

/**
 * 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 contentResolver;
    /**
     * 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
         */
        contentResolver = 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
         */
        contentResolver = context.getContentResolver();
        ...
    }

添加数据传输代码

同步适配器组件不会自动执行数据传输,相反, 封装您的数据传输代码,以便同步适配器框架可以运行 在后台进行数据传输,而无需您的应用参与。当框架准备就绪时 同步应用数据后,它会调用方法的实现, onPerformSync()

为了便于将数据从主应用代码传输到同步适配器组件, 同步适配器框架调用 onPerformSync() 替换为 以下参数:

账号
与触发的事件相关联的 Account 对象 同步适配器如果您的服务器不使用账号,则不需要使用 信息。
其他功能
一个 Bundle,包含由触发同步的事件发送的标志 适配器。
职权
系统中内容提供器的授权。您的应用必须有权访问 该提供商。通常,该授权与您自己的应用中的内容提供程序相对应。
content provider 客户端
一个 ContentProviderClient,用于通过 权威性论据。ContentProviderClient 是轻量级公共资源 与内容提供程序相关联它的基本功能与 ContentResolver。如果您使用 content provider 来存储数据 您可以使用此对象连接到提供程序。否则,您可以忽略 。
同步结果
用于向同步发送信息的 SyncResult 对象 适配器框架的方法。

以下代码段显示了 onPerformSync():

Kotlin

/*
 * 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 fun onPerformSync(
        account: Account,
        extras: Bundle,
        authority: String,
        provider: ContentProviderClient,
        syncResult: SyncResult
) {
    /*
     * Put the data transfer code here.
     */
}

Java

/*
 * 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()。 通过将所有网络任务集中到此方法中,您可以节省 启动和停止网络接口所需的资源。详细了解如何进一步提高网络访问权限 请参阅在不消耗电池电量的情况下传输数据培训课程,其中介绍了几种网络访问 可以添加到数据传输代码中的一些任务。

将同步适配器绑定到框架

现在,您已将您的数据传输代码封装在同步适配器组件中,但是 为框架提供对您代码的访问权限为此,您需要创建一个 Service,用于从同步适配器传递特殊的 Android binder 对象 添加到框架中有了这个 binder 对象,框架就可以调用 onPerformSync() 方法和 向其传递数据

将同步适配器组件实例化为 onCreate() 方法。通过实例化 onCreate() 中的组件,您可以延迟 创建该容器,直到 Service 启动为止,这发生在框架首次尝试运行您的 数据转移。您需要以线程安全的方式实例化组件,以防出现同步 适配器框架会将您的同步适配器的多次执行加入队列,以响应触发器或 。

例如,以下代码段展示了如何创建实现 绑定 Service,实例化您的同步适配器组件,并获取 Android binder 对象:

Kotlin

package com.example.android.syncadapter
/**
 * Define a Service that returns an [android.os.IBinder] for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
class SyncService : Service() {
    /*
     * Instantiate the sync adapter object.
     */
    override fun onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized(sSyncAdapterLock) {
            sSyncAdapter = sSyncAdapter ?: SyncAdapter(applicationContext, true)
        }
    }

    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     *
     */
    override fun onBind(intent: Intent): IBinder {
        /*
         * 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()
         *
         * We should never be in a position where this is called before
         * onCreate() so the exception should never be thrown
         */
        return sSyncAdapter?.syncAdapterBinder ?: throw IllegalStateException()
    }

    companion object {
        // Storage for an instance of the sync adapter
        private var sSyncAdapter: SyncAdapter? = null
        // Object to use as a thread-safe lock
        private val sSyncAdapterLock = Any()
    }
}

Java

package com.example.android.syncadapter;
/**
 * Define a Service that returns an <code><a href="/reference/android/os/IBinder.html">IBinder</a></code> 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();
    }
}

注意:如需查看同步适配器绑定服务的更详细示例, 查看示例应用。

添加框架所需的账号

同步适配器框架要求每个同步适配器都具有一种账号类型。您已声明 部分中的账号类型值 添加身份验证器元数据文件。现在,您必须在 Android 系统。要设置账号类型,请添加使用账号类型的占位账号 调用 addAccountExplicitly()

调用此方法的最佳位置是 onCreate() 方法 打开 activity。以下代码段展示了如何执行此操作:

Kotlin

...
// Constants
// The authority for the sync adapter's content provider
const val AUTHORITY = "com.example.android.datasync.provider"
// An account type, in the form of a domain name
const val ACCOUNT_TYPE = "example.com"
// The account name
const val ACCOUNT = "placeholderaccount"
...
class MainActivity : FragmentActivity() {

    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       ...
        // Create the placeholder account
        mAccount = createSyncAccount()
       ...
    }
    ...
    /**
     * Create a new placeholder account for the sync adapter
     */
    private fun createSyncAccount(): Account {
        val accountManager = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
        return Account(ACCOUNT, ACCOUNT_TYPE).also { newAccount ->
            /*
             * 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 your <provider> element 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.
                 */
            }
        }
    }
    ...
}

Java

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 = "placeholderaccount";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Create the placeholder account
        mAccount = CreateSyncAccount(this);
        ...
    }
    ...
    /**
     * Create a new placeholder 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 your <provider> element 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.
             */
        }
    }
    ...
}

添加同步适配器元数据文件

要将同步适配器组件插入框架,您需要提供框架 其中包含描述组件并提供其他标记的元数据。元数据指定 您为同步适配器创建的账号类型,声明 content provider 授权 与您的应用关联的、控制系统界面中与同步适配器相关的部分, 并声明其他与同步相关的标志在存储在 应用项目中的 /res/xml/ 目录。你可以为该文件指定任何名称 但通常称为 syncadapter.xml

此 XML 文件包含一个 XML 元素 <sync-adapter>,该元素具有 以下属性:

android:contentAuthority
内容提供程序的 URI 授权。如果您为以下服务创建了桩内容提供器: 在上一课创建存根内容提供程序中构建桩内容提供程序,请使用您为 属性 android:authorities 在您添加到应用清单的 <provider> 元素中。该属性为 部分 在清单中声明提供程序
如果您使用同步适配器将数据从内容提供器传输到服务器, 值应与您对该数据使用的内容 URI 授权相同。此值 也是您在 android:authorities <provider> 元素的属性,用于在应用清单中声明您的提供程序。
android:accountType
同步适配器框架所需的账号类型。值必须相同 作为您在创建身份验证器元数据文件时提供的账号类型值,例如 添加身份验证器元数据文件部分中所述。该值也是您为 本部分代码段中的 ACCOUNT_TYPE 常量 添加框架所需的账号
设置属性
android:userVisible
设置同步适配器账号类型的可见性。默认情况下, 与账号类型相关联的账号图标和标签会显示在 账号部分,因此您应该进行同步 除非您将某个账号类型或网域 应用互动情况如果您将账号类型设为不可见,您仍然可以允许用户执行以下操作: 通过应用的某个 activity 中的界面控制同步适配器。
android:supportsUploading
允许您将数据上传到云端。如果应用仅适用于应用,请将此属性设为 false 下载数据。
android:allowParallelSyncs
允许同步适配器组件的多个实例同时运行。 如果您的应用支持多个用户账号,并且您希望允许多个用户账号,请使用此选项。 并行传输数据。如果您从不运行,则此标志不会产生任何影响 进行多次数据传输
android:isAlwaysSyncable
向同步适配器框架表明它可以在任何时间运行您的同步适配器 指定的时间如果您希望以编程方式控制 将此标志设置为 false,然后调用 requestSync(),以运行 同步适配器。如需详细了解如何运行同步适配器,请参阅 运行同步适配器

以下示例展示了使用单个占位符账号和 只支持下载

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
        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"/>

在清单中声明同步适配器

将同步适配器组件添加到应用后,您必须请求权限 与使用组件相关,而且您必须声明绑定的 Service 您所添加的任何项目

由于同步适配器组件运行的代码在网络和 您需要请求访问互联网的权限。此外,您的应用需要 来请求读取和写入同步适配器设置的权限,以便您可以控制同步 适配器。您还需要请求 一种特殊权限,可让应用使用您创建的身份验证器组件 创建存根身份验证程序一课。

如需请求这些权限,请将以下内容作为 <manifest>:

android.permission.INTERNET
允许同步适配器代码访问互联网,以便下载或上传数据 从设备传输到服务器如果您 。
android.permission.READ_SYNC_SETTINGS
允许您的应用读取当前的同步适配器设置。例如,您需要 权限,以便调用 getIsSyncable()
android.permission.WRITE_SYNC_SETTINGS
允许应用控制同步适配器设置。您需要有此权限才能 使用 addPeriodicSync() 设置定期运行同步适配器。调用 API 时需要此权限 requestSync()。要详细了解 运行同步适配器,请参阅运行同步适配器

以下代码段展示了如何添加这些权限:

<manifest>
...
    <uses-permission
            android:name="android.permission.INTERNET"/>
    <uses-permission
            android:name="android.permission.READ_SYNC_SETTINGS"/>
    <uses-permission
            android:name="android.permission.WRITE_SYNC_SETTINGS"/>
    <uses-permission
            android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
...
</manifest>

最后,声明 Service 框架用于 与您的同步适配器交互,请将以下 XML 作为子元素添加到您的应用清单中 (共 <application> 个):

        <service
                android:name="com.example.android.datasync.SyncService"
                android:exported="false"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter"
                    android:resource="@xml/syncadapter" />
        </service>

通过 <intent-filter> 元素设置由 intent 操作触发的过滤器 android.content.SyncAdapter,由系统发送以运行同步适配器。当过滤器 触发了,系统将启动您创建的绑定服务,在本例中为 SyncService。属性 android:exported="false" 仅允许您的应用和系统访问 Service。属性 android:process=":sync" 指示系统在名为 Service 的全局共享进程中运行 sync。如果您的应用中有多个同步适配器,那么它们可以共享此过程, 从而降低开销

通过 <meta-data> 元素提供您之前创建的同步适配器元数据 XML 文件的名称。 通过 android:name 属性指明这是用于同步适配器框架的元数据。通过 android:resource 元素指定该元数据文件的名称。

现在您已拥有同步适配器的所有组件。下一课将介绍如何 告知同步适配器框架运行您的同步适配器,可以在响应事件时运行,也可以在 定期更新。