Android OkHttp代理与路由的彻底理解

目录
  • 代理
    • 什么是代理?
    • 代理的类型
    • 代理选择器
  • 路由
    • 什么是路由?
    • 路由数据库
  • 路由选择器
    • RouteSelector 内部类 Selection
    • RouteSelector 成员变量
    • RouteSelector 成员方法
    • resetNextProxy-初始化代理列表
    • hasNextProxy-是否还有代理
    • hasNext-是否还有路由集合
    • resetNextInetSocketAddress-初始化地址列表
    • nextProxy-返回代理列表中下一个代理
    • next-返回路由集合
  • 总结

代理

OkHttp 支持设置代理,使用OkHttpClient.proxy()即可设置。

什么是代理?

  • 根据代理的对象不同,可分为正向代理和反向代理。正向代理代理的是客户端,负责接收客户端的请求转发到目标服务器,并将结果返回给客户端。反向代理代理的是服务端,服务端将反向代理看做客户端。
  • 正向代理一般用于突破访问限制(如访问外网),提高访问速度。反向代理则用于负载均衡(如nginx),资源防护。
  • 正向代理服务器部署在客户端侧,反向代理服务器部署在服务端侧。
  • 使用正向代理,目标服务器对客户端来说是透明的,客户端将代理服务器看做是目标服务器。
  • 使用反向代理,客户端对目标服务器来说的透明的,目标服务器将代理服务器看做是客户端。

代理的类型

根据代理服务器使用代理协议的不同,可分为 Http 代理,Http Tunnel(隧道)代理,Socks 代理。3种代理协议的实现原理各有不同,读者可自行查找相关资料了解。

Http 代理:我们知道若一个请求直接发送到目标服务器时,请求行中只会包含相对路径的 URL (完整 URL 的 path 部分)。而一个请求发送到 http 代理服务器,要求它请求行的url为绝对路径,这遵循了 www.ietf.org/rfc/rfc2616… 5.1.2小节标准的规定。

Http Tunnel 代理:也称为 Http 隧道代理,最早在 www.ietf.org/rfc/rfc2817… 5.1 小节定义,隧道代理的出现为了让代理服务器能跑 https 的流量。隧道代理需要客户端首先发送一个请求方法为CONNECT 的报文,请求隧道代理创建一条到达任意目的服务器和端口的 TCP 连接,并对客户端和目的服务器之间的后继数据进行原样转发。

Socks 代理:Socks 是最常见的代理服务协议,服务通常使用 1080 端口。Socks 代理与其他类型的代理不同,它只是简单地传递数据包,而并不关心是何种应用协议,所以 Socks 代理服务器比其他类型的代理服务器速度要快得多。Socks 代理又分为 Socks4 和 Socks5,二者不同的是 Socks4 代理只支持 TCP 协议,而 Socks5 代理则既支持 TCP 协议又支持 UDP 协议,还支持各种身份验证机制、服务器端域名解析等。

早在 jdk 1.5中就提供了一个Proxy类来表示代理。

public class Proxy {
    // 代理类型
    public enum Type {
        // 不使用代理,直连目标服务器
        DIRECT,
        // HTTP 协议代理
        HTTP,
        // SOCKS 协议代理
        SOCKS
    };
    // 代理类型
    private Type type;
    // 代理的 IP 套接字地址(IP + 端口号)
    private SocketAddress sa;
    public final static Proxy NO_PROXY = new Proxy();
    // 默认不使用代理
    private Proxy() {
        type = Type.DIRECT;
        sa = null;
    }
}

代理选择器

jdk 提供了一个名为ProxySelector的类,意为“代理选择器”。ProxySelector是个抽象类,继承它的类需要实现selectconnectFailed方法,这说明我们可通过继承ProxySelector自定义代理选择器,在select方法中返回自定义的代理列表。而当一个代理服务器无法连接时,调用connectFailed方法通知代理选择器当前代理服务器不可用。如下代码,ProxySelector的静态代码块中使用Class对象的newInstance方法创建了一个DefaultProxySelector的对象。

