零、前言
[1].最近在总结了安卓的知识点,现在来穿插一个小项目,算是对前面的一个小小总结
[2].本案例是以前在慕课网的学习的一个免费课程,代码看不懂的可以去看看:号码归属地查询实用工具
[3].相比与视频课程,本案例按照我的理解,对项目结构进行了优化。
[4].案例网络模块是自己对okhttp3的封装,当然你也完全可以使用其他的上层网络框架
[5].相比课程,加入缓存机制,使用前面写过的缓存封装:详见:缓存文件封装
[6].使用前面的listview封装类:详见:ListView的封装
[7].Json的解析详见:Json在Android中的使用--Gson
项目结构及类图
号码归属地查询MVP类图.png
号码归属地查询.gif
手机归属地查询缓存.png
一、M:模型层:负责数据的请求、解析、过滤等数据层操作
[1].将网络加载数据逻辑放在模型层,提供数据给控制层。
[2].模型层中设置加载监听用来传递数据,及状态(利于控制层对视图层控制)
1.模型层接口--定义模型的方法接口
/**
* 作者:张风捷特烈<br/>
* 时间:2018/8/31 0031:13:32<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:模型层接口
*/
public interface ICallModel {
/**
* 获取数据
*
* @param ctx 上下文
* @param phoneNum 电话号码
*/
void getData(Context ctx, String phoneNum);
}
2.模型层接口实现类:负责数据的获取
/**
* 作者:张风捷特烈<br/>
* 时间:2018/8/31 0031:13:34<br/>
* 邮箱:1981462002@qq.com<br/>
* 说明:模型层,提供数据
*/
public class CallModelImpl implements ICallModel {
private String mUrl = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm";
/**
* 获取Json
*
* @param phoneNum 电话
*/
public void getData(Context ctx, String phoneNum) {
//获取网络数据}
HashMap<String, String> param = new HashMap<>();
param.put("tel", phoneNum);
new GetDataWithCache(mUrl, param, new InnerFileStrategy(ctx)) {
@Override
public void success(String response) {
String[] split = response.split("=");
Phone phone = new Gson().fromJson(split[1].trim(), Phone.class);
if (mOnLoadListener != null) {
mOnLoadListener.success(phone);
}
}
@Override
public void error(String error) {
if (mOnLoadListener != null) {
mOnLoadListener.error(error);
}
}
};
}
///////////////设置加载监听,传递数据,及状态(利于控制层对视图层控制)////////////////
public interface OnLoadListener {
/**
* 成功监听
*
* @param phone 解析后得到的实体
*/
void success(Phone phone);
/**
* 失败监听
*
* @param str 失败信息
*/
void error(String str);
}
private OnLoadListener mOnLoadListener;
public void setOnLoadListener(OnLoadListener onLoadListener) {
mOnLoadListener = onLoadListener;
}
}
3.实体类:Phone
public class Phone {
/**
* mts : 1871507
* province : 安徽
* catName : 中国移动
* telString : 18715079389
* areaVid : 30509
* ispVid : 3236139
* carrier : 安徽移动
*/
private String mts;
private String province;
private String catName;
private String telString;
private String areaVid;
private String ispVid;
private String carrier;
//get set 方法省略......
二、控制层:View和Model沟通的桥梁
1.基类BasePresenter:为了方便让控制层和Activity生命周期同步
/**
* 作者:张风捷特烈
* 时间:2018/4/25:9:40
* 邮箱:1981462002@qq.com
* 说明:Presenter基类--为了方便让控制层和Activity生命周期同步
*/
public class BasePresenter {
private Context mCtx;
public void attach(Context ctx) {
mCtx = ctx;
}
public void onPause() {
}
public void onResume() {
}
public void onDestroy() {
mCtx = null;
}
public Context getCtx() {
return mCtx;
}
}
2.控制层实现类:CallPresenterImpl
/**
* 作者:张风捷特烈
* 时间:2018/4/25:9:38
* 邮箱:1981462002@qq.com
* 说明:控制层实现类
*/
public class CallPresenterImpl extends BasePresenter {
public ICallView mICallView;
private Phone mPhone;
private final CallModelImpl mCallModel;
Handler handler = new Handler();
public CallPresenterImpl(ICallView iCallView) {
mICallView = iCallView;
mCallModel = new CallModelImpl();
}
/**
* 从网络获取数据
*
* @param phoneNum 手机号
*/
public void getDataFromInternet(final String phoneNum) {
if (!checkPhoneNum(phoneNum)) {
//号码错误让视图层处理
mICallView.numError();
return;
}
//校验正确--视图层:显示进度条
mICallView.showLoading();
//耗时操作新开线程
new Thread() {
@Override
public void run() {
handler.post(
new Thread(new Runnable() {
@Override
public void run() {
getDataFromModel(phoneNum);
}
}));
}
}.start();
}
/**
* 从模型层获取数据,并根据监听设置视图层
* @param phoneNum 号码
*/
private void getDataFromModel(String phoneNum) {
mCallModel.getData(getCtx(), phoneNum);
mCallModel.setOnLoadListener(new CallModelImpl.OnLoadListener() {
@Override
public void success(Phone phone) {
//接收模型层数据
mPhone = phone;
//网络加载、解析数据完成--视图层:隐藏进度条
mICallView.hideLoading();
//视图层--更新视图
mICallView.updateView();
}
@Override
public void error(String str) {
//网络加载失败--视图层:隐藏进度条
mICallView.hideLoading();
mICallView.loadDataError();
}
});
}
public Phone getPhone() {
return mPhone;
}
/**
* 检查手机号是否合法
*
* @param phone 手机号
* @return 是否合法
*/
public boolean checkPhoneNum(String phone) {
Pattern pattern = Pattern.compile("^[1][3,4,5,8][0-9]{9}$"); // 验证手机号
Matcher matcher = pattern.matcher(phone);
return matcher.matches();
}
}
三、视图层 V:负责视图部分展示、视图事件处理
1.显示和消失对话框接口--由于非常常见所以单独抽出
/**
* 作者:张风捷特烈
* 时间:2018/4/25:9:37
* 邮箱:1981462002@qq.com
* 说明:显示和消失对话框接口--由于非常常见所以单独抽出
*/
public interface ILoadingView {
/**
* 显示进度条
*/
void showLoading();
/**
* 隐藏进度条
*/
void hideLoading();
}
2.视图层接口
/**
* 作者:张风捷特烈
* 时间:2018/4/25:9:31
* 邮箱:1981462002@qq.com
* 说明:视图层接口
*/
public interface ICallView extends ILoadingView {
/**
* 更新视图
*/
void updateView();
/**
* 号码错误视图操作
*/
void numError();
/**
* 网络获取数据异常视图操作
*/
void loadDataError();
}
3.视图层基类--已封装进度条显隐逻辑
/**
* 作者:张风捷特烈
* 时间:2018/4/25:12:52
* 邮箱:1981462002@qq.com
* 说明:视图层基类--已封装进度条显隐逻辑
*/
public abstract class BaseICallActivity extends AppCompatActivity implements ICallView {
private ProgressDialog mProgressDialog;
/**
* 更新视图
*/
public abstract void updateView();
@Override
public void showLoading() {
if (mProgressDialog == null) {
mProgressDialog = ProgressDialog.show(this, "",
"Loading...", true, false);
} else if (mProgressDialog.isShowing()) {
mProgressDialog.setTitle("");
mProgressDialog.setMessage("Loading...");
}
mProgressDialog.show();
}
@Override
public void hideLoading() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
@Override
public void onBackPressed() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
super.onBackPressed();
}
}
4.视图层核心:CallPhoneActivity
/**
* 作者:张风捷特烈
* 时间:2018/4/25:12:52
* 邮箱:1981462002@qq.com
* 说明:视图层核心
*/
public class CallPhoneActivity extends BaseICallActivity {
@BindView(R.id.et_phone)
EditText mEtPhone;
@BindView(R.id.btn_search)
Button mBtnSearch;
@BindView(R.id.textView)
TextView mTextView;
@BindView(R.id.lv_info)
ListView mLvInfo;
private CallPresenterImpl mCallPresenterImpl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call);
ButterKnife.bind(this);
//视图层持有控制层对象
mCallPresenterImpl = new CallPresenterImpl(this);
mCallPresenterImpl.attach(this);
}
@OnClick(R.id.btn_search)
public void onViewClicked() {
//控制层对象执行逻辑,将视图层数据传入控制层
mCallPresenterImpl.getDataFromInternet(mEtPhone.getText().toString());
}
@Override
public void updateView() {
Phone phone = mCallPresenterImpl.getPhone();
ArrayList<String> infos = new ArrayList<>();
infos.add("手机号:" + phone.getTelString());
infos.add("运营商:" + phone.getCatName());
infos.add("省份:" + phone.getProvince());
infos.add("详情信息:" + phone.getCarrier());
mLvInfo.setAdapter(new MyLVAdapter<String>(this, infos, R.layout.simple_item_tv1) {
@Override
public void setData(MyLVHolder holder, String data, int position) {
holder.setText(R.id.tv_item1, data);
}
});
}
@Override
public void numError() {
mTextView.setError("");
ToastUtil.show(this, "输入正确的手机号");
}
@Override
public void loadDataError() {
ToastUtil.show(this, "无网络,无缓存");
}
@Override
protected void onDestroy() {
super.onDestroy();
mCallPresenterImpl.onDestroy();
}
}
网络封装
public class ZHttp {
private String mUrl;
private Map<String, String> mParam;
private HttpResponse mHttpResponse;
private OkHttpClient mHttpClient = new OkHttpClient();
private Handler mHandler = new Handler(Looper.getMainLooper());
public interface HttpResponse {
void onSuccess(String result);
void onFail(String error);
}
public ZHttp(HttpResponse httpResponse) {
mHttpResponse = httpResponse;
}
public void sendPostHttp(String url, Map<String, String> param) {
sendHttp(url, param, true);
}
public void sendGetHttp(String url, Map<String, String> param) {
sendHttp(url, param, false);
}
private void sendHttp(String url, Map<String, String> param, boolean isPost) {
mUrl = url;
mParam = param;
//获取网络数据
getData(isPost);
}
/**
* 获取网络数据
*
* @param isPost
*/
private void getData(boolean isPost) {
//创建request请求
final Request request = createRequest(isPost);
mHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (mHttpResponse != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
mHttpResponse.onFail("请求错误");
}
});
}
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
if (mHttpResponse == null) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
if (!response.isSuccessful()) {
mHttpResponse.onFail("请求失败:code" + response);
} else {
try {
mHttpResponse.onSuccess(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
});
}
/**
* @param isPost
* @return
*/
private Request createRequest(boolean isPost) {
Request request;
if (isPost) {
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
//遍历map请求参数
Iterator<Map.Entry<String, String>> iterator = mParam.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
builder.addFormDataPart(entry.getKey(), entry.getValue());
}
request = new Request.Builder().url(mUrl).post(builder.build()).build();
} else {
String urlStr = mUrl + "?" + MapParamToString(mParam);
request = new Request.Builder().url(urlStr).build();
}
return request;
}
/**
* @param param
* @return
*/
private String MapParamToString(Map<String, String> param) {
StringBuilder sb = new StringBuilder();
Iterator<Map.Entry<String, String>> iterator = mParam.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
sb.append(entry.getKey() + "=" + entry.getValue() + "&");
}
String str = sb.toString().substring(0, sb.length() - 1);
return str;
}
}
网络封装,连接网络获取数据,并写缓存
/**
* 作者:张风捷特烈
* 时间:2018/3/29:10:21
* 邮箱:1981462002@qq.com
* 说明:网络封装,连接网络获取数据,并写缓存
*/
public abstract class GetDataWithCache {
private CacheStrategy mCacheStrategy;
private String mUrl;
private Map<String, String> mParam;
public GetDataWithCache(String url, Map<String, String> param, CacheStrategy cacheStrategy) {
mCacheStrategy = cacheStrategy;
mUrl = url;
mParam = param;
String cache = new CacheWorker(mCacheStrategy).getCache(mUrl);//读缓存
if (!TextUtils.isEmpty(cache)) {//如果有缓存
success(cache);//解析数据抽象方法
}
getDataFromService();//连接网络获取数据,并写缓存
}
/**
* 从网络获取数据
*/
private void getDataFromService() {
new ZHttp(new ZHttp.HttpResponse() {
@Override
public void onSuccess(String response) {
if (response != null) {
success(response);
}
mCacheStrategy.setCache(mUrl, response, 10);//写缓存
}
@Override
public void onFail(String error) {
if (mCacheStrategy.getCache(mUrl) != null) {
success(mCacheStrategy.getCache(mUrl));
} else {
error(error);
}
}
}).sendGetHttp(mUrl, mParam);
}
public abstract void success(String response);
public abstract void error(String error);
}
这是我第一次接触MVP模式,如有不恰,欢迎指正
后记、
1.声明:
[1]本文由张风捷特烈原创,转载请注明
[2]欢迎广大编程爱好者共同交流
[3]个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
[4]你的喜欢与支持将是我最大的动力
2.连接传送门:
更多安卓技术欢迎访问:安卓技术栈
我的github地址:欢迎star
简书首发,腾讯云+社区同步更新
张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com
3.联系我
QQ:1981462002
邮箱:1981462002@qq.com
微信:zdl1994328
4.欢迎关注我的微信公众号,最新精彩文章,及时送达:
公众号.jpg