diff --git a/gradle.properties b/gradle.properties
index b601fcd72e..487a2710b0 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -49,6 +49,7 @@ VERSION_NAME=4.15.0-SNAPSHOT
## SDK versioning
COMPILE_SDK_VERSION=33
MIN_SDK_VERSION=14
+OK_HTTP_4_MIN_SDK_VERSION=21
TARGET_SDK_VERSION=32
## AndroidX versions
@@ -105,6 +106,7 @@ MOCKITO_ANDROID_VERSION=2.24.0
MOCKITO_VERSION=2.24.0
MOCKWEBSERVER_VERSION=3.0.0-RC1
OK_HTTP_VERSION=3.10.0
+OK_HTTP_4_VERSION=4.10.0
PMD_VERSION=6.0.0
ROBOLECTRIC_VERSION=4.8.1
TRUTH_VERSION=1.1.3
diff --git a/integration/okhttp4/build.gradle b/integration/okhttp4/build.gradle
new file mode 100644
index 0000000000..3ead9875b0
--- /dev/null
+++ b/integration/okhttp4/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.library'
+
+dependencies {
+ implementation project(':library')
+ annotationProcessor project(':annotation:compiler')
+
+ api "com.squareup.okhttp3:okhttp:${OK_HTTP_4_VERSION}"
+ api "androidx.annotation:annotation:${ANDROID_X_ANNOTATION_VERSION}"
+}
+
+android {
+ compileSdk COMPILE_SDK_VERSION as int
+
+ defaultConfig {
+ minSdk OK_HTTP_4_MIN_SDK_VERSION as int
+ targetSdk TARGET_SDK_VERSION as int
+
+ versionName VERSION_NAME as String
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+apply from: "${rootProject.projectDir}/scripts/upload.gradle"
diff --git a/integration/okhttp4/gradle.properties b/integration/okhttp4/gradle.properties
new file mode 100644
index 0000000000..bd37533a8f
--- /dev/null
+++ b/integration/okhttp4/gradle.properties
@@ -0,0 +1,4 @@
+POM_NAME=Glide OkHttp 4.x Integration
+POM_ARTIFACT_ID=okhttp4-integration
+POM_PACKAGING=aar
+POM_DESCRIPTION=An integration library to use OkHttp 4.x to fetch data over http/https in Glide
diff --git a/integration/okhttp4/lint.xml b/integration/okhttp4/lint.xml
new file mode 100644
index 0000000000..d2a05b5ae7
--- /dev/null
+++ b/integration/okhttp4/lint.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/integration/okhttp4/src/main/AndroidManifest.xml b/integration/okhttp4/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9ad5e952bf
--- /dev/null
+++ b/integration/okhttp4/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpLibraryGlideModule.java b/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpLibraryGlideModule.java
new file mode 100644
index 0000000000..0d605ce9ce
--- /dev/null
+++ b/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpLibraryGlideModule.java
@@ -0,0 +1,26 @@
+package com.bumptech.glide.integration.okhttp3;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.Registry;
+import com.bumptech.glide.annotation.GlideModule;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.module.AppGlideModule;
+import com.bumptech.glide.module.LibraryGlideModule;
+import java.io.InputStream;
+
+/**
+ * Registers OkHttp related classes via Glide's annotation processor.
+ *
+ *
For Applications that depend on this library and include an {@link AppGlideModule} and Glide's
+ * annotation processor, this class will be automatically included.
+ */
+@GlideModule
+public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
+ @Override
+ public void registerComponents(
+ @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
+ registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
+ }
+}
diff --git a/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpStreamFetcher.java b/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpStreamFetcher.java
new file mode 100644
index 0000000000..ac9ca2bbd5
--- /dev/null
+++ b/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpStreamFetcher.java
@@ -0,0 +1,109 @@
+package com.bumptech.glide.integration.okhttp3;
+
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.HttpException;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.util.ContentLengthInputStream;
+import com.bumptech.glide.util.Preconditions;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import okhttp3.Call;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+/** Fetches an {@link InputStream} using the okhttp library. */
+public class OkHttpStreamFetcher implements DataFetcher, okhttp3.Callback {
+ private static final String TAG = "OkHttpFetcher";
+ private final Call.Factory client;
+ private final GlideUrl url;
+ private InputStream stream;
+ private ResponseBody responseBody;
+ private DataCallback super InputStream> callback;
+ // call may be accessed on the main thread while the object is in use on other threads. All other
+ // accesses to variables may occur on different threads, but only one at a time.
+ private volatile Call call;
+
+ // Public API.
+ @SuppressWarnings("WeakerAccess")
+ public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
+ this.client = client;
+ this.url = url;
+ }
+
+ @Override
+ public void loadData(
+ @NonNull Priority priority, @NonNull final DataCallback super InputStream> callback) {
+ Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
+ for (Map.Entry headerEntry : url.getHeaders().entrySet()) {
+ String key = headerEntry.getKey();
+ requestBuilder.addHeader(key, headerEntry.getValue());
+ }
+ Request request = requestBuilder.build();
+ this.callback = callback;
+
+ call = client.newCall(request);
+ call.enqueue(this);
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "OkHttp failed to obtain result", e);
+ }
+
+ callback.onLoadFailed(e);
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ responseBody = response.body();
+ if (response.isSuccessful()) {
+ long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
+ stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
+ callback.onDataReady(stream);
+ } else {
+ callback.onLoadFailed(new HttpException(response.message(), response.code()));
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ // Ignored
+ }
+ if (responseBody != null) {
+ responseBody.close();
+ }
+ callback = null;
+ }
+
+ @Override
+ public void cancel() {
+ Call local = call;
+ if (local != null) {
+ local.cancel();
+ }
+ }
+
+ @NonNull
+ @Override
+ public Class getDataClass() {
+ return InputStream.class;
+ }
+
+ @NonNull
+ @Override
+ public DataSource getDataSource() {
+ return DataSource.REMOTE;
+ }
+}
diff --git a/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpUrlLoader.java b/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpUrlLoader.java
new file mode 100644
index 0000000000..6eb9823779
--- /dev/null
+++ b/integration/okhttp4/src/main/java/com/bumptech/glide/integration/okhttp3/OkHttpUrlLoader.java
@@ -0,0 +1,78 @@
+package com.bumptech.glide.integration.okhttp3;
+
+import androidx.annotation.NonNull;
+import com.bumptech.glide.load.Options;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.bumptech.glide.load.model.MultiModelLoaderFactory;
+import java.io.InputStream;
+import okhttp3.Call;
+import okhttp3.OkHttpClient;
+
+/** A simple model loader for fetching media over http/https using OkHttp. */
+public class OkHttpUrlLoader implements ModelLoader {
+
+ private final Call.Factory client;
+
+ // Public API.
+ @SuppressWarnings("WeakerAccess")
+ public OkHttpUrlLoader(@NonNull Call.Factory client) {
+ this.client = client;
+ }
+
+ @Override
+ public boolean handles(@NonNull GlideUrl url) {
+ return true;
+ }
+
+ @Override
+ public LoadData buildLoadData(
+ @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
+ return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
+ }
+
+ /** The default factory for {@link OkHttpUrlLoader}s. */
+ // Public API.
+ @SuppressWarnings("WeakerAccess")
+ public static class Factory implements ModelLoaderFactory {
+ private static volatile Call.Factory internalClient;
+ private final Call.Factory client;
+
+ private static Call.Factory getInternalClient() {
+ if (internalClient == null) {
+ synchronized (Factory.class) {
+ if (internalClient == null) {
+ internalClient = new OkHttpClient();
+ }
+ }
+ }
+ return internalClient;
+ }
+
+ /** Constructor for a new Factory that runs requests using a static singleton client. */
+ public Factory() {
+ this(getInternalClient());
+ }
+
+ /**
+ * Constructor for a new Factory that runs requests using given client.
+ *
+ * @param client this is typically an instance of {@code OkHttpClient}.
+ */
+ public Factory(@NonNull Call.Factory client) {
+ this.client = client;
+ }
+
+ @NonNull
+ @Override
+ public ModelLoader build(MultiModelLoaderFactory multiFactory) {
+ return new OkHttpUrlLoader(client);
+ }
+
+ @Override
+ public void teardown() {
+ // Do nothing, this instance doesn't own the client.
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 08a0115371..decc3f1ce3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -30,6 +30,7 @@ include ':integration:gifencoder'
include ':integration:ktx'
include ':integration:okhttp'
include ':integration:okhttp3'
+include ':integration:okhttp4'
include ':integration:recyclerview'
include ':integration:volley'
include ':testutil'