本文谈谈博主在实际项目开发的过程中,对代码组织的一种方式。
问题的提出
Android系统本身已经给了我们一个MVC。
其中model是业务逻辑,应该是纯Java实现,与平台无关。view一般指xml和自定义view。但是最后的controller应该归于activity,可是写过一段时间代码后我们就会发现,activity承担的责任很多,既要负责网络请求、缓存处理、数据显示,又要处理来自view的事件,代码杂乱不堪,大量的代码积累在activity里面,无论是易读性还是可维护性都大打折扣。
想到这里,controller是不是可以继续分层呢?
IO层和UI层
博主经过一番思考以及比较后,将原本的controller分成了UI层和IO层,IO层负责网络请求,UI层负责数据展示,两者通过接口回调实现数据传递。
这样做的好处是不言自明的。首先,activity的代码行数可以大幅度减少,代码变得整齐有序。其次,网络请求分出来后实现了数据展示的解耦,事实上,UI层可以完全不管IO层是怎么实现的,符合面向对象的开放-封闭原则。博主在开发中使用xUtils作为网络请求库,UI/IO解耦后,IO层换一种网络请求库对UI层没有任何影响。
工程结构
代码实现
Talking is cheap, show me the code.
接下来以一个显示用户信息的页面为例,说明IO/UI具体是怎么实现的。
IOCallback接口定义了网络请求不同阶段和结果处理的方法,其中重载的onSuccess是为了区分返回的List数据和Object数据,使用泛型提高可扩展性,也可以使用Map类型。
IOHandler作为一个handler(处理器,不要理解为Android传递信息的Handler类)父类,在构造函数中初始化ACache,ACache是一个开源库,用来实现缓存。
UserHandler继承自IOHandler,实现具体的handle功能,此处是实现userDetail的处理。
SettingActivity属于UI层,调用UserHandler获取userDetail。其中的getUserDetail方法里面完成调用UserHandler的getUserDetail方法,同时实现IOCallback接口,在回调里拿到数据并显示。
说了那么多,最后上代码吧。
IOCallback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| /** * 实现获取数据的回调接口,包括从缓存获取和从服务器获取 * @link IOHandler * @param <T> */ public interface IOCallback<T> { /** * 开始获取数据 */ void onStart();
/** * 成功获取数据,数据为list形式 * @param result */ void onSuccess(List<T> result);
/** * 成功获取数据,数据为object形式 * @param result */ void onSuccess(T result) throws UnsupportedEncodingException;
/** * 获取数据失败,一般为网络错误 * @param error */ void onFailure(String error); }
|
IOHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| /** * IOHandler用于处理数据的获取,在内部调用IOCallback将数据返回给UI层 */ public class IOHandler { /** * Acache对象 */ protected ACache cache;
/** * 构造函数,初始化Acache * @param context */ public IOHandler(Context context){ this.cache = ACache.get(context); } }
|
具体的UserHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class UserHandler extends IOHandler{
public UserHandler(Context context) { super(context); }
public void getUserDetail(RequestParams params, IOCallback ioCallback) { final IOCallback callback = ioCallback; String userDetailJson = cache.getAsString(CacheConfig.Key.USER_DETAIL); //优先从缓存中读取 if(userDetailJson!=null){ try { callback.onSuccess(GsonUtil.getMap(userDetailJson)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }
HttpUtils http = new HttpUtils(); http.send(HttpRequest.HttpMethod.GET, Api.Users.getUserDetail, params, new RequestCallBack<String>() { @Override public void onStart() { callback.onStart(); }
@Override public void onSuccess(ResponseInfo<String> responseInfo) { Map<String, Object> map = GsonUtil.getMap(responseInfo.result); if(!map.containsKey("code")){ cache.put(CacheConfig.Key.USER_DETAIL,responseInfo.result, CacheConfig.Time.USER_DETAIL); } try { callback.onSuccess(map); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }
@Override public void onFailure(HttpException e, String s) { callback.onFailure(s); } }); } }
|
最后是UI层SettingActivity的调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class SettingActivity extends BaseActivity { private LoadingDialog dialog; private UserHandler userHandler;
private TextView unameText; private TextView accountText;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); unameText = (TextView) findViewById(R.id.unameText); accountText = (TextView) findViewById(R.id.accountText); getUserDetail(); }
private void getUserDetail(){ RequestParams params = new RequestParams(); params.addQueryStringParameter("token", Constants.token); userHandler.getUserDetail(params, new IOCallback<Map<String, Object>>() { @Override public void onStart() {
}
@Override public void onSuccess(List<Map<String, Object>> result) {
}
@Override public void onSuccess(Map<String, Object> result) { if (result.containsKey("code")) { Toast.makeText(SettingActivity.this, "getUserDetail error", Toast.LENGTH_SHORT).show(); } else { String username = result.get("username").toString(); String remark = result.get("remark").toString(); unameText.setText(username); accountText.setText(remark); } }
@Override public void onFailure(String error) { Toast.makeText(SettingActivity.this, "Internet error", Toast.LENGTH_SHORT).show(); } }); } }
|