21

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

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

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

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

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

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


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


还有一件事……;)

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

我见过的所有关于 onPerformSync() 的例子都非常简单,而且没有使用自定义的 ContentProvider,还有任何现实世界的例子吗?文档有点像鸟巢。Virgil Dobjanschi,先生,你让我在没有桨的情况下离开了小溪。

4

2 回答 2

3

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

所以这是我最终采用的模式。在我的活动中,我有一个处理程序,它从自定义处理程序超类中简单添加(可以检查m_bStopped布尔值):

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();以避免将 HTTP 响应信号返回给非活动活动等。事实证明,这是一种非常强大的方法。onPause()mHandler.startHandler();onResume()

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用于将结果返回给上述ResponseHandlerI 的参数。SyncAdapter使用syncResultin out 参数来执行指数退避等。我在 中使用它executeRequest(),增加它的各种错误计数等。同样,文档记录非常差,PITA 开始工作。parseXML()利用了极好的Simple XML 库

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 请求。

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

于 2012-08-18T04:55:32.090 回答
1

观察者设计模式呢?您的活动可以成为 SyncAdapter 或数据库的观察者吗?这样,当更新失败时,适配器将通知其观察者,然后可以对更改的数据采取行动。SDK 中有一堆 Observable 类,看看哪一个最适合你的情况。http://developer.android.com/search.html#q=Observer&t=0

于 2011-11-04T01:54:58.437 回答