public abstract class ProxySelector {
    private static ProxySelector theProxySelector;
    // 创建 DefaultProxySelector 对象
    static {
        try {
            Class<?> c = Class.forName("sun.net.spi.DefaultProxySelector");
            if (c != null && ProxySelector.class.isAssignableFrom(c)) {
                theProxySelector = (ProxySelector) c.newInstance();
            }
        } catch (Exception e) {
            theProxySelector = null;
        }
    }
    public static ProxySelector getDefault() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SecurityConstants.GET_PROXYSELECTOR_PERMISSION);
        }
        return theProxySelector;
    }
    public abstract List<Proxy> select(URI uri);
    public abstract void connectFailed(URI uri, SocketAddress sa, IOException ioe);
}

ProxySelector有个两个子类DefaultProxySelectorNullProxySelector

DefaultProxySelector:jdk 中提供的代理选择器,也是 OkHttp 默认使用的代理选择器,select返回系统设置的代理列表。

NullProxySelector:OkHttp 中提供的代理选择器,select返回的代理列表只包含一个NO_PROXY,即不使用代理。

在 OkHttp 中可以使用OkHttpClient.proxy(proxy)设置代理,也可以使用OkHttpClient.proxySelector设置代理选择器。OkHttp 会优先使用设置的代理去连接代理服务器,而不是从代理列表中选择。如下代码, OkHttpClient默认使用DefaultProxySelector代理选择器,除非getDefault返回null,才使用NullProxySelector

public Builder() {
  proxySelector = ProxySelector.getDefault();
  if (proxySelector == null) {
    proxySelector = new NullProxySelector();
  }
}

路由

什么是路由?

在 OkHttp 中,路由表示一个请求到目标服务器或代理服务器的具体路线。对于一个请求来说,如果它的url是域名,经过 DNS 解析之后可能会对应多个 IP 地址,这意味着一个请求到达服务器的路由就有多个

如下程序在我本机环境下使用InetAddress类解析baidu.com这个域名,IP 地址就有两个。

public void domainResolution() throws UnknownHostException {
    InetAddress[] inetAddresses = InetAddress.getAllByName("baidu.com");
    for (InetAddress inetAddress : inetAddresses) {
        System.out.println(inetAddress.toString());
    }
}

baidu.com/39.156.66.10
baidu.com/110.242.68.66

OkHttp 会选择其中一个路由来建立到服务器的连接。Route类描述了一个路由应该包含的信息:配置信息,代理信息,代理或目标服务器地址,是否使用 Http 隧道代理。

public final class Route {
  // 与目标服务器建立连接所需要的配置信息,包括目标主机名、端口、dns 等
  final Address address;
  // 该路由的代理信息
  final Proxy proxy;
  // 代理服务器或目标服务器的地址
  final InetSocketAddress inetSocketAddress;
  // 该路由是否使用 Http 隧道代理
  public boolean requiresTunnel() {
    return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
  }
}

路由数据库

路由数据库是一个路由黑名单库,存储了那些连接到特定 IP 地址或代理服务器失败的路由。这样在创建新的连接时,就可以避免使用这些路由。RouteDatabase类如下。

  • 内部使用 Set 结构来存储路由,保证数据不重复。
  • failed方法将失败的路由加入到 Set 中。
  • connected方法表示该路由连接成功,将它从 Set 中移除。
  • shouldPostpone方法用于判断该路由是否在黑名单中。
final class RouteDatabase {
  private final Set<Route> failedRoutes = new LinkedHashSet<>();
  /** Records a failure connecting to {@code failedRoute}. */
  public synchronized void failed(Route failedRoute) {
    failedRoutes.add(failedRoute);
  }
  /** Records success connecting to {@code route}. */
  public synchronized void connected(Route route) {
    failedRoutes.remove(route);
  }
  /** Returns true if {@code route} has failed recently and should be avoided. */
  public synchronized boolean shouldPostpone(Route route) {
    return failedRoutes.contains(route);
  }
}

