Android Tutorial [B4X] AsyncStreams Tutorial

Status
Not open for further replies.
New video tutorial:


AsyncStreams allows you to read data from an InputStream and write data to an OutputStream without blocking your program. The reading and writing are done with two separate threads.

When new data is available the NewData event is raised with the data.
When you write data to the OutputStream the data is added to an internal queue and then sent in the background.

AsyncStreams is very useful when working with slow streams like network streams or Bluetooth streams.

If you work with such streams using the main thread your program may hang or block while waiting for a value.
The way we tried to deal with it in the Serial tutorial and Network tutorial is with a Timer that checks whether there are bytes waiting in the buffer. However even if there are bytes available there is a chance that not enough are available and then our program will hang or block until those are available.
Using AsyncStreams is usually simpler and safer.

AsyncStreams can work in two modes. Regular mode and "prefix mode". Both work as described above.

The following code demonstrates a simple program that sends text to a connected device or computer:
B4X:
Sub Process_Globals
    Dim AStreams As AsyncStreams
    Dim Server As ServerSocket
    Dim Socket1 As Socket
End Sub
Sub Globals
    Dim EditText1 As EditText
End Sub

Sub Activity_Create(FirstTime As Boolean)
    If FirstTime Then
        Server.Initialize(5500, "Server")
        Server.Listen
        Log("MyIp = " & Server.GetMyIP)
    End If
    EditText1.Initialize("EditText1")
    EditText1.ForceDoneButton = True
    Activity.AddView(EditText1, 10dip, 10dip, 300dip, 60dip)
End Sub

Sub Server_NewConnection (Successful As Boolean, NewSocket As Socket)
    If Successful Then
        ToastMessageShow("Connected", False)
        Socket1 = NewSocket
         'Can only use prefix mode if both sides of the connection implement the prefix protocol!!!
        AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")
    Else
        ToastMessageShow(LastException.Message, True)
    End If
    Server.Listen
End Sub

Sub AStreams_NewData (Buffer() As Byte)
    Dim msg As String
    msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
    ToastMessageShow(msg, False)
    Log(msg)
End Sub

Sub AStreams_Error
    ToastMessageShow(LastException.Message, True)
    Log("AStreams_Error")
End Sub

Sub AStreams_Terminated
Log("AStreams_Terminated")
End Sub

'press on the Done button to send text
Sub EditText1_EnterPressed
    If AStreams.IsInitialized = False Then Return
    If EditText1.Text.Length > 0 Then
        Dim buffer() As Byte
        buffer = EditText1.Text.GetBytes("UTF8")
        AStreams.Write(buffer)
        EditText1.SelectAll
        Log("Sending: " & EditText1.Text)
    End If
End Sub

Sub Activity_Pause(UserClosed As Boolean)
    If UserClosed Then
        Log("closing")
        AStreams.Close
        Socket1.Close
    End If
End Sub
Once there is a connection we initialize the AsyncStreams object:
B4X:
AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")
Then when there is new data available we convert the bytes to string and show it:
B4X:
Sub AStreams_NewData (Buffer() As Byte)
    Dim msg As String
    msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
    ToastMessageShow(msg, False)
End Sub
When the user enters text in the EditText, the text is being sent:
B4X:
Sub EditText1_EnterPressed
    If AStreams.IsInitialized = False Then Return
    If EditText1.Text.Length > 0 Then
        Dim buffer() As Byte
        buffer = EditText1.Text.GetBytes("UTF8")
        AStreams.Write(buffer)
        EditText1.SelectAll
        Log("Sending: " & EditText1.Text)
    End If
End Sub
Prefix mode

When the object is initialized in prefix mode the data is expected to adhere to the following protocol: every message (bytes array) should be prefixed with the bytes array length (as an Int). So if another device sends us a message made of 100 bytes. The stream is expected to include 4 bytes with a value of 100 and then the 100 bytes.
The NewData event will be raised with the 100 bytes. It will not include the 4 prefix bytes.
When you send data with Write or Write2 the bytes length will be added automatically as the prefix of this message.
If you can work in prefix mode, which is usually only possible if you are implementing both sides, it is highly recommended to do so. Under the cover AsyncStreams uses the message length prefix to make sure that the NewData event is always raised with complete messages. If for example it expects 100 bytes and only 60 bytes arrived, it will wait till the other 40 bytes arrive. In regular mode the event will be raised twice and you will need to handle the two parts of the message.
AsyncStreams also handles the case where 100 bytes are expected and more than 100 bytes arrived (which means that there are several messages).

