我看过Google I / O REST谈话并阅读幻灯片:http://www.google.com/events/io/2010/sessions/developing-RESTful-android-apps.html
我仍然有点不清楚如何很好地处理远程服务器抛出的更新错误。我已经实现了自己的ContentProvider和SyncAdapter。请考虑以下情况:
通过REST调用更新用户的联系人详细信息:
我的问题是,SyncAdapter向我的ContentProvider发出信号的最佳方式是,此更改需要从应用程序的本地数据库中退出,并向我的Activity发出更新请求失败的信号(并传递从服务器返回的错误消息?
我的活动需要在等待结果时显示进度微调器,并知道请求是成功还是失败。
为了使用来自服务器的内容更新本地应用程序数据库,SyncAdapter模式对我来说完全合理,我的工作正常。但是对于 应用到服务器的更新,我似乎无法找到处理上述方案的好方法。
另一件事......;)
假设我调用ContentResolver.notifyChange(uri,null,true);来自我的ContentProvider的update()方法。 true
以及android:supportsUploading="true"
将导致我的SyncAdapter onPerformSync()被调用。太棒了,但是在onPerformSync()中,如何判断我应该同步哪个URI?每次收到Sync请求时,我都不想简单地刷新整个数据库。但你甚至无法将Bundle传递给notifyChangeCall()以传递给onPerformSync()。
我在onPerformSync()上看到的所有例子都非常简单,并没有使用自定义的ContentProvider,那里有任何现实世界的例子吗?而且文档有点像燕窝。先生,维吉尔·多布扬斯基,你没有划桨就把我留在了小溪里。
答案 0 :(得分:3)
简短回答,如果您的目标是~API级别7,则“不要”。在以后的API中情况可能有所改善但是因为它...我强烈建议完全避免使用SyncAdapter;它的记录非常糟糕,“自动”帐户/身份验证管理的价格很高,因为它的API也很复杂且记录不足。除了最琐碎的用例之外,还没有考虑过API的这一部分。
所以这是我最终的模式。在我的活动中,我有一个处理程序,从自定义Handler超类中添加一个简单的处理程序(可以检查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();
中调用onPause()
,在mHandler.startHandler();
调用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请求的更多详细信息。这是之前在Runnable中调用的processRequest()
方法。请注意handler
参数,该参数用于将结果通知我上面描述的ResponseHandler
。 syncResult
是在SyncAdapter用于指数退避的输出参数等。我在executeRequest()
中使用它,增加它的各种错误计数等。再次,文档记录很差,PITA开始工作。 parseXML()
利用精湛的Simple XML lib。
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类,而不是像上面那样创建自己的Thread来生成HTTP请求。
我希望这种漫无边际的解释对某人有所帮助。
答案 1 :(得分:1)
观察者设计模式怎么样?您的活动可以成为SyncAdapter或数据库的观察者吗?这样,当更新失败时,适配器将通知其观察者,然后可以对更改的数据进行操作。 SDK中有一堆Observable类,看看哪种类在您的情况下最有效。 http://developer.android.com/search.html#q=Observer&t=0