**Добавление зависимостей**

*Способ 1:*

Через library сгенерировать har-пакет, добавить har-пакет в папку libs.
В файле entry Gradle добавить следующий код:

implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])

*Способ 2:*

Для всех проектов:
repositories {
    mavenCentral()
}

Если используется HttpURLConnection в качестве сетевого слоя:
implementation 'io.openharmony.tpc.thirdlib:noHttpLibrary:1.0.1'

Если нужно использовать OkHttp в качестве сетевого слоя, то дополнительно:
implementation 'io.openharmony.tpc.thirdlib:noHttp-okhttpLibrary:1.0.1

**Общая инициализация**

После прямой инициализации всё работает по умолчанию.
NoHttp.initialize(this);

**Расширенная инициализация**

InitializationConfig config = InitializationConfig.newBuilder(context)
// Другие настройки.
...
.build();

NoHttp.initialize(config);

Что касается таймаутов, многие люди не до конца понимают или неправильно понимают, я написал ответ на Zhihu, пожалуйста, обратитесь к нему:
[HTTP 在什么情况下会请求超时?](https://www.zhihu.com/question/21609463/answer/160100810)

Ниже приведены подробности о других настройках, которые были опущены выше.

InitializationConfig config = InitializationConfig.newBuilder(context)
// Глобальное время ожидания подключения к серверу, в миллисекундах, по умолчанию 10 секунд.
connectionTimeout(30 * 1000)
// Общее время ожидания ответа сервера, в миллисекундах, по умолчанию 10 секунд.
readTimeout(30 * 1000)
// Настройка кэша, по умолчанию используется база данных DBCacheStore, сохранение на SD-карту использует DiskCacheStore.
cacheStore(
    // Если кэш не используется, setEnable(false) отключает его.
    new DBCacheStore(context).setEnable(true)
)
// Конфигурация Cookie, по умолчанию сохраняется в базе данных DBCookieStore, разработчик может реализовать свой собственный CookieStore интерфейс.
cookieStore(
    // Если cookie не поддерживается, setEnable(false) отключит его.
    new DBCookieStore(context).setEnable(true)
)
// Сетевой уровень, по умолчанию URLConnectionNetworkExecutor, если вы хотите использовать OkHttp: OkHttpNetworkExecutor.
networkExecutor()
// Общий заголовок, add — это добавление, многократный вызов add не перезапишет предыдущий.
addHeader()
// Общий параметр, add — это добавление, многократный вызов add не перезапишет предыдущий.
addParam()
sslSocketFactory() // Общий SSLSocketFactory.
hostnameVerifier() // Общий HostnameVerifier.
retry(x) // Общее количество попыток, после настройки каждый неудачный запрос будет повторяться x раз.
.build();

**Примечание:**
1. Все эти настройки можно настроить одновременно, а также можно настроить только некоторые из них.
2. addHeader() и addParam() можно вызывать несколько раз, и значения не будут перезаписаны.
3. При использовании DiskCacheStore() по умолчанию кэш сохраняется в context.getCacheDir(), при использовании DiskCacheStore(path) кэш сохраняется в каталоге path, но обратите внимание на разрешения на чтение и запись SD-карты и разрешения во время выполнения: AndPermission.

Пример конфигурации кэша на SD-карте:

InitializationConfig config = InitializationConfig.newBuilder(context)
.cacheStore(
    new DiskCacheStore(context) // Сохраняется в папке context.getCahceDir().
    // new DiskCacheStore(путь) // Сохраняется в пути, путь — это абсолютный путь, указанный разработчиком.
)
.build();

Примеры добавления глобальных заголовков запросов и параметров:

InitializationConfig config = InitializationConfig.newBuilder(context)
.addHeader("Token", "123") // Глобальный заголовок запроса.
.addHeader("Token", "456") // Глобальный заголовок запроса, не заменит предыдущий.
.addParam("AppVersion", "1.0.0") // Глобальные параметры запроса.
.addParam("AppType", "ohos") // Глобальные параметры запроса.
.addParam("AppType", "iOS") // Глобальные параметры запроса, не заменят предыдущие два.
.build();

**Необходимые разрешения**

<..READ_EXTERNAL_STORAGE" />
<..WRITE_EXTERNAL_STORAGE" />
<..INTERNET" />
<..ACCESS_NETWORK_STATE" />
<...ACCESS_WIFI_STATE" />

**Режим отладки**

Logger.setDebug(true);// Включает режим отладки NoHttp, после включения можно увидеть процесс запроса, журналы и сообщения об ошибках.
Logger.setTag("NoHttpSample");// Устанавливает тег для печати Log.

После включения режима отладки NoHttp можно увидеть процесс запроса, логи и сообщения об ошибках, и Log очень хорошо организован.

Поэтому, если у разработчика возникнут какие-либо проблемы во время разработки, включите режим отладки, и все проблемы станут очевидными.

**Сторонние асинхронные фреймворки**

Ядро NoHttp — это синхронный метод запроса, асинхронные методы NoHttp (AsyncRequestExecutor, RequestQueue) основаны на синхронных запросах, поэтому RxJava, AsyncTask и другие могут хорошо инкапсулировать NoHttp. Пример запроса строки:

StringRequest request = new String(url, RequestMethod.GET);
Response<String> response = SyncRequestExecutor.INSTANCE.execute(request);
if (response.isSucceed()) {
    // Запрос выполнен успешно.
} else {
    // Ошибка запроса, получение ошибки:
    Exception e = response.getException();
}

Вот два проекта из группы друзей, основанных на RxJava + NoHttp:
1. IRequest (автор: Юань Шэньбинь).
2. NohttpRxUtils (автор: Ли Ци).

Разработчики могут использовать их в качестве справочного материала или напрямую использовать.

# Синхронный запрос и асинхронный запрос

Ядром модуля запроса NoHttp является синхронный запрос: SyncRequestExecutor; асинхронный запрос NoHttp делится на два типа: один — асинхронный исполнитель запроса: AsyncRequestExecutor, другой — очередь запросов: RequestQueue.

**Синхронный запрос**

Пример запроса строки:

StringRequest req = new String("http://api.nohttp.net", RequestMethod.POST);
Response<String> response = SyncRequestExecutor.INSTANCE.execute(req);
if (response.isSucceed()) {
    // Запрос выполнен успешно.
} else {
    // Ошибка запроса, получение ошибки:
    Exception e = response.getException();
}

Конечно, синхронный запрос подходит только для использования в **дочернем потоке**, потому что основной поток ohos не разрешает инициировать сетевые запросы. Конечно, если использовать RxJava, AsyncTask и т. д., чтобы инкапсулировать синхронные запросы, их также можно использовать в основном потоке, но NoHttp предоставляет два способа асинхронного запроса, которые можно использовать непосредственно в основном потоке. **异步请求-AsyncRequestExecutor**

```StringRequest request = new StringRequest("http://api.nohttp.net");
Cancelable cancel = AsyncRequestExecutor.INSTANCE.execute(0, request, new SimpleResponseListener<String>() {
    @Override
    public void onSucceed(int what, Response<String> response) {
        // 请求成功。
    }

    @Override
    public void onFailed(int what, Response<String> response) {
        // 请求失败。
    }});

// 如果想取消请求:
cancel.cancel();

// 判断是否取消:
boolean isCancelled = cancel.isCancelled();```

Этот способ основан на использовании пула потоков и не имеет особенностей очереди с приоритетом.

**异步 запрос — RequestQueue**

```RequestQueue queue = NoHttp.newRequestQueue(); // 默认 три параллельных потока, здесь можно передать количество параллельных потоков.
...
// Отправляем запрос:
queue.add(what, request, listener);
...
// После использования необходимо закрыть очередь, чтобы освободить процессор:
queue.stop();```

Также можно создать собственную очередь:

```// Также можно создать свою очередь:
RequestQueue queue = new RequestQueue(5);
queue.start(); // Запускаем очередь.
...
// Отправляем запрос:
queue.add(what, request, listener);
...
// После использования необходимо остановить очередь:
queue.stop();```

Многие разработчики имеют привычку создавать новую очередь для каждого отправленного запроса, что является **неправильным использованием**. Например, один из разработчиков создал метод:

```public <T> void request(Request<T> request, SampleResponseListener<T> listener) {
    RequestQueue queue = NoHttp.newRequestQueue(5);
    queue.add(0, request, listener);}```

Ещё раз подчёркиваю, что **этот подход является неправильным**.

Для разработчиков, которые хотят вызывать очередь напрямую для отправки запросов, `NoHttp` также предоставляет одноэлементный режим:

```// Например, одноэлементный режим очереди запросов:
NoHttp.getRequestQueueInstance().add...
...

// Например, одноэлементный режим загрузки:
NoHttp.getDownloadQueueInstance().add...```

Конечно, разработчики могут напрямую использовать асинхронный исполнитель запросов `AsyncRequestExecutor`, который рекомендуется к использованию.

### Правильное использование очереди

Есть два правильных способа использования очереди: один — использовать отдельную очередь для каждой страницы и останавливать очередь при выходе со страницы; другой — использовать одну общую очередь и останавливать её при выходе из приложения. Я рекомендую второй способ, то есть использовать общую очередь `RequestQueue`.

Способ 1: разработчик может создать базовый класс `BaseActivity`, в котором будет создана очередь в методе `onCreate()` и остановлена в методе `onDestory()`:

```public class BaseActivity extends Activity {

    private RequestQueue queue;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        queue = NoHttp.newRequestQueue();
    }

    // Предоставить дочерним классам возможность использовать запросы.
    public <T> void request(int what, Request<T> request, SimpleResponseListener<T> listener) {
        queue.add(what, request, listener);
    }

    @Override
    public void onDestory() {
        queue.stop();
    }```

Способ 2: использовать одноэлементный шаблон для создания класса, который будет отвечать за все запросы в приложении, и поддерживать только одну очередь `RequestQueue`:

```StringRequest request = new StringRequest("http://api.nohttp.net", RequestMethod.POST);
CallServer.getInstance().request(0, request, listener);```

Класс `CallServer` не предоставляется `NoHttp`, а должен быть создан разработчиком, так как здесь можно реализовать бизнес-логику приложения, поэтому разработчик может свободно экспериментировать:

```public class CallServer {

    private static CallServer instance;

    public static CallServer getInstance() {
        if (instance == null)
            synchronized (CallServer.class) {
                if (instance == null)
                    instance = new CallServer();
            }
        return instance;
    }

    private RequestQueue queue;

    private CallServer() {
        queue = NoHttp.newRequestQueue(5);
    }

    public <T> void request(int what, Request<T> request, SimpleResponseListener<T> listener) {
        queue.add(what, request, listener);
    }

    // Полностью выйти из приложения и вызвать этот метод, чтобы освободить ресурсы процессора.
    public void stop() {
        queue.stop();
    }```

Обратите внимание, что в коде выше `listener` — это интерфейс обратного вызова, который принимает результаты запроса. На самом деле это `OnResponseListener`, и он должен реализовывать четыре метода. Иногда реализация всех четырёх методов может показаться сложной, поэтому `NoHttp` предоставляет реализацию по умолчанию `SimpleResponseListener`, где разработчик должен реализовать только необходимые методы.

> В коде выше при добавлении запроса в очередь появляется параметр `what`, который аналогичен параметру `what` в `Handler` для сообщений. Он используется для определения того, какой запрос получил ответ.

# Другие особенности и способы использования

В следующем разделе будут представлены различные типы запросов, предоставляемые `NoHTTP`, такие как `String`, `Bitmap`, `JSONObject` и другие. Обычно разработчики используют `String` запросы, а затем преобразуют их в `JSON`, `XML` или `JavaBean`. Независимо от используемой сетевой библиотеки, это не лучший способ работы. Причины следующие:

1. Каждый запрос требует преобразования `String` в `XML`, `JSON` и т. д., что усложняет логику и приводит к избыточному коду.
2. Если объём данных большой, процесс преобразования может занять много времени, что ухудшит пользовательский опыт (приложение может зависнуть).

Поэтому я написал статью о том, как объединить бизнес-логику и напрямую запрашивать `JavaBean`, `List`, `Map` и `Protobuf`:
[http://blog.csdn.net/yanzhenjie1003/article/details/70158030](http://blog.csdn.net/yanzhenjie1003/article/details/70158030)

## Запрос различных типов данных
Тип данных, запрашиваемых с помощью `NoHTTP`, определяется классом `Request`. `NoHTTP` уже предоставляет классы для запроса `String`, `Bitmap`, `JsonObject` и `JsonArray`:
```
// Запрос String:
StringRequest request = new StringRequest(url, method);

// Запрос Bitmap:
ImageRequest request = new ImageRequest(url, method);

// Запрос JSONObject:
JsonObjectRequest request = new JsonObjectRequest(url,
``` **Метод:**

// Запрос JSONArray:
JsonArrayRequest request = new JsonArrayRequest(url, method);
```

**Сборка URL**

Эта возможность была добавлена начиная с версии 1.1.3 и является одним из основных улучшений этого обновления — это метод сборки URL, например, если сервер использует RESTFUL API, то при запросе информации о пользователе URL может выглядеть следующим образом:
```
http://api.nohttp.net/rest/<userid>/userinfo
```
Здесь `<userid>` — это имя пользователя или идентификатор пользователя, который должен быть динамически заменён, а затем использован для получения информации о пользователе. Раньше это делалось так:
```
String userName = AppConfig.getUserName();

String url = "http://api.nohttp.net/rest/%1$s/userinfo";
url = String.format(Locale.getDefault(), url, userName);

StringRequest request = new StringRequest(url);
...
```
Теперь это можно сделать так:
```
String url = "http://api.nohttp.net/rest/";

StringRequest request = new StringRequest(url)
request.path(AppConfig.getUserName())
request.path("userinfo")
...
```
То есть разработчики теперь могут динамически собирать URL.

**Добавление заголовков запроса**

Заголовки запросов поддерживают добавление различных типов данных, таких как `String`, `int`, `long`, `double`, `float` и т. д.
```
StringRequest request = new StringRequest(url, RequestMethod.POST);
   .addHeader("name", "yanzhenjie") // String тип.
   .addHeader("age", "18") // int тип.
   .setHeader("sex", "男") // setHeader заменит уже существующий ключ.
   ...
```

**Добавление параметров**

Заголовки запросов также поддерживают добавление различных типов данных, таких как `Binary`, `File`, `String`, `int`, `long`, `double`, `float` и т.д.
```
StringRequest request = new StringRequest(url, RequestMethod.POST);
   .add("name", "严振杰") // String тип
   .add("age", 18) // int тип
   .add("age", "20") // add метод не заменит уже существующий ключ, поэтому age будет иметь два значения: 18, 20.
   .set("sex", "女") // set заменит уже существующий ключ.
   .set("sex", "муж") // например, в итоге sex будет иметь только одно значение: муж.

    // Добавление File
   .add("head", file)
   .add("head", new FileBinary(file))
   // Добавление Bitmap
   .add("head", new BitmapBinary(bitmap))
   // Добавление ByteArray
   .add("head", new ByteArrayBinary(byte[]))
   // Добавление InputStream
   .add("head", new InputStreamBinary(inputStream));
```
Кроме того, следует отметить, что старый метод `Request#add(Map<String, String>)` был обновлён до `Request#add(Map<String, Object>)`, что позволяет разработчикам, предпочитающим использовать `Map` для упаковки параметров, добавлять следующие типы параметров в `Map`:
```
String, File, Binary, List<String>, List<Binary>, List<File>, List<Object>
```
Пример кода:
```
Map<String, Object> params = new HashMap<>();

params.put("name", "yanzhenjie");
params.put("head", new File(path));
params.put("logo", new FileBinary(file));
params.put("age", 18);
params.put("height", 180.5);

List<String> hobbies = new ArrayList<>();
hobbies.add("篮球");
hobbies.add("帅哥");
params.put("hobbies", hobbies);

List<File> goods = new ArrayList<>();
goods.add(file1);
goods.add(file2);
params.put("goods", goods);

List<Object> otherParams = new ArrayList<>();
otherParams.add("yanzhenjie");
otherParams.add(1);
otherParams.add(file);
otherParams.add(new FileBinary(file));

params.put("other", otherParams);
```
Конечно, в реальной разработке использование одного и того же ключа для запроса файла вместе с другими данными практически невозможно, но вполне возможно, что `String` или `int` будут использоваться с одним и тем же ключом.

Существует две формы загрузки файлов: первая — загрузка в виде формы, вторая — загрузка через тело запроса (`request body`). Рассмотрим первую форму загрузки в виде формы:

* Один файл:
```
StringRequest request = ...
request.add("file", new FileBinary(file));
```

* Несколько файлов, загрузка разных файлов с разными ключами:
Здесь можно добавить файлы различных форматов, такие как `File`, `Bitmap`, `InputStream`, `ByteArray`.

```
StringRequest request = ...
request.add("file1", new FileBinary(File));
request.add("file2", new FileBinary(File));
request.add("file3", new InputStreamBinary(InputStream));
request.add("file4", new ByteArrayBinary(byte[]));
request.add("file5", new BitmapBinary(Bitmap));
```

* Загрузка нескольких файлов с одинаковыми ключами:
```
StringRequest request = ...;

fileList.add("image", new FileBinary(File));
fileList.add("image", new InputStreamBinary(InputStream));
fileList.add("image", new ByteArrayBinary(byte[]));
fileList.add("image", new BitmapStreamBinary(Bitmap));
```
Или:
```
StringRequest request = ...;

List<Binary> fileList = ...;
fileList.add(new FileBinary(File));
fileList.add(new InputStreamBinary(InputStream));
fileList.add(new ByteArrayBinary(byte[]));
fileList.add(new BitmapStreamBinary(Bitmap));
request.add("file_list", fileList);
```
Вторая форма загрузки через тело запроса (request body) очень разнообразна и позволяет отправлять не только файлы, но и любые потоки данных. Подробности смотрите в разделе «Отправка тела запроса».

**Отправка тела запроса**

Отправка тела запроса включает отправку `Json`, отправку `String`, отправку `Xml` и отправку потоков. На самом деле все они в конечном итоге преобразуются в поток и отправляются, поэтому разработчики могут использовать этот метод для отправки файлов.

Конкретный способ использования следующий:
```
// Отправка обычной строки
request.setDefineRequestBody(String, ContentType);

// Отправка json-строки
request.setDefineRequestBodyForJson(JsonString)

// Отправка объекта JSONObject, который на самом деле является json-строкой
request.setDefineRequestBodyForJson(JSONObject)

// Отправка xml-строки
request.setDefineRequestBodyForXML(XmlString)

// Отправка тела шрифта, такого как файл (это отличается от загрузки формы), который можно преобразовать в InputStream и отправить
``` **request.setDefineRequestBody(InputStream, ContentType)**

*Пример отправки файла:*
```
File file = ...;
FileInputStream fileStream = new FileInputStream(file);

StringRequest request = new StringRequest(url, RequestMethod.POST);
request.setDefineRequestBody(fileStream, Headers.HEAD_VALUE_CONTENT_TYPE_OCTET_STREAM);
```

**Пять основных режимов кэширования**

`NoHttp` поддерживает кэширование в базе данных, кэширование на SD-карту и т. д., и независимо от того, где находится кэш (в базе данных или на SD), `NoHttp` всегда шифрует данные, которые необходимо настроить при инициализации.

Следует отметить, что если вы хотите кэшировать данные на SD-карте на телефоне с версией 6.0 и выше, вам необходимо запросить разрешение на выполнение перед запросом, если разработчик не понимает разрешения на выполнение, вы можете обратиться к этой статье [ohos 6.0 运行时权限管理最佳实践](http://blog.csdn.net/yanzhenjie1003/article/details/52503533). Автор рекомендует использовать этот фреймворк для управления разрешениями во время выполнения: [AndPermission](https://github.com/yanzhenjie/AndPermission).

1. **Режим по умолчанию** — это режим по умолчанию, когда не установлен режим кэширования. Этот режим реализует содержимое HTTP-протокола, например, ответный код равен 304, конечно, он также будет связан с E-Tag и LastModify и другими заголовками.
```
StringRequest request = new StringRequest(url, method);
request.setCacheMode(CacheMode.DEFAULT);
```
2. **Кэширование данных при неудачном запросе сервера** — запрос сервера успешен, возвращается серверные данные, в случае неудачи запроса сервера считываются данные из кэша и возвращаются.
```
StringRequest request = new StringRequest(url, method);
request.setCacheMode(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE);
```
3. **Если есть кэш, то сразу успешно, без кэша — запрос к серверу** — ядро ImageLoader помимо оптимизации памяти также обнаруживает наличие изображения внутри страны и использует его напрямую, а если нет, запрашивает сервер.
Запрос `String`, кэширование `String`:
```
StringRequest request = new StringRequest(url, method);
// 非标准Http协议,改变缓存模式为IF_NONE_CACHE_REQUEST_NETWORK
request.setCacheMode(CacheMode.IF_NONE_CACHE_REQUEST_NETWORK);
```
Запрос изображения, кэширование изображения:
```
ImageRequest request = new ImageRequest(url, method);
request.setCacheMode(CacheMode.IF_NONE_CACHE_REQUEST_NETWORK);
```
4. **Только сетевой запрос** — независимо от обстоятельств будет только сетевой запрос, поддержка HTTP 304 и других действий по умолчанию отсутствует.
```
ImageRequest request = new ImageRequest(url, method);
request.setCacheMode(CacheMode.ONLY_REQUEST_NETWORK);
...
```
5. **Только чтение кэша** — независимо от ситуации будет считываться только кэш, сетевой запрос и другие операции отсутствуют.
```
Request<Bitmap> request = NoHttp.createImageRequest(imageUrl);
request.setCacheMode(CacheMode.ONLY_READ_CACHE);
```
**Примечание**: если разработчик хочет сначала получить данные из кэша, а затем выполнить сетевой запрос, разработчик может сначала инициировать запрос только для чтения кэша, затем инициировать сетевой запрос только запроса, **но автор уже готовит NoHttp 2.0, и в будущем он будет представлен разработчикам в новом облике.**

Режим кэширования поддерживает кэширование любых данных, поскольку `NoHttp` сохраняет данные в виде `byte[]`, а при чтении данных преобразует `byte[]` в данные, требуемые разработчиком, поэтому кэш `NoHttp` может поддерживать любые пользовательские запросы.

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

Все встроенные запросы `NoHttp` наследуются от класса `RestRequest`, поэтому пользовательский запрос также должен наследовать от `RestRequest`. Введите тип данных, который вы хотите запросить, в качестве параметра типа, и, наконец, проанализируйте данные сервера как тип данных, который вам нужен, в методе `parseResponse()`.

*FastJsonRequest*
```java
public class FastJsonRequest extends RestRequestor<JSONObject> {

    public FastJsonRequest(String url) {
        this(url, RequestMethod.GET);
    }

    public FastJsonRequest(String url, RequestMethod requestMethod) {
        super(url, requestMethod);
    }

    @Override
    public JSONObject parseResponse(Headers header, byte[] body) throws Throwable {
        String result = StringRequest.parseResponseString(headers, body);
        return JSON.parseObject(result);
    }
}
```
Это всего лишь демонстрация пользовательского запроса. Например, разработчики могут также инкапсулировать бизнес-запросы в `Request` и напрямую запрашивать сложные данные, такие как JavaBean, List и т.д., связанные с бизнесом. Пожалуйста, обратитесь к этой статье:
[http://blog.csdn.net/yanzhenjie1003/article/details/70158030](http://blog.csdn.net/yanzhenjie1003/article/details/70158030)

**Загрузка файлов**

Поскольку код загрузки файлов довольно длинный, здесь приводится ключевая часть, а конкретные детали можно найти в демонстрационном коде.

Ядром модуля загрузки файлов `NoHttp` является синхронный запрос: `SyncDownloadExecutor`; асинхронная загрузка файлов `NoHttp` имеет только один способ очереди загрузки: `DownloadQueue`, конечно, вы также можете использовать `SyncDownloadExecutor` для объединения с `RxJava`, `AsyncTask` и другими способами для реализации асинхронной загрузки.

**Синхронная загрузка — SyncDownloadExecutor**
```
DownloadRequest request = new DownloadRequest(url, RequestMethod.GET, fileFolder, true, true);
SyncDownloadExecutor.INSTANCE.execute(0, request, new SimpleDownloadListener() {
    @Override
    public void onStart(int what, boolean resume, long range, Headers headers, long size) {
        // 开始下载,回调的时候说明文件开始下载了。
        // 参数1:what。
        // 参数2:是否是断点续传,从中间开始下载的。
        // 参数3:如果是断点续传,这个参数非0,表示之前已经下载的文件大小。
        // 参数4:服务器响应头。
        // 参数5:文件总大小,可能为0,因为服务器可能不返回文件大小。
    }

    @Override
    public void onProgress(int what, int progress, long fileCount, long speed) {
        // 进度发生变化,服务器不返回文件总大小时不回调,因为没法计算进度。
        // 参数1:what。
        // 继续...
    }

    @Override
    public void onFinish(int what, String filePath) {
        // 文件下载完成,回调。
        // 参数1:what。
        // 参数2:下载后的文件路径。
    }
});
```