Note that the WriteStream method that is only available in prefix mode uses an internal protocol which includes error detection.

Errors

The Error event will be raised if there is any error. You can use LastException to find the reason for the error. In most cases you will want to close the connection when an error occurs.

The Terminated event will be raised when the connection is unexpectedly terminated.

Related links:
AsyncStreamsText class offers an alternative to prefix mode when working with streams of text.
New example that uses the Starter service and B4XSerializator: https://www.b4x.com/android/forum/threads/network-asyncstreams-b4xserializator.72149/

Which mode / framework to choose?

There are three modes or frameworks available: AsyncStreams, AsyncStreams in prefix mode and AsyncStreamsText class.

Prefix mode can only be used if both sides of the connection follow the "prefix" protocol. In most cases you can only use this mode when you implement both sides of the connection.


If you are communicating with a non B4X app (and cannot implement the prefix protocol) then you have two options:
- If the data sent and received is text based and each message ends with an end of line character(s) then you should use AsyncStreamsText. For example if you are connecting to an external GPS. Note that it is also possible to modify the class and support different delimiters.
The advantage of using AsyncStreamsText is that it will build the messages correctly. You will not receive partial messages (or multiple messages together).
- In other cases you should use AsyncStreams in regular mode. This means that there is no guarantee that each sent message will be received as a single message. In fact it is more or less guaranteed not to happen. So your code is responsible for correctly collecting and receiving the message.
You can use BytesBuilder class to collect and parse the messages.

Notes

When a new message is available the background thread that is responsible for reading data sends a message to the main thread message queue. This message causes the NewData event to be raised. If you are receiving many messages repeatedly and you show a modal dialog (like Msgbox) then it is possible that the order of events will be changed.
 
Last edited:

mbatgr

Active Member
Licensed User
Longtime User
I have the following code. This is a working code I used to send one digit number to the server (VB6) except that times the is a delay and the server received a message with more digits instead of 1 (i send a digit when i press a particular button, so pressing in a row eg 3 buttons, I expect the server to receive 3 messages with 1 digit length. But because of the delay I receive a message with 3 digits!. Is there I can do in the code to trap this situation?
Thanking you the colleges in advance :)

Sub btnConnect_Click
Select Case btnConnect.Text
Case "Connect"
btnConnect.Text = "Disconnect"
Socket1.Initialize("Socket1")
Socket1.Connect("xxx.xxx.x.xxx",9000,15000)
Case "Disconnect"
btnConnect.Text = "Connect"
Socket1.Close
Case Else
End Select
End Sub

Sub Socket1_Connected(Connected As Boolean)As Boolean
If Connected = True Then
ToastMessageShow("Connected",True)
AStreams.Initialize(Socket1.InputStream,Socket1.OutputStream,"Astreams")
SendData("+") 'send + as a sign of succesfull link
Else
ToastMessageShow("Server not available",True)
btnConnect.Text = "Connect"
End If
btnConnect.Enabled = True
End Sub

Sub AStreams_NewData (Buffer() As Byte)
Dim msg As String
msg = BytesToString(Buffer, 0, Buffer.Length, "Windows-1252")
log(msg)
End Sub

Sub SendData(msg As String)
Dim Buffer() As Byte
Buffer = Msg.GetBytes("Windows-1252")
AStreams.Write(Buffer)
End Sub

Sub Buttons_Click
Dim btn As Button
Dim msg As String
Dim b As Beeper

btn = Sender
b.Beep
msg = btn.Tag

SendData(msg)

End Sub
 

mbatgr

Active Member
Licensed User
Longtime User
Also while the code above is working, sometimes when the program runs for the very first time I got a crash
The reference line where the eroor happens is the "AStreams.Initialize(Socket1.InputStream,Socket1.OutputStream,"Astreams")"


