如何使用 SyncAdapter 处理远程服务器的 RESTful 更新

2024-05-10

我观看了 Google I/O REST 演讲并阅读了幻灯片:http://www.google.com/events/io/2010/sessions/developing-RESTful-android-apps.html http://www.google.com/events/io/2010/sessions/developing-RESTful-android-apps.html

我仍然有点不清楚如何很好地处理远程服务器抛出的更新错误。我已经实现了自己的 ContentProvider 和 SyncAdapter。考虑这种情况:

通过 REST 调用更新用户的联系方式:

  1. 使用 ContentResolver 请求更新。
  2. 我的内容提供商立即地更新应用程序的本地 Sqlite 数据库并请求同步(按照 Google I/O 演讲中的建议)。
  3. 我的 SyncAdapter.onPerformSync() 被调用并执行 REST 调用来更新远程数据。
  4. 远程服务器响应“错误:无效的电话号码”(例如)。

我的问题是,SyncAdapter 向我的 ContentProvider 发出信号表明此更改需要从应用程序的本地数据库中撤回,并向我的 Activity 发出信号表明更新请求失败(并传递返回的错误消息)的最佳方式是什么从服务器)?

我的活动需要在等待结果时显示进度微调器,并了解请求是成功还是失败。


为了使用服务器中的内容更新本地应用程序数据库,SyncAdapter 模式对我来说完全有意义,而且我工作得很好。但对于更新from该应用程序to服务器,我似乎找不到处理上述情况的好方法。


还有一件事......;)

假设我调用 ContentResolver.notifyChange(uri, null, true);从我的 ContentProvider 的 update() 方法中。true随着android:supportsUploading="true"将导致我的 SyncAdapter 的 onPerformSync() 被调用。很好,但是在 onPerformSync() 内部,我如何知道应该同步哪个 URI?我不想每次收到同步请求时都简单地刷新整个数据库。但您甚至无法将 Bundle 传递到 notificationChangeCall() 中以传递给 onPerformSync()。

我见过的 onPerformSync() 的所有示例都非常简单,并且没有使用自定义 ContentProvider,有任何现实世界的示例吗?这些文档有点像鸟巢。 Virgil Doobjanschi,先生,您让我在没有桨的情况下留在小溪里。


如果您的目标是 API 级别 7,简短的回答是“不要”。这种情况在后来的 API 中可能有所改善,但事实上……我强烈建议完全避免使用 SyncAdapter;它的记录非常糟糕,并且“自动”帐户/身份验证管理的代价很高,因为它的 API 也很复杂且记录不足。除了最琐碎的用例之外,API 的这一部分还没有经过深思熟虑。

这就是我最终采用的模式。在我的活动中,我有一个处理程序,其中包含来自自定义处理程序超类的简单添加(可以检查m_bStopped bool):

private ResponseHandler mHandler = new ResponseHandler();

class ResponseHandler extends StopableHandler {

    @Override
    public void handleMessage(Message msg) {
        if (isStopped()) {
            return;
        }
        if (msg.what == WebAPIClient.GET_PLANS_RESPONSE) {
            ...
        } 
        ...
    }
}

该活动将调用 REST 请求,如下所示。请注意,处理程序被传递到 WebClient 类(用于构建/发出 HTTP 请求等的帮助程序类)。 WebClient 在接收到返回给活动的消息的 HTTP 响应时使用此处理程序,并让它知道数据已被接收,并且在我的情况下,已存储在 SQLite 数据库中(我建议这样做)。在大多数活动中,我会调用mHandler.stopHandler(); in onPause() and mHandler.startHandler(); in onResume()以避免 HTTP 响应被发送回非活动活动等。事实证明这是一个非常强大的方法。

final Bundle bundle = new Bundle();
bundle.putBoolean(WebAPIRequestHelper.REQUEST_CREATESIMKITORDER, true);
bundle.putString(WebAPIRequestHelper.REQUEST_PARAM_KIT_TYPE, sCVN);       
final Runnable runnable = new Runnable() { public void run() {
    VendApplication.getWebClient().processRequest(null, bundle, null, null, null,
                    mHandler, NewAccountActivity.this);
    }};
mRequestThread = Utils.performOnBackgroundThread(runnable);

Handler.handleMessage()在主线程上调用。因此,您可以在此处停止进度对话框并安全地执行其他活动操作。

我声明了一个 ContentProvider:

<provider android:name="au.com.myproj.android.app.webapi.WebAPIProvider"
          android:authorities="au.com.myproj.android.app.provider.webapiprovider"
          android:syncable="true" />

并实现它来创建和管理对 SQLite 数据库的访问:

public class WebAPIProvider extends ContentProvider

因此,您可以将光标放在活动中的数据库上,如下所示:

mCursor = this.getContentResolver().query (
          WebAPIProvider.PRODUCTS_URI, null, 
          Utils.getProductsWhereClause(this), null, 
          Utils.getProductsOrderClause(this));
