Virgil Dobjanschi 的 Rest 模式的示例实现
这是 Virgil Dobjanschi 在 Google IO 2010 大会上的 REST 模式 A 的示例实现。
引言
在观看了 Virgil Dobjanschi 在 Google IO 2010 大会上关于 RESTful Android 应用的精彩演讲后,我在网上搜索他的模式的实现,结果却寥寥无几。
这是我尝试实现他演讲中模式 A 的一个版本。
如果您使用了该代码,我很想知道您是如何使用的,例如,您是用于个人项目还是商业项目,该应用是否会上架?我很想看到任何使用该代码的应用。
我欢迎任何评论,包括任何改进建议。背景
我建议,如果您还没有看过 Dobjanschi 的演讲,请务必观看,它可以在这里找到:
http://www.youtube.com/watch?v=xHXn3Kg2IQE
我选择该演讲中的模式 A 的主要原因是,您可以为 REST 方法提供任何您想要的接口,您不必仅限于 ContentProvider API。
实施
我将从数据库和 REST 调用开始解释代码,然后向上移至 UI。
REST
我有一个抽象类,它向子类公开 Post、Put、Get、Delete 方法。
我有一些子类,它们将调用基类上的这些方法并将结果解析为数据对象。
对于我希望使用的每个 REST 方法,我都拥有一个类似这样的方法:
public RowDTO[] getRows()
{
// Make rest call by calling PUT/POST etc on the base class
// From result of HTTP call create an array of RowDTO's and return it
}
我在此级别同步执行 HTTP 调用,因为这将在其自己的线程上运行,正如我们稍后在 ProcessorService
中看到的。
Processor
我基本上为数据库中的每个表实现了一个处理器。
处理器的作用是调用 REST 并根据需要更新 SQL。
我从服务中传递 Context 的引用,以便处理器可以使用以下方式访问数据库:
Context.getContentResolver()
我不会在此提供任何代码,因为我认为每种应用的实现都会发生很大变化,而且 Dobjanschi 在他的视频中对如何实现这一点进行了很好的描述。
ServiceProvider
我有一个 IServiceProvider
接口,它为 ProcessorService
中的处理器提供了一个通用接口。
此类主要目的是将整数常量转换为处理器上的特定方法,并将参数从 Bundle
解析为处理器方法的类型化参数。
import android.os.Bundle;
/**
* Implementations of this interface should allow methods on their respective Processor to be called through the RunTask method.
*/
public interface IServiceProvider
{
/**
* A common interface for all Processors.
* This method should make a call to the processor and return the result.
* @param methodId The method to call on the processor.
* @param extras Parameters to pass to the processor.
* @return The result of the method
*/
boolean RunTask(int methodId, Bundle extras);
}
该接口的一个示例如下:
import android.content.Context;
import android.os.Bundle;
public class RowsServiceProvider implements IServiceProvider
{
private final Context mContext;
public RowsServiceProvider(Context context)
{
mContext = context;
}
/**
* Identifier for each provided method.
* Cannot use 0 as Bundle.getInt(key) returns 0 when the key does not exist.
*/
public static class Methods
{
public static final int REFRESH_ROWS_METHOD = 1;
public static final int DELETE_ROW_METHOD = 2;
public static final String DELETE_ROW_PARAMETER_ID = "id";
}
@Override
public boolean RunTask(int methodId, Bundle extras)
{
switch(methodId)
{
case Methods.REFRESH_ROWS_METHOD:
return refreshRows();
case Methods.DELETE_ROW_METHOD:
return deleteRow(extras);
}
return false;
}
private boolean refreshRows()
{
return new RowsProcessor(mContext).resfreshRows();
}
private boolean deleteRow(Bundle extras)
{
int id = extras.getInt(Methods.DELETE_ROW_PARAMETER_ID);
return new RowsProcessor(mContext).deleteRow(id);
}
}
ProcessorService
在我看来,这是模式中最复杂的部分。
此服务负责在各自的线程上运行每个处理器调用。
它还确保如果一个方法当前正在运行并且再次使用相同的参数调用它,那么不会并行多次运行该方法,而是只通知两个调用者何时完成。
要开始调用方法,应将 Intent 发送到此服务的 onStart
方法,如果服务尚未运行,这将启动该服务。
Intent 将包含以下详细信息:
- 意向调用的处理器。
- 要调用的方法。
- 方法的参数。
- 用于结果 Intent 的标签。
结果标签用于调用者标识用于发送结果 Intent 的标签,以便在方法调用完成后发送。当对同一个方法进行两次调用时,该方法只会被调用一次,但每个调用者都可以指定自己的结果标签,以便能够单独收到完成通知以及调用是否成功。
结果 Intent 包含启动服务的所有附加信息(包括调用的处理器、调用的方法、传递的任何参数),以及一个布尔结果,指示调用是否成功。
当所有方法完成后,此服务将自行关闭。
import java.util.ArrayList;
import java.util.HashMap;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
/**
* This service is for making asynchronous method calls on providers.
* The fact that this is a service means the method calls
* will continue to run even when the calling activity is killed.
*/
public class ProcessorService extends Service
{
private Integer lastStartId;
private final Context mContext = this;
/**
* The keys to be used for the required actions to start this service.
*/
public static class Extras
{
/**
* The provider which the called method is on.
*/
public static final String PROVIDER_EXTRA = "PROVIDER_EXTRA";
/**
* The method to call.
*/
public static final String METHOD_EXTRA = "METHOD_EXTRA";
/**
* The action to used for the result intent.
*/
public static final String RESULT_ACTION_EXTRA = "RESULT_ACTION_EXTRA";
/**
* The extra used in the result intent to return the result.
*/
public static final String RESULT_EXTRA = "RESULT_EXTRA";
}
private final HashMap<String, AsyncServiceTask> mTasks = new HashMap<String, AsyncServiceTask>();
/**
* Identifier for each supported provider.
* Cannot use 0 as Bundle.getInt(key) returns 0 when the key does not exist.
*/
public static class Providers
{
public static final int ROWS_PROVIDER = 1;
}
private IServiceProvider GetProvider(int providerId)
{
switch(providerId)
{
case Providers.ROWS_PROVIDER:
return new RowsServiceProvider(this);
}
return null;
}
/**
* Builds a string identifier for this method call.
* The identifier will contain data about:
* What processor was the method called on
* What method was called
* What parameters were passed
* This should be enough data to identify a task to detect if a similar task is already running.
*/
private String getTaskIdentifier(Bundle extras)
{
String[] keys = extras.keySet().toArray(new String[0]);
java.util.Arrays.sort(keys);
StringBuilder identifier = new StringBuilder();
for (int keyIndex = 0; keyIndex < keys.length; keyIndex++)
{
String key = keys[keyIndex];
// The result action may be different for each call.
if (key.equals(Extras.RESULT_ACTION_EXTRA))
{
continue;
}
identifier.append("{");
identifier.append(key);
identifier.append(":");
identifier.append(extras.get(key).toString());
identifier.append("}");
}
return identifier.toString();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
// stopSelf will be called later and if a new task is being added we do not want to stop the service.
lastStartId = startId;
Bundle extras = intent.getExtras();
String taskIdentifier = getTaskIdentifier(extras);
Log.i("ProcessorService", "starting " + taskIdentifier);
// If a similar task is already running then lets use that task.
AsyncServiceTask task = mTasks.get(taskIdentifier);
if (task == null)
{
task = new AsyncServiceTask(taskIdentifier, extras);
mTasks.put(taskIdentifier, task);
// AsyncTasks are by default only run in serial (depending on the android version)
// see android documentation for AsyncTask.execute()
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
// Add this Result Action to the task so that the calling activity can be notified when the task is complete.
String resultAction = extras.getString(Extras.RESULT_ACTION_EXTRA);
if (resultAction != "")
{
task.addResultAction(extras.getString(Extras.RESULT_ACTION_EXTRA));
}
}
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent)
{
return null;
}
public class AsyncServiceTask extends AsyncTask<Void, Void, Boolean>
{
private final Bundle mExtras;
private final ArrayList<String> mResultActions = new ArrayList<String>();
private final String mTaskIdentifier;
/**
* Constructor for AsyncServiceTask
*
* @param taskIdentifier A string which describes the method being called.
* @param extras The Extras from the Intent which was used to start this method call.
*/
public AsyncServiceTask(String taskIdentifier, Bundle extras)
{
mTaskIdentifier = taskIdentifier;
mExtras = extras;
}
public void addResultAction(String resultAction)
{
if (!mResultActions.contains(resultAction))
{
mResultActions.add(resultAction);
}
}
@Override
protected Boolean doInBackground(Void... params)
{
Log.i("ProcessorService", "working " + mTaskIdentifier);
Boolean result = false;
final int providerId = mExtras.getInt(Extras.PROVIDER_EXTRA);
final int methodId = mExtras.getInt(Extras.METHOD_EXTRA);
if (providerId != 0 && methodId != 0)
{
final IServiceProvider provider = GetProvider(providerId);
if (provider != null)
{
try
{
result = provider.RunTask(methodId, mExtras);
} catch (Exception e)
{
result = false;
}
}
}
return result;
}
@Override
protected void onPostExecute(Boolean result)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
Log.i("ProcessorService", "finishing " + mTaskIdentifier);
// Notify the caller(s) that the method has finished executing
for (int i = 0; i < mResultActions.size(); i++)
{
Intent resultIntent = new Intent(mResultActions.get(i));
resultIntent.putExtra(Extras.RESULT_EXTRA, result.booleanValue());
resultIntent.putExtras(mExtras);
resultIntent.setPackage(mContext.getPackageName());
mContext.sendBroadcast(resultIntent);
}
// The task is complete so remove it from the running tasks list
mTasks.remove(mTaskIdentifier);
// If there are no other executing methods then stop the service
if (mTasks.size() < 1)
{
stopSelf(lastStartId);
}
}
}
}
}
ServiceHelper
服务助手只是为上层提供了一个方便的接口,并“帮助”创建 Intent 和启动 ProcessService。
抽象类如下:
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
/**
* The service helpers are a facade for starting a task on the ProcessorService.
* The purpose of the helpers is to give a simple interface to the upper layers to make asynchronous method calls in the service.
*/
public abstract class ServiceHelperBase
{
private final Context mcontext;
private final int mProviderId;
private final String mResultAction;
public ServiceHelperBase(Context context, int providerId, String resultAction)
{
mcontext = context;
mProviderId = providerId;
mResultAction = resultAction;
}
/**
* Starts the specified methodId with no parameters
* @param methodId The method to start
*/
protected void RunMethod(int methodId)
{
RunMethod(methodId, null);
}
/**
* Starts the specified methodId with the parameters given in Bundle
* @param methodId The method to start
* @param bundle The parameters to pass to the method
*/
protected void RunMethod(int methodId, Bundle bundle)
{
Intent service = new Intent(mcontext, ProcessorService.class);
service.putExtra(ProcessorService.Extras.PROVIDER_EXTRA, mProviderId);
service.putExtra(ProcessorService.Extras.METHOD_EXTRA, methodId);
service.putExtra(ProcessorService.Extras.RESULT_ACTION_EXTRA, mResultAction);
if (bundle != null)
{
service.putExtras(bundle);
}
mcontext.startService(service);
}
}
一个示例子类
import android.content.Context;
public class RowsServiceHelper extends ServiceHelperBase
{
public RowsServiceHelper(Context context, String resultAction)
{
super(context, ProcessorService.Providers.ROWS_PROVIDER, resultAction);
}
public void refreshRows()
{
RunMethod(RowsServiceProvider.Methods.REFRESH_ROWS_METHOD);
}
public void deleteRow(int id)
{
Bundle extras = new Bundle();
extras.putInt(RowsServiceProviderMethods.DELETE_ROW_PARAMETER_ID, id);
RunMethod(RowsServiceProvider.Methods.DELETE_ROW_METHOD, extras);
}
}
使用 RowsProcessor
现在到上层,通常这将在 activity 中。
要接收结果 Intent,请使用以下代码:
在您的代码中为返回 Intent 创建一个 Intent 过滤器:
private final static String RETURN_ACTION = "com.MyApp.RowsActivity.ActionResult";
private final IntentFilter mFilter = new IntentFilter(RETURN_ACTION);
创建 Broadcast receiver 来处理返回 Intent:
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
Bundle extras = intent.getExtras();
boolean success = extras.getBoolean(ProcessorService.Extras.RESULT_EXTRA);
// Which method is the result for
int method = extras.getInt(ProcessorService.Extras.METHOD_EXTRA);
String text;
if (success)
{
text = "Method " + method + " passed!";
}
else
{
text = "Method " + method + " failed!";
}
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
};
在您的 activity 的 onStart 方法中:
registerReceiver(mBroadcastReceiver, mFilter);
mServiceHelper = new RowsServiceHelper(mActivity, RETURN_ACTION);
在 onStop 中:
unregisterReceiver(mBroadcastReceiver);
现在,您只需在 activity 中调用 mServiceHelper
的任何方法即可在自己的线程上进行 REST 调用并更新数据库,您将通过 mBroadcastReceiver
收到通知。
历史
2012年7月28日 - 初始帖子