Обёртка для RxJava2+, Retrofit2+ и OkHttp3, обеспечивающая элегантное выполнение запросов к API и загрузки файлов.
Переведено:
Остальные строки переведены на русский язык.# Выполнение запросов через RxRequest
RequestSetting
или DefaultRequestSetting
, и переопределите некоторые методы.public class RxHttpRequestSetting extends DefaultRequestSetting {
@NonNull
@Override
public String getBaseUrl() {
return Config.BASE_URL;
}
@Override
public int getSuccessCode() {
return 200;
}
}
Application
и передайте экземпляр конфигурационного класса.RxHttp.init(this);
RxHttp.initRequest(new RxHttpRequestSetting());
public class PublicHeadersInterceptor implements Interceptor {
private static String TIME = "";
private static String TOKEN = "";
public static void updateTime(String time) {
PublicHeadersInterceptor.TIME = time;
}
public static void updateToken(String token) {
PublicHeadersInterceptor.TOKEN = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
request = request.newBuilder()
.header(Constant.PUBLIC_HEADER_TIME_NAME, TIME)
.header(Constant.PUBLIC_HEADER_SIGN_NAME, getSign(request))
.build();
return chain.proceed(request);
}
private String getSign(Request request){
return MD5Coder.encode(request.url().url().toString() + "?token=" + TOKEN);
}
}
public class PublicParamsInterceptor implements Interceptor {
private static final String GET = "GET";
private static final String POST = "POST";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String method = request.method();
if (TextUtils.equals(method, GET)) {
request = addForGet(request);
} else if (TextUtils.equals(method, POST)) {
request = addForPost(request);
}
return chain.proceed(request);
}
}
``````markdown
Приватный метод `Request` **addForPost**(Request request) {
RequestBody requestBody = request.body();
если (requestBody == null) {
вернуть request;
} ещё если (requestBody instanceof FormBody) {
Список<Параметр> параметры = получитьОбщиеПараметры();
FormBody formBody = (FormBody) requestBody;
для (int i = 0; i < formBody.size(); i++) {
параметры.add(новый Параметр(formBody.name(i), formBody.value(i)));
}
JsonObjUtils json = JsonObjUtils.create();
для (Параметр param : параметры) {
json.add(param.getКлюч(), param.getЗначение());
}
LogUtils.i("PublicParamsInterceptor", "data=" + json.toJson());
FormBody.Builder formBodyBuilder = новый FormBody.Builder().add(Constant.PUBLIC_PARAM_KEY, json.toJson());
вернуть request.newBuilder().post(formBodyBuilder.build()).build();
} ещё если (requestBody instanceof MultipartBody) {
вернуть request;
} ещё {
попробовать {
если (requestBody.contentLength() == 0) {
вернуть request;
}
} catch (IOException e) {
LogUtils.e("PublicParamsInterceptor", "Error while getting content length", e);
}
}
}
Список<Параметр> параметры = получитьОбщиеПараметры();
JsonObjUtils json = JsonObjUtils.create();
для (Параметр param : параметры) {
json.add(param.getКлюч(), param.getЗначение());
}
LogUtils.i("PublicParamsInterceptor", "data=" + json.toJson());
FormBody.Builder formBodyBuilder = новый FormBody.Builder()
.add(Constant.PUBLIC_PARAM_KEY, json.toJson());
вернуть request.newBuilder()
.post(formBodyBuilder.build())
.build();
} ещё {
вернуть request;
}
} перехватить (IOException e) {
вернуть request;
}
}
}
## Четвертый раздел: Определение структуры ответа
Определите `ResponseBean<E>` как наследника от `BaseResponse<E>`, создайте члены переменных и реализуйте методы.
```java
public class ResponseBean<E> implements BaseResponse<E> {
@SerializedName(value = "code", alternate = {"status"})
private int code;
@SerializedName(value = "data", alternate = {"result"})
private E data;
@SerializedName(value = "msg", alternate = {"message"})
private String msg;
@Override
public int getCode() {
return code;
}
@Override
public void setCode(int code) {
this.code = code;
}
@Override
public E getData() {
return data;
}
@Override
public void setData(E data) {
this.data = data;
}
@Override
public String getMsg() {
return msg;
}
@Override
public void setMsg(String msg) {
this.msg = msg;
}
}
```
## Пятый раздел: Определение структуры данных API
```java
public class TimeBean extends BaseBean {
private String token;
private String time;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
```
## Шестой раздел: Определение класса API
1. Создайте новый подкласс, который наследуется от `Api`.
2. Определите внутренний класс `Service` для объявления запросов.
3. Определите статический безпараметрический метод для возврата экземпляра `Api`.
``````java
public class FreeApi extends Api {
public static Service api() {
return Api.api(Service.class);
}
public interface Config {
String BASE_URL = Config.HTTP_HOST + Config.HTTP_VERSION;
long HTTP_TIMEOUT = 5000;
}
}
```
## Интерфейсы кода и сервиса
```java
public interface Code {
int TIMEOUT = 1000; // Timeout for response
int REQUEST_ERROR = 1001; // Request error
int ILLEGAL_PARAMETER = 1002; // Invalid parameter
int SUCCESS = 2000; // Successful information retrieval
int SUCCESS_OLD = 104; // Successful information retrieval (old version)
int SUCCESS_NO_DATA = 2001; // No data available
int FAILED = 3000; // Failed information retrieval
int PHONE_EXISTS = 3001; // Phone number already registered
int PASSWORD_ERROR = 3002; // Password error
int PHONE_ILLEGAL = 3003; // Invalid phone number
int PHONE_NOT_BOUND = 3004; // Please bind the phone number
int PHONE_NOT_REGISTERED = 3005; // This phone number has not been registered yet
int ACCOUNT_DOES_NOT_EXIST = 4001; // Account does not exist
int ACCOUNT_EXCEPTION = 4002; // Account exception, please log in again
int ACCOUNT_FROZEN = 4003; // This account has been frozen, please contact the administrator
int ACCOUNT_DELETED = 4004; // This account was deleted by the administrator
int ERROR = 5000; // General access error
int NETWORK_ERROR = 5001; // Network error
}
``````java
public interface Service {
@GET("public/time")
Observable<ResponseBean<TimeBean>> getTime();
}
```
### Седьмой раздел: Определение обратного вызова запроса
```java
public interface RequestBackListener<T> {
void onStart();
void onSuccess(int code, T data);
void onFailed(int code, String message);
void onNoNetwork();
void onError(Throwable error);
void onFinish();
}
```
### Восьмой раздел: Упаковка базового класса BaseRequest
Упаковываем логику получения подписи и обработки обратного вызова.
Перед тем как сделать запрос к основному интерфейсу, возможно, потребуется получить время через другой интерфейс. Полученное время и поле подписи следует добавить в общие параметры или заголовок запроса перед отправкой основного запроса. Поэтому этот базовый класс упаковывает эту логику, выделяя общую часть кода.
```Если интерфейс времени также возвращает поля для проверки версий, такие как номер версии, то эти данные могут использоваться для определения процесса выполнения запроса, например, если требуется принудительное обновление и дальнейший запрос не нужен, это можно обработать здесь самостоятельно.
```java
public class BaseRequest {
protected static <T> Disposable requestWithSign(@NonNull RequestCallback<T> observable, @NonNull RequestBackListener<T> callback) {
return request(ProjectApi.api().getTime()
.flatMap(new Function<ResponseBean<TimeBean>, ObservableSource<ResponseBean<T>>>() {
@Override
public Observable<ResponseBean<T>> apply(ResponseBean<TimeBean> bean) {
PublicHeadersInterceptor.updateTime(bean.getData().getTime());
PublicHeadersInterceptor.updateToken(bean.getData().getToken());
return observable.request().subscribeOn(Schedulers.io());
}
}), callback);
}
}
``````markdown
## 9. Создание запроса
```java
public class PublicRequest extends BaseRequest {
/**
* Получение системного времени
*/
public static Disposable getTime(final RequestBackListener<TimeBean> listener) {
return request(ProjectApi.api().getTime(), listener);
}
}
```
``````markdown
/**
* Получение типа обратной связи
*/
public static Disposable feedbackType(final RequestBackListener<OtherBean> listener) {
return requestWithSign(new RequestCallback<OtherBean>() {
@Override
public Observable<ResponseBean<OtherBean>> request() {
return ProjectApi.api().feedbackTypeApi();
}
}, listener);
}
}
```## Класс Presenter
```java
void onCreate(V baseView) {
this.baseView = baseView;
context = baseView.getContext();
rxLife = RxLife.create();
}
void onDestroy() {
baseView = null;
context = null;
rxLife.destroy();
rxLife = null;
}
public RxLife getRxLife() {
return rxLife;
}
public void addToRxLife(Disposable disposable) {
if (rxLife != null) {
rxLife.add(disposable);
}
}
public V getBaseView() {
return baseView;
}
``````markdown
## API
### JsonObjUtils
Создает объект `JSONObject` и генерирует JSON-строку.
### RequestBodyUtils
Создает объект `RequestBody` для POST-запросов.
Например, для загрузки изображений:
```java
/**
* Ключ Значение
* img File
* content String
*/
@Multipart
@POST("public/img")
Observable<ResponseBean<UploadImgBean>> uploadImg(@PartMap Map<String, RequestBody> img);
```
Запрос отправляется следующим образом:
```java
File file = new File("/path/to/file");
Map<String, RequestBody> params = new HashMap<>();
params.put("img", RequestBody.create(MediaType.parse("image/*"), file));
params.put("content", RequestBody.create(MediaType.parse("text/plain"), "Some text"));
uploadImg(params).subscribe(response -> {
// Обработка ответа
});
```
``````java
public static Disposable uploadImg(String content, File imgFile, final RequestBackListener<UploadImgBean> listener) {
return requestWithSign(new RequestCallback<UploadImgBean>() {
@Override
public Observable<ResponseBean<UploadImgBean>> request() {
Map<String, RequestBody> map = RequestBodyUtils.builder()
.add("content", content)
.add("img", imgFile)
.build();
return ProjectApi.api().uploadImg(map);
}
}, listener);
}
```### HttpsCompat
Основной целью является предоставление семи статических методов для реализации игнора сертификатов и включения поддержки TLS 1.2 в Android версиях 4.4 и ниже.
```java
/**
* Игнорирует проверку сертификата, делая запрос аналогичным HTTP, что приводит к потере безопасности. Использование не рекомендуется.
*/
public static void ignoreSSLForOkHttp(OkHttpClient.Builder builder);
/**
* Включает поддержку TLS 1.2 для OkHttpClient.
*/
public static void enableTls12ForOkHttp(OkHttpClient.Builder builder);
/**
* Игнорирует проверку сертификата, делая запрос аналогичным HTTP, что приводит к потере безопасности. Использование не рекомендуется.
* Должен вызываться до использования HttpsURLConnection. Рекомендовано использовать в Application.
*/
public static void ignoreSSLForHttpsURLConnection();
/**
* Включает поддержку TLS 1.2 для HttpsURLConnection.
* Должен вызываться до использования HttpsURLConnection. Рекомендовано использовать в Application.
*/
public static void enableTls12ForHttpsURLConnection();
/**
* Получает SSLSocketFactory с поддержкой TLS 1.2.
* Рекомендовано использование в Android версиях 4.4 и ниже.
*/
public static SSLSocketFactory getEnableTls12SSLSocketFactory();
/**
* Получает HostnameVerifier, который игнорирует проверку сертификата.
* Должен использоваться вместе с {@link #getIgnoreSSLSocketFactory()}.
*/
public static HostnameVerifier getIgnoreHostnameVerifier();
/**
* Получает SSLSocketFactory, который игнорирует проверку сертификата.
* Должен использоваться вместе с {@link #getIgnoreHostnameVerifier()}.
*/
public static SSLSocketFactory getIgnoreSSLSocketFactory();
```## Часто задаваемые вопросы
### Отсутствие ответа на HTTP-запросы в системах Android 9.0 и выше
Официальная информация содержится в разделе "Изменения в области безопасности" документации. Приложение, ориентированное на платформу Android 9.0 или более позднюю версию, использует по умолчанию протокол сетевой транспортировки уровня безопасности (TLS), то есть функция `isCleartextTrafficPermitted()` возвращает значение `false`. Если вашему приложению требуется включение режима передачи данных без шифрования для конкретных доменов, вы должны явно установить значение `cleartextTrafficPermitted` равным `true` для этих доменов в конфигурации сети вашего приложения. Поэтому существует два способа решения:
Первый способ, включите HTTP, чтобы позволить открытое соединение (не рекомендуется).
1. Создайте `network_security_config.xml` в папке `res/xml`.
```xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
```
2. Установите атрибут `networkSecurityConfig` в теге `<application>` файла `AndroidManifest.xml`, указывая на созданный конфиг.
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
</manifest>
```
Второй способ, используйте протокол HTTPS для всех интерфейсов (рекомендуется).Этот метод требует правильной настройки сервера. Если после настройки остаются проблемы с доступом и сообщения о проблемах с сертификатами, проверьте конфигурацию сервера.```markdown
## Получение менеджера доверия
```java
private static TrustManager[] getTrustManager() {
return new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] цепочка, String тип_аутентификации) {
}
@Override
public void checkServerTrusted(X509Certificate[] цепочка, String тип_аутентификации) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void init(SecureRandom secure_random) {
}
}
};
}
```
### Предупреждение при запросе HTTPS о проблемах с сертификатами
Обычно это происходит из-за неправильно настроенного сертификата на сервере. Проверьте конфигурацию сервера.
При тестировании можно временно игнорировать сертификат, что сделает запрос аналогичным HTTP, но потеряет безопасность. Это недопустимо для использования в продакшене.
**Можно использовать класс-инструмент `HttpsCompat`.**
Код реализации представлен ниже:
```java
public static void ignoreSSLForOkHttp(OkHttpClient.Builder builder) {
builder.hostnameVerifier(getIgnoreHostnameVerifier())
.sslSocketFactory(getIgnoreSSLSocketFactory());
}
public static void ignoreSSLForHttpsURLConnection() {
HttpsURLConnection.setDefaultHostnameVerifier(getIgnoreHostnameVerifier());
HttpsURLConnection.setDefaultSSLSocketFactory(getIgnoreSSLSocketFactory());
}
/**
* Получаем объект IgnoreHostnameVerifier для игнора сертификатов
* Используется вместе с {@link #getIgnoreSSLSocketFactory()}
*/
private static HostnameVerifier getIgnoreHostnameVerifier() {
return new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession ssl_session) {
return true;
}
};
}
/**
* Получаем объект IgnoreSSLSocketFactory для игнора сертификатов
* Используется вместе с {@link #getIgnoreHostnameVerifier()}
*/
private static SSLSocketFactory getIgnoreSSLSocketFactory() {
try {
SSLContext ssl_context = SSLContext.getInstance("SSL");
ssl_context.init(null, getTrustManager(), new SecureRandom());
return ssl_context.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
``` @Override
public void checkServerTrusted(X509Certificate[] цепочка, String тип_аутентификации) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
}
```
### HTTPS запросы в Android 4.4 и ниже не могут быть выполнены
Сервер правильно настроен с SSL-сертификатами, а также открыты TLS 1.1 и TLS 1.2. Однако при попытках доступа через Android 4.4 и ниже возникают проблемы. Это связано с тем, что в этих версиях Android по умолчанию не поддерживаются TLS 1.2. Для решения этой проблемы требуется активировать поддержку TLS 1.2. Пример кода:
```java
public static void enableTls12ForHttpsURLConnection() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
SSLSocketFactory ssl = getEnableTls12SSLSocketFactory();
if (ssl != null) {
HttpsURLConnection.setDefaultSSLSocketFactory(ssl);
}
}
}
public static void enableTls12ForOkHttp(OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
SSLSocketFactory ssl = HttpsCompat.getEnableTls12SSLSocketFactory();
if (ssl != null) {
builder.sslSocketFactory(ssl);
}
}
}
public static SSLSocketFactory getEnableTls12SSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null);
return new Tls12SocketFactory(sslContext.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static class Tls12SocketFactory extends SSLSocketFactory {
private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};
private final SSLSocketFactory delegate;
private Tls12SocketFactory(SSLSocketFactory base) {
this.delegate = base;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
} @Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
}
```
@Override
public Connection createConnection(Connection s, String host, int port, boolean autoClose) throws IOException {
return patch(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Connection createConnection(String host, int port) throws IOException, UnknownHostException {
return patch(delegate.createSocket(host, port));
}
@Override
public Connection createConnection(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return patch(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Connection createConnection(InetAddress host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}
@Override
public Connection createConnection(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return patch(delegate.createSocket(address, port, localAddress, localPort));
}
private Connection patch(Connection s) {
if (s instanceof SSLSocket) {
((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
}
return s;
}
}
```
### Glide в Android 4.4 и ниже: проблемы с загрузкой изображений
Причина аналогична вышеописанной, требуется создание пользовательского модуля `AppGlideModule` для Glide, который передаст `OkHttpClient`, поддерживающий TLS 1.2.
```java
@GlideModule
public class CustomAppGlideModule extends AppGlideModule {
``` @Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(getOkHttpClient()));
} @Override
public boolean isManifestParsingEnabled() {
return false;
}
private static OkHttpClient getOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpsCompat.enableTls12ForOkHttp(builder);
return builder.build();
}
}
```
### RxDownload для загрузки файлов
#### Инициализация
Инициализацию можно выполнить как в классе `Application`, так и в начальной активности приложения.
```java
RxHttp.init(this);
// Опционально, если не указаны настройки, будет использована DefaultDownloadSetting
RxHttp.initDownload(new DefaultDownloadSetting() {
@Override
public long getTimeout() {
return 60000; // 60 секунд
}
});
```### Вызов
```java
RxDownload mRxDownload = RxDownload.create(et_url.getText().toString())
.setDownloadListener(new RxDownload.DownloadListener() {
@Override
public void onStarting(DownloadInfo info) {
tv_start.setText("Запуск...");
}
@Override
public void onDownloading(DownloadInfo info) {
tv_start.setText("Скачивание...");
}
@Override
public void onError(DownloadInfo info, Throwable e) {
tv_start.setText("Ошибка скачивания");
}
@Override
public void onStopped(DownloadInfo info) {
tv_start.setText("Остановлено");
}
@Override
public void onCanceled(DownloadInfo info) {
tv_start.setText("Отменено");
pb_ Yöntem1.setProgressBar(0);
}
``` @Override
public void onCompletion(DownloadInfo info) {
tv_start.setText("Завершение успешно");
}
})
.setProgressListener(new RxDownload.ProgressListener() {
@Override
public void onProgress(float progress, long downloadLength, long contentLength) {
pb_1.setProgress((int) (progress * 10000));
}
})
.setSpeedListener(new RxDownload.SpeedListener() {
@Override
public void onSpeedChange(float bytePerSecond, String speedFormat) {
tv_start.setText("Скачивание (" + speedFormat + ")");
}
});```markdown
tv_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRxDownload.start();
}
});
tv_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRxDownload.stop();
}
});
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRxDownload.cancel();
}
});
```
## Описание часто используемых классов
### RxHttp
Используется для инициализации и конфигурирования
### DownloadSetting/DefaultDownloadSetting
Настройки RxDownload
- #### `String getBaseUrl()`
Устанавливает базовый URL; передайте любой допустимый URL.
- #### `long getTimeout()`
Устанавливает время ожидания соединения; рекомендуется использовать значение больше 60 секунд.
- #### `long getConnectTimeout()`
Устанавливает время ожидания подключения; установка значения равного нулю приведёт к использованию значения `getTimeout()`; единица измерения — миллисекунды.
- #### `long getReadTimeout()`
Устанавливает время ожидания чтения; установка значения равного нулю приведёт к использованию значения `getTimeout()`; единица измерения — миллисекунды.
- #### `long getWriteTimeout()`
Устанавливает время ожидания записи; установка значения равного нулю приведёт к использованию значения `getTimeout()`; единица измерения — миллисекунды.
- #### `String getSaveDirPath()`
Устанавливает путь к каталогу по умолчанию для скачивания файлов.
- #### `DownloadInfo.Mode getDefaultDownloadMode()`
Получает режим по умолчанию при наличии файла в указанном пути, но отсутствии сохранённого прогресса скачивания.
### DownloadInfo
Используется для хранения информации о скачивании; если требуется поддержка возобновляемого скачивания, необходимо самостоятельно сохранять следующие обязательные поля:
```- #### String url
Адрес для скачивания файла **(обязательное поле)**
- #### String saveDirPath
Кастомный путь к каталогу для сохранения файла **(обязательное поле при возобновлении скачивания)**
- #### String saveFileName
Кастомное имя файла для сохранения, должно содержать расширение **(обязательное поле при возобновлении скачивания)**
- #### long downloadedLength
Длина уже скачанного файла **(обязательное поле при возобновлении скачивания)**
- #### long contentLength
Общая длина файла для скачивания
- #### State состояние
Текущее состояние скачивания
- ##### STARTING
Начало
- ##### DOWNLOADING
Скачивание
- ##### STOPPED
Не начато / Остановлено
- ##### ERROR
Ошибка скачивания
- ##### COMPLETION
Завершение скачивания
- #### Mode режим
Получает режим при наличии файла в указанном пути, но отсутствии сохраненного прогресса скачивания
- ##### APPEND
Добавление
- ##### REPLACE
Замена
- ##### RENAME
Переименование
- #### create(String)
Создаёт объект для скачивания, параметром является url
- #### create(String, String, String)
Создаёт объект для скачивания, параметрами являются url/путь к каталогу/имя файла
- #### create(String, String, String, long, long)
Создаёт объект для скачивания, параметрами являются url/путь к каталогу/имя файла/длина скачанного файла/общая длина файла
### RxDownload
- #### create(DownloadInfo)
Используется для создания новой задачи скачивания- #### setDownloadListener(DownloadListener)
Устанавливает слушатель состояния скачивания
- ##### onStarting()
Начало, установка соединения с сервером
- ##### onDownloading()
Скачивание
- ##### onStopped()
Остановлено, удалённая часть скачивания не будет удалена, поддерживается возобновление скачивания
- ##### onCanceled()
Отменено, удалённая часть скачивания будет удалена, повторное начало скачивания приведёт к его полной перезагрузке
- ##### onCompletion(DownloadInfo)
Загрузка завершена
- ##### onError(Throwable)
Ошибка при загрузке
- #### setProgressListener(ProgressListener)
- ##### onProgress(float)
Обратный вызов прогресса загрузки (от 0 до 1)
- #### setSpeedListener(SpeedListener)
- ##### onSpeedChange(float, String)
Обратный вызов скорости загрузки, два значения представляют количество байт, скачиваемых в секунду, и отформатированную скорость (например: 1.2 КБ/с, 3.24 МБ/с)
- #### start()
Начать загрузку/Продолжить загрузку
- #### stop()
Остановить загрузку, не удаляет уже загруженную часть, поддерживает возобновление с места прерывания
- #### cancel()
Отменить загрузку, удаляет уже загруженные части файла, повторное начало загрузки начнётся заново
### UnitFormatUtils
Инструмент форматирования единиц измерения
- #### calculateSpeed(long, float)
Вычислить скорость
- #### formatSpeedPerSecond(float)
Отформатировать скорость за секунду (например: 1.12 МБ/с, 628 КБ/с)
- #### formatSpeed(float, TimeUnit) Отформатировать скорость (например: 1.12 МБ/с, 628 КБ/с)
- #### formatBytesLength(float)
Отформатировать значение байтов (например: 12.1 КБ, 187.24 МБ, 154 ГБ)
- #### formatTimeUnit(TimeUnit)
Отформатировать единицы времени (например: секунды как s, миллисекунды как ms)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )