编程知识 cdmana.com

Explorer le cadre open source Android - 1. Okhttp Source Analysis

Utiliser

1. Ajouter une dépendance

implementation 'com.squareup.okhttp3:okhttp:3.14.9'
Copier le Code

2. Méthode de demande commune

1. SynchroniserGETDemande

  • L'opération demandée est bloquée,Jusqu'àhttpRetour de la réponse
1. CréationOkHttpClientObjet
  1. Création directe
val client = OkHttpClient()
Copier le Code
  1. AdoptionBuilderCréation de modèles
val client = OkHttpClient.Builder()
    .build()
Copier le Code
2. CréationRequestObjet
val request = Request.Builder()
    .url("https://www.baidu.com")
    .get()
    .build()
Copier le Code
3. Oui.requestEncapsulé danscallObjet
val call = client.newCall(request)
Copier le Code
4. Appelezcall.executeEnvoyer une demande de synchronisation
val response = call.execute()
if (response.isSuccessful) {
    log(response.body()?.string())
} else {
    log(IOException("Unexpected code $response").message)
}
Copier le Code
  • Attention!: Doit être appelé dans le Sous - thread ,Après l'envoi de la demande,Le thread actuel est bloqué, Jusqu'à ce qu'une réponse soit reçue
lifecycleScope.launch {
    withContext(Dispatchers.IO) {
        getSync()
    }
}
Copier le Code
  • N'oubliez pas d'ajouter les permissions de demande de réseau
<uses-permission android:name="android.permission.INTERNET" />
Copier le Code
  • Si oui nonhttpsDemande,Peut signaler une erreur:java.net.UnknownServiceException: CLEARTEXT communication to...
  • CLEARTEXT, C'est le sens du texte. ,InAndroid P Sur l'équipement du système ,Si l'application utilise du trafic texte clair non chiffréhttpDemande de réseau,L'application ne peut pas faire de demande réseau,

https Ne sera pas affecté ,De même, Si la nidification est appliquée webView,webView Ne peut être utilisé que httpsDemande;

  • La résolution de cette exception doit être changée enhttpsDemande,Ou AndroidManifest.xmlDocumentApplicationAjouterandroid:usesCleartextTraffic="true"

2. AsynchronegetDemande

  • L'opération demandée n'est pas bloquée,Les résultats de l'exécution sont communiqués à l'appelant au moyen d'un rappel d'interface
  • Les trois premières étapes sont les mêmes ,La quatrième étape appelle la méthode asynchrone call.enqueue
val client = OkHttpClient()
val request = Request.Builder()
    .url("https://www.baidu.com")
    .get()
    .build()
val call = client.newCall(request)
// Appeler une méthode asynchrone enqueue
call.enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        log("onFailure:${e.message}")
        runOnUiThread { tv.text = e.message }
    }

    override fun onResponse(call: Call, response: Response) {
        val result = response.body()?.string()
        log("onResponse:${result}")
        runOnUiThread { tv.text = "onResponse${result}" }
    }

})
Copier le Code
  • Attention!:Méthode de rappelonResponse,onFailureOui. Sous - thread/Fils de travail Exécuté dans, Alors...onResponseUtilisé dansrunOnUiThreadPour mettre à jourUI;

3. AsynchronePOST Demande de soumission de paires de valeurs clés

  • Une étape de plus pour créer FormBody,PourPOSTParamètres demandés
val client = OkHttpClient()
//CréationFormBody
val formBody = FormBody.Builder()
    .add("k", "wanAndroid")
    .build()
val request = Request.Builder()
    .url("https://www.wanandroid.com/article/query/0/json")
    .post(formBody)
    .build()
val call = client.newCall(request)
call.enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        log("onFailure:${e.message}")
        runOnUiThread { tv.text = e.message }
    }

    override fun onResponse(call: Call, response: Response) {
        val result = response.body()?.string()
        log("onResponse:${result}")
        runOnUiThread { tv.text = "onResponse${result}" }
    }
})
Copier le Code

4. Post Mode de communication (Télécharger des fichiers)

private fun postFile() {
    val client = OkHttpClient()
    // Obtenir le fichier à télécharger 
    val file=File(externalCacheDir,"ljy.txt")
    //CréationRequestBody:
    val requestBody=RequestBody.create(
        MediaType.parse("text/x-markdown; charset=utf-8"),
        file
    )
    val request=Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build()
    client.newCall(request).enqueue(object : Callback{
        override fun onFailure(call: Call, e: IOException) {
            log("onFailure:${e.message}")
        }

        override fun onResponse(call: Call, response: Response) {
            log("onResponse:${ response.body()?.string()}")
        }
    })
}
Copier le Code
  • Besoin deAndroidManifest.xml Ajouter des permissions de lecture et d'écriture , Et demande de permission d'exécution
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
    && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
) {
    [email protected](arrayOf(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE), 10001)
} else {
    ...
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == 10001) {
        ...
    }
}
Copier le Code

5. Téléchargement asynchrone de fichiers

private fun downloadFile() {
    val client = OkHttpClient()
    val url = "https://pic3.zhimg.com/v2-dc32dcddfd7e78e56cc4b6f689a24979_xl.jpg"
    val request = Request.Builder()
        .url(url)
        .build()
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            log("onFailure:${e.message}")
        }

        override fun onResponse(call: Call, response: Response) {
            val inputStream = response.body()?.byteStream()
            val fileOutputStream = FileOutputStream(File(externalCacheDir, "ljy.jpg"))
            val buffer = ByteArray(2048)
            var len: Int
            while (inputStream?.read(buffer).also { len = it ?: -1 } != -1) {
                fileOutputStream.write(buffer, 0, len)
            }
            fileOutputStream.flush()
            log("Téléchargement de fichiers réussi")
        }
    })
}
Copier le Code

6. PostSoumettre le formulaire

  • Parfois, les fichiers sont téléchargés en même temps que d'autres types de champs
private fun sendMultipart() {
    val client = OkHttpClient()
    val file = File(externalCacheDir, "ljy.jpg")
    val requestBody: RequestBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("name", "ljy")
        .addFormDataPart("age", "18")
        .addFormDataPart(
            "image", "header.jpg",
            RequestBody.create(MediaType.parse("image/png"), file)
        )
        .build()
    val request: Request = Request.Builder()
        .header("Authorization", "Client-ID " + "...")
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build()
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            log("onFailure:${e.message}")
        }

        override fun onResponse(call: Call, response: Response) {
            log("onResponse:${response.body()?.string()}")
        }
    })
}
Copier le Code

Paramètres communs

1. Réglage du temps d'arrêt

val client = OkHttpClient.Builder()
    .connectTimeout(30,TimeUnit.SECONDS)
    .readTimeout(60,TimeUnit.SECONDS)
    .writeTimeout(90,TimeUnit.SECONDS)
    .build()
Copier le Code

2. Définir le cache

//Définir le chemin et la taille du cache, Et l'intercepteur de cache 
val client = OkHttpClient.Builder()
    .addNetworkInterceptor(CacheInterceptor())
    .cache(
        Cache(
            File(cacheDir, "httpCache2"),
            100 * 1024 * 1024L
        )
    ).build()

//Intercepteur de cache
class CacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request: Request = chain.request()
        val var10000: Response
        val response: Response
        if (NetUtil.isNetworkAvailable([email protected])) {
            //S'il y a un filet,Retour à un30 Réponse interne efficace ,Et30La même requête sera lue directement à partir du cache en quelques secondes
            response = chain.proceed(request)
            //ConstruiremaxAge = 30SecondCacheControl
            val cacheControl = CacheControl.Builder()
                .maxAge(30, TimeUnit.SECONDS)
                .build()
                .toString()
            var10000 = response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control") //Remplissage30SecondCacheControl
                .header("Cache-Control", cacheControl)
                .build()
        } else {
            // Sans filet ,Reconstruire une requête qui force à lire à partir du cache avec la requête originale
            request = request.newBuilder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .build()
            var10000 = chain.proceed(request)
        }
        return var10000
    }
}
Copier le Code
  • OkHttpClient.cacheLe paramètre d'entréeCacheLes constructeurs sont les suivants:
public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
}

Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
Copier le Code
  • Ça marche aussi. DiskLruCache;

3. Configuration Échec retry

val client = OkHttpClient.Builder()
    .retryOnConnectionFailure(true)
    .build()
Copier le Code

4. Persistancecookie

// Ajouter une dépendance de bibliothèque tripartite 
implementation 'com.zhy:okhttputils:2.6.2'
//Persistancecookie,Tiens bon.sessionSession:
val cookieJar = new CookieJarImpl(new PersistentCookieStore(CommonModule.getAppContext()))
val client = OkHttpClient.Builder()
    .cookieJar(cookieJar)
    .build()
Copier le Code

Analyse du code source

Request

  1. Request.Builder()La méthode de construction est la suivante:,methodPar défautGET
public Builder() {
  this.method = "GET";
  this.headers = new Headers.Builder();
}

public Request build() {
  if (url == null) throw new IllegalStateException("url == null");
  return new Request(this);
}

//RequestMéthode de construction
Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
}
Copier le Code
  1. Request.BUilderDepostLa méthode est la suivante::
public Builder post(RequestBody body) {
  return method("POST", body);
}

public Builder method(String method, @Nullable RequestBody body) {
  if (method == null) throw new NullPointerException("method == null");
  if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
  if (body != null && !HttpMethod.permitsRequestBody(method)) {
    throw new IllegalArgumentException("method " + method + " must not have a request body.");
  }
  if (body == null && HttpMethod.requiresRequestBody(method)) {
    throw new IllegalArgumentException("method " + method + " must have a request body.");
  }
  this.method = method;
  this.body = body;
  return this;
}
Copier le Code

OkHttpClient

  1. OkHttpClient La méthode de construction est la suivante: :
public OkHttpClient() {
    this(new Builder());
}

// builderLes valeurs par défaut sont fournies dans la méthode de construction pour:
public Builder() {
    dispatcher = new Dispatcher();
    protocols = DEFAULT_PROTOCOLS;
    connectionSpecs = DEFAULT_CONNECTION_SPECS;
    eventListenerFactory = EventListener.factory(EventListener.NONE);
    proxySelector = ProxySelector.getDefault();
    if (proxySelector == null) {
    proxySelector = new NullProxySelector();
    }
    cookieJar = CookieJar.NO_COOKIES;
    socketFactory = SocketFactory.getDefault();
    hostnameVerifier = OkHostnameVerifier.INSTANCE;
    certificatePinner = CertificatePinner.DEFAULT;
    proxyAuthenticator = Authenticator.NONE;
    authenticator = Authenticator.NONE;
    connectionPool = new ConnectionPool();
    dns = Dns.SYSTEM;
    followSslRedirects = true;
    followRedirects = true;
    retryOnConnectionFailure = true;
    callTimeout = 0;
    connectTimeout = 10_000;
    readTimeout = 10_000;
    writeTimeout = 10_000;
    pingInterval = 0;
}

public OkHttpClient build() {
    return new OkHttpClient(this);
}
Copier le Code
  1. OkHttpClient.newCall
@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}
// Appelé en interne RealCall.newRealCall:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
}

//RealCallLa méthode de construction est la suivante::
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
}
Copier le Code

Call

  1. Call.execute Sync request method source
// Call Ça dépend. RealCallMise en œuvre,dispatcherResponsable principalement de l'enregistrement et de la suppression des demandes de synchronisation
 @Override public Response execute() throws IOException {
    //Jugementexecuted, Assurez - vous que le même HTTP La demande n'est exécutée qu'une seule fois 
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
       //AppelezdispatcherDeexecutedAjouter une demande à la file d'attente des demandes de synchronisation
      client.dispatcher().executed(this);
      // Obtenu par la chaîne d'interception response
      return getResponseWithInterceptorChain();
    } finally {
      // Récupérer la demande de synchronisation 
      client.dispatcher().finished(this);
    }
}
Copier le Code
  1. Call.enqueue Source de la méthode de demande asynchrone
//RealCallMise en œuvre:
@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
       //Assurez - vous quecallUne seule fois
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copier le Code
  • Vous pouvez voir qu'ils ont tous appelédispatcherMéthode
DispatcherCalendrier des tâches
  • Demandes de contrôle de la concurrence,Les variables suivantes sont principalement maintenues
/**
 Nombre maximum de demandes concurrentes 
 */
private int maxRequests = 64;
/**
Nombre maximum de demandes par hôte
 */
private int maxRequestsPerHost = 5;
/**
Pool de fils de consommation
 */
private ExecutorService executorService;
/**
File d'attente de requêtes asynchrones à exécuter
 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**
Exécution de la file d'attente de requêtes asynchrones
 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/**
Exécution de la file d'attente des demandes de synchronisation
 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Copier le Code
  • dispatcherDeexecutedLa méthode est la suivante::
synchronized void executed(RealCall call) {
    //Ajouter une demande à la file d'attente des demandes de synchronisation
    runningSyncCalls.add(call);
}
Copier le Code
  • dispatcher().finished Pour récupérer les demandes de synchronisation ,La réalisation est la suivante:
void finished(RealCall call) {
    finished(runningSyncCalls, call);
}

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
       // Supprimer la demande de synchronisation 
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
}
Copier le Code
  • dispatcherDeenqueueLa méthode est la suivante::
void enqueue(AsyncCall call) {
    synchronized (this) {
       //Ajouter une demande à une file d'attente de demandes asynchrones préparée
      readyAsyncCalls.add(call);
      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        //Adoptionhost Trouver ce qui existe déjà Call
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        // Réutiliser s'il existe callsPerHost
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
}
Copier le Code
  • Ses paramètres d'entréeAsyncCall- Oui.RealCallClasse interne,Les paramètres entrants du constructeur sont ceux que nous avons passéscallback,Et dansexecuteAppelé dans la méthodecallback,EtNamedRunnableDerunAppelé dansexecuteMéthodes
final class AsyncCall extends NamedRunnable {
     ...
    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } catch (Throwable t) {
        cancel();
        if (!signalledCallback) {
          IOException canceledException = new IOException("canceled due to " + t);
          canceledException.addSuppressed(t);
          responseCallback.onFailure(RealCall.this, canceledException);
        }
        throw t;
      } finally {
        client.dispatcher().finished(this);
      }
    }
}

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}
Copier le Code
  • Là - hautAsyncCallDeexecuteMoyenne,À la finfinally Aussi appelé finished Pour récupérer les demandes asynchrones
void finished(AsyncCall call) {
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
}
Copier le Code
  • finished Et asynchrone promoteAndExecuteMéthodes,Les résultats sont les suivants:
private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    //Traverser une file d'attente de demandes asynchrones préparée, Mise en œuvre listEt dans la file d'attente:
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }

      //Recalculer le nombre de demandes asynchrones synchrones à exécuter
      isRunning = runningCallsCount() > 0;
    }

    // Traverser exécutable AsyncCall list,AppelezexecuteOnMéthode d'exécution du pool de Threads entrants
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
}

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
}

public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
}
Copier le Code
Ordre d'invocation des requêtes asynchrones:
  1. Appel du consommateurCall.enqueue(Callback);
  2. Call.enqueueAppelé dansclient.dispatcher().enqueue(new AsyncCall(responseCallback));
  3. dispatcher().enqueueAppelezpromoteAndExecute;
  4. promoteAndExecute Traversée moyenne readyAsyncCalls,Mets - le.executableCallsEtrunningAsyncCallsMoyenne,Et appellerunningCallsCountRecalculer le nombre de demandes asynchrones synchrones à exécuter,Et traverserexecutableCalls,Appelez asyncCall.executeOn(executorService());
  5. asyncCall.executeOnAppel moyenexecutorService.execute(this),Parmi euxthisPourrunnableTypeasyncCall, Il est appelé à la fin runMéthodes;
  6. NamedRunnableDerunAppelé dans la méthodeexecuteMéthodes,asyncCallDans la réalisation deexecuteMéthodes;
  7. asyncCall.executeAppelé dans Response response = getResponseWithInterceptorChain(),Et appellecallback,Appel finaldispatcher().finished;
  8. dispatcher().finished Encore une fois. promoteAndExecuteMéthodes,Jusqu'à ce que toutes les demandes dans la file d'attente soient exécutées;

Chaîne d'interception

  • L'intercepteur estokhttp Un mécanisme puissant , Surveillance du réseau possible , Demande et réponse ,Demande d'échec retry et autres fonctions;
  • Synchrone request asynchrone request above has calls in the sourcegetResponseWithInterceptorChainMéthodes,Les codes sont les suivants:
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    // Créer une série d'intercepteurs ,Et mettrelistMoyenne
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    //1. Retry and Failure redirection Interceptor
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //2.  Intercepteur d'adaptateur de pont ( En - tête de demande supplémentaire ,Mode de codage,Mode de compression)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //3. Intercepteur de cache
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //4. Connectez L'intercepteur
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //5. Réseauio Intercepteur de flux 
    interceptors.add(new CallServerInterceptor(forWebSocket));

    // Créer une chaîne d'intercepteurs chain,Et la mise en œuvrechain.proceedMéthodes
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
}
Copier le Code
  • La méthode ci - dessus crée une série d'intercepteurs,Et mettrelistMoyenne, Recréer la chaîne d'interception RealInterceptorChain,Et la mise en œuvrechain.proceedMéthodes
  • proceedLa méthode est la suivante::
@Override public Response proceed(Request request) throws IOException {
    return proceed(request, transmitter, exchange);
}

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    ...

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
   ...
    return response;
}
Copier le Code
  • Son Code de base, neuf lignes au - dessus du cœur,Créer la prochaine chaîne d'intercepteurs,Appelezinterceptors.get(index) Obtenir l'intercepteur actuel ,Et la mise en œuvreinterceptor.interceptMéthoderesponseRetour;
  • getResponseWithInterceptorChainEntrée dansindexPour0, L'intercepteur actuel est RetryAndFollowUpInterceptor, Alors Regardons - le. interceptComment la méthode est - elle mise en œuvre?
RetryAndFollowUpInterceptor
  • RetryAndFollowUpInterceptorDeintercept Les codes de méthode sont les suivants:
 @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    //Tu te souviens?interceptor.intercept(next)- Oui.,Donc icirealChain C'est la prochaine chaîne d'interception. 
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        //Qui appelle la prochaine chaîne d'intercepteursproceedMéthodes
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
        //Quand ça arriveIOExceptionOuRouteExceptionSera exécutérecoverMéthodes
      } 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, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // 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();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      Request followUp = followUpRequest(response, route);

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }

      // Jugement des temps de retry 
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
}
Copier le Code
  • recover Les codes de méthode sont les suivants:
private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure()) return false;

    // We can't send the request body again.
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // This exception is fatal.
    if (!isRecoverable(e, requestSendStarted)) return false;

    // No more routes to attempt.
    if (!transmitter.canRetry()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
}
Copier le Code
  • RetryAndFollowUpInterceptorDeinterceptQui appelle la chaîne d'interception suivante dans la méthodeproceedMéthode d'acquisitionresponse, Et danswhile (true) Déterminer si une nouvelle demande doit être faite en boucle en fonction des résultats anormaux ou des résultats de la réponse, Si cela se produit IOExceptionOuRouteExceptionSera exécutérecoverMéthodes, Et à travers++followUpCount > MAX_FOLLOW_UPS Déterminer le nombre maximal de retraits , Hors de la boucle ;
  • ParRealInterceptorChain.proceedOn sait qu'il va continuer à appeler le prochain intercepteurinterceptMéthodes,PargetResponseWithInterceptorChainL'ordre du milieu indique que le prochain intercepteur estBridgeInterceptor
  • Continue de regarder. BridgeInterceptorDeinterceptMéthodes
BridgeInterceptor
  • BridgeInterceptorDeinterceptLa méthode est la suivante:
@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    //SupplémentRequestBodyEn - tête de la demande
    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");
    }

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

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    //Qui appelle la prochaine chaîne d'intercepteursproceedMéthodes
    Response networkResponse = chain.proceed(requestBuilder.build());


    // En - tête de réponse supplémentaire 

    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)) {
        //Response.body Flux d'entrée pour GzipSource,Lire les données de flux de manière décompressée
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
}
Copier le Code
  • BridgeInterceptorDeinterceptMoyenne, Tout d'abord. RequestBody En - tête de la demande ,Convertir en une demande d'accès au réseau,Puis appelez la prochaine chaîne d'intercepteursproceedMéthode d'acquisitionresponse,Encore une fois.respone En - tête de réponse pour compléter ,Comme définicookieJar,gzipDécompresser, Réponse à la demande de retour response Convertir en disponible pour l'utilisateur response;
  • Qui appelle la prochaine chaîne d'intercepteursproceed,Le prochain intercepteur sera appeléinterceptMéthodes, L'intercepteur suivant est CacheInterceptor
CacheInterceptor
  • CacheInterceptorDeinterceptLa méthode est la suivante:
@Override public Response intercept(Chain chain) throws IOException {

    // Essayer d'obtenir le cache Response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    //CacheStrategyPolitique de mise en cache,EntretiennetworkRequest Et cacheResponse
    //Obtenir la politique de cache en fonction du temps,Il renvoie le test de cache correspondant en combinaison avec des conditions telles que le temps
    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.
    }

    //Si l'accès au réseau est interdit et qu'il n'y a pas de cache,Directement.new Un échec Response
    // 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();
    }

    //Si l'accès au réseau n'est pas nécessaire,Renvoie directement le cacheresponse
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    // Accès au réseau requis ,Appelle la chaîne d'interception suivanteproceedAccèsresponse
    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());
      }
    }

    //Si on avait un cache localResponse
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      //Le serveur retourne304,Renvoie directement le cache localresponse
      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();

    //S'il y a un cache, Mettre à jour le cache 
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      //Si ce n'est pas le cas,get Supprimer le cache sur demande 
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
}
Copier le Code
  • CacheInterceptorDeinterceptDivers jugements sont faits sur l'utilisation du cache et si le cache est mis à jour,La prochaine chaîne d'intercepteurs sera également appelée si une demande réseau est utiliséeproceedMéthode d'acquisitionresponse,
  • Alors le prochain intercepteur estConnectInterceptor
ConnectInterceptor
  • ConnectInterceptorDeinterceptLa méthode est la suivante:, Officiellement ouvertokhttpDemande de réseau pour
@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    return realChain.proceed(request, transmitter, exchange);
  }
Copier le Code
  • Appelé ci - dessustransmitter.newExchangeAccèsExchange,Et appelle la prochaine chaîne d'intercepteursproceed Passer à l'intercepteur suivant ,Accèsresponse,newExchangeLa méthode est la suivante:
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    ...
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
    ...
}
Copier le Code
  • C'est appelé ci - dessus.exchangeFinder.findAccèsExchangeCodec, Dont adoptionfindHealthyConnectionJe l'ai.RealConnection,Encore.return RealConnection.newCode
 public ExchangeCodec find(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    ...
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
      writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
    return resultConnection.newCodec(client, chain);
    ...
}
Copier le Code
  • findHealthyConnectionEncore appeléfindConnectionMéthodes, findConnection Les codes de méthode sont les suivants: , Où le pool de connexion ou new RealConnectionAccèsRealConnection, Et l'a appelé connectMéthodes
  • Le code source est long,Voici une liste des étapes clés
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    ...
    // D'abord à partir du pool de connexion 
    if (result == null) {
        // Attempt to get a connection from the pool.
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
       }
       ...
    }
    ...
    //Il n'y en a pas dans le pool de connexion pour continuer
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      return result;
    }

   ...
   // Le pool de connexion n'est pas juste newUn,Et appelleconnectMéthodes
    result = new RealConnection(connectionPool, selectedRoute);

    // Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    // Ajouter au pool de connexion 
    connectionPool.routeDatabase.connected(result.route());
    ...
    return result;
}

Copier le Code
CallServerInterceptor
  • Voyons enfin. CallServerInterceptorDeintercept
@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    //Verssocket Écrire les informations de l'en - tête de la requête dans 
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    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"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        if (request.body().isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          //ÉcrirebodyInformation
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      exchange.noRequestBody();
    }

    //Fin de la demande
    if (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    // Lire l'en - tête de réponse 
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

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

    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

    //Lire la réponsebody
    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(exchange.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }

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

    return response;
}

Copier le Code

Je suis Jin Yang,Si vous souhaitez progresser et en savoir plus sur les produits secs,Bienvenue à Wechat public “Le soleil dit” Recevoir mes derniers articles

版权声明
本文为[Jin Yang]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/09/20210915055132980w.html

Scroll to Top