P1-Android基于MVP实现号码归属地查询

零、前言

[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模式,如有不恰,欢迎指正


本文由张风捷特烈原创,转载请注明 更多安卓技术欢迎访问:https://www.jianshu.com/c/004f3fe34c94 张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com 你的喜欢与支持将是我最大的动力

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Volley解析之表单提交篇

要实现表单的提交,就要知道表单提交的数据格式是怎么样,这里我从某知名网站抓了一条数据,先来分析别人提交表单的数据格式。  数据包: Connection: ...

2295
来自专栏酷玩时刻

Android版-微信APP支付

服务端源码地址:http://git.oschina.net/javen205/weixin_guide 客户端源码地址:https://github.com...

1.1K2
来自专栏醒者呆

算法族的集中管理——策略模式

策略模式是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 我们正好可以使用策略模式加...

29410
来自专栏緣來來來

安卓基础干货(二):安卓测试以及解析

711
来自专栏Android开发指南

3.数据存储

3177
来自专栏cmazxiaoma的架构师之路

Android多线程+单线程+断点续传+进度条显示下载

5493
来自专栏Android先生

RxJava2 实战知识梳理(5) - 简单及进阶的轮询操作

今天,我们介绍一种新的场景,轮询操作。也就是说,我们会尝试间隔一段时间就向服务器发起一次请求,在使用RxJava之前,该需求的实现一般有两种方式:

2342
来自专栏Fish

Android判断网络状况

啊,调bug的时候发现在没有网络的时候程序会崩,因此决定加个网络判断的。就是这个代码啦~然后到了要用的时候,new一个类对象调用这个方法就可以了。 packag...

2309
来自专栏非著名程序员

Android 内存泄露简介、典型情景及检测解决

什么是内存泄露? Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到...

2128
来自专栏difcareer的技术笔记

Android架构分析【三】mvp-cleanClean Architecture代码分析总结

乍一看这张图,就像洋葱,一层包一层,其核心理念就是向内依赖。也就是说:外层依赖内层,内层不能依赖外层,层层向内依赖。越往内,越稳定。在这个核心理念下,定义...

862

扫码关注云+社区

领取腾讯云代金券