开发问题记录随笔之更换网络请求框架由HttpUrlConnection至OkHttp,并由GSON解析JSON数据时如何避免在主线程操作

昨天心血来潮想把自己的项目里的网络请求框架从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单独拿出来用于跳转。但这是我个人项目里的逻辑,服务于我个人的项目。你应该去写自己的逻辑噢


欢迎指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容