startManagingCursor(mCursor);

我找到了org.apache.commons.lang3.text.StrSubstitutor类对于构建 REST API 所需的笨拙 XML 请求非常有帮助,我必须与例如集成在WebAPIRequestHelper我有类似的辅助方法:

public static String makeAuthenticateQueryString(Bundle params)
{
    Map<String, String> valuesMap = new HashMap<String, String>();
    checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTNUMBER);
    checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTPASSWORD);

    valuesMap.put(REQUEST_PARAM_APIUSERNAME, API_USERNAME);
    valuesMap.put(REQUEST_PARAM_ACCOUNTNUMBER, params.getString(REQUEST_PARAM_ACCOUNTNUMBER));
    valuesMap.put(REQUEST_PARAM_ACCOUNTPASSWORD, params.getString(REQUEST_PARAM_ACCOUNTPASSWORD));

    String xmlTemplate = VendApplication.getContext().getString(R.string.XMLREQUEST_AUTHENTICATE_ACCOUNT);
    StrSubstitutor sub = new StrSubstitutor(valuesMap);
    return sub.replace(xmlTemplate);
}

我会将其附加到适当的端点 URL。

以下是有关 WebClient 类如何执行 HTTP 请求的更多详细信息。这是processRequest()之前在 Runnable 中调用的方法。注意handler参数,用于将结果消息返回给ResponseHandler我在上面描述过。这syncResult是 SyncAdapter 使用的 out 参数来进行指数退避等。我在executeRequest(),增加它的各种错误计数等。同样,文档非常少,需要 PITA 才能工作。parseXML()发挥了卓越的作用简单的 XML 库 http://simple.sourceforge.net/.

public synchronized void processRequest(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult, Handler handler, Context context)
{
    // Helper to construct the query string from the query params passed in the extras Bundle.
    HttpUriRequest request = createHTTPRequest(extras);
    // Helper to perform the HTTP request using org.apache.http.impl.client.DefaultHttpClient.
    InputStream instream = executeRequest(request, syncResult);

    /*
     * Process the result.
     */
    if(extras.containsKey(WebAPIRequestHelper.REQUEST_GETBALANCE))
    {
        GetServiceBalanceResponse xmlDoc = parseXML(GetServiceBalanceResponse.class, instream, syncResult);
        Assert.assertNotNull(handler);
        Message m = handler.obtainMessage(WebAPIClient.GET_BALANCE_RESPONSE, xmlDoc);
        m.sendToTarget();
    }
    else if(extras.containsKey(WebAPIRequestHelper.REQUEST_GETACCOUNTINFO))
    {
      ...
    }
    ...

}

您应该对 HTTP 请求设置一些超时,这样应用程序就不会在移动数据丢失或从 Wifi 切换到 3G 时永远等待。如果发生超时,这将导致抛出异常。

    // Set the timeout in milliseconds until a connection is established.
    int timeoutConnection = 30000;
    HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
    // Set the default socket timeout (SO_TIMEOUT) in milliseconds which is the timeout for waiting for data.
    int timeoutSocket = 30000;
    HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
    HttpClient client = new DefaultHttpClient(httpParameters);          

总的来说,SyncAdapter 和 Accounts 的东西非常痛苦,花费了我很多时间却没有任何收获。 ContentProvider 相当有用,主要用于游标和事务支持。 SQLite 数据库非常好。 Handler 类非常棒。我现在将使用 AsyncTask 类,而不是像上面那样创建自己的线程来生成 HTTP 请求。

我希望这个漫无目的的解释对某人有所帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 SyncAdapter 处理远程服务器的 RESTful 更新 的相关文章

  • 如何在 Android 中使用 Assets 中预加载的 SQLite 数据库

    我想用preloaded database在我的应用程序中意味着尝试在安装 apk 时获取数据库 以便可以使用已保存在其中的数据 我复制了 成分 db 文件位于资产文件夹中 并使用以下代码 但这会出现错误 从资源文件复制数据库时出现问题 我
  • 使用 android AudioTrack 在左或右扬声器中播放声音

    我正在应用程序中的 AudioTrack 的帮助下播放声音 但我想在特定扬声器 耳朵中播放声音 即左扬声器或右扬声器或两个扬声器 以下代码是我用来播放声音的 private AudioTrack generateTone double fr
  • 如何获取.so文件的依赖项列表?

    假设我有libFoo so为 android arm 编译 我不确定它链接到什么STL实现 有options http developer android com intl ru ndk guides standalone toolchai
  • 如何避免服务器端和客户端的重复验证?

    如何避免服务器端和客户端的重复验证 是否有一个网络编程平台可以从另一个生成一个 这样我就不必保持两者同步 有些框架可以从服务器端验证配置生成客户端验证 现在我不知道你使用什么语言或框架 但也许这篇文章可以有所帮助 http www emad
  • Firestore - RecycleView - 图像持有者

    我不知道如何编写图像的支架 我已经设置了 2 个文本 但我不知道图像的支架应该是什么样子 你能帮我告诉我图像的文字应该是什么样子才能正确显示吗 holder artistImage setImageResource model getArt
  • Android:应用内计费V3超时返回哪个响应码?

    出现网络超时情况时 Google Play 应用内结算服务 ice er V3 将返回哪些响应状态代码 它的所有功能都是统一的吗 我将在这里描述我的发现 我通过拔掉主机插头 在安装了全功能 GP GP Store V3 10 10 GP S
  • 如何防止布局的方向改变,而不是整个屏幕/活动的方向改变

    我需要一个子布局 可以是任何布局 例如FrameLayout or RelativeLayout 忽略方向变化并始终保持横向 但不是它的父级或任何其他兄弟布局 视图 它们应该相应地改变它们的方向 因此 我不能使用setRequestedOr
  • 如何使用 Firebase UI 在 recyclerView 中显示时从 Firebase 数据库中的子级引用父级

    我目前正在引用 quote text 子项 这些数据显示在recyclerview using firebase用户界面 我想获取 quote text 的父名称 如何获取 quote text 孩子的父母姓名 When user clic
  • 导航抽屉默认片段

    我是一名新手开发人员 我正在将导航抽屉与 android support v7 集成到我的应用程序中 我有一个问题 当我启动应用程序时 主要布局是这样的
  • git 是否有任何静态接口?

    我一直在寻找一个宁静的 git api 但似乎没有找到 我得到的最接近的是 Github 的 api 来访问一些存储库信息 还有其他的实施吗 Orion Git API http wiki eclipse org Orion Server
  • Android Studio IDE 上的“文本/设计”选项卡缺少新的 Android 项目

    如何在创建新项目期间自动创建的 Activity main xml 文件的 src main res layout 文件夹中启用文本 设计选项卡 如果我右键单击并在所述文件夹上创建 xml 文件 则设计 文本选项卡存在 有什么建议吗 谢谢
  • 我可以在 PHP 会话变量中安全地存储用户名和密码吗?

    我想在 REST api 之上制作一个轻量级的 web 应用程序 用户只需进行一次身份验证 从那时起 所有针对 web api 的请求都希望通过以某种方式保持用户名和密码有效来完成 我已经做了一个工作原型我在哪里将用户名和密码存储在会话变量
  • 将图像添加到自定义 AlertDialog

    我制作了一个 AlertDialog 让用户可以从我显示的 4 个选项中选择一个 前 3 个让他们在单击号码时直接拨打号码 第 4 个显示不同的视图 现在看起来是这样的 由于第四个选项的目的是不同的任务 我想让它看起来不同 因为用户可能会感
  • 直接使用从密钥库加载的 SecretKey 时,密钥用户未经过身份验证

    我正在尝试使用 Cipher 和在 KeyStore 中加载的 SecretKey 来加密数据 但总是收到此错误 导致 android security KeyStoreException 关键用户未经过身份验证 我尝试自己创建 Secre
  • FCM onMessageReceived 应用程序运行时返回空白消息和标题

    正如您在标题中所写 当应用程序关闭时 它运行良好 并且onMessageReceived获取消息正文和标题 但如果应用程序处于前台模式 运行模式 则可以发送通知 但没有消息和标题 请问该怎么办 代码 Override public void
  • 安卓的限制

    我需要构建一个应用程序 该应用程序拍摄相机图像并将其上传到网络 在网络上进行一些处理并返回真 假 我在这方面遇到了一些问题 希望得到澄清 1 我的应用程序有什么方法可以知道 Android 相机捕获的图像吗 我从这里明白了什么 Androi
  • 按字母顺序过滤 Firestore 数据以对 Google Cloud 中的文档读取进行分类/减少

    基于这样的事实Cloud Firestore 不支持全文搜索 https firebase google com docs firestore solutions search到目前为止 我决定问这个question https stack
  • Android ScrollView fillViewport 不工作

    我有一个简单的布局 名称位于顶部 按钮位于屏幕底部 或者超出该按钮 以防我添加更多项目 所以我使用带有 LinearLayout 的 ScrollView 如下所示
  • Android 和 Java 中绘制椭圆的区别

    在Java中由于某种原因Ellipse2D Double使用参数 height width x y 当我创建一个RectF在Android中参数是 left top right bottom 所以我对适应差异有点困惑 如果在 Java 中创
  • 当ScrollView滚动到底部时加载更多数据

    我有一个带有动态加载内容的滚动视图 有时可能会有很多内容 所以我想在用户滚动到底部时加载更多内容 我搜索了合适的方法 发现了两种 onScrollChanged and getScrollY 但我不知道如何将它用于我的目的 请给我一些建议

随机推荐