简短的回答是,如果你正在针对 ~API 级别 7,那么请不要使用 SyncAdapter。尽管后续 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类传递(WebClient是一个帮助构建/制作HTTP请求等的辅助类)。当WebClient接收到HTTP响应以向活动发送消息并让其知道数据已被接收并在我的情况下存储在SQLite数据库中(我建议这样做)时,WebClient使用此处理程序。在大多数活动中,我会在onPause()中调用mHandler.stopHandler(),并在onResume()中调用mHandler.startHandler(),以避免将HTTP响应返回给非活动状态的Activity等。这证明是一种相当健壮的方法。
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()
被调用在主线程上。因此您可以在这里停止进度对话框,安全地执行其他Activity操作。
我声明了一个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()
中使用它,增加各种错误计数等。再次强调,文档非常差,让人感到很麻烦。 parseXML()
利用了出色的Simple XML lib。
public synchronized void processRequest(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult, Handler handler, Context context)
{
HttpUriRequest request = createHTTPRequest(extras);
InputStream instream = executeRequest(request, syncResult);
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时应用程序无限等待。如果发生超时,这将导致抛出异常。
int timeoutConnection = 30000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
int timeoutSocket = 30000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
HttpClient client = new DefaultHttpClient(httpParameters);
总的来说,SyncAdapter和Accounts相关的东西让我付出了很多时间但没有收获。ContentProvider相当有用,主要是因为它支持游标和事务。SQLite数据库非常好用。Handler类也很棒。现在我会使用AsyncTask类来代替像我之前那样创建自己的线程来生成HTTP请求。
希望这篇冗长的解释能对某些人有所帮助。