OkHttp3.0源码解析---拦截器_奔跑_小子的博客-程序员秘密

技术标签: okhttp3  笔试面试  

使用方法就不说了,重点记录下源码读取的过程和思路:
1.使用同步调用进行分析

 response = client.newCall(builder.build()).execute();

解析看到的newCall方法:

 /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

返回了一个new RealCall(),之后调用了Call接口的实现类RealCall的execute()方法。

这里写代码片

 @Override public Response execute() throws IOException {
    synchronized (this) {
   //进行同步,一次只能一个线程访问
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();//对调用栈进行追踪
    try {
      client.dispatcher().executed(this);//调用分发器进行执行
      Response result = getResponseWithInterceptorChain();//获取Response从拦截器链中
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);//调用dispatcher来结束任务的执行。
    }
  }

其中的client 是OkHttpClient类型。

这个地方的有以下几个疑问:
1. Dispather的作用是什么?
2. Dispather中的executed()内部是如何实现的。
3. 为什么不是从executed执行完了直接从executed方法中返回Response,而是用InterceptorChain中进行获取?

首先第一个疑问:Dispather:翻看源码对Dispather的描述为:Policy on when async requests are executed;就是在异步请求时添加其上的策略。
第二个疑问:这里只有一行代码:

/** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

那么接下来的疑问是runningSyncCalls是什么类型或数据结构?

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

注释中提到是用来运行同步调用的,包括取消还没有结束的调用。
数据结构是队列,那就是一定满足先进先出的特性。那么,在execute中仅仅是将RealCall 对象添加进行,并没有看到执行。那么什么时候进行执行?我们还是回到RealCall中的getResponseWithInterceptorChain()中获取结果。

这里写代码片
 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//添加客户端的拦截器
    interceptors.add(retryAndFollowUpInterceptor);//添加RetryAndFollowUpInterceptor拦截器。这个拦截器是可以从失败中进行恢复并可以进行冲定向。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//应用代码到网络代码的桥,第一步,建立一个来自用户的网络请求,之后,被网络处理调用,最后,从网络上建立一个用户响应
    interceptors.add(new CacheInterceptor(client.internalCache()));//缓冲拦截器,为缓存中的requests服务同时,将响应写到缓存中。
    interceptors.add(new ConnectInterceptor(client));//打开一个目标服务和进行下一个拦截器的连接。
    if (!forWebSocket) {
   //如果webSocket关闭了,就添加网络连接器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));//CallServerInterceptor这个是最后一次的拦截器,它可是网络访问服务器。

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);//构造真正的责任链
    return chain.proceed(originalRequest);//由责任链处理请求,并返回响应。
  }

通过上面的注释可以看到最后的处理是交给了RealInterceptorChain类进行处理。
接下来看:proceed方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.调用下一个拦截器
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);//获取拦截器
    Response response = interceptor.intercept(next);//从下一个拦截器中获取response?

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

存在疑问?从拦截器中并没有看到如何处理请求?那么会在什么地方进行处理呢?还是得注意这行代码:

 Response response = interceptor.intercept(next);//从下一个拦截器中获取response?

这行代码中的interceptor是RetryAndFollowUpInterceptor类型的。跳转到RetryAndFllowUpInterceptor类文件中。

这里写代码片
 @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);//分配内存空间

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
   //死循环
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);//交给下一个拦截器
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), false, request)) {
   //进行断开重连
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);//
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
   //释放连接
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp = followUpRequest(response);//

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());//关闭

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
   //
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
   //不是相同的连接,
        streamAllocation.release();
        streamAllocation = new StreamAllocation(//从连接池中创建新的连接,复用新的连接
            client.connectionPool(), createAddress(followUp.url()), callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

以上可以看出,一个拦截器的Intercept方法所执行的逻辑大致分为三部分:
1. 在发起请求前对request进行处理
2. 调用下一个拦截器,获取response
3. 对response进行处理,返回给上一个拦截器

一个网络请求实际上就是一个个拦截器执行其intercept方法的过程。而这其中除了用户自定义的拦截器外还有几个核心拦截器的完成了网络访问的核心逻辑,按照先后顺序依次是:
1. RetryAndFollowUpInterceptor
2. BridgeInterceptor
3. CacheInterceptor
4. ConnectInterceptor
5. CallServerInterceptor

BridgeInterceptor类,将用户请求转换成一个网络请求,之后处理网络请求,最后从网络上构建一个用户请求。
在其中使用的gzip进行解压网络流数据。

  • 设置内容长度,内容编码
  • 设置gzip压缩,并在接收到内容后进行解。
  • 添加cookie
  • 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤
这里写代码片
public final class BridgeInterceptor implements Interceptor {
    
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();//取出链中的请求
    Request.Builder requestBuilder = userRequest.newBuilder();
//重新构建
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");//接受编码gzip压缩
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
   //将cookies进行转换
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
   //添加用户代理
      requestBuilder.header("User-Agent", Version.userAgent());
    }
//有链进行处理
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
        //数据的解压
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

  /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
  private String cookieHeader(List<Cookie> cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }
}

CacheInterceptor类的功能是服务来自缓冲区的请求,同时写响应给缓冲区:

  • 当网络请求有符合要求的Cache时直接返回Cache;
  • 当服务器返回内容有改变时更新当前cache;
  • 如果当前cache失效,删除;
这里写代码片
public final class CacheInterceptor implements Interceptor {
    
  final InternalCache cache;

  public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
//缓冲策略工厂
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);//追踪响应
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.缓冲区没有使用,进行关闭
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
   //从缓存中构建一个响应
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.//不要泄露内存
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();//命中
        cache.update(cacheResponse, response);//更新
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (HttpHeaders.hasBody(response)) {
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }

    return response;
  }

//剥开body
  private static Response stripBody(Response response) {
    return response != null && response.body() != null
        ? response.newBuilder().body(null).build()
        : response;
  }

  private CacheRequest maybeCache(Response userResponse, Request networkRequest,
      InternalCache responseCache) throws IOException {
    if (responseCache == null) return null;

    // Should we cache this response for this request?如果没有被缓存了,进行缓存
    if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
   //判断网络请求的方法是否有效
        try {
          responseCache.remove(networkRequest);//移除旧的缓存
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
      return null;
    }

    // Offer this request to the cache.
    return responseCache.put(userResponse);
  }

  /**
   * Returns a new source that writes bytes to {@code cacheRequest} as they are read by the source
   * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
   * may never exhaust the source stream and therefore not complete the cached response.
   */
  private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
      throws IOException {
    // Some apps return a null body; for compatibility we treat that like a null cache request.
    if (cacheRequest == null) return response;
    Sink cacheBodyUnbuffered = cacheRequest.body();
    if (cacheBodyUnbuffered == null) return response;

    final BufferedSource source = response.body().source();
    final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);

    Source cacheWritingSource = new Source() {
      boolean cacheRequestClosed;

      @Override public long read(Buffer sink, long byteCount) throws IOException {
        long bytesRead;
        try {
          bytesRead = source.read(sink, byteCount);
        } catch (IOException e) {
          if (!cacheRequestClosed) {
            cacheRequestClosed = true;
            cacheRequest.abort(); // Failed to write a complete cache response.
          }
          throw e;
        }

        if (bytesRead == -1) {
          if (!cacheRequestClosed) {
            cacheRequestClosed = true;
            cacheBody.close(); // The cache response is complete!
          }
          return -1;
        }

        sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
        cacheBody.emitCompleteSegments();
        return bytesRead;
      }

      @Override public Timeout timeout() {
        return source.timeout();
      }

      @Override public void close() throws IOException {
        if (!cacheRequestClosed
            && !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
          cacheRequestClosed = true;
          cacheRequest.abort();
        }
        source.close();
      }
    };

    return response.newBuilder()
        .body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource)))
        .build();
  }

  /** Combines cached headers with a network headers as defined by RFC 2616, 13.5.3. */
  private static Headers combine(Headers cachedHeaders, Headers networkHeaders) {
    Headers.Builder result = new Headers.Builder();

    for (int i = 0, size = cachedHeaders.size(); i < size; i++) {
      String fieldName = cachedHeaders.name(i);
      String value = cachedHeaders.value(i);
      if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) {
        continue; // Drop 100-level freshness warnings.
      }
      if (!isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
        Internal.instance.addLenient(result, fieldName, value);
      }
    }

    for (int i = 0, size = networkHeaders.size(); i < size; i++) {
      String fieldName = networkHeaders.name(i);
      if ("Content-Length".equalsIgnoreCase(fieldName)) {
        continue; // Ignore content-length headers of validating responses.
      }
      if (isEndToEnd(fieldName)) {
        Internal.instance.addLenient(result, fieldName, networkHeaders.value(i));
      }
    }

    return result.build();
  }

  /**
   * Returns true if {@code fieldName} is an end-to-end HTTP header, as defined by RFC 2616,
   * 13.5.1.
   */
  static boolean isEndToEnd(String fieldName) {
    return !"Connection".equalsIgnoreCase(fieldName)
        && !"Keep-Alive".equalsIgnoreCase(fieldName)
        && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
        && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
        && !"TE".equalsIgnoreCase(fieldName)
        && !"Trailers".equalsIgnoreCase(fieldName)
        && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
        && !"Upgrade".equalsIgnoreCase(fieldName);
  }
}

ConnectInterceptor类,打开一个目标服务器和处理下一个拦截器。即为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接有连接池负责决定。

这里写代码片
public final class ConnectInterceptor implements Interceptor {
    
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//强转为真正的拦截器链
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();//获取真正的连接

    return realChain.proceed(request, streamAllocation, httpCodec, connection);//返回真实的处理响应
  }
}

CallServerInterceptor类,这是拦截链中最后的一个拦截器,负责向服务器发起真正的访问请求,并在接受到服务器返回后读取响应返回。

  @Override public Response intercept(Chain chain) throws IOException {
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

引用下面参考链接的图:
这里写图片描述

这个是我个人的随笔。
参考:https://yq.aliyun.com/articles/78104

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013583931/article/details/77989970

智能推荐

React Router v5 和 v6 的比较_小公鸡卡哇伊呀~的博客-程序员秘密

v5 是老的版本, v6 是最新版,v6 比 v5 好,所以应尽可能使用 v6,安装 v6 命令如下:npm install [email protected]或者npm install [email protected]以下是两个版本之间的区别:1. v6 中 Switch 名称变为 Routesimport { Routes } from "react-router-dom";function App() { return ( &lt;Layout&gt;

前端面试刷题网站汇总_刷题刷到手抽筋的博客-程序员秘密_前端刷题

灵题库http://www.lingtiku.com收集一线大厂面试真题,还有专项训练(Promise、作用域、数据类型、React、Vue…等等)以针对性提升,每个题目有对应知识点的详细介绍,**同名公众号“灵题库”**还有高频题目解析。javascript-questionshttps://github.com/lydiahallie/javascript-questions在github上开源的“说出代码执行结果”系列题目,主要练习JavaScript的基础语法和内置类、内置方法的使用。

gridcontrol导出Excel通用方法_fyhs的博客-程序员秘密_gridcontrol导出excel

#region excel /// &lt;summary&gt; /// DevExpress通用导出Excel,支持多个控件同时导出在同一个Sheet表 /// eg:ExportToXlsx("",gridControl1,gridControl2); /// 将gridControl1和gridControl2的数据一同导出到同一张工作表 /// &lt;/summary&gt; /...

mpls基础配置与解析_padovan的博客-程序员秘密

1、理解MPLS LDP 的标签分配、分发、关联。2、理解OSPF 作为底层协议对标签的影响。3、掌握MPLS 的配置流程及校验方式。接口ip配置完成R2 R3 R4 R5的底层协议OSPF 配置观察参与OSPF 的LOOPBACK 接口路由条目状态。如上现象表明LOOPBACK接口是被当作主机学习的。R1 R6 校验BGP 转发表及路由表,并且利用PING 做测试。R2 R3 R4 R5 完成MPLS 的配置:R2 R3 R4 R5 校验LDP 邻居的

hp服务器pe系统安装系统安装系统安装系统,惠普星14-CE启动盘PE重装win7系统教程..._weixin_39708854的博客-程序员秘密

惠普星14-CE富有线条感的棱角设计、时尚的外观、强悍性能让惠普星系列刚刚推出就备受关注,十分适合年轻群体使用。这样一款笔记本在需要重装系统的时候,用启动盘PE进行重装再好不过。那么下面就让小编为大家带来惠普星14-CE启动盘PE重装win7系统教程。U盘重装win7详细步骤:1.制作一个韩博士U盘启动盘。首先打开韩博士装机大师选择“U盘模式”,然后插入一个8G左右的U盘,U盘数据需提前备份,制作...

android事件分发log打印,【Android】View事件分发、拦截和消费过程解析_赵伊辰的博客-程序员秘密

目 录(本篇字数:955)案例我们来看一个这样的案例,目的是熟悉android事件分发、拦截的流程,例子如下: 如上图,ViewGroupA内嵌ViewGroupB存放着一个ImageView,一个简单明了的布局,让我们以此来揭开View的事件分发机制。代码非常简单,ViewGroupA和ViewGroupB分别重写了dispatchTouchEvent()、onInterceptTouch...

随便推点

android 事件分发 拦截 (onInterceptTouchEvent dispatchTouchEvent onTouchEvent)_郜景涛的博客-程序员秘密

Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。View在ViewGroup内,ViewGroup也可以在其他ViewGrou

Android事件分发,事件拦截,事件处理总结_布丁西西的博客-程序员秘密

对于安卓的事件分发,拦截及事件处理无论是面试还是在日常应用中都涉及的比较多,网上的帖子也很多,感觉都没说透,或者没直接点出来,我认为郭神这篇博客写的事件分发理解的挺好 http://blog.csdn.net/sinyu890807/article/details/90974631 对于事件的分发,安卓中有decorView的概念,点击一个button,首先是由其父类来执行事件的分发,从父类到

OC_06字符串_Barry_任先森的博客-程序员秘密

这一章我们来学习一下,字符串。 字符串分为:可变字符串和不可变字符串。 首先,我们来看NSString 不可变字符串的用法 //1.初始化一个字符串对象 // NSString *string1 = @”iBokanWisdom”; NSString *string2 = [NSString stringWithFormat:@”WeiJian”];//NSString *stri

Tomcat 停止时 JAVA进程未停止 的解决方法_monkeyking1987的博客-程序员秘密

我想, 当你搜索到这篇文章时, 你也是在为Tomcat服务器在停止时输出以下的日志而犯愁吧.2013-6-26 20:18:20 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads严重: The web application [] appears to have started a thread nam

9.2 Git 与其他系统 - 迁移到 Git_shaonbean的博客-程序员秘密

迁移到 Git如果你现在有一个正在使用其他 VCS 的代码库,但是你已经决定开始使用 Git,必须通过某种方式将你的项目迁移至 Git。 这一部分会介绍一些通用系统的导入器,然后演示如何开发你自己定制的导入器。 你将会学习如何从几个大型专业应用的 SCM 系统中导入数据,不仅因为它们是大多数想要转换的用户正在使用的系统,也因为获取针对它们的高质量工具很容易。Subversion如果

“倒霉”系列之90%的程序员都遇到的bug坑_程序员二黑的博客-程序员秘密

Bug描述里一般会列出这些条件:操作系统的版本号,环境变量配置了什么值(这些变量也许会是一个长长的列表),发生bug之前做了什么操作(这些操作也许会是一个长长的列表)。