android学习笔记----手机号码查询归属地
demo中遇到的问题直接跳转见这里:问题总结
学习目标:实现归属地查询,通过OkHttp网络框架请求手机号数据,并能掌握流行的MVP设计模式以及如何使用目前主流的Json和Gson解析框架。
难度点:在gson解析时,遇到了key会动态变化的json数据,如何处理花费了很长时间。
利用淘宝接口只能显示到省份,不能具体到城市,json数据简单:
淘宝接口:
https://tcc.taobao.com/cc/json/mobile_tel_segment.htm
利用淘宝接口的demo:https://github.com/liuchenyang0515/SearchPhone
利用百度接口可以显示到具体的城市,就是json数据嵌套多
百度接口:
http://mobsec-dianhua.baidu.com/dianhua_api/open/location
利用百度接口的demo:https://github.com/liuchenyang0515/SearchPhone1
有人经过多方接口对比,据说百度接口更加准确,我们就采用百度接口。
在以上URL后面加上手机号即可,比如?tel=159xxxxxxxx
自己新建一个project,关掉android studio,再把app目录替换到自己的app目录,再打开即可。
mvp设计思路:
关于mvc和mvp更加详细的讲解:http://kaedea.com/2015/10/11/android-mvp-pattern/
接下来以百度接口讲解:
返回的json数据如下:
大致的demo目录如下:
activity充当view,presenter处理业务逻辑,p和v采用接口交互,大致上算一个mvp框架模式。
运行结果如图:
代码如下:(或者更直观的去看demo)
MainActivity.java
package com.example.searchphone;
import android.app.ProgressDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.searchphone.model.GsonParsePhone;
import com.example.searchphone.model.JsonparsePhone;
import com.example.searchphone.mvp.MvpMainView;
import com.example.searchphone.mvp.impl.MainPresenter;
public class MainActivity extends AppCompatActivity implements MvpMainView {
EditText input_phone;
Button btn_search;
TextView result_phone;
TextView result_province;
TextView result_type;
TextView result_carrier;
MainPresenter mainPresenter;
ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input_phone = findViewById(R.id.input_phone);
btn_search = findViewById(R.id.btn_search);
result_phone = findViewById(R.id.result_phone);
result_province = findViewById(R.id.result_province);
result_type = findViewById(R.id.result_type);
result_carrier = findViewById(R.id.result_carrier);
btn_search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mainPresenter.searchPhoneInfo(input_phone.getText().toString());
}
});
mainPresenter = new MainPresenter(this);
//mainPresenter.attach(this);
}
// mvpMainView接口的方法
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
/* // 用JSONObject解析时用这个
@Override
public void updateView() {
JsonparsePhone phone = mainPresenter.getPhoneInfo();
result_phone.setText("手机号:" + phone.getTelString());
result_province.setText("省份:" + phone.getProvince());
result_type.setText("城市:" + phone.getCity());
result_carrier.setText("运营商:" + phone.getOperator());
}*/
// 用Gson解析时用这个
@Override
public void updateView() {
GsonParsePhone phone = mainPresenter.getPhoneInfo1();
result_phone.setText("手机号:" + input_phone.getText().toString()); // 手机号码直接在EditView获取就行
result_province.setText("省份:" + phone.getResponse().getPhoneNumber().getDetail().getProvince());
result_type.setText("城市:" + phone.getResponse().getPhoneNumber().getDetail().getArea().get(0).getCity());
result_carrier.setText("运营商:" + phone.getResponse().getPhoneNumber().getDetail().getOperator());
}
@Override
public void showLoading() {
if (progressDialog == null) {
progressDialog = ProgressDialog.show(this, "", "正在加载...", true, false);
} else if (progressDialog.isShowing()) {
progressDialog.setTitle("");
progressDialog.setMessage("正在加载...");
}
progressDialog.show();
}
@Override
public void hidenLoading() {
if (progressDialog != null && progressDialog.isShowing()){
progressDialog.dismiss();
}
}
}
MvpMainView.java
package com.example.searchphone.mvp;
public interface MvpMainView extends MvpLoadingView{
void showToast(String msg);
void updateView();
}
MvpLoadingView.java
package com.example.searchphone.mvp;
public interface MvpLoadingView {
void showLoading();
void hidenLoading();
}
MainPresenter.java
package com.example.searchphone.mvp.impl;
import android.util.Log;
import com.example.searchphone.business.HttpUtils;
import com.example.searchphone.model.JsonparsePhone;
import com.example.searchphone.model.GsonParsePhone;
import com.example.searchphone.mvp.MvpMainView;
import com.google.gson.Gson;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class MainPresenter extends BasePresenter {
//String mUrl = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm";
String mUrl = "http://mobsec-dianhua.baidu.com/dianhua_api/open/location";
MvpMainView mvpMainView;
JsonparsePhone mPhone;
GsonParsePhone mPhone1;
String phoneNum;
private static final String TAG = "MainPresenter";
public JsonparsePhone getPhoneInfo() {
return mPhone;
}
public GsonParsePhone getPhoneInfo1() {
return mPhone1;
}
public MainPresenter(MvpMainView mainView) {
mvpMainView = mainView;
}
public void searchPhoneInfo(String phone) {
if (phone.length() != 11) {
mvpMainView.showToast("请输入正确的手机号");
return;
}
phoneNum = phone;
mvpMainView.showLoading();
// 写上http请求处理逻辑
sendHttp(phone);
}
private void sendHttp(String phone) {
Map<String, String> map = new HashMap<String, String>();
map.put("tel", phone);
HttpUtils httpUtils = new HttpUtils(new HttpUtils.HttpResponse() {
@Override
public void onSuccess(Object object) {
String json = object.toString();
// 使用Gson时需要下面2句,使用JSONObject时注释下面2句
StringBuilder str = new StringBuilder(json);
json = str.substring(0, 14) + "phoneNumber" + str.substring(25);
// 使用JSONObject
//mPhone = parseModelWithOrgJson(json);
// Gson
mPhone1 = parseModelWithGson(json);
// FastJson
// mPhone = parseModelWithFastJson(json);
mvpMainView.hidenLoading();
mvpMainView.updateView();
}
@Override
public void onFail(String error) {
mvpMainView.showToast(error);
mvpMainView.hidenLoading();
}
});
httpUtils.sendGetHttp(mUrl, map);
}
// 手动练习json解析,根据自己选择需要来修改,这里写的是未修改json的手机号
// 还是动态手机号解析
private JsonparsePhone parseModelWithOrgJson(String json) {
JsonparsePhone phone = new JsonparsePhone();
Log.d(TAG, "=====================" + json);
try {
JSONObject jsonObject = new JSONObject(json);
JSONObject response = jsonObject.getJSONObject("response");
// Log.d(TAG, "response=====================" + response);
JSONObject num = response.getJSONObject(phoneNum);// 外面传进来的手机号,json可以一步步处理动态key
phone.setTelString(phoneNum);
// Log.d(TAG, "num======================" + num);
JSONObject detail = num.getJSONObject("detail");
String province = detail.getString("province");
phone.setProvince(province);
String operator = detail.getString("operator");
phone.setOperator(operator);
JSONArray areaArr = detail.getJSONArray("area");
JSONObject obj = areaArr.getJSONObject(0);
String city = obj.getString("city");
phone.setCity(city);
} catch (JSONException e) {
e.printStackTrace();
}
return phone;
}
private GsonParsePhone parseModelWithGson(String json) {
Gson gson = new Gson();
GsonParsePhone phone = gson.fromJson(json, GsonParsePhone.class);
return phone;
}
private JsonparsePhone parseModelWithFastJson(String json) {
JsonparsePhone phone = com.alibaba.fastjson.JSONObject.parseObject(json, JsonparsePhone.class);
return phone;
}
}
BasePresenter.java
package com.example.searchphone.mvp.impl;
import android.content.Context;
public class BasePresenter {
Context mContext;
public void attach(Context context) {
mContext = context;
}
public void onPause() {
}
public void onResume() {
}
public void Destroy() {
mContext = null;
}
}
JsonparsePhone.java
package com.example.searchphone.model;
public class JsonparsePhone {
String telString;
String province;
String city;
String operator;
public String getTelString() {
return telString;
}
public void setTelString(String telString) {
this.telString = telString;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
}
GsonparsePhone.java
package com.example.searchphone.model;
import java.util.List;
public class GsonParsePhone {
/**
* response : {"phoneNumber":{"detail":{"area":[{"city":"武汉"}],"province":"湖北","type":"domestic","operator":"移动"},"location":"湖北武汉移动"}}
* responseHeader : {"status":200,"time":1533717512727,"version":"1.1.0"}
*/
private ResponseBean response;
private ResponseHeaderBean responseHeader;
public ResponseBean getResponse() {
return response;
}
public void setResponse(ResponseBean response) {
this.response = response;
}
public ResponseHeaderBean getResponseHeader() {
return responseHeader;
}
public void setResponseHeader(ResponseHeaderBean responseHeader) {
this.responseHeader = responseHeader;
}
public static class ResponseBean {
/**
* phoneNumber : {"detail":{"area":[{"city":"武汉"}],"province":"湖北","type":"domestic","operator":"移动"},"location":"湖北武汉移动"}
*/
private PhoneNumberBean phoneNumber;
public PhoneNumberBean getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(PhoneNumberBean phoneNumber) {
this.phoneNumber = phoneNumber;
}
public static class PhoneNumberBean {
/**
* detail : {"area":[{"city":"武汉"}],"province":"湖北","type":"domestic","operator":"移动"}
* location : 湖北武汉移动
*/
private DetailBean detail;
private String location;
public DetailBean getDetail() {
return detail;
}
public void setDetail(DetailBean detail) {
this.detail = detail;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public static class DetailBean {
/**
* area : [{"city":"武汉"}]
* province : 湖北
* type : domestic
* operator : 移动
*/
private String province;
private String type;
private String operator;
private List<AreaBean> area;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public List<AreaBean> getArea() {
return area;
}
public void setArea(List<AreaBean> area) {
this.area = area;
}
public static class AreaBean {
/**
* city : 武汉
*/
private String city;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
}
}
}
public static class ResponseHeaderBean {
/**
* status : 200
* time : 1533717512727
* version : 1.1.0
*/
private int status;
private long time;
private String version;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
}
HttpUtils.java
package com.example.searchphone.business;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class HttpUtils {
private static final String TAG = "HttpUtils";
String mUrl;
Map<String, String> mParam;
HttpResponse mHttpResponse;
Handler myhandler = new Handler(Looper.getMainLooper());
private final OkHttpClient client = new OkHttpClient();
public interface HttpResponse {
void onSuccess(Object object);
void onFail(String error);
}
public HttpUtils(HttpResponse response) {
mHttpResponse = response;
}
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;
// 编写http请求逻辑
run(isPost);
}
private void run(boolean isPost) {
// request请求创建
final Request request = createRequest(isPost);
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (mHttpResponse != null) {
myhandler.post(new Runnable() {
@Override
public void run() {
mHttpResponse.onFail("请求错误");
}
});
}
}
@Override
public void onResponse(Call call, final Response response) {
if (mHttpResponse == null) return;
myhandler.post(new Runnable() {
@Override
public void run() {
if (!response.isSuccessful()) {
mHttpResponse.onFail("请求失败:code" + response);
} else {
try {
String s = response.body().string(); // response.body().string();使用一次流就关闭了
mHttpResponse.onSuccess(s);
} catch (IOException e) {
e.printStackTrace();
mHttpResponse.onFail("结果转换失败");
}
}
}
});
}
});
}
private Request createRequest(boolean isPost) {
Request request;
if (isPost) {
MultipartBody.Builder requestBodyBuilder = new MultipartBody.Builder();
requestBodyBuilder.setType(MultipartBody.FORM);
Iterator<Map.Entry<String, String>> iterator = mParam.entrySet().iterator();
// 遍历map请求参数
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
requestBodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
}
request = new okhttp3.Request.Builder().url(mUrl)
.post(requestBodyBuilder.build()).build();
} else {
String urlStr = mUrl + "?" + MapParamToString(mParam);
request = new Request.Builder().url(urlStr).build();
}
return request;
}
private String MapParamToString(Map<String, String> param) {
StringBuilder stringBuilder = new StringBuilder();
Iterator<Map.Entry<String, String>> iterator = param.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
stringBuilder.append(entry.getKey() + "=" + entry.getValue() + "&");
}
String str = stringBuilder.toString();
return str;
}
}
问题总结:
写的过程中遇到了一些问题,比如想让gson直接转换这种多层嵌套并且key会动态变化的json数据成java bean,开始想利用gsonformat插件生成java bean,但是还是有问题,像用gson解析key(手机号)是动态变化的value,试了半天做不到,想利用反射修改static final String a = "phoneNum"...@SerializedName(a) 注释的值,因为这个注释可以强制修改字段的值,能将每次输入的手机号转换成相同的key去解析,可惜失败了,虽然强制修改了static final修饰的a的值,再把这个值的引用传给@SerializedName(a) 做参数,很遗憾,编译器已经把这里的参数优化成了常量,比如static final a = "qqq";那么编译器已经优化成了@SerializedName("qqq"),哪怕强制修改了a为其他值,这个这个注释了值还是没变,达不到想要的效果,但是阻止编译器优化之后,又会提醒这个属性值必须是常量。所以这种方法办不到。以下是我参考的网址,虽然失败了,但还是学到了新东西,因为我把这些知识都试了一次,知识点熟悉了一遍,但是这些知识在这个问题上行不通。
所以真的没办法了吗?其实是有的,在传回json数据的时候,也没规定我们不能修改啊,只要显示给别人看的时候是正确的就行了,自己修改自己解析是没有问题的。
修改如下:
// 使用Gson时需要下面2句,使用JSONObject时注释下面2句
StringBuilder str = new StringBuilder(json);
json = str.substring(0, 14) + "phoneNumber" + str.substring(25);
因为json数据是这样的:
{"response":{"12345678910":{"detail":{"area":[{"city":"武汉"}],"province":"湖北","type":"domestic","operator":"移动"},"location":"湖北武汉移动"}},"responseHeader":{"status":200,"time":1533717512727,"version":"1.1.0"}}
修改后是这样的:
{"response":{"phoneNumber":{"detail":{"area":[{"city":"武汉"}],"province":"湖北","type":"domestic","operator":"移动"},"location":"湖北武汉移动"}},"responseHeader":{"status":200,"time":1533717512727,"version":"1.1.0"}}
只要将手机号码这个部分替换成其他字符串即可,就不用去硬生生的解决key会动态变化的json数据。然后利用gsonformat生成java bean,最后直接利用gson解析即可。
如何下载gsonformat:
最后创建一个jave bean类,右键generate->GsonFormat,勾上就行
操作的参考网址:https://www.cnblogs.com/bdsdkrb/p/7204912.html
在遇到的问题中,有过这样的学习经历,如下网址:
如何阻止编译器优化static final修饰的值为常量,内容参见:https://www.cnblogs.com/damonhuang/p/5421563.html
Java反射-修改字段值, 反射修改static final修饰的字段:http://www.cnblogs.com/noKing/p/9038234.html
论 f.getModifiers() & Modifier.FINAL &Modifier.STATIC 如何成立:https://blog.csdn.net/vi__iv/article/details/47294243
Java反射之如何判断类或变量、方法的修饰符(Modifier解析):https://blog.csdn.net/xiao__gui/article/details/8141216
===============================Talk is cheap, show me the code=============================