1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/xuexiangjys-XHttp2

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

XHttp2

Один из мощных сетевых запросов, использующий RxJava2 + Retrofit2 + OKHttp.

api I Star

XHttp2 — это библиотека для сетевых запросов с широкими возможностями:

  • поддержка конфигураций по умолчанию, глобальных и локальных;
  • динамическая конфигурация и настройка базовых фреймворков OkHttpClient и Retrofit;
  • базовый ApiService для уменьшения избыточности API;
  • поддержка различных протоколов запросов (GET, POST, PUT, DELETE и др.);
  • поддержка кэширования данных, шесть стратегий на выбор;
  • добавление заголовков (фиксированных и динамических);
  • добавление глобальных параметров и динамическое добавление локальных параметров;
  • загрузка файлов, многофайловая загрузка и отправка данных форм;
  • обработка ошибок и прогресс загрузки файлов;
  • автоматическое декодирование любых структур данных;
  • возможность добавления динамических параметров (например, timestamp, token, sign);
  • расширение API;
  • объединение нескольких запросов;
  • управление файлами cookie;
  • асинхронные и синхронные запросы;
  • доступ к сайтам с HTTPS, самоподписанными сертификатами и двусторонней аутентификацией;
  • механизм повторных попыток с возможностью настройки количества попыток и интервала между ними;
  • удаление и очистка кэша по ключу;
  • стандартный разбор результатов APIResult (в соответствии с OpenApi) и возможность его настройки;
  • отмена запросов данных, подписки и диалоговых окон;
  • два способа получения результатов запроса: через обратные вызовы и подписку;
  • три способа выполнения сетевых запросов: «по умолчанию», «протокол интерфейса» и «унифицированный запрос»;
  • унифицированная обработка результатов и исключений, настраиваемая обработка исключений;
  • гибкость при переключении потоков благодаря использованию RxJava;
  • настройка сетевых запросов через аннотации в запросах;
  • единый механизм отмены запросов.

Посмотреть UML-диаграмму классов проекта можно здесь.


О разработчике

| Публичный аккаунт | juejin | Zhihu | CSDN | Jianshu | SegmentFault | Bilibili | Toutiao | | --- | --- | --- | --- | --- | --- | --- | | | Мой путь в мир Android-разработки | Ссылка | Ссылка | Ссылка | Ссылка | Ссылка | Ссылка | Ссылка


Быстрый старт с библиотеками X-серии

Для удобства интеграции библиотек X-серии разработчик предлагает шаблон проекта: https://github.com/xuexiangjys/TemplateAppProject.


Особенности

  • Поддержка конфигураций трёх уровней: по умолчанию, глобального и локального.
  • Динамическая конфигурация и пользовательские базовые фреймворки OkHttpClient и Retrofit.
  • Добавление базового ApiService для сокращения избыточности API.
  • Поддержка различных протоколов запросов: GET, POST, PUT, DELETE и других.
  • Кэширование данных с шестью стратегиями на выбор, охватывающими большинство сценариев использования.
  • Фиксированные и динамические заголовки.
  • Глобальные параметры и динамическое добавление локальных параметров.
  • Загрузка файлов, множественная загрузка файлов и отправка данных формы.
  • Обработка ошибок и прогресса загрузки файлов.
  • Автоматическое декодирование любых структур данных.
  • Возможность добавления динамических параметров, таких как timestamp, token и sign.
  • Расширение API.
  • Объединение нескольких запросов.
  • Управление файлами cookie.
  • Асинхронные и синхронные запросы.
  • Доступ к сайтам с HTTPS, самозаверяющими сертификатами и взаимной аутентификацией.
  • Механизм повторных попыток с настройкой количества попыток и интервалом между ними.
  • Удаление и очистка кэша по ключу.
  • Стандартный разбор результатов APIResult в соответствии с OpenApi и возможность настройки.
  • Отмена запросов данных, подписок и диалогов.
  • Два способа получения результатов запросов: обратные вызовы и подписка.
  • Три способа выполнения сетевых запросов: «по умолчанию», «интерфейсный протокол» и «унифицированный запрос».
  • Унифицированная обработка результатов и исключений с настраиваемой обработкой исключений.
  • Гибкость при переключении потоков с использованием RxJava.
  • Настройка сетевых запросов через аннотации в запросах.
  • Единый механизм отмены запросов. Как выполнять сетевые запросы

Следует отметить, что все результаты запросов должны соответствовать следующему формату:

{
    "Code": 0, // ответный код, 0 означает успех, иначе — сбой
    "Msg": "", // причина сбоя запроса
    "Data": {} // объект возвращаемых данных
}

Рекомендуется использовать заглавные буквы для полей «Code», «Msg» и «Data». В противном случае разбор может быть неудачным.

Обратите внимание, что запрос считается успешным только в том случае, если Code равен 0. Если ваш код успеха отличается от 0, вы можете использовать XHttpSDK.setSuccessCode для установки значения, означающего успех.

Если вам нужно настроить возвращаемый объект API, обратитесь к разделу #Настройка API-запроса.

1. Выполнение запросов с использованием стандартных API XHttp

  1. Создайте запрос с помощью методов XHttp.post, XHttp.get, XHttp.delete, XHttp.put или XHttp.downLoad.
  2. Измените параметры запроса request.
Метод Тип Значение по умолчанию Примечание
baseUrl String / Устанавливает базовый URL для этого запроса
timeOut long 15000 Устанавливает время ожидания
accessToken boolean false Определяет необходимость проверки токена
threadType String / Задаёт тип планирования запроса
syncRequest boolean false Указывает, является ли запрос синхронным (без создания дочернего потока)
onMainThread boolean true Определяет, должен ли запрос выполняться в основном потоке после завершения
upJson String "" Отправляет данные в формате JSON
keepJson boolean false Сохраняет ответ в формате JSON
retryCount int / Определяет количество попыток при таймауте
retryDelay int / Устанавливает задержку повторной попытки при таймауте
retryIncreaseDelay int / Увеличивает задержку при повторных попытках при таймауте
headers HttpHeaders / Добавляет информацию в заголовок запроса
params HttpParams / Устанавливает параметры формы запроса
cacheMode CacheMode CacheMode.NO_CACHE Определяет режим кэширования
  1. Вызовите метод execute для выполнения запроса. Метод execute имеет два варианта использования:
  • execute(CallBack callBack): немедленно возвращает результат.
  • execute(Class clazz) и execute(Type type): возвращает Observable, который можно получить через подписку.
  1. Пример использования запроса:

    XHttp.get("/user/getAllUser")
            .syncRequest(false) // асинхронный запрос
            .onMainThread(true) // вернуться в основной поток
            .execute(new SimpleCallBack<List<User>>() {
                @Override
                public void onSuccess(List<User> response) {
                    refreshLayout.finishRefresh(true);
                    if (response != null && response.size() > 0) {
                        mUserAdapter.refresh(response);
                        mLlStateful.showContent();
                    } else {
                        mLlStateful.showEmpty();
                    }
                }
    
                @Override
                public void onError(ApiException e) {
                    refreshLayout.finishRefresh(false);
                    mLlStateful.showError(e.getMessage(), null);
                }
            });
    XHttp.post("/user/deleteUser")
            .params("userId", item.getUserId())
            .execute(Boolean.class)
            .subscribeWith(new TipRequestSubscriber<Boolean>() {
                @Override
                protected void onSuccess(Boolean aBoolean) {
                    ToastUtils.toast("Удаление успешно!");
                    setFragmentResult(RESULT_OK, null);
                    popToBack();
                }
            });

---

**2. Использование унифицированных запросов XHttpRequest**

Перед использованием необходимо загрузить или определить соответствующий протокол сущности. Например:

@RequestParams(url = "/user/addUser", accessToken = false) public static class UserService_AddUser extends XHttpRequest {

/**
 *
 */
public User request;

@Override
protected Boolean getResponseEntityType() {
    return null;
}

}


| Аннотированный параметр | Тип | По умолчанию | Примечание |
| --- | --- | --- | --- |
| @RequestParams | — | — | |
| url | String | — | Адрес сетевого интерфейса запроса |
| timeout | long | 15000 | Время ожидания ответа |
| keepJson | boolean | false | Следует ли сохранять формат JSON |
| accessToken | boolean | true | Нужно ли проверять токен |
| cacheMode | CacheMode | CacheMode.NO_CACHE | Режим кэширования запроса |
| cacheTime | long | -2 (использует глобальные настройки) | Срок действия кэша | **Использование XHttpSDK для выполнения запросов**

1. Использование XHttpSDK для выполнения запроса:
* post(XHttpRequest xHttpRequest, boolean isSyncRequest, boolean toMainThread): получение PostRequest-запроса (используя имя сущности в качестве ключа запроса).
* postToMain(XHttpRequest xHttpRequest): получение PostRequest-запроса (основной поток → основной поток).
* postToIO(XHttpRequest xHttpRequest): получение PostRequest-запроса (основной поток → дочерний поток).
* postInThread(XHttpRequest xHttpRequest): получение PostRequest-запроса (дочерний поток → дочерний поток).
* execute(XHttpRequest xHttpRequest, boolean isSyncRequest, boolean toMainThread): выполнение PostRequest-запроса и возврат observable-объекта (используя имя сущности в качестве ключа запроса).
* `executeToMain(XHttpRequest xHttpRequest)`: выполнение post-запроса и возвращение observable-объекта (основной поток → основной поток)
* `executeToMain(XHttpRequest xHttpRequest, BaseSubscriber<T> subscriber)`: выполнение post-запроса с последующей подпиской и возвращение информации о подписке (основной поток → основной поток)  

2. Пример использования:

XHttpRequest req = ApiProvider.getAddUserReq(getRandomUser()); XHttpSDK.executeToMain(req, new ProgressLoadingSubscriber(mIProgressLoader) { @Override public void onSuccess(Boolean aBoolean) { ToastUtils.toast("用户添加成功!"); mRefreshLayout.autoRefresh(); } });

---

**Использование XHttpProxy для выполнения запросов через прокси**

Перед использованием необходимо загрузить/определить соответствующий протокол интерфейса:

/**

  • 图书管理 / public interface IBook { /*

    • 购买书
    • @param bookId 图书ID
    • @param userId 用户ID
    • @param number 购买数量 / @NetMethod(parameterNames = {"bookId", "userId", "number"}, url = "/order/addOrder/", accessToken = false) Observable buyBook(int bookId, int userId, int number); /*
    • 获取图书
    • @param pageNum 第几页数
    • @param pageSize 每页的数量 */ @NetMethod(parameterNames = {"pageNum", "pageSize"}, paramType = FORM_BODY, url = "/book/findBooks/", accessToken = false) Observable<List> getBooks(int pageNum, int pageSize);

    /**

    • 获取所有图书

    */ @NetMethod(action = GET, url = "/book/getAllBook", accessToken = false) Observable<List> getAllBooks(); }


1. Описание аннотаций:
| Аннотации | Параметры | Тип | Значение по умолчанию | Описание |
| --- | --- | --- | --- | --- |
| @NetMethod | parameterNames | String[] | {} | Набор имён параметров |
| | paramType | int | JSON=1 | Тип параметра |
| | action | String | POST="post" | Действие запроса |
| | baseUrl | String | "" | Устанавливает базовый URL данного запроса |
| | url | String | "" | Адрес сетевого интерфейса |
| | timeout | long | 15000 | Время ожидания |
| | keepJson | boolean | false | Сохранять ли JSON |
| | accessToken | boolean | true | Требуется ли токен аутентификации |
| | cacheMode | CacheMode | CacheMode.NO_CACHE | Режим кэширования запроса |
| | cacheTime | long | -2 (используется глобальная настройка) | Срок действия кэша |
| | cacheKeyIndex | int | -1 (все параметры) | Индекс параметра, используемого в качестве ключа кэша |

2. Выполнение запроса с помощью XHttpProxy:

Создайте экземпляр XHttpProxy и передайте ему определённый интерфейс API. По умолчанию используется ThreadType.TO_MAIN.

* TO_MAIN: executeToMain (main → io → main)
> Обратите внимание: убедитесь, что сетевой запрос выполняется в основном потоке [фактически это асинхронный запрос (переключение на поток io), а ответ снова переключается на основной поток].

* TO_IO: executeToIO (main → io → io)
> Обратите внимание: убедитесь, что сетевой запрос выполняется в основном потоке [фактически это асинхронный запрос (переключение на поток io), но поток ответа остаётся прежним, то есть поток, который инициировал запрос].

* IN_THREAD: executeInThread (io → io → io)
> Обратите внимание: запрос должен выполняться в дочернем потоке, чтобы использовать этот тип [фактически синхронный запрос без переключения потоков].

3. Пример использования:

// Использование XHttpProxy для прокси-запросов к интерфейсу XHttpProxy.proxy(TestApi.IOrder.class) .buyBook(mBookAdapter.getItem(position).getBookId(), UserManager.getInstance().getUser().getUserId(), 1) .subscribeWith(new TipRequestSubscriber() { @Override public void onSuccess(Boolean aBoolean) { ToastUtils.toast("Покупка книги" + (aBoolean ? "успешна" : "неудачна") + "!"); mRefreshLayout.autoRefresh(); } });

---

**Загрузка и скачивание файлов**

1. Загрузка файлов (multipart/form-data):

Используйте метод post для загрузки файлов с данными формы. Используйте XHttp.post, затем используйте params для передачи связанных параметров, используйте uploadFile для передачи файла, который необходимо загрузить, и используйте следующий пример:

mIProgressLoader.updateMessage("Загружаю..."); XHttp.post("/book/uploadBookPicture") .params("bookId", book.getBookId()) .uploadFile("file", FileUtils.getFileByPath(mPicturePath), new IProgressResponseCallBack() { @Override public void onResponseProgress(long bytesWritten, long contentLength, boolean done) {

        }
    }).execute(Boolean.class)
    .compose(RxLifecycle.with(this).<Boolean>bindToLifecycle())
    .subscribeWith(new ProgressLoadingSubscriber<Boolean>(mIProgressLoader) {
        @Override
        public void onSuccess(Boolean aBoolean) {
            mIsEditSuccess = true;

Файл загружен

Используя XHttp.downLoad, передав адрес url для загрузки, путь сохранения файла и имя файла, можно завершить загрузку файла. Пример использования:

        .savePath(PathUtils.getExtPicturesPath())
        .execute(new DownloadProgressCallBack<String>() {
            @Override
            public void onStart() {
                HProgressDialogUtils.showHorizontalProgressDialog(getContext(), "Загрузка картинки...", true);
            }

            @Override
            public void onError(ApiException e) {
                ToastUtils.toast(e.getMessage());
                HProgressDialogUtils.cancel();
            }

            @Override
            public void update(long bytesRead, long contentLength, boolean done) {
                HProgressDialogUtils.onLoading(contentLength, bytesRead); //обновляем прогресс-бар
            }

            @Override
            public void onComplete(String path) {
                ToastUtils.toast("Загрузка картинки завершена, путь сохранения: " + path);
                HProgressDialogUtils.cancel();
            }
        });

Высокоуровневые операции с сетевыми запросами

Привязка к жизненному циклу запроса

  1. Загрузка и привязка к жизненному циклу запросов

При выполнении запроса подпишитесь на ProgressLoadingSubscriber или ProgressLoadingCallBack, передавая загрузчик сообщений IProgressLoader. Пример использования:

XHttpRequest req = ApiProvider.getAddUserReq(getRandomUser());
    XHttpSDK.executeToMain(req, new ProgressLoadingSubscriber<Boolean>(mIProgressLoader) {
        @Override
        public void onSuccess(Boolean aBoolean) {
            ToastUtils.toast("Добавление пользователя успешно!");
            mRefreshLayout.autoRefresh();
        }
    });
  1. Привязка жизненного цикла сетевого запроса к Activity/Fragment

(1) Здесь необходимо добавить зависимость RxUtil2: implementation 'com.github.xuexiangjys:rxutil2:1.1.2'

(2) В методе onCreate() Activity выполните блокировку:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RxLifecycle.injectRxLifecycle(this);
}

(3) Затем в запросе используйте оператор compose RxJava для привязки:

.compose(RxLifecycle.with(this).<Boolean>bindToLifecycle())

Перехватчики

Перехватчик логирования

(1) По умолчанию фреймворк предоставляет готовый перехватчик логирования HttpLoggingInterceptor, который можно настроить с помощью XHttpSDK.debug("XHttp"). Он имеет 5 режимов ведения журнала:

  • NONE: не вести журнал;

  • BASIC: регистрировать только «заголовок запроса» и «ответный заголовок»;

  • HEADERS: регистрировать все заголовки запроса и ответа;

  • PARAM: регистрировать только параметры запроса и ответа;

  • BODY: регистрировать всю информацию (по умолчанию).

(2) Если требуется выполнить пользовательскую регистрацию параметров сетевых запросов, то можно наследовать HttpLoggingInterceptor и реализовать собственный перехватчик регистрации сетевых запросов. Для этого нужно переопределить методы logForRequest и logForResponse.

(3) Настройка пользовательского перехватчика логирования:

XHttpSDK.debug(new CustomLoggingInterceptor());

Динамическое добавление параметров в перехватчики

Иногда требуется добавить некоторые фиксированные параметры запроса, но значения этих параметров могут меняться. В этом случае необходимо динамически добавлять параметры запроса [например, токен, время и подпись].

(1) Наследуйте BaseDynamicInterceptor, реализуйте метод updateDynamicParams:

@Override
protected TreeMap<String, Object> updateDynamicParams(TreeMap<String, Object> dynamicMap) {
    if (isAccessToken()) {//добавлять ли токен
        dynamicMap.put("token", TokenManager.getInstance().getToken());
    }
    if (isSign()) {//добавлять ли подпись
        dynamicMap.put("sign", TokenManager.getInstance().getSign());
    }
    if (isTimeStamp()) {//добавлять ли время
        dynamicMap.put("timeStamp", DateUtils.getNowMills());
    }
    return dynamicMap;//dynamicMap: существующие глобальные параметры + локальные параметры + новые динамические параметры
}

(2) Настройка перехватчика динамического добавления параметров:

XHttpSDK.addInterceptor(new CustomDynamicInterceptor()); //настройка перехватчика динамического добавления параметров

Проверка устаревших запросов с помощью перехватчиков

Когда сервер возвращает некоторые уникальные коды ошибок (обычно это ошибки проверки токена, устаревания или слишком частых запросов), необходимо выполнить глобальный перехват и обработку этих запросов. В этом случае требуется определить специальный перехватчик для обработки таких запросов.

(1) Наследовать BaseExpiredInterceptor, реализовать методы isResponseExpired и responseExpired:

/**
 * Определить, является ли ответ устаревшим
 *
 * @param oldResponse
 * @param bodyString
 * @return {@code true} : устаревший <br>  {@code false} : актуальный
 */
@Override
protected ExpiredInfo isResponseExpired(Response oldResponse, String bodyString) {
    int code = JSONUtils.getInt(bodyString, ApiResult.CODE, 0);
    ExpiredInfo expiredInfo = new ExpiredInfo(code);
    switch (code) {
        case TOKEN_INVALID:
        case TOKEN_MISSING:
            expiredInfo.setExpiredType(KEY_TOKEN_EXPIRED)
                    .setBodyString(bodyString);
            break;
        case AUTH_ERROR:
            expiredInfo.setExpiredType(KEY_UNREGISTERED_USER)
                    .setBodyString(bodyString);
            break;
        default:
            break;
    } (2) **Установка перехватчика для проверки недействительных запросов.**

```XHttpSDK.addInterceptor(new CustomExpiredInterceptor()); // Перехватчик для проверки действительности запросов```

### **Пользовательский API-запрос**

#### **Структура пользовательского API-запроса**

Если вы не хотите использовать стандартный объект `ApiResult` в качестве унифицированного объекта ответа сервера, например, вы хотите использовать следующую структуру ответа:

private int errorCode; // Код ошибки запроса private String errorInfo; // Описание причины ошибки запроса private T result; // Результат запроса private long timeStamp; // Время, возвращённое сервером


(1) Сначала наследуйте объект `ApiResult`, переопределите его методы `getCode()`, `getMsg()`, `isSuccess()`, `getData()` и `setData()`.

```public class CustomApiResult<T> extends ApiResult<T>{
    private int errorCode;
    private String errorInfo;
    private T result;
    private long timeStamp;

    public int getErrorCode() {
        return errorCode;
    }

    // Другие методы

    @Override
    public int getCode() {
        return errorCode;
    }

    @Override
    public String getMsg() {
        return errorInfo;
    }

    @Override
    public boolean isSuccess() {
        return errorCode == 0;
    }

    @Override
    public void setData(T data) {
        result = data;
    }

    @Override
    public T getData() {
        return result;
    }}```

(2) При выполнении запроса используйте метод `execute(CallBackProxy)` или `execute(CallClazzProxy)` для выполнения запроса. 60)
    // Кэш будет хранить данные в течение 300 секунд, по умолчанию используется постоянное хранение.  
    // Работают как okhttp, так и пользовательский кэш.
    .cacheDiskConverter(new GsonDiskConverter()) // По умолчанию используется new SerializableDiskConverter();
    .timeStamp(true)
    .execute(new ProgressLoadingCallBack<CacheResult<List<Book>>>(mIProgressLoader) {
        @Override
        public void onSuccess(CacheResult<List<Book>> cacheResult) {
            ToastUtils.toast("Запрос успешно выполнен!");
            String from;
            if (cacheResult.isFromCache) {
                from = "Данные взяты из кэша";
            } else {
                from = "Данные получены из удалённой сети";
            }
            showResult(from + "\n" + JsonUtil.toJson(cacheResult.data));
        }

        @Override
        public void onError(ApiException e) {
            super.onError(e);
            ToastUtils.toast(e.getDisplayMessage());
        }
    });

## Конфигурация запутывания

#XHttp2 -keep class com.xuexiang.xhttp2.model.** { ; } -keep class com.xuexiang.xhttp2.cache.model.* { ; } -keep class com.xuexiang.xhttp2.cache.stategy.**{;} -keep class com.xuexiang.xhttp2.annotation.** { *; }

#okhttp -dontwarn com.squareup.okhttp3.** -keep class com.squareup.okhttp3.** { ;} -dontwarn okio.* -dontwarn javax.annotation.Nullable -dontwarn javax.annotation.ParametersAreNonnullByDefault -dontwarn javax.annotation.**

Retrofit

-dontwarn retrofit2.** -keep class retrofit2.** { *; } -keepattributes Exceptions

RxJava RxAndroid

-dontwarn sun.misc.** -keepclassmembers class rx.internal.util.unsafe.ArrayQueueField* { long producerIndex; long consumerIndex; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { rx.internal.util.atomic.LinkedQueueNode producerNode; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { rx.internal.util.atomic.LinkedQueueNode consumerNode; }

#Если использовать пакет для анализа Gson, просто добавьте следующие несколько строк, и всё будет работать без ошибок -keepattributes Signature -keep class com.google.gson.stream.** { ; } -keepattributes EnclosingMethod -keep class org.xz_sale.entity.**{;} -keep class com.google.gson.** {;} -keep class com.google.**{;} -keep class sun.misc.Unsafe { ; } -keep class com.google.gson.stream.* { ; } -keep class com.google.gson.examples.android.model.* { *; }


---

## Особая благодарность

https://github.com/zhou-you/RxEasyHttp

## Если вам нравится проект, рассмотрите возможность поддержать его

> Ваша поддержка — это моя мотивация, я опубликую список всех спонсоров в качестве доказательства, пожалуйста, оставьте комментарий перед тем, как сделать пожертвование!

![pay.png](https://raw.githubusercontent.com/xuexiangjys/Resource/master/img/pay/pay.png)

Спасибо следующим спонсорам:

Имя | Сумма | Способ
:-|:-|:-
*Голос | 50¥ | WeChat
**Восток | 5¥ | Alipay

## Контакты

[![](https://img.shields.io/badge/нажмите%20для%20быстрого%20присоединения%20к%20группе%20QQ-602082750-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=9922861ef85c19f1575aecea0e8680f60d9386080a97ed310c971ae074998887)

> Для получения дополнительной информации, пожалуйста, отсканируйте QR-код и подпишитесь на мой личный публичный аккаунт WeChat: [Мой Android с открытым исходным кодом]

![](https://s1.ax1x.com/2022/04/27/LbGMJH.jpg)

[demo-gif]: ./img/demo.gif
[download-svg]: https://img.shields.io/badge/downloads-2.61M-blue.svg
[download-url]: https://github.com/xuexiangjys/XHttp2/blob/master/apk/xhttp2_demo_1.0.apk?raw=true
[download-img]: ./img/download.png

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Мощная библиотека сетевых запросов, которая использует комбинацию RxJava2, Retrofit2 и OKHttp. Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/xuexiangjys-XHttp2.git
git@api.gitlife.ru:oschina-mirror/xuexiangjys-XHttp2.git
oschina-mirror
xuexiangjys-XHttp2
xuexiangjys-XHttp2
master