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:

TAK

Member
Licensed User
Longtime User
Sample working code posted

Hi,
As a beginner I know that it is difficult to wrap your head around Sockets, Server Sockets and Asyncstreams...

....
Hi,
the tutorial on page 1 is difficult to understand for a beginner. The example of flyingbag ist easier. I understand how i can send the stream from a device, but i dont understand how i can get and read this stream with B4J.
 

deantangNYP

Active Member
Licensed User
Longtime User
I would like to use AsyncStream.
1) May i know how can i TEST this code from post #1? install on 2 device?
2) How can i establish CONNECTION?
Please help. Tks.

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
        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)
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
 
Last edited:

deantangNYP

Active Member
Licensed User
Longtime User
Can you point me to the post that requires 2 android devices.
thanks

Yes. You need to install it on two devices. Another option is to implement one side in B4J and run it on the PC (there are several such examples).
 

Coffeeman95

New Member
Licensed User
Longtime User
It's not a 'network" connection, it's a USB serial connection - a 2 foot piece of wire directly to a single device. In all my years developing low speed asynchronous serial data links, even via USB serial adaptors, I've never had this problem or had to resort to the complex and obtuse workarounds required to make this work reliably. Even VB, which runs on top of a lot of OS layers, is much more intuitive, easier and reliable in this respect. Isn't there a simpler method to handle simple USB serial comms or are these difficulties implicit to Android and will always be there?
I agree! I have been programming in VB for 10 years and B4A one of the hardest ones to learn! Even the examples have errors. If I have to debug the examples just to make them work, then perhaps I have selected the wrong platform.
 

Coffeeman95

New Member
Licensed User
Longtime User
You agree to something not related to B4A.

USB serial connection is not a core feature in Android and is not a simple task to use.

If you encountered an example that doesn't work then please make a post in the relevant thread. I don't know when you have bought B4A however we offer 30 days of full money back guarantee.
I appreciate you getting back with me so soon, and I believe I will stick with B4A, because the support is pretty darn good. I sometimes get a little frustrated when I can not see what is plain to others. After I posted that message, I took a small break and came back to re-read some examples and this time everything fell into place. Again, I thank everyone here for their support.
 

jay0103

Member
Licensed User
Longtime User
hi

I have two activitys : Activity1 and Activity2.

Acitvity1 : (Socket1 connected)
If receiving datas , AStreams_NewData event occurs.

Acitvity2 : (Socket1 connected)
If receiving datas , AStreams_NewData event doesn't occur.
When it returns Activity1, AStreams_NewData event occur.

How could the event AStreams_NewData occur, either program is in activity1 or activity2 ?
 

Coffeeman95

New Member
Licensed User
Longtime User
Hello Everyone.
First, Thank you again for the great support from everyone. My question regarding the example in this thread is this: The code complied with NO errors and ran with no errors as well, but my question is where did my data go? How can I check to be sure the data I sent is correct and not corrupted. I do have 2 Android devices, but I did not see the data on the receiving Android. Both Android devices showed the " Log("MyIp = " & Server.GetMyIP) as correct 192.168.1.103 and the other 192.168.1.104" I have looked and pondered the example and while it is probably simple to see, I am missing it somehow. Thank you in advance for a nudge in the right direction.
Gary
 

Coffeeman95

New Member
Licensed User
Longtime User
AsyncStream always not initialized

I'm having a problem with AsyncStreams. IsInitialized always returns false.
I tried the flyingbag example above and it does the same thing.
Any pointers to what the problem might be?
Thanks.

Hi flyingbag. I downloaded your example .zip files and hopefully this will answer the question I asked previously. I will need some time, as you can tell I am not the brightest programmer compared the rest of the group. Thanks!
Gary
 

r.babazadeh

Member
Licensed User
Longtime User
HI
I Send below data Whit micro controller via Bluetooth

Tx_tbl(01) = "L"
Tx_tbl(02) = Ip
Tx_tbl(03) = Nip
Tx_tbl(04) = L_speed
Tx_tbl(05) = H_speed
Tx_tbl(06) = light


And In B4A, I Cant Receive byte
I receive: L?????

Each character also has a 3-byte ASCII code that the data submitted do not change
In Vb6 i can comfortable with ASCII command , converted received characters to bytes
 

Manuel Moreno-Eguilaz

New Member
Licensed User
Longtime User
Is there any kind of "DoEvents" for AsyncStreams?

Using USBSerial, we have noticed it is necessary to finish one subroutine in the main activity before receiving data from asyncstreams.

Thank you.

Manuel
 
Status
Not open for further replies.
Top