Differences
This shows you the differences between two versions of the page.
helloandroid [2014/11/10 10:50] andre [Execution] |
helloandroid [2017/07/21 03:08] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== HelloAndroid - Ping Application ====== | ||
- | {{ : | ||
- | This tutorial will guide you through the basic concepts on how to use SDDL with an Android device. We are going to show how to create a MrUDP connection using Android Services (this service will be named ConnectionService) and exchange messages between the device and a node on server. We will also show a different implementation using AsyncTasks that, while easier to implement, is recommended only for prototypes, not for final applications. | ||
- | Our tutorial will be divided in two, the first part is the Android code and implementation, | ||
- | |||
- | But before we dive into the code, I am going to introduce two concepts quickly, Android services and Local Broadcast Manager. | ||
- | |||
- | <WRAP tip> | ||
- | You do not need to copy the source code snippets shown in this tutorial. At the end there is a link to download the entire source code. | ||
- | </ | ||
- | |||
- | ===== Android :: Service ===== | ||
- | |||
- | The Android service is a component that runs on background for a long period of time without a user interface (UI). Each service must be declared on '' | ||
- | |||
- | An Android service has two stages, the first call is to the method '' | ||
- | |||
- | ===== Android :: Local Broadcast Manager ===== | ||
- | |||
- | The Local Broadcast Manager was introduced to the Android Support Library to simplify the process of registering for, and sending, Broadcast Intents between components within your application. The Local Broadcast Manager is restricted to your application only, it's not a global broadcast (a global broadcast is an intent sent to all the components installed on the device), this improve the security of your application because all broadcast messages are local. | ||
- | |||
- | <WRAP important> | ||
- | To use Local Broadcast Manager, you must include the **Android Support Library** in your application. | ||
- | </ | ||
- | |||
- | Using Local Broadcast Manager is very simple. To get a new instance you do: | ||
- | <code java> | ||
- | LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); | ||
- | </ | ||
- | Where '' | ||
- | |||
- | To send a local broadcast you do, first you need to create an intent with a String object being the action, like this: | ||
- | <code java> | ||
- | String action = " | ||
- | Intent intent = new Intent(action); | ||
- | lbm.sendBroadcast(intent); | ||
- | </ | ||
- | |||
- | To receive a local broadcast on the component you need to, first create an object that has type as '' | ||
- | <code java> | ||
- | /* First: creating BroadcastReceiver object */ | ||
- | private BroadcastReceiver mBroadcast = new BroadcastReceiver () { | ||
- | @Override | ||
- | public void onReceive(Context c, Intent i) { | ||
- | /* Obtain the action string from the intent that we are receiving */ | ||
- | String action = i.getAction(); | ||
- | /* Compare this intent with our action declared earlier */ | ||
- | if (action.equals(" | ||
- | // Action matches, do something. | ||
- | } | ||
- | } | ||
- | }; | ||
- | |||
- | /* Second and Third: create a IntentFilter and assign this IntentFilter to the Local Broadcast */ | ||
- | IntentFilter filter = new IntentFilter(); | ||
- | /* Assign to this IntentFilter the action */ | ||
- | filter.addAction(" | ||
- | /* Register this intent filter with this broadcast receiver */ | ||
- | lbm.registerReceiver(mBroadcast, | ||
- | </ | ||
- | |||
- | Now with the '' | ||
- | |||
- | <WRAP important> | ||
- | You can only send a local broadcast if you have an action registered, if you try to call **lbm.sendBroadcast()** with an action that is not registered with **any** broadcast receiver, it will fail and the method **sendBroadcast()** will return **false**. | ||
- | </ | ||
- | |||
- | ===== Android + SDDL :: Connection Service ===== | ||
- | |||
- | The first thing to know is, a Service by default runs on the '' | ||
- | |||
- | This is the network Thread implementation: | ||
- | <code java> | ||
- | /*start a new Thread for the connection*/ | ||
- | private void startThread () { | ||
- | t = new Thread(new Runnable () { | ||
- | public void run () { | ||
- | try { | ||
- | / | ||
- | connection = new MrUdpNodeConnection(); | ||
- | |||
- | /* Obs.: the **listener** is declared in onStartCommand() */ | ||
- | |||
- | / | ||
- | connection.addNodeConnectionListener(listener); | ||
- | |||
- | / | ||
- | String ip = AppConfig.getIp(c); | ||
- | int port = AppConfig.getPort(c); | ||
- | |||
- | / | ||
- | socket = new InetSocketAddress(ip, | ||
- | |||
- | / | ||
- | connection.connect(socket); | ||
- | |||
- | while (keepRunning) { | ||
- | if (!isConnected) { | ||
- | / | ||
- | keepRunning = false; | ||
- | /* disconnect the MrUDP */ | ||
- | connection.disconnect(); | ||
- | stopThread(t); | ||
- | } | ||
- | if (isConnected) { | ||
- | / | ||
- | while (lstMsg.size() > 0) { | ||
- | connection.sendMessage(lstMsg.get(0)); | ||
- | lstMsg.remove(0); | ||
- | } | ||
- | } | ||
- | synchronized (t) { | ||
- | / | ||
- | * call notify()*/ | ||
- | t.wait(); | ||
- | } | ||
- | } | ||
- | } | ||
- | catch (Exception e) { | ||
- | e.printStackTrace(); | ||
- | } | ||
- | } | ||
- | }); | ||
- | |||
- | /*start the thread*/ | ||
- | t.start(); | ||
- | } | ||
- | </ | ||
- | |||
- | The part before '' | ||
- | |||
- | The '' | ||
- | |||
- | <WRAP info> | ||
- | The current implementation, | ||
- | </ | ||
- | |||
- | Now the initialization of the connection service, the '' | ||
- | |||
- | The '' | ||
- | <code java> | ||
- | @Override | ||
- | public void onCreate() { | ||
- | super.onCreate(); | ||
- | |||
- | / | ||
- | t = null; /*thread = null*/ | ||
- | keepRunning = true; /*keep WHILE running*/ | ||
- | isConnected = false; / | ||
- | lstMsg = new ArrayList< | ||
- | } | ||
- | </ | ||
- | |||
- | The '' | ||
- | <code java> | ||
- | @Override | ||
- | public int onStartCommand(Intent i, int flags, int startId) { | ||
- | |||
- | /*get context*/ | ||
- | c = ConnectionService.this; | ||
- | |||
- | /*set IS_SERVICE_RUNNING flag to RUNNING*/ | ||
- | AppConfig.saveService(c, | ||
- | |||
- | / | ||
- | lbm = LocalBroadcastManager.getInstance(c); | ||
- | |||
- | /*register broadcast*/ | ||
- | registerBroadcast(); | ||
- | |||
- | /*if its not connected, connect*/ | ||
- | if (!isConnected) { | ||
- | / | ||
- | * Check if we have a valid UUID, if not create one*/ | ||
- | uuid = AppConfig.getUuid(c); | ||
- | if (uuid == null) { | ||
- | / | ||
- | uuid = AppConfig.generateUuid(); | ||
- | if (!AppConfig.saveUuid(c, | ||
- | // Log.d() removed | ||
- | } | ||
- | |||
- | / | ||
- | listener = new ConnectionListener(c); | ||
- | |||
- | / | ||
- | isConnected = true; | ||
- | |||
- | / | ||
- | startThread(); | ||
- | |||
- | } | ||
- | |||
- | return START_STICKY; | ||
- | } | ||
- | </ | ||
- | |||
- | Let's go step-by-step, | ||
- | |||
- | I have made a SharedPreferences to save the state of the services and flags to be shown on the Activity (the screen), so we save that the service is running with '' | ||
- | |||
- | After that we initialize the Local Broadcast Manager and register the broadcast receiver for this service, code of the broadcast receiver below: | ||
- | <code java> | ||
- | /*all the received messages will be executed here*/ | ||
- | private BroadcastReceiver mBroadcast = new BroadcastReceiver () { | ||
- | /*it is called when we receive a new broadcast message*/ | ||
- | @Override | ||
- | public void onReceive(Context c, Intent i) { | ||
- | /*get the action from the Intent*/ | ||
- | String action = i.getAction(); | ||
- | |||
- | /*check each broadcast message action if we have more than one*/ | ||
- | |||
- | /*message received, retrieve and call the create and queue*/ | ||
- | if (action.equals(BroadcastMsg.ACTION_CHAT_SEND_MSG)) { | ||
- | String msg = i.getStringExtra(BroadcastMsg.EXTRA_CHAT_MSG); | ||
- | createAndQueueMsg(msg); | ||
- | } | ||
- | } | ||
- | }; | ||
- | </ | ||
- | |||
- | Now inside the '' | ||
- | |||
- | <WRAP important> | ||
- | The user of the device has total control about the SharedPreferences, | ||
- | </ | ||
- | |||
- | <WRAP info> | ||
- | It is **not** a good practice to obtain a unique ID for the device using the device ID, if the device does not have support for telephony it will return null. MAC address may not work if it is off or not present on the device. | ||
- | |||
- | If you sell the device the ID will not change, so a miss interpretation of the UUID, since it is the wrong person now using the device. | ||
- | |||
- | Best practice is to set a unique installation ID, like I did with the UUID for the service, if the user uninstall the app and reinstall, generate a new one. Track user by installation, | ||
- | </ | ||
- | |||
- | The last important thing to know is the flag used on the return statement. The '' | ||
- | |||
- | ===== Android + SDDL :: Connection Listener ===== | ||
- | |||
- | The connection listener is the callback for all MrUDP actions, when MrUDP establishes a connection the listener '' | ||
- | |||
- | <code java> | ||
- | @Override | ||
- | public void connected(NodeConnection nc) { | ||
- | /*save the we have a MrUDP connection to shared preferences*/ | ||
- | AppConfig.saveIsMrUdpConnected(c, | ||
- | |||
- | /*We need to send a message to be identified*/ | ||
- | ApplicationMessage am = new ApplicationMessage(); | ||
- | am.setContentObject(" | ||
- | am.setTagList(new ArrayList< | ||
- | /*for security check if we are not getting NULL, not done here*/ | ||
- | am.setSenderID(AppConfig.getUuid(c)); | ||
- | try { | ||
- | nc.sendMessage(am); | ||
- | } catch (IOException e) { | ||
- | e.printStackTrace(); | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | Another important callback is when we receive a message from the Gateway, I am using Local Broadcast Manager. When we receive a message and this is a simple chat program, it will be passed the correct action to the Intent and broadcasted to the component that it is listening for this action on the broadcast receiver, in this case is the '' | ||
- | |||
- | We are adding additional data to the Intent with the '' | ||
- | |||
- | <code java> | ||
- | @Override | ||
- | public void newMessageReceived(NodeConnection nc, Message m) { | ||
- | /*send the message to the chat*/ | ||
- | String msg = m.getContentObject().toString(); | ||
- | /*send the message to the client*/ | ||
- | Intent iMsg = new Intent(BroadcastMsg.ACTION_CHAT_RECEIVED_MSG); | ||
- | iMsg.putExtra(BroadcastMsg.EXTRA_CHAT_MSG, | ||
- | lbm.sendBroadcast(iMsg); | ||
- | } | ||
- | </ | ||
- | |||
- | ===== Server + SDDL :: Node Client ===== | ||
- | |||
- | This is the second part, a project that prints on console the messages sent from the Android and it is possible to send message to Android. | ||
- | |||
- | First we initiate the SDDL Layer, code: | ||
- | |||
- | <code java> | ||
- | /*create a layer*/ | ||
- | sddlLayer = UniversalDDSLayerFactory.getInstance(); | ||
- | |||
- | /*create a participant*/ | ||
- | sddlLayer.createParticipant(UniversalDDSLayerFactory.CNET_DOMAIN); | ||
- | |||
- | /*we are going to receive and write topics to domain*/ | ||
- | sddlLayer.createPublisher(); | ||
- | sddlLayer.createSubscriber(); | ||
- | |||
- | /*creates a topic that we are going to listen*/ | ||
- | Object receiveTopic = sddlLayer.createTopic(Message.class, | ||
- | |||
- | /*creates a topic to send to mobile node*/ | ||
- | Object sendTopic = sddlLayer.createTopic(PrivateMessage.class, | ||
- | |||
- | /*set the data reader with the topic we created*/ | ||
- | sddlLayer.createDataReader(this, | ||
- | |||
- | /*set the data writer*/ | ||
- | sddlLayer.createDataWriter(sendTopic); | ||
- | </ | ||
- | |||
- | This code above is equal to others tutorials check [[HelloCore]]. | ||
- | |||
- | Now the '' | ||
- | <code java> | ||
- | public static void main(String[] args) { | ||
- | new SDDLServer(); | ||
- | try { | ||
- | while (true) { /* keep running */ | ||
- | /*print on screen the input message*/ | ||
- | System.out.print(" | ||
- | |||
- | /*create and get the input from console*/ | ||
- | BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in)); | ||
- | String inputMsg = bufferRead.readLine(); | ||
- | | ||
- | /*create a private message*/ | ||
- | PrivateMessage pMsg = new PrivateMessage(); | ||
- | pMsg.setGatewayId(gatewayId); | ||
- | pMsg.setNodeId(nodeId); | ||
- | | ||
- | /*create a application message with the MESSAGE*/ | ||
- | ApplicationMessage appMsg = new ApplicationMessage(); | ||
- | appMsg.setContentObject(inputMsg); | ||
- | | ||
- | /*assign the private message the application message to be sent to mobile node*/ | ||
- | pMsg.setMessage(Serialization.toProtocolMessage(appMsg)); | ||
- | | ||
- | /*write topic to DDS*/ | ||
- | sddlLayer.writeTopic(PrivateMessage.class.getSimpleName(), | ||
- | } | ||
- | | ||
- | } catch (IOException e) { | ||
- | e.printStackTrace(); | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | The first part is simple, Java waits for the input from console, a message to be sent to Android. After that we create a '' | ||
- | |||
- | <WRAP important> | ||
- | The way this project is written the first message must be send from Android, because I am getting the **Gateway ID** and **Node ID** from the message received on the callback '' | ||
- | |||
- | If you try to send the first message from '' | ||
- | </ | ||
- | |||
- | To listen for new messages we need to add a listener '' | ||
- | |||
- | <code java> | ||
- | /*called when there is new data on the DDS domain*/ | ||
- | @Override | ||
- | public void onNewData(ApplicationObject topicSample) { | ||
- | Message msg = null; | ||
- | /*check if the topic type is a Message*/ | ||
- | if (topicSample instanceof Message) { | ||
- | /*we can cast safely*/ | ||
- | msg = (Message) topicSample; | ||
- | |||
- | /*get the Gateway IP and NodeID, we have only one*/ | ||
- | gatewayId = msg.getGatewayId(); | ||
- | nodeId | ||
- | |||
- | /* we have a serializable object, so using the Serialization class | ||
- | * we obtain converting to a Serializable object | ||
- | */ | ||
- | Serializable s = Serialization.fromJavaByteStream(msg.getContent()); | ||
- | |||
- | /*check if is a String object, we sent as a String from Android*/ | ||
- | if (s instanceof String) { | ||
- | /*just print on console and filter the `ack` message to be | ||
- | * identified by the Gateway | ||
- | */ | ||
- | if (!s.equals(" | ||
- | System.out.println("" | ||
- | System.out.println(" | ||
- | System.out.print(" | ||
- | } | ||
- | } | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | We now are listening for new messages sent from the Gateway, this code is just getting this message and checking for the correct type. Android is sending a String object, so we check for this and if everything is OK this String is printed on console. | ||
- | |||
- | The '' | ||
- | |||
- | ===== Execution ===== | ||
- | To test the projects do: | ||
- | |||
- | 1. Import the projects to Eclipse, for the **Ping Example**: | ||
- | * SDDL-PingServer (Server Node) | ||
- | * SDDL-PingServiceTest and SDDL-PingTaskTest (Android Nodes) | ||
- | * SDDL-Ping-Model, | ||
- | or, for the **LongRunning Example**: | ||
- | * SDDL-LongRunningServer (Server Node) | ||
- | * SDDL-LongRunningTest (Android Node) | ||
- | * InfoPAE-Model, | ||
- | |||
- | 2. Get the local IP and copy it to the run configurations for the Gateway, then run it | ||
- | |||
- | 3. Run the SDDL-LongRunningServer project or the SDDL-PingServer | ||
- | |||
- | **Ping Example** | ||
- | 4 On any of the Ping apps, simply type the IP:PORT and click on Ping to send a Ping message to the server. The server will respond the message and you should see a " | ||
- | |||
- | **LongRunning Example** | ||
- | 5.1 Click "Start Services" | ||
- | > 5.2 If anything fails check IP and Port | ||
- | |||
- | 6. Send a first message from Android, this message will be printed on the console on the respective server project. | ||
- | |||
- | 7. Now you can send a message back to Android from the console, just click with the mouse in the console and write a message and click enter to send. | ||
- | ===== Download ===== | ||
- | Link to download files | ||
- | |||
- | - For the **LongRunning Example**: | ||
- | * SDDL LongRunningServer: | ||
- | * SDDL LongRunningTest: | ||
- | * InfoPAE Model: https:// | ||
- | |||
- | - For the **Ping Example**: | ||
- | * SDDL Ping Application (all projects): https:// | ||
- | or {{: |