LogCat connected to: 07961702
--------- beginning of /dev/log/system


--------- beginning of /dev/log/main


** Activity (main) Create, isFirst = true **


1920 x 1104, scale = 2.0 (320 dpi)


** Activity (main) Resume **


** Activity (main) Pause, UserClosed = false **


sending message to waiting queue (socket1_connected)


running waiting messages (1)


Error occurred on line: 123 (main)
main$ResumeMessagerun (java line: 252)


java.lang.NullPointerException
at anywheresoftware.b4a.objects.SocketWrapper.getInputStream(SocketWrapper.java:133)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:485)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:229)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:174)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:93)
at anywheresoftware.b4a.BA$3.run(BA.java:312)
at anywheresoftware.b4a.BA.setActivityPaused(BA.java:382)
 

mbatgr

Active Member
Licensed User
Longtime User
sorry erel, i was tired on the front of my pc :)
I just stareted a new conversation about
 

shmulik taiber

Member
Licensed User
Longtime User
Hello Erel .
I've managed to communicate between Arduino and Nexus 7 using the Bluetooth example - without the prefix mode .
however, the data sometimes gets broken into parts, and I'm trying to get a fixed size in an orderly fashion for an array of sensor data - which means prefix mode .
I've attempted to adhere to the protocol from the arduino side - with 4 first Bytes as size indicators- for example buf (2,2,2,2,0,0) to get 2 data bytes of zero . the program finds my arduino, connects, but shows nothing.
any advise ?
 

shmulik taiber

Member
Licensed User
Longtime User
Thank you Erel .
There is some progress, although not the promised land of full communication :)
now, in the screen at least something appears after the "You" - line after line of a trapezoid with a question mark in it.......
my arduino code is as simple as can be, serial.write buf (0,0,0,2,0,0), baud rate 38,400, delay(1000) .
I'm so desperate I'm going to take cyanide pills.... at least my wife won't mind since I've seriously neglected her in the time consuming attempt to crack this thing....
 

shmulik taiber

Member
Licensed User
Longtime User
Hi Erel .
The example you have for text class is not for Bluetooth, and I don't know the right structure for it . there is no documentation of it in the auto complete.... besides, the array of bytes (prefix) is more suitable for my control needs .
I can send an array of data and commands in one stroke .
It connects, but gives me line after line of "you" without anything . this is arduino simple code :

void setup() {

Serial.begin(38400);

}

void loop() {

byte buf[6]={0,0,0,2,3,4};

Serial.write(buf,6)
;
delay(1000);

}

what gives ?
 

shmulik taiber

Member
Licensed User
Longtime User
Thnx again .
I was hoping to see 3 and 4 displayed . the array "buf" has 6 bytes . as far as I understood, the first four are for prefix and the remaining two are the real data which is converted in BytesToString (in the Bluetooth example) for Logging . At least that's what I thought .
What do you mean by not valid ?
 

whcir

Member
Licensed User
Longtime User
Hi,

Using the Testclientsocket example flyingbag posted, and everthing seems to be working fine. temperature is sent from a microcontroller and received on my tablet, only problem is after many hours (over night) the tablet stops updating with the new data sent and there are no errors. used a computer with a tcp/ip terminal to make sure the data is still being transmitted, and it is, so its something on the android side. any help would appreciated. restarting the app, and it works again
 

whcir

Member
Licensed User
Longtime User
Hi Erel,

Under the developer options I checked the option "stay awake". Is there something else I need to do so the tablet dosent go to sleep? The version of android my tablet is running is 4.1.1
 

Turbo3

Active Member
Licensed User
Longtime User
I am using the ASteam_NewData (Buffer() As Byte) to receive a byte stream from a BT device (non-prefix).

I noticed that sometimes the data coming from one BT device is being truncated at 255 bytes. The stream is ASCII characters with CR (0x0d) but no line feeds. What is the size limit on the receive Buffer? If it is 255 bytes can I increase it?

Normally I see data of 10 to 40 bytes in the Buffer for each interrupt except for this one device so I was thinking it is the device that must be truncating the data. I just want to be sure it is not something on my side or if it is how to increase the Async receive buffer.
 
Status
Not open for further replies.
Top