昨天心血来潮想把自己的项目里的网络请求框架从HttpUrlConnection更换为OkHttp,本来一开始没想着用GSON解析JSON数据的,后来发现GSON解析是真的方便,便在此记录一下
先来看看原来用HttpUrlConnection是怎么写的吧
HttpUrlConnection
new Thread() {
public void run() {
// 准备一个要访问的链接地址
//此处我把自己的网址隐藏了噢,你应该要自己去申请一个网址噢
String site = "***************";
try {
// 转换字符串为URL对象
URL url = new URL(site);
// 获得网络请求对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求模式为GET
conn.setRequestMethod("GET");
// 获得网络请求状态码
int httpCode = conn.getResponseCode();
// 如果正常
if (httpCode == 200) {
// 获得字节输入流
InputStream is = conn.getInputStream();
// 字节流转字符流
InputStreamReader isr = new InputStreamReader(is);
// 套一层缓冲流提高效率(字符输入缓冲流)
BufferedReader br = new BufferedReader(isr);
// 用于拼接服务器返回的字符数据的字符串对象
String data = new String();
// 每次循环读取的buffer
String buf;
// 循环读取
while ((buf = br.readLine()) != null) {
// 拼接结果
data += buf;
}
// 关闭流
br.close();
// 输出接收到的服务器的数据
Log.d("HomeFragment", data);
// 解析JSON数据(一层一层解析)
// 最外层是一个JSONObject对象
JSONObject object = new JSONObject(data);
// 通过“result”键取得对应的值:JSONObject对象
JSONObject result = object.getJSONObject("result");
// 拿到“data”键对应的值:JSONArray(JSONObject数组)
JSONArray dataArray = result.getJSONArray("data");
// 遍历数组
for (int i = 0; i < dataArray.length(); i++) {
//每一条新闻的JSONObject对象
JSONObject jsonObjectNews = dataArray.getJSONObject(i);
//拿到新闻中需要需要的数据
String name = jsonObjectNews.getString("title");
String url2 = jsonObjectNews.getString("url");
String image = jsonObjectNews.getString("thumbnail_pic_s");
//添加到数据源中
News news = new News(name,image);
mNewsList.add(news);
mUrlList.add(url2);
}
//切换到主线程刷新UI
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//主线程代码
newsAdapter.upDateItem(mNewsList);
}
});
} else {
Log.d("MainActivity", "http请求失败,状态码:" + httpCode);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}.start();
其实具体每一步做了什么注释都有写,具体思路还是在这里说一下吧
首先网络请求操作是耗时的,要新开一个线程new Thread,里面的网址我是申请的聚合数据的API进行测试的,如果你也想要就需要自己去注册噢,然后把它给你的网址复制在上面
try块中就是HttpUrlConnection的操作,实例化URL对象,网络请求对象,并且设置请求模式为GET,判断得到的网络状态码是不是200,因为网络请求几个状态码里只有200是可以正常操作的,往下就是java里熟悉的字符流包裹一层缓冲流加快速度,每一次将字符串拼接,老实说这样是非常耗空间的。记得要关闭流噢
再往下就是解析JSON数组,你可以看下图,申请的JSON数组还算是比较复杂的,所以基本步骤就是先获取result,再获取data,然后遍历,遇到我们需要的title,url,imageUrl这三个数据就取出来,放到List里,然后切换到主线程去更新界面,最后抛异常结束,子线程最后不要忘了调用start()方法启动
image.png
我之前的文章记录了学习OkHttp的相关内容,学习之后思考如何更换网络请求框架,因为还不是很熟悉,所有没有封装成框架,所以我们先看代码,再分析思路吧
OkHttp
private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient okHttpClient = new OkHttpClient();
Request.Builder requestBuilder = new Request.Builder().url("http://v.juhe.cn/toutiao/index?type=youxi&page=1&page_size=25&key=2cb4dfb90827deb9158e0a1f4e4f1e45");
requestBuilder.method("GET",null);
Request request = requestBuilder.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
//此方法在主线程中回调,因此不能做耗时操作,要么重开子线程,要么改为同步方法直接接收Response
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseData = response.body().string();
new Thread(new Runnable() {
@Override
public void run() {
parseJSONWIthGson(responseData);
showResponse(responseData);
}
}).start();
}
});
}
}).start();
}
private void parseJSONWIthGson(String jsonData) {
Gson gson = new Gson();
News news = gson.fromJson(jsonData,News.class);
List<News.DataBean> newsDataBean = news.getResult().getData();
for (News.DataBean ndb : newsDataBean) {
mNewsListTitle.add(ndb);
mUrlList.add(ndb.getUrl());
}
}
private void showResponse(final String response) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//主线程代码
newsAdapter.upDateItem(mNewsListTitle);
}
});
}
GSON解析部分我们先不看,我们来分析OkHttp,其实具体写法是和《第一行代码》,《Android进阶之光》差不多的
先写一个方法sendRequestWithOkHttp(),之前说过了网络请求是耗时操作,所以这里也要new Thread,在run()方法里走OkHttp基本三步骤:实例化OkHttpClient,实例化Request,实例化Call。然后调用call的enqueue异步GET请求方法(同步GET请求调用execute()就好啦),在onResponse()方法里进行GSON解析。
问题就出在这里了,如果按这个写法的话,在我的app里首次进入刷新新闻时需要大概五六秒的时间,甚至还没有我之前用HttpUrlConnection快,简单思考之后判断应该是还好当前数据量小,不然大的话就要ANR了,经过Debug后发现在onResponse()方法执行时,居然会回到主线程!就像我注释里写的那样,即使在之前开了线程,即使做了异步GET请求,在onResponse()方法里依然会回到主线程,那么我下面的解析GSON的过程必然是在主线程进行的了,所以会造成如上所说的情况
这里解决方法有两种:
一种是将上面的GET请求变为同步的,确实新开一个线程又异步GET有点多余
另一种是在onResponse()方法里再开一个线程,这样的话需要把responseData这个数据拿出来并且final修饰,因为这种操作不加final的话有可能会引起数据在读取过程中出现各种各样奇奇怪怪的问题,final修饰让其不可变就不会有数据问题了
最后一定记得如果像我一样在异步请求的onResponse()方法里又开一个线程进行网络请求操作的话,最后一定要通过Handler切回主线程,切回主线程有很多方式,我这里使用的是runOnUiThread()方法,需要用getActivity()调用是因为我的首页是在一个Fragment里的,需要获取上下文
聊完了框架更换,我们看GSON解析JSON数据带来了哪些便利吧
JSONBean
public class News implements Serializable {
private String reason;
private Result result;
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public Result getResult() {
return result;
}
public void setResult(Result result) {
this.result = result;
}
public static class Result{
private String stat;
private List<DataBean> data;
public String getStat() {
return stat;
}
public void setStat(String stat) {
this.stat = stat;
}
public List<DataBean> getData() {
return data;
}
public void setData(List<DataBean> data) {
this.data = data;
}
}
public static class DataBean implements Serializable{
private String uniquekey;
private String title;
private String date;
private String category;
private String author_name;
private String url;
private String thumbnail_pic_s;
private String thumbnail_pic_s02;
private String thumbnail_pic_s03;
public String getUniquekey() {
return uniquekey;
}
public void setUniquekey(String uniquekey) {
this.uniquekey = uniquekey;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getAuthor_name() {
return author_name;
}
public void setAuthor_name(String author_name) {
this.author_name = author_name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getThumbnail_pic_s() {
return thumbnail_pic_s;
}
public void setThumbnail_pic_s(String thumbnail_pic_s) {
this.thumbnail_pic_s = thumbnail_pic_s;
}
public String getThumbnail_pic_s02() {
return thumbnail_pic_s02;
}
public void setThumbnail_pic_s02(String thumbnail_pic_s02) {
this.thumbnail_pic_s02 = thumbnail_pic_s02;
}
public String getThumbnail_pic_s03() {
return thumbnail_pic_s03;
}
public void setThumbnail_pic_s03(String thumbnail_pic_s03) {
this.thumbnail_pic_s03 = thumbnail_pic_s03;
}
}
}
我在这里解析的JSON是选择全部解析的,因为以后我还会添加新功能,所以就索性全部解析出来了,这样的话bean代码会有点多,如果你还记得上面那张JSON数据图的话,你就会发现我是一一对应的,只不过创建了两个静态内部类用于存放同级的JSON数据,比如reason,result是同级的。stat,data是同级的,如此。
image.png
GSON
private void parseJSONWIthGson(String jsonData) {
Gson gson = new Gson();
News news = gson.fromJson(jsonData,News.class);
List<News.DataBean> newsDataBean = news.getResult().getData();
for (News.DataBean ndb : newsDataBean) {
mNewsListTitle.add(ndb);
mUrlList.add(ndb.getUrl());
}
}
private void showResponse(final String response) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//主线程代码
newsAdapter.upDateItem(mNewsListTitle);
}
});
}
可以明显看出GSON解析比JSON解析代码要少很多,但是对于我个人来说JSON解析还是挺符合我的逻辑思路的,一层层数据剥开最后取自己需要的数据,可是以后还是要渐渐习惯用GSON来解析数据吧,因为确实很方便,代码很简单,gson自带的方法fromJson()将数据先放到news里,之后使用泛型集合将data数据放入,最后一个增强for循环遍历把每一个DataBean类型的数据放到mNewsListTitle里,在ViewHolder里进行TextView和ImageView的视图绑定操作。把String类型的url单独拿出来用于跳转。但这是我个人项目里的逻辑,服务于我个人的项目。你应该去写自己的逻辑噢
欢迎指正。
网友评论