Я разрабатываю приложение для Android, которое получает изображение через BluetoothSocket, и я пытаюсь получить его Bitmap с помощью метода BitmapFactory.decodeStream(), поэтому что-то вроде:

// Create BluetoothSocket socket and connect
// ...

// Get input stream
InputStream inputStream = socket.getInputStream();

// Get image bitmap
Bitmap bmp = BitmapFactory.decodeStream(inputStream);

Что происходит, так это то, что приложение зависает на BitmapFactory.decodeStream(). Чтобы приложение не зависало навсегда, мне приходится вручную закрывать сокет с удаленного устройства, и только после этого изображение успешно декодируется и возвращается, но это невыполнимое решение для моего приложения, так как я хочу, чтобы изображение передавалось программно. . Еще одна вещь, которую я пробовал, заключалась в том, чтобы создать отдельный Thread и вызвать decodeStream() внутри него, затем вызвать Thread.start(), а затем Thread.join(milliseconds) с жестко запрограммированным количеством времени ожидания до убить ветку. Это работает, но я хотел бы избежать этого по двум причинам:

  • Я хочу, чтобы передача была максимально быстрой, но в этом случае, если изображение меньше ожидаемого, поток занимает больше времени, чем необходимо.
  • Если изображение больше ожидаемого, поток может завершиться, пока данные еще обрабатываются, что приведет к некоторой потере данных.

Я просмотрел документацию в надежде найти способ предварительно установить ожидаемый размер изображения перед декодированием изображения, чтобы приложение знало, когда данные были получены правильно, что-то вроде:

// Estimate length of expected data (size of image in bytes)
byte[] numOfBytes = new byte[inputStream.available()];
inputStream.read(numOfBytes)

// Set preliminary options
BitmapFactory.Options options = new BitmapFactory.Options();
options.setExpectedImageSize(numOfBytes)          // what I would ideally want: set fixed number of bytes 
                                                  // to let the BitmapFactory.decodeStream() method know when
                                                  // the decoding is over

// Get image bitmap
Bitmap bmp = BitmapFactory.decodeStream(inputStream, null, options);

Но такой процедуры, кажется, не существует.

Есть ли способ добиться этого?

КОД

Серверная сторона (C#)

// Create bluetooth socket 
// ...

DataWriter writer = new DataWriter(socket.OutputStream);

// Send image byte array
writer.WriteBytes(imageByteArray);
await writer.StoreAsync();
await writer.FlushAsync();

writer.Dispose();
socket.Dispose();

Клиентская сторона (Java)

// Create bluetooth socket and connect
// ...

InputStream inputStream = socket.getInputStream();

Bitmap bmp = BitmapFactory.decodeStream(inputStream);

return bmp;

Отправил: Отправленное изображение

Получила: Полученное изображение

0
galligio 26 Янв 2022 в 12:20
1
inputStream.read(numOfBytes) Вы не можете потреблять весь поток и после этого использовать BitmapFactory.decodeStream(), так как тогда поток потребляется.
 – 
blackapps
26 Янв 2022 в 15:54

2 ответа

Если decodeStream не завершается, это, скорее всего, означает, что InputStream, из которого он пытается прочитать, заблокирован. А это значит, что он не получил ожидаемых данных.

Существует еще одно возможное объяснение: сервер может отправлять искаженное изображение, из-за чего декодер зависает на стороне клиента. Одним из примеров в декодере изображений Java 5... давно исправленном... был CVE -2006-6745. Могут быть более свежие.

По-видимому, в отличие от классов декодера изображений Java SE, decodeStream Android не вызывает close(). Это означает, что нет никакого способа определить, завершилась ли часть вызова decodeStream, проверив его результат. В javadoc указано:

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

Но я думаю, что вы действительно упускаете суть. Поправьте меня, если я ошибаюсь, но на самом деле вы пытаетесь не допустить, чтобы размер клиента блокировал сервер и наоборот. Решением этого является использование таймеров или тайм-аутов.

  • На стороне сервера используйте таймер, чтобы определить, что отправка (полного) файла изображения заняла слишком много времени.

  • На стороне клиента используйте таймер, чтобы определить, что decoderStream заняло слишком много времени.

  • В любом случае ваш код должен иметь возможность вызывать закрытие потока. На стороне клиента вызов decoderStream должен возвращать null. (По крайней мере, это то, что, похоже, подразумевает документация.)


Ваша идея исправить это была примерно следующей:

byte[] numOfBytes = new byte[inputStream.available()];
inputStream.read(numOfBytes);
BitmapFactory.Options options = new BitmapFactory.Options();
options.setExpectedImageSize(numOfBytes);
Bitmap bmp = BitmapFactory.decodeStream(inputStream, null, options);

Такой подход фатально ошибочен.

  1. Вызов InputStream.available для входного потока сокета сообщает вам, сколько данных можно прочитать без блокировки. По сути, это данные, которые уже были получены в TCP/IP-буферах ядра на стороне клиента. Это будет не весь образ.

  2. Поэтому inputStream.read(numOfBytes) не будет читать все изображение.

  3. Но тогда вы не сделаете ничего полезного с numOfBytes.

  4. Невозможно установить «ожидаемый размер изображения» в Options.

0
Stephen C 26 Янв 2022 в 17:58

Когда BitmapFactory.decodeStream() будет готов, он закроет поток. И, следовательно, сокет тоже будет закрыт. Вы говорите, что он висит? Ну... хммм. Хм.

Вот почему я сказал вам ранее, что сервер должен закрыть сокет после отправки файла. Что вызывает мой изображение портится при передаче по Bluetooth RFCOMM с использованием FTP?

Если вы хотите сохранить соединение открытым после отправки/получения изображения, вы не можете использовать BitmapFactory.decodeStream(). –

надеясь найти способ предварительно установить ожидаемый размер изображения перед его декодированием,

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

Клиент сначала считывает это целое число и может создать массив байтов нужного размера, куда он помещает полученные байты файла. Затем вы можете использовать BitmapFactory.decodeByteArray(). (или что-то вроде того).

0
blackapps 26 Янв 2022 в 12:57
Спасибо за ответ. Если я попытаюсь закрыть сокет с сервера сразу после отправки файла, клиент получит изображение только частично. Кроме того, в моем случае BitmapFactory.decodeStream() не закрывает поток, а просто зависает навсегда, пока я не закрою сокет вручную, и я не понимаю, почему это нормальное поведение? Я не хочу оставлять соединение открытым, поэтому decodeStream() будет хорошим вариантом.
 – 
galligio
26 Янв 2022 в 13:12
Я также пытался использовать decodeByteArray (без разделения данных на подмассивы), но иногда получаю поврежденные данные. Я открыл проблему по этому поводу: f" title="что вызывает повреждение моего изображения во время передачи через Bluetooth rfcomm с использованием f">stackoverflow.com/questions/70850912/…
 – 
galligio
26 Янв 2022 в 13:17
I don't want to keep the connection open so decodeStream() would be a good option. ?? Нет. Это плохой вариант. Тогда вам лучше использовать decodeByteArray().
 – 
blackapps
26 Янв 2022 в 13:30
Вы не можете использовать decodeByteArray() до того, как сначала отправите/получите целое число размера. Пожалуйста, покажите полный код.
 – 
blackapps
26 Янв 2022 в 13:31
Вы сказали, что «Если вы хотите сохранить соединение открытым после отправки/получения изображения, вы не можете использовать BitmapFactory.decodeStream()», но это не так, поскольку я действительно хочу, чтобы соединение было закрыто после передачи изображения. Извините, если я не ясно выразился.
 – 
galligio
26 Янв 2022 в 13:47