路由选择器

RouteSelector是 OkHttp 中的路由选择器,它的next方法可以返回一个合适的路由集合(Selection)用于连接目标服务器。它的整体工作流程如下所示。

RouteSelector 内部类 Selection

Selection表示被next方法选中的路由集合。内部有一个路由列表和下一个路由的索引。

public static final class Selection {
    // 路由列表
    private final List<Route> routes;
    // 下一个路由的索引
    private int nextRouteIndex = 0;
    Selection(List<Route> routes) {
      this.routes = routes;
    }
    // 是否有下一个路由
    public boolean hasNext() {
      return nextRouteIndex < routes.size();
    }
    // 返回下一个路由
    public Route next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      return routes.get(nextRouteIndex++);
    }
    // 返回路由列表
    public List<Route> getAll() {
      return new ArrayList<>(routes);
    }
}

RouteSelector 成员变量

  • address:目标服务器地址信息,包括 url,dns,端口信息等。
  • routeDatabase:路由黑名单库
  • call:Call 对象
  • eventListener:Http 请求事件监听器
  • proxies:代理列表
  • nextProxyIndex:下一个代理的索引
  • inetSocketAddresses:用于连接代理或目标服务器可用的地址列表
  • postponedRoutes:不可用的路由列表
private final Address address;
private final RouteDatabase routeDatabase;
private final Call call;
private final EventListener eventListener;
/* State for negotiating the next proxy to use. */
private List<Proxy> proxies = Collections.emptyList();
private int nextProxyIndex;
/* State for negotiating the next socket address to use. */
private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
/* State for negotiating failed routes */
private final List<Route> postponedRoutes = new ArrayList<>();

RouteSelector 成员方法

// 初始化代理列表
private void resetNextProxy(HttpUrl url, Proxy proxy);
// 是否有下一个代理
private boolean hasNextProxy();
// 是否含有路由可以尝试连接
public boolean hasNext();
// 初始化连接代理或目标服务器的地址列表
private void resetNextInetSocketAddress(Proxy proxy) throws IOException;
// 返回代理列表中下一个代理
private Proxy nextProxy() throws IOException;
// 返回路由集合
public Selection next() throws IOException;

resetNextProxy-初始化代理列表

resetNextProxy是个私有方法,在RouteSelector类的构造函数内被调用,用于初始化代理列表。前文我们说过,若OkHttpClient设置了代理,则仅会使用这1个代理。而若没有设置代理则会从代理选择器获取代理列表。resetNextProxy方法的实现正遵循这样的规则。

private void resetNextProxy(HttpUrl url, Proxy proxy) {
    // 若设置了代理,仅使用这一个代理
    if (proxy != null) {
      // If the user specifies a proxy, try that and only that.
      proxies = Collections.singletonList(proxy);
    } else {
      // 若没有设置代理,则调用代理选择器的 select 方法获取代理列表
      // Try each of the ProxySelector choices until one connection succeeds.
      List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
      // 若 select 返回的代理列表为空,认为不使用代理,以 Proxy.NO_PROXY 初始化
      proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
          ? Util.immutableList(proxiesOrNull)
          : Util.immutableList(Proxy.NO_PROXY);
    }
    nextProxyIndex = 0;
}

hasNextProxy-是否还有代理

hasNextProxy返回代理列表中是否还有下一个代理用于连接。

private boolean hasNextProxy() {
	return nextProxyIndex &lt; proxies.size();
}

hasNext-是否还有路由集合

public boolean hasNext() {
	return hasNextProxy() || !postponedRoutes.isEmpty();
}

resetNextInetSocketAddress-初始化地址列表

resetNextInetSocketAddress用于初始化地址列表,这个地址列表是通往代理服务器或目标服务器的,这取决于所使用的代理类型。

  • 对于DIRECT(直连)和SOCKS类型的代理来说,会使用目标服务器的主机名和端口号。而HTTP类型的代理则会使用代理服务器的主机名和端口号。
  • SOCKS 类型的代理只会生成一个通往目标服务器的地址。
  • 直连类型的代理,经 DNS 解析目标服务器主机名后,可能生成多个通往目标服务器的地址。
  • HTTP 类型的代理,经 DNS 解析目标服务器主机名后,可能生成多个通往代理服务器的地址。
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
    // Clear the addresses. Necessary if getAllByName() below throws!
    inetSocketAddresses = new ArrayList<>();
    // 主机名
    String socketHost;
    // 端口号
    int socketPort;
    // 若代理类型为直连或 SOCKS,则使用目标服务器的主机名和端口号
    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
      socketHost = address.url().host();
      socketPort = address.url().port();
    } else {
      // 若代理类型为 HTTP,则使用代理服务器的主机名和端口号
      SocketAddress proxyAddress = proxy.address();
      if (!(proxyAddress instanceof InetSocketAddress)) {
        throw new IllegalArgumentException(
            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
      }
      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
      socketHost = getHostString(proxySocketAddress);
      socketPort = proxySocketAddress.getPort();
    }
    if (socketPort < 1 || socketPort > 65535) {
      throw new SocketException("No route to " + socketHost + ":" + socketPort
          + "; port is out of range");
    }
    // SOCKS 类型的代理只会生成一个通往目标服务器的地址
    if (proxy.type() == Proxy.Type.SOCKS) {
      inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
    } else {
      eventListener.dnsStart(call, socketHost);
      // Try each address for best behavior in mixed IPv4/IPv6 environments.
      List<InetAddress> addresses = address.dns().lookup(socketHost);
      if (addresses.isEmpty()) {
        throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
      }
      eventListener.dnsEnd(call, socketHost, addresses);
      for (int i = 0, size = addresses.size(); i < size; i++) {
        InetAddress inetAddress = addresses.get(i);
        inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
      }
    }
}

nextProxy-返回代理列表中下一个代理

nextProxy会从代理列表中取出一个代理返回,同时会调用resetNextInetSocketAddress方法传入当前取出的代理,根据这个代理来初始化地址列表。一个代理对应一个地址列表。

private Proxy nextProxy() throws IOException {
    if (!hasNextProxy()) {
      throw new SocketException("No route to " + address.url().host()
          + "; exhausted proxy configurations: " + proxies);
    }
    Proxy result = proxies.get(nextProxyIndex++);
    resetNextInetSocketAddress(result);
    return result;
}

next-返回路由集合

nextRouteSelector类中最重要的方法,供外部调用。包含了路由选择器一次完整的工作流程。

public Selection next() throws IOException {
    // 若没有路由集合了,抛出异常
    if (!hasNext()) {
      throw new NoSuchElementException();
    }
    // Compute the next set of routes to attempt.
    List<Route> routes = new ArrayList<>();
	// 循环直到没有代理可用
    while (hasNextProxy()) {
      // Postponed routes are always tried last. For example, if we have 2 proxies and all the
      // routes for proxy1 should be postponed, we'll move to proxy2. Only after we've exhausted
      // all the good routes will we attempt the postponed routes.
      // 从代理列表中取出一个代理
      Proxy proxy = nextProxy();
      // 遍历该代理对应的地址列表
      for (int i = 0, size = inetSocketAddresses.size(); i < size; i++) {
        // 创建该地址对应的路由
        Route route = new Route(address, proxy, inetSocketAddresses.get(i));
        // 若该路由在黑名单,则添加到 postponedRoutes
        if (routeDatabase.shouldPostpone(route)) {
          postponedRoutes.add(route);
        } else {
        // 否则添加到 routes
          routes.add(route);
        }
      }
      // 若该代理对应的地址列表不为空,退出循环
      if (!routes.isEmpty()) {
        break;
      }
    }
	// 若所有代理的地址列表均为空,则尝试使用黑名单中的路由
    if (routes.isEmpty()) {
      // We've exhausted all Proxies so fallback to the postponed routes.
      routes.addAll(postponedRoutes);
      postponedRoutes.clear();
    }
	// 返回路由集合
    return new Selection(routes);
}

总结

本小节详细分析了RouteSelector路由选择器的源码,并对它的整体工作流程做了分析。最后返回的路由集合就是能到达代理或目标服务器的全部路线,客户端只需要从中选择一条路由进行连接就行了。

更多关于Android OkHttp代理路由的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android开发OkHttp执行流程源码分析

    目录 前言 介绍 执行流程 OkHttpClient client.newCall(request): RealCall.enqueue() Dispatcher.enqueue() Interceptor RetryAndFollowUpInterceptor BridgeInterceptor CacheInterceptor 前言 OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用. 对于 Androi

  • Android内置的OkHttp用法介绍

    目录 1.异步GET请求 2.异步POST请求 3.异步上传文件 4.异步下载文件 5.异步上传Multipart文件 6.设置超时时间和缓存 Okhttp 处理了很多网络疑难杂症,比如从很多常用的连接问题中自动恢复.如果你服务器配置了多个IP地址,当一个IP地址连接失败后Okhttp会自动尝试下一个IP,从Android4.4版本后,系统内置了Okhttp,可见Okhttp功能的强大. 远程依赖添加,okio作为Okhttp的IO组件,也是必须要引入的. api 'com.squareup.o

  • Android使用OKhttp3实现登录注册功能+springboot搭建后端的详细过程

    目录 一.Android前端实现 二.数据库 三.SpringBoot后端搭建 四.部署至服务器 五.运行测试 一.Android前端实现 新建一个login的项目,主要的几个文件在这里 1.gradle引入OKhttp3依赖 implementation 'com.squareup.okhttp3:okhttp:3.14.7' implementation 'com.squareup.okio:okio:1.17.5' 2.activity_main.xml布局文件 <?xml version

  • Android基于OkHttp实现文件上传功能

    本文实例为大家分享了Android基于OkHttp实现文件上传的具体代码,供大家参考,具体内容如下 一.相关概述 Android请求访问服务端大多数情况下依旧是使用http协议,故而可以参照web端的数据传输形式来实现. multipart/form-data是浏览器提交表单上传文件的一种方式. 有关于http的get,post请求大家可以自行百度了解. OkHttp是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZI

  • Android 网络请求框架解析之okhttp与okio

    安卓网络请求 先看一下今天的大纲 导入okhttp和okio依赖 禁用掉明文流量请求的检查 添加访问权限 布局及代码实现 运行结果 下面是具体步骤 一.导入okhttp和okio的依赖 1.打开File-Project Structure-Dependencies, 2.选择自己的程序文件,点击加号,选择Library Dependency 3.搜索okhttp,选择Com.squareup.okhttp3,点击ok按钮,此时可能需要较长时间 4.okio同上 5.应用,确认 6.此时我们可以看

  • Android的简单前后端交互(okHttp+springboot+mysql)

    前言 前阵子发现了个有意思又好用的框架--okHttp.由于课程设计需要,无意间发现了这个框架,打算利用此框架与后端交互,可以参考前后端分离的项目,把android当做前端,springboot当做后端,以下是二者的简单交互. okHttp说明 (1)android网络框架之OKhttp 一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso) 用于替代HttpUrlConnection和Apache HttpClient (2)ok

  • Android okhttp的启动流程及源码解析

    前言 这篇文章主要讲解了okhttp的主要工作流程以及源码的解析. 什么是OKhttp 简单来说 OkHttp 就是一个客户端用来发送 HTTP 消息并对服务器的响应做出处理的应用层框架. 那么它有什么优点呢? 易使用.易扩展. 支持 HTTP/2 协议,允许对同一主机的所有请求共用同一个 socket 连接. 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟. 支持 GZIP,减小了下载大小. 支持缓存处理,可以避免重复请求. 如果你的服务有多个 IP 地址,当第一次连接失败,OkHt

  • Android OKHTTP的单例和再封装的实例

    Android OKHTTP的单例和再封装的实例 /** * Created by zm on 16-2-1 * okhttp的再封装,对于2.x版本,3.x版本将原有对okhttpclient配置 * 改成了builder模式配 * 置,对于超时.代理.dns,okhttp已经做好了配置, * 若不需要特殊配置,可以跳过 */ public class OkHttpUtil { private static OkHttpClient singleton; //非常有必要,要不此类还是可以被ne

  • Android编程中关于单线程模型的理解与分析

    本文讲述了Android编程中关于单线程模型的理解与分析.分享给大家供大家参考,具体如下: 当一个Android程序启动时,Android系统会同时启动一个对应的主线程(Main Thread). 由于这个主线程(Main Thread)主要的任务就是对UI相关的事件进行处理(例如显示文本,处理点击事件,显示图片等),系统对每一个组件的调用都是从主线程中分发出去的,所以又常被称为UI线程. IMP,Android单线程模型的核心原则就是:只能在UI线程(Main Thread)中对UI进行处理.

  • Android OkHttp Post上传文件并且携带参数实例详解

    Android OkHttp Post上传文件并且携带参数 这里整理一下 OkHttp 的 post 在上传文件的同时,也要携带请求参数的方法. 使用 OkHttp 版本如下: compile 'com.squareup.okhttp3:okhttp:3.4.1' 代码如下: protected void post_file(final String url, final Map<String, Object> map, File file) { OkHttpClient client = n

  • Android OkHttp的简单使用和封装详解

    Android OkHttp的简单使用和封装详解 1,昨天把okHttp仔细的看了一下,以前都是调用同事封装好了的网络框架,直接使用很容易,但自己封装却不是那么简单,还好,今天就来自我救赎一把,就和大家写写从最基础的OKHttp的简单get.post的使用,再到它的封装. 2,OkHttp的简单使用 首先我们创建一个工程,并在布局文件中添加三个控件,TextView(用于展示获取到json后的信息).Button(点击开始请求网络).ProgressBar(网络加载提示框) ①简单的异步Get请

  • Android Okhttp请求查询购物车的实例代码

    查询购物车的model层 public class SelectCarModel { private String url="http://120.27.23.105/product/getCarts"; private HashMap<String, String> map = new HashMap<>(); public void verifySelectCarInfo(int uid, final ISelectCarPresenter iSelectC

  • Android OkHttp基本使用详解

    Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient. 尽管Google在大部分安卓版本中推荐使用HttpURLConnection,但是这个类相比HttpClient实在是太难用,太弱爆了. OkHttp是一个相对成熟的解决方案,据说Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp实现了.所以我们更有理由相信OkHttp的强大. 使用范围 OkHttp支持Android 2.3及其以上版本. 对于Jav

  • Android从源码的角度彻底理解事件分发机制的解析(下)

    记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻的理解了. 还未阅读过的朋友,请先参考Android从源码的角度彻底理解事件分发机制的解析. 那么今天我们将继续上次未完成的话题,从源码的角度分析ViewGroup的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是And

  • Android :okhttp+Springmvc文件解析器实现android向服务器上传照片

    A.前言:为了解决安卓端向服务器上传照片的问题 1.获得相册权限,选取照片,取到照片的url 2.使用okhttp访问服务器并向服务器传照片 3.配置springmvc文件解析器 4.搭建服务器,获取数据保存照片 B.Android添加一个按钮和一个ImageView,设置它的点击事件,打开相册选择照片,解析得到照片的本机url,并把照片显示到ImageView里 添加权限: <uses-permission android:name="android.permission.INTERNE

  • Android okhttp使用的方法

    简介 OKHttp是一个十分常用的网络请求框架了,用于android中请求网络. 除了OKHttp,如今Android中主流的网络请求框架有: Android-Async-Http Volley OkHttp Retrofit 依赖库导入 在build.gradle 添加如下依赖 implementation 'com.squareup.okhttp3:okhttp:4.9.0' 添加网络权限 <uses-permission android:name="android.permissio

随机推荐