okHttp用于android的http请求。据说很厉害,我们来一起尝尝鲜。但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来。

首先需要了解一点,这里说的UI线程和主线程是一回事儿。就是唯一可以更新UI的线程。这个只是点会在给okHttp填坑的时候用到。而且,这个内容本身在日常的开发中也经常用到,值得好好学一学。

okHttp发起同步请求

第一个列子是一个同步请求的例子。

  1. private void performSyncHttpRequest() {
  2. OkHttpClient client = new OkHttpClient();
  3. Request request = new Request.Builder()
  4. .url("http://www.baidu.com")
  5. .build();
  6. Call call = client.newCall(request);
  7. Response response = call.execute();
  8. }

但是这样的直接在android的主线程里调用一个网络请求的方法是行不通的,直接抛出UI Thread 请求网络的异常。所以我们这里为了可以掩饰要做一点小小的改动。把请求写成同步请求的方式,但是放在一个worker线程里异步的做这个操作。

  1. private Handler requestHandler = new Handler() {
  2. @Override
  3. public void handleMessage(Message msg) {
  4. switch (msg.what) {
  5. case REQUEST_SUCCESS:
  6. Toast.makeText(MainActivity.this, "SUCCESSFUL", Toast.LENGTH_SHORT).show();
  7. break;
  8. case REQUEST_FAIL:
  9. Toast.makeText(MainActivity.this, "request failed", Toast.LENGTH_SHORT).show();
  10. break;
  11. default:
  12. super.handleMessage(msg);
  13. }
  14. }
  15. };
  16. private void performSyncHttpRequest() {
  17. Runnable requestTask = new Runnable() {
  18. @Override
  19. public void run() {
  20. Message msg = requestHandler.obtainMessage();
  21. try {
  22. OkHttpClient client = new OkHttpClient();
  23. Request request = new Request.Builder()
  24. .url("http://www.baidu.com")
  25. .build();
  26. Call call = client.newCall(request);
  27. // 1
  28. Response response = call.execute();
  29. if (!response.isSuccessful()) {
  30. msg.what = REQUEST_FAIL;
  31. } else {
  32. msg.what = REQUEST_SUCCESS;
  33. }
  34. } catch (IOException ex) {
  35. msg.what = REQUEST_FAIL;
  36. } finally {
  37. // send the message
  38. // 2
  39. msg.sendToTarget();
  40. }
  41. }
  42. };
  43. Thread requestThread = new Thread(requestTask);
  44. requestThread.start();
  45. }

所以同步的请求都是这么做的Response response = call.execute();

  1. 发起同步请求之前先新初始化一个OkHttpClient。然后是具体的请求,用请求builder来创建这个Request。我们这里为了简单url就是http://www.baidu.com了。接下来用前面初始化好的client发起一个call:Call call = client.newCall(request);。最后执行这个call:Response response = call.execute();并获得请求的response。
  2. 这一部分的可以暂时不要关注。因为这个例子只是为了能以运行起来的方式展示okHttp如何发起同步请求。

okHttp发起异步请求

既然android本身不支持发起同步请求,当然也没人要发起同步请求。这么做是能导致严重的用户体验问题。想象一下,如果你有一个瀑布流,然后瀑布流里全部显示的都是图片。现在用户要不断地往下翻看瀑布流的图片。如果这些图片都用同步请求的话,什么时候可以翻一页不说,系统的ANR早就跳出来了。

所以我们就探究一下如何发起一个异步的请求。

  1. private void performAsyncHttpRequest() {
  2. OkHttpClient client = new OkHttpClient();
  3. Request request = new Request.Builder()
  4. .url("http://www.baidu.com")
  5. .build();
  6. Call call = client.newCall(request);
  7. // 1
  8. call.enqueue(new Callback() {
  9. // 2
  10. @Override
  11. public void onFailure(Call call, IOException e) {
  12. //Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
  13. if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
  14. Log.d(TAG, "Main Thread");
  15. } else {
  16. Log.d(TAG, "Not Main Thread");
  17. }
  18. }
  19. @Override
  20. public void onResponse(Call call, final Response response) throws IOException {
  21. // 3
  22. if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
  23. Log.d(TAG, "Main Thread");
  24. } else {
  25. Log.d(TAG, "Not Main Thread");
  26. }
  27. }
  28. });
  29. }
  1. 同步请求用execute方法,异步就用call.enqueue(new Callback()方法。
  2. 这个Callback接口提供了两个方法,一个是onFailure,一个是onResponse。这两个方法分别在请求失败和成功的时候调用。
  3. 本来一切都似乎应该很简单。网络请求成功或者失败直接在界面更新了。但是木有想到这样会抛异常。然后看了看发现原来onFailureonResponse两个方法不是在主线程执行。打印出来的log是:okhttp.demo.com.okhttpdemo D/###okHttp: Not Main Thread

所以要在主线程中更新view只好想别的办法了。在worker线程里更新主线程会抛异常。一般来说有这么几个方法在子线程里更新view。

在子线程更新UI线程

一、Activity的runOnUiThread方法

Activity中有这么一个方法runOnUiThread。这个方法需要一个Runnable实例作为参数。

  1. MainActivity.this.runOnUiThread(new Runnable() {
  2. @Override
  3. public void run() {
  4. Log.d(TAG, "code: ");
  5. Toast.makeText(MainActivity.this, String.valueOf(response.code()), Toast.LENGTH_SHORT).show();
  6. }
  7. });

二、View的post方法

View的post方法也是一样,扔一个Runnable的实例进去。然后就在主线程执行了。Toast肯定是没有这个方法的。

  1. MainActivity.this.mView.post(new Runnable() {
  2. public void run() {
  3. Log.d("UI thread", "I am the UI thread");
  4. }
  5. });

三、其他

  1. 用Handler,这个前面的okHttp同步请求的例子可以用。
  2. AsyncTask, 有两个方法可以在主线程中执行:onProgressUpdateonPostExecute。这里我们并不是要更新进度,所以考虑的是后一个方法。
  1. private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
  2. protected void onPostExecute(Bitmap result) {
  3. Log.d("UI thread", "I am the UI thread");
  4. }
  5. }

综合以上,更新UI线程的方法里最后说到的Handler方法和AsyncTask都太重。尤其是AsyncTask。还要继承实现一堆的方法之后才可以能达到目的,同时还和我们要用的okHttp的使用方法很多不兼容的地方。

所以我们只考虑前面的两种。但是两种方法其实是不一样的。当然,这里并不是说方法的名字不一样。我们来看看android的源代码,这两个方法是如何实现的。

  1. public final void runOnUiThread(Runnable action) {
  2. if (Thread.currentThread() != mUiThread) {
  3. mHandler.post(action);
  4. } else {
  5. action.run();
  6. }
  7. }
  8. // mHandler.post(action); 之post方法的实现
  9. public final boolean post(Runnable r) {
  10. return sendMessageDelayed(getPostMessage(r), 0);
  11. }

方法runOnUiThread最后会调用HandlersendMessageDelayed。但是这里只delay了0。也就是方法传到这里的时候会立即执行runOnUiThread的参数Runnable实例会立即执行。

下面看看View的post方法:

  1. public boolean post(Runnable action) {
  2. final AttachInfo attachInfo = mAttachInfo;
  3. if (attachInfo != null) {
  4. return attachInfo.mHandler.post(action);
  5. }
  6. // Assume that post will succeed later
  7. ViewRootImpl.getRunQueue().post(action);
  8. return true;
  9. }

一般会执行的是ViewRootImpl.getRunQueue().post(action);。也就是Runnable的实例只是添加到了事件队列中,按照顺序执行。并不一定会立即执行。

我们探讨了那么多,最后就使用runOnUiThread来更新界面,也就是方法一了。

  1. call.enqueue(new Callback() {
  2. @Override
  3. public void onFailure(Call call, IOException e) {
  4. final String errorMMessage = e.getMessage();
  5. if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
  6. Log.d(TAG, "Main Thread");
  7. } else {
  8. Log.d(TAG, "Not Main Thread");
  9. }
  10. MainActivity.this.runOnUiThread(new Runnable() {
  11. @Override
  12. public void run() {
  13. Toast.makeText(MainActivity.this, errorMMessage, Toast.LENGTH_SHORT).show();
  14. }
  15. });
  16. }
  17. @Override
  18. public void onResponse(Call call, final Response response) throws IOException {
  19. if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
  20. Log.d(TAG, "Main Thread");
  21. } else {
  22. Log.d(TAG, "Not Main Thread");
  23. }
  24. MainActivity.this.runOnUiThread(new Runnable() {
  25. @Override
  26. public void run() {
  27. Log.d(TAG, "code: ");
  28. Toast.makeText(MainActivity.this, String.valueOf(response.code()), Toast.LENGTH_SHORT).show();
  29. }
  30. });
  31. }
  32. });

无论请求成功还是失败,都弹出一个Toast。用MainActivity.this.runOnUiThread在UI线程中弹出这个Toast

