Это продолжение моего другого вопроса, потому что я заметил, что другие типы файлов тоже затронуты (не только изображения).
Мое приложение для Android (Java 8, минимальный SDK 24, целевой SDK 27) загружает изображения (jpg, png) и текстовые файлы (txt) с FTP-сервера, используя Apache Common FTPClient
(версия 3.6).
Проблема:
В определенном проценте этих файлов отсутствуют байты, и это также кажется довольно случайным:
- Если я загружаю файлы с сервера Win 10 в той же сети, около 50% файлов отсутствуют до 30 байт (изображения, для текстовых файлов обычно 1-2 байта).
- Если я загружаю файлы с сервера Linux в той же сети, обычно затрагиваются только 1-2 файла (из 10), и даже в затронутых изображениях обычно отсутствует только 1-2 байта. Редко никакие файлы не «сбоят».
Текстовые файлы, кажется, не сильно затронуты отсутствующими байтами, но изображения тем более. В зависимости от того, сколько байтов пропущено в итоге, они могут быть заметно повреждены: от отсутствующих/прозрачных строк внизу изображения (1-2 байта) до полностью перепутанных цветов или артефактов в нижней половине изображения (> 2 байта не хватает).
Я также протестировал свой код с двумя другими серверами, которые не находятся в одной сети:
- Частный сервер (не знаю, Win или Linux): он действительно глючит, и соединение часто просто обрывается при загрузке файлов, плюс он автоматически удаляет файлы раз в час, поэтому я не смог провести много тестов, но пока никакие загруженные файлы не были затронуты.
- DLP-тест: загруженные файлы удаляются через 15 или 30 минут, поэтому я не могу долгие тесты, но пока со скачанными файлами все в порядке.
Для тестирования я создал совершенно свежий проект. Я использую изображения, которые я создал сам (извините, я не могу ими поделиться) и случайные изображения, которые я нашел в Google — они весят от 12 КБ (которые всегда загружаются полностью) до около 8 МБ каждое. Вы можете найти альбом этих изображений (и поврежденных образцов загрузки) здесь. Файлы txt, которые я создал сам (извините, я тоже не могу ими поделиться), имеют размер около 14 МБ каждый.
Мое подозрение: файлы загружаются намного быстрее из той же сети, чем с внешнего сервера, поэтому, возможно, более быстрая загрузка вызывает «икоту», которая пропускает пару байтов. Но тогда почему это повлияет только на те, что в конце?
Мой код (без обработки исключений, так как это было бы слишком долго):
private boolean login() {
try {
ftpClient.connect(url,port);
if(ftpClient.isConnected()) {
if(ftpClient.login(username,password)) {
return true;
}
}
} catch (IOException e) {
//Ignoring this for now
}
return false;
}
private void logout() {
if(ftpClient!=null && ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
//Ignoring this for now
}
}
}
public void downloadFiles(final String remoteFolder, final String localFolder, final ArrayList<String> filenames) {
@SuppressLint("StaticFieldLeak") AsyncTask asyncTask = new AsyncTask<Object, Void, Void>() {
@Override
protected Void doInBackground(Object... params) {
if(login()) {
//Comment for version 1:
//In my actual code (with error handling) I create the OutputStream
//object here, so I can close it in "finally" but I still open a new
//one for every file:
//FileOutputStream out = null;
try {
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
if(ftpClient.changeWorkingDirectory(remoteFolder)) {
for (String filename : filenames) {
FTPFile[] singleFile = ftpClient.listFiles(filename);
if (singleFile != null && singleFile.length > 0) { //check if the file exists
String localPath = localFolder + File.separator + filename;
/////////////// Version 1 ///////////////
FileOutputStream out = new FileOutputStream(localPath);
if (!ftpClient.retrieveFile(filename, out)) {
out.close();
break;
}
out.close();
//Only for version 1:
File ff = new File(localPath);
long remoteLength = singleFile[0].getSize();
long newLength = ff.length();
if(newLength<remoteLength) {
Log.d(TAG,filename+" - should be: "+remoteLength+" / is: "+newLength);
} else {
Log.d(TAG,filename+" - OKAY");
}
//For version 2 & 3:
/*FTPFile single = singleFile[0];
long remoteLength = single.getSize();
InputStream is = ftpClient.retrieveFileStream(filename);
int len;
byte[] buffer = new byte[(int) (remoteLength)];
int newLength = 0;
/////////////// Version 2 ///////////////
BufferedInputStream bis = new BufferedInputStream(is);
while ((len = bis.read(buffer)) >= 0) {
if(len>0) {
newLength += len;
out.write(buffer,0,len);
} else {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
}
}
bis.close();
/////////////// Version 3 ///////////////
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = is.read(buffer, 0, buffer.length)) != -1) {
baos.write(buffer, 0, len);
newLength += len;
}
baos.flush();
FileOutputStream fos = new FileOutputStream(localPath);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
is.close();
//For version 2 & 3:
ftpClient.completePendingCommand();
if(newLength<remoteLength) {
Log.d(TAG,filename+" - should be: "+remoteLength+" / is: "+newLength);
} else {
Log.d(TAG,filename+" - OKAY");
}*/
} else {
break;
}
}
}
} catch (IOException e) {
//Ignoring this for now
}
}
return null;
}
@Override
protected void onPostExecute(Void param) {
logout();
}
};
asyncTask.execute();
}
В моей версии с полной обработкой исключений нет никаких исключений/ошибок.
Версия 1, по-видимому, имеет самый высокий уровень успеха (около 1/20 файлов отсутствуют байты), и, похоже, нет разницы между версией 2 и версией 3.
Я заметил одну вещь: чем меньше buffer
(версии 2 и 3), тем больше файлов полностью загружается с сервера 1. Размером, например, . 1000 только в 1-3 файлах не хватает байтов, но с 100000 это примерно половина "сбоя". Однако размер буфера, похоже, не сильно влияет на сервер 2, или, по крайней мере, это незаметно, потому что вероятность успеха уже довольно высока.
Вопрос:
Я что-то упустил в своем коде или я делаю что-то не так? Что может быть причиной того, что байты просто не «загружаются»? Следовательно: что я могу сделать, чтобы решить эту проблему?
FileOutputStream
вместоOutputStream
(ответ AndroidTeam)? - person Neph   schedule 09.06.2020FTPClient.retrieveFile
сFileOutputStream
. - person Martin Prikryl   schedule 09.06.2020FileOutputStream out = null;
находится в строке послеif(login())
, поэтому я могу закрыть его в своемfinally
(если нужно), но я все равно открываю новый для каждого файла, так что, надеюсь, это не должно иметь большого значения (я добавил это в код выше). - person Neph   schedule 09.06.2020ByteArrayOutputStream
— это только одна версия. Их 3, и все они в какой-то момент выдают ошибку. Если вы хотите протестировать его, выберите любой, который вам нравится, но я бы рекомендовал версию 2/3, чтобы быстрее увидеть ошибку. ;) Комментарий к объявлению 2: Внизу естьLog.d
для версии 2/3, и я только что добавил еще один для версии 1.Nor did you inform us
- Что ты имеешь в виду? Комментарий к объявлению 3: Сделать размер буфераremoteLength
— хороший способ проверить, сохраняется ли проблема. Тот факт, что вероятность успеха с меньшим размером составляет 90%, не делает его рабочим решением, на которое я могу полагаться/использовать. - person Neph   schedule 10.06.2020FileOutputStream
) использовала толькоInputStream
вместо этого +BufferedInputStream
, но я добавил последний, когда заметил проблему, пытаясь исправить это с помощью этого. Хотя, похоже, это не имеет значения. - person Neph   schedule 10.06.2020Nor did you inform us.
- О чем я вам не сообщил? - person Neph   schedule 10.06.2020newLength
иremoteLength
не всегда равна 2. Иногда отсутствует 0 байтов, иногда 1, а иногда может быть 20. Кажется, довольно случайно, какие файлы затронуты и сколько байтов отсутствует. Я только заметил, что определенные версии моего кода в сочетании с определенными серверами чаще вызывают проблему, чем другие. Я написал об этом в своем вопросе и поделился всеми своими выводами. - person Neph   schedule 10.06.2020AsyncTask
на обычныйThread
, но проблема осталась с первыми двумя серверами. Затем я написал свою собственную небольшую библиотеку для входа/выхода из системы и загрузки, используя Java по умолчаниюSocket
(поэтому без использования библиотеки Apache), но с этим тоже не повезло. Я также запустил свою библиотеку (но сSwingWorker
вместоAsyncTask
) как обычное приложение Windows сJFrame
иJButton
и протестировал ее примерно 30 раз с разными файлами: ни в одном из них не было пропущено ни одного байта. Похоже, это проблема с Android, сокетами и серверами в одной сети. :/ - person Neph   schedule 02.07.2020