开发者社区> 张风捷特烈> 正文

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

简介: 零、前言 [1].最近在总结了安卓的知识点,现在来穿插一个小项目,算是对前面的一个小小总结 [2].本案例是以前在慕课网的学习的一个免费课程,代码看不懂的可以去看看:号码归属地查询实用工具 [3].相比与视频课程,本案例按照我的理解,对项目结构进行了优化。
+关注继续查看

零、前言

[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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
安卓SO层开发 -- 编译指定平台的SO文件
安卓SO层开发 -- 编译指定平台的SO文件
33 0
安卓SO层开发 -- 第一个NDK项目
安卓SO层开发 -- 第一个NDK项目
32 0
Android组件化开发(七)--从零开始教你分析项目需求并实现
前面几篇文章我们封装了几个组件化功能组件:包括:**网络请求组件,图片加载请求组件,应用保活组件,音乐播放组件封装。** > 每个组件都可以直接拿到自己项目中使用,当然还需根据自己项目要求进行优化。
29 0
Android组件化开发(六)-- 短视频播放组件封装
前面几篇文章我们封装了几个组件化功能组件: 包括:`网络请求组件`,`图片加载请求组件`,`应用保活组件`,`音乐播放组件封装`。 > 每个组件都可以直接拿到自己项目中使用,当然还需根据自己项目要求进行优化。
135 0
Android组件化开发(五)--完整版音乐播放组件的封装
前面几篇系列文章我们讲解了`组件化开发`中几个常用功能组件的开发,包括:`网络请求组件`,`图片加载请求组件`,`应用保活组件`。今天我们来封装一个`音乐播放组件`。
31 0
Android组件化开发(四)--进程保活组件的封装
前面文章我们封装了网络请求组件`lib_nework`和图片加载组件`lib_image_loader`,今天我们来封装一个进程保活的组件`lib_pull_alive`
43 0
Android组件化开发(三)--图片加载组件封装
今天我们来封装一个`图片加载库`:`lib_image_loader`
42 0
Android组件化开发(二)--网络请求组件封装
前面一篇文章我们讲解了`maven私服`的搭建,maven私服在`组件化框架`中有一个很重要的地位就是可以将我们的`lib`库放到局域网中,供公司其他开发者使用,实现类库的分享。 下面是这个系列准备实现的一个`组件化实战项目框架`:
43 0
Android组件化开发(一)--Maven私服的搭建
今天我们先不讲解项目框架,我们先来创建一个maven私服,来为我们的组件化框架项目先铺一条路子。
48 0
Android 使用Linphone SDK开发SIP客户端
Android 使用Linphone SDK开发SIP客户端
79 0
+关注
张风捷特烈
安卓工程师,全站工程师
文章
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载