OkHttp3几个简单的例子和在子线程更新UI线程的方法的更多相关文章

  1. 扩展Python模块系列(二)----一个简单的例子

    本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...

  2. 基于Android 下载文件时,更新UI简单帮助类

    因为在项目开发时.有这种简单需求,问谷歌,网络上也有好多Utils工具类,可是比較冗余.自己就简单的写了一个简单帮助类. /** * 下载文件,更新UI简单帮助类 * * @author jarlen ...

  3. 简单的例子了解自定义ViewGroup(一)

    在Android中,控件可以分为ViewGroup控件与View控件.自定义View控件,我之前的文章已经说过.这次我们主要说一下自定义ViewGroup控件.ViewGroup是作为父控件可以包含多 ...

  4. CSharpGL(1)从最简单的例子开始使用CSharpGL

    CSharpGL(1)从最简单的例子开始使用CSharpGL 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo ...

  5. 用一个简单的例子来理解python高阶函数

    ============================ 用一个简单的例子来理解python高阶函数 ============================ 最近在用mailx发送邮件, 写法大致如 ...

  6. Spring-Context之一:一个简单的例子

    很久之前就想系统的学习和掌握Spring框架,但是拖了很久都没有行动.现在趁着在外出差杂事不多,就花时间来由浅入深的研究下Spring框架.Spring框架这几年来已经发展成为一个巨无霸产品.从最初的 ...

  7. C#调用存储过程简单完整例子

    CREATE PROC P_TEST@Name VARCHAR(20),@Rowcount INT OUTPUTASBEGIN SELECT * FROM T_Customer WHERE NAME= ...

  8. 关于apriori算法的一个简单的例子

    apriori算法是关联规则挖掘中很基础也很经典的一个算法,我认为很多教程出现大堆的公式不是很适合一个初学者理解.因此,本文列举一个简单的例子来演示下apriori算法的整个步骤. 下面这个表格是代表 ...

  9. 为什么C语言在2013年仍然很重要:一个简单的例子

    附注:在最初的文章里,我没说明进行模2^64的计算——我当然明白那些不是“正确的”斐波那契数列,其实我不是想分析大数,我只是想探寻编译器产生的代码和计算机体系结构而已. 最近,我一直在开发Dynvm— ...

随机推荐

  1. javascript中的类型转换(进制转换|位运算)

    1:parseInt(string) : 这个函数的功能是从string的开头开始解析,返回一个整数 parseInt("123hua"); //输出 123 parseInt(& ...

  2. Mysql数据库(一)

    一 什么是数据库 一般来说,所有的数据都要存储在硬盘中,为了方便对这些数据的管理因此就出现了例如MySQL SQLserver oracle等数据库管理软件. 数据库中的数据按一定的数据模型组织.描述 ...

  3. EmguCV Image类中的函数(二)使用MorphologyEx进行更多的变换

    MorphologyEx中所有的变换如下图所示 调用方法: Mat aaa = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.R ...

  4. hdu 1541 (基本树状数组) Stars

    题目http://acm.hdu.edu.cn/showproblem.php?pid=1541 n个星星的坐标,问在某个点左边(横坐标和纵坐标不大于该点)的点的个数有多少个,输出n行,每行有一个数字 ...

  5. CentOS6.2网卡绑定配置

    下面主要介绍在CentOS6.2下使用系统自带的bonding进行网卡绑定的详细步骤,在此之前你可以看一下Linux网卡绑定探析,你也可以使用网卡绑定的脚本进行网卡绑定操作. 注意:请在配置前关闭Ne ...

  6. python imaplib无痕取信的主要

    typ, data = M.fetch(num, (UID BODY.PEEK[]))  

  7. 基于稀疏表(Sparse Table)的RMQ(区间最值问题)

    在RMQ的其他实现方法中,有一种叫做ST的算法比较常见. [构建] dp[i][j]表示的是从i起连续的2j个数xi,xi+1,xi+2,...xi+2j-1( 区间为[i,i+2j-1] )的最值. ...

  8. django添加装饰器

    引入模块: from django.utils.decorators import method_decorator 添加:@method_decorator(func) from django.ut ...

  9. jedis常用API

    一.Redis Client介绍 1.1.简介 Jedis Client是Redis官网推荐的一个面向java客户端,库文件实现了对各类API进行封装调用. Jedis源码工程地址:https://g ...

  10. zookeeper和keepalived的区别

    zookeeper主要就是为了保持数据的一致性来的,举个栗子,通俗点就是 本来是存储在各个服务器上的配置文件,现在我不存储在各个服务器上了,我就把全部配置文件都存储在zookeeper服务器上,应用服 ...