ContentProvider 模仿SpringMVC 进行开发

纯整活,多半不好用。但是看起来像web开发
1. 基本思路
模仿springMvc,构建一个dispatcherServlet,进行请求转发
2. 约定
为了实现这个目的,同时简化开发,进行以下约定:
-
内容提供者实现的方法: 仅实现query方法。因为这个方法相对其他方法,可以返回一个Cursor,返回格式限制较小 接收方面,也提供了较多可选位置
-
请求路径格式:
content://authorities/api/内容提供者id/方法名?参数名1=值1&参数名2=值2
例如:content://com.example.server.provider.GeoInfoProvider/api/geo/findById?id=1
- 支持的参数:
为了便于解析,不允许使用path参数。只允许使用query参数,放置在uri中 支持请求体参数,以json格式放置在selectionArgs[0],不支持二进制类型请求体
- 请求方法:
为便于解析,不保留 get put post delete等请求方式 所有请求以方法名作为标识符
- 响应:
响应统一以json格式放置在 Cursor的第一行第一列
- 内容提供者的“接口”开发:
每新增一个“接口”, 都需要在adapter方法的switch中,添加一个case "方法名",内部进行方法调用, 所有接口方法,必须返回一个Result类型的对象 示例:
public Result adapter(String methodName,Uri uri,String requestBodyJson){
Result result = null;
switch (methodName) {
case "findById":
result = findById(uri, requestBodyJson);
break;
case "deleteById":
result = deleteById(uri, requestBodyJson);
break;
case "updateById":
result = updateById(uri, requestBodyJson);
break;
case "addGeoInfo":
result = addGeoInfo(uri, requestBodyJson);
break;
}
return result;
}
开发者仅负责实现业务方法内容,剩余部分交给固定代码
- “客户端” 对“接口”进行请求:
“客户端” 首先要知道要请求的目标内容提供者的 authorities,以及 该内容提供者的“id” 即填写 AUTHORITIES 和 PROVIDER_ID 变量 示例:
private static final String AUTHORITIES = "com.example.content_provider_07_server.provider.GeoInfoProvider";
private static final String PROVIDER_ID = "geo";
private static final String BASE_PATH = AUTHORITIES + "/api/"+PROVIDER_ID;
组合后得到 BASE_PATH
其次,需要知道要请求的方法名,以及需要传递的参数。
最后,调用 request(String methodName, Map<String, String> parameter, Object body) , 将参数传入,会得到一个 Result对象作为返回值,该对象就是接口的响应。
示例:Result r1 = request("addGeoInfo", null, geoInfo);
接下来请看代码实现。
3. “服务端示例代码”
public class GeoInfoProvider extends ContentProvider {
private final Gson gson = new Gson();
/**
* 响应结果类
*/
static class Result implements Serializable {
private Integer code;
private String msg;
private Object data;
public Result() {
}
public static Result success(Object data){
return build(200,"成功",data);
}
public static Result success(){
return build(200,"成功",null);
}
public static Result success(String msg){
return build(200,msg,null);
}
public static Result fail(String msg){
return build(400,msg,null);
}
public static Result build(Integer code,String msg,Object data){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
public GeoInfoProvider() {
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
throw new UnsupportedOperationException("Not yet implemented");
}
private static final String AUTHORITIES = "com.example.server.provider.GeoInfoProvider";
private static final String BASE_PATH = AUTHORITIES + "/api/geo";
public GeoInfoDao geoInfoDao(){
return ServerApplication.getServerApplication().getAppDatabase().geoInfoDao();
}
/**
* 本方法相当于SpringMvc 的dispatcherServlet,负责对请求进行转发
* 增删改,都不要,因为他们不能返回额外的数据,只保留 query
* 方法名:
* 此处,不保留请求方式,因为太麻烦,请求方式全部在接口名上进行体现
* path路径中,前两位分别是 api geo,第三位开始是方法名,不允许有第四层
* <p>
* 参数:
* 为了便于解析,不允许使用path参数。只允许使用query参数
* 请求体参数,selectionArgs[0],不支持二进制请求体
* <p>
* 响应:统一以json形式放在Cursor的第一列
*
* @param uri
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//方法名处理
List<String> pathSegments = uri.getPathSegments();
String methodName = pathSegments.get(2);
//请求体参数的处理
String requestBodyJson = "";
if (selectionArgs.length > 0) {
requestBodyJson = selectionArgs[0];
}
//进行接口转发
Result result = adapter(methodName,uri,requestBodyJson);
return obj2Cursor(result);
}
/**
* 接口方法适配转发
* @param methodName
* @param uri
* @param requestBodyJson
* @return
*/
public Result adapter(String methodName,Uri uri,String requestBodyJson){
Result result = null;
switch (methodName) {
case "findById":
result = findById(uri, requestBodyJson);
break;
case "deleteById":
result = deleteById(uri, requestBodyJson);
break;
case "updateById":
result = updateById(uri, requestBodyJson);
break;
case "addGeoInfo":
result = addGeoInfo(uri, requestBodyJson);
break;
}
return result;
}
/**
* 添加一条新记录
* @param uri
* @param requestBodyJson
* @return
*/
private Result addGeoInfo(Uri uri, String requestBodyJson) {
if (TextUtils.isEmpty(requestBodyJson)) {
return Result.fail("参数错误!");
}
GeoInfo geoInfo = gson.fromJson(requestBodyJson, GeoInfo.class);
geoInfo.setId(null);
Long lineNum = geoInfoDao().insertOne(geoInfo);
if (lineNum!=-1){
return Result.success("添加成功!");
}else {
return Result.fail("添加失败!");
}
}
/**
* 修改数据
* @param uri
* @param requestBodyJson
* @return
*/
private Result updateById(Uri uri, String requestBodyJson) {
if (TextUtils.isEmpty(requestBodyJson)) {
return Result.fail("参数错误!");
}
GeoInfo geoInfo = gson.fromJson(requestBodyJson, GeoInfo.class);
if (geoInfo.getId()==null){
return Result.fail("参数错误!");
}
int update = geoInfoDao().update(geoInfo);
return Result.success("修改"+update+"行");
}
/**
* 根据id删除数据
* @param uri
* @param requestBodyJson
* @return
*/
private Result deleteById(Uri uri, String requestBodyJson) {
GeoInfoDao geoInfoDao = geoInfoDao();
String idStr = uri.getQueryParameter("id");
if (TextUtils.isEmpty(idStr)) {
return Result.fail("参数错误!");
}
int i = geoInfoDao.deleteById(Integer.parseInt(idStr));
return Result.success("删除"+i+"行");
}
/**
* 根据id查询数据
*
* @param uri
* @param requestBodyJson
* @return
*/
private Result findById(Uri uri, String requestBodyJson) {
GeoInfoDao geoInfoDao = geoInfoDao();
String idStr = uri.getQueryParameter("id");
if (TextUtils.isEmpty(idStr)) {
return Result.fail("参数错误!");
}
GeoInfo geoInfo = geoInfoDao.findById(Integer.parseInt(idStr));
return Result.success(geoInfo);
}
/**
* 构造一个Cursor,将obj的json放入第一行第一列
*
* @param obj
* @return
*/
public Cursor obj2Cursor(Object obj) {
return json2Cursor(gson.toJson(obj));
}
/**
* 构造一个Cursor,将json数据放入第一行第一列
*
* @param json
* @return
*/
public Cursor json2Cursor(String json) {
MatrixCursor cursor = new MatrixCursor(new String[]{"body"});
cursor.addRow(new Object[]{json});
return cursor;
}
}
4. “客户端”示例代码
private static final String AUTHORITIES = "com.example.server.provider.GeoInfoProvider";
private static final String PROVIDER_ID = "geo";
private static final String BASE_PATH = AUTHORITIES + "/api/"+PROVIDER_ID;
public static final Gson gson = new Gson();
@RequiresApi(api = Build.VERSION_CODES.N)
private void click(View view) {
HashMap<String, String> map = new HashMap<>();
map.put("id",id.getText().toString());
switch (view.getId()) {
case R.id.bt_save:
//添加
GeoInfo geoInfo = new GeoInfo(et_01.getText().toString(),
Float.parseFloat(et_02.getText().toString()),
Float.parseFloat(et_03.getText().toString())
);
Result r1 = request("addGeoInfo", null, geoInfo);
ToastUtil.show(this, r1.getMsg());
break;
case R.id.bt_delete:
//根据id删除
Result r2 = request("deleteById", map, null);
ToastUtil.show(this, r2.getMsg());
break;
case R.id.bt_update:
GeoInfo updateParam = new GeoInfo();
updateParam.setLat(Float.parseFloat(et_02.getText().toString()));
updateParam.setLng(Float.parseFloat(et_03.getText().toString()));
updateParam.setDeviceName(et_01.getText().toString());
updateParam.setId(Integer.valueOf(id.getText().toString()));
Result r3 = request("updateById", null, updateParam);
ToastUtil.show(this, r3.getMsg());
break;
case R.id.bt_search_id:
Result r4 = request("findById", map, null);
GeoInfo info = gson.fromJson(gson.toJson(r4.getData()),GeoInfo.class);
if (r4.getData()==null){
ToastUtil.show(this, "无此记录,"+r4.getMsg());
}else {
ToastUtil.show(this, "查询成功,刷新界面");
et_01.setText(info.getDeviceName());
et_02.setText(String.valueOf(info.getLat()));
et_03.setText(String.valueOf(info.getLng()));
}
break;
}
}
/**
* 向内容提供者发起请求
* @param methodName 方法名
* @param parameter 请求query参数
* @param body 请求体
* @return
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public Result request(String methodName, Map<String, String> parameter, Object body) {
StringBuilder stringBuilder = new StringBuilder("content://");
stringBuilder.append(BASE_PATH).append("/").append(methodName);
if (parameter!=null){
stringBuilder.append("?");
parameter.forEach((s, s2) -> stringBuilder.append(s).append("=").append(s2).append("&"));
}
Cursor cursor = getContentResolver().query(
Uri.parse(stringBuilder.toString()),
null,
null,
new String[]{gson.toJson(body)},
null);
Result result = null;
if (cursor.moveToNext()) {
String responseBodyJson = cursor.getString(0);
result = gson.fromJson(responseBodyJson, Result.class);
}
return result;
}
/**
* 响应结果类
*/
static class Result implements Serializable {
private Integer code;
private String msg;
private Object data;
public Result() {
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}