Android
Internet

Modified

Downloads

Calculator example using the EchoServer

EchoServer

The EchoServer below:

  1. sleeps for 1000ms
  2. waits for a connection from a client
  3. reads what the client sends
  4. echos back to the client
  5. disconnects
EchoServer.java
import java.net.*;
import java.io.*;

public class EchoServer
{
   public static void main(String args[]) throws Exception
   {
         ServerSocket connection = new ServerSocket( 1490 ); // Listen on port 1490
        while(true) {
           Thread.sleep( 1000 );                                    // Thread sleeps for 1000ms.
           Socket s = connection.accept();                     // Wait for connection
	System.out.println("Connected");
			
	BufferedReader in = new BufferedReader(     // Socket input and output
                   new InputStreamReader(
                          s.getInputStream() ) );
           PrintStream out = new PrintStream(s.getOutputStream());
           String str=in.readLine();                               // Read one line to \n or \r
           out.print("Echo:"+str);                                 // Echo input to client
           System.out.println( str );                              // Echo input to console
           in.close();
           out.close();                                                  // Close file
           s.close();                                                     // Close connection
           System.out.println("Connection closed");
        }
   }
}

Android Internet

Below is a first attempt to implement on Android an Echo client, that connects and sends a string to a server, reads the server response, and attempts to update an UI object. The EchoServer above is the server.

Here we examine four critical points in using the Internet on a device such as a phone.

  1. The first point is that methods that can take a long time to complete, such as connecting to a server, should not be executed in the UI execution thread.

    While the lengthy method executes, the UI is essentially frozen until the method returns.

The solution is to execute the Internet portion within a separate thread.

Below is the basic code to create and execute an independent thread.

new Thread( new Runnable() {
	public void run() { }
}).start();

Time consuming operations can then be implemented within the {} of the run() method.

Android requires that the UI execute in one thread and the Internet client execute in another.

  1. The second point is that a non-UI thread should not access UI objects.

    Android UI is not thread safe, meaning that calls to UI methods, such as:
message.setText( line );

when executed by a thread other than the UI thread, can cause race-conditions between threads leading to unpredictable execution outcomes.

Running the below example results in an Exception at the line above because a UI object is accessed in a non-UI thread.

The solution is to execute methods that change the UI within the UI thread which can be done using a Handler object, given in the final EchoActivity.java example.

  1. A third point is that permission to access the Internet must be defined in the AndroidManifest.xml file.
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
  1. A final point is the Android device needs the IP number or DNS name of the EchoServer since the server is accessed over the Internet.
Socket s = new Socket("192.168.1.73", 1490);

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="edu.ius.rwisman.Echo"
    android:versionCode="1"
    android:versionName="1.0">
  <uses-sdk android:minSdkVersion="13" />
  <uses-permission android:name="android.permission.INTERNET"></uses-permission>
  <application android:icon="@drawable/icon" android:label="@string/app_name">
   <activity android:name=".EchoActivity"  android:label="@string/app_name">
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
   </activity>
  </application>
</manifest>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 android:orientation="vertical" android:layout_width="fill_parent"
                 android:layout_height="fill_parent" >
<TextView android:layout_width="fill_parent"
                 android:layout_height="wrap_content" android:text="Enter message:"/>
<EditText android:layout_width="match_parent" android:layout_height="wrap_content"
                android:id="@+id/message" android:text=" ">
<requestFocus/>
</EditText>
<Button android:text="Send" android:id="@+id/send"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content" android:onClick="onClickSend"/>
</LinearLayout>
EchoActivity.java
package edu.ius.rwisman.Echo;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import edu.ius.rwisman.Echo.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class EchoActivity extends Activity {

    EditText message = null;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        message = (EditText) findViewById(R.id.message);
    }
    
    public void onClickSend(View view) {
	new Thread(new Runnable() {
		public void run() {
		   try {
Socket s = new Socket("192.168.1.73", 1490);
			BufferedReader in = new BufferedReader(          // Socket input and output
			     new InputStreamReader( s.getInputStream() ) );
			PrintStream out = new PrintStream(s.getOutputStream());
						
			out.println(message.getText());
			 
			String line = in.readLine();			         // read server
			System.out.println("Echo: " + line);
message.setText( line );                                   // Throws Exception
			in.close();
			out.close();                                                        // Close stream
			s.close();                                                           // and connection
		   }
		   catch(Exception e) { System.out.print(e+""); }
	         }
	}).start();
    }
}

Handlers

As mentioned, the race-condition solution is to execute methods that change the UI within the UI thread which can be done using a Handler object.

 String line = "";

Assign line a value within the run() method:

 line = in.readLine();

Each Handler instance is associated with a single thread and that thread's message queue.

When you create a new Handler, it is bound to the thread/message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue. (from http://developer.android.com/reference/android/os/Handler.html)

The Handler is created by the EchoActivity thread so is bound to deliver runnables to it. Note that EchoActivity also is the thread for the UI to execute message.setText(line) statement.

 final Handler handler = new Handler();
final Runnable updateUI = new Runnable() {
          public void run() {
               message.setText( line );
          }
};
EchoActivity.java
package edu.ius.rwisman.Echo;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import edu.ius.rwisman.Echo.R;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.EditText;

public class EchoActivity extends Activity {

	EditText message = null;
    String line = "";

    final Handler handler = new Handler();
    final Runnable updateUI = new Runnable() {
         public void run() {
               message.setText( line );
         }
     };
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        message = (EditText) findViewById(R.id.message);
    }
    
    public void onClickSend(View view) {
	new Thread(new Runnable() {
	    public void run() {
		try {
			 Socket s = new Socket("192.168.1.73", 1490);
					
			 BufferedReader in = new BufferedReader(          // Socket input/output
			                new InputStreamReader(
			                        s.getInputStream() ) );
			 PrintStream out = new PrintStream(s.getOutputStream());
						
			 out.println(message.getText());
			 
			 line = in.readLine();				// input echoed text
			 System.out.println( line );
handler.post( updateUI );
			 in.close();
			 out.close();                                                        // Close stream
			 s.close();                                                           // Close connection
		}
		catch(Exception e) { System.out.print(e+""); }
	   }
	}).start();
    }
}

Serial access using synchronized

While that seems to have solved all the thread problems here but a major one remains when an object can be accessed simultaneously by multiple threads.

In the above, line is accessed in two threads, potentially creating a race condition.

The solution serializes object access to a single thread at a time using synchronized. Within onClickSend() method:

 line = in.readLine();

 System.out.println(line);
 
  synchronized( line ) {
     line = in.readLine();

     System.out.println(line);
  }

and the Runnable:

 final Runnable updateUI = new Runnable() {
         public void run() {
               message.setText( line );
         }
     };
  final Runnable updateUI = new Runnable() {
      public void run() {
              synchronized( line ) {
                    message.setText( line );
              }
      }
  };

Now, whichever thread reaches its synchronized( line ) first gains exclusive access to the line object until exiting the synchronized block.

 

Calculator refactored example

Recall the Calculator example using a Model-View-Controller.

The original Controller updated the display (MyView) directly in the UI thread with the Model state.

Here we refactor the Controller to send the EchoServer the Model state and update the display with the echoed text.

package edu.ius.rwisman.Calculator;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import edu.ius.rwisman.Calculator.MyModel;
import edu.ius.rwisman.Calculator.MyView;
import edu.ius.rwisman.Calculator.R;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Button;

class MyController extends LinearLayout {	
	
	final MyModel myModel;
	final MyView myView;
	View view;
    String echo=null; 

    final Handler handler = new Handler(); 
    final Runnable updateUI = new Runnable() {
       public void run() {
          myView.setView( echo );
       }
    }; 
	private void sendEchoServer(final String str) {
	    new Thread(new Runnable() {
		public void run() {
		      try {
			Socket s = new Socket("192.168.1.75", 1490);
					
			BufferedReader in = new BufferedReader(            // Socket input and output
			                new InputStreamReader(
			                        s.getInputStream() ) );
			PrintStream out = new PrintStream(s.getOutputStream());

			out.println(str);					// Write to EchoServer
			 
			echo = (String)in.readLine();			// Read from EchoServer 
			System.out.println("Echo " + echo);
			
handler.post(updateUI);
			in.close();
			out.close();                                                        // Close file
			s.close();                                                           // Close connection
		      }
		      catch(Exception e) {
			System.out.print(e+"");
		      }
	         }
	     }).start();
	}

	public MyController(Activity activity, final MyModel myModel, final MyView myView) {
		super(activity, null);
		LayoutInflater layoutInflater = 
                         (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		View view = layoutInflater.inflate(R.layout.keyboard, this);
		this.myView = myView;
		this.myModel = myModel;
 
		final Button button0 = (Button) view.findViewById(R.id.button0);
		button0.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('0');
  sendEchoServer( myModel.getModel() ); 
			}
		});
		
		final Button button1 = (Button) view.findViewById(R.id.button1);
		button1.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('1');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button2 = (Button) view.findViewById(R.id.button2);
		button2.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('2');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button3 = (Button) view.findViewById(R.id.button3);
		button3.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('3');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button4 = (Button) view.findViewById(R.id.button4);
		button4.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('4');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button5 = (Button) view.findViewById(R.id.button5);
		button5.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('5');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button6 = (Button) view.findViewById(R.id.button6);
		button6.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('6');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button7 = (Button) view.findViewById(R.id.button7);
		button7.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('7');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button8 = (Button) view.findViewById(R.id.button8);
		button8.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('8');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button button9 = (Button) view.findViewById(R.id.button9);
		button9.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('9');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button buttonPlus = (Button) view.findViewById(R.id.buttonPlus);
		buttonPlus.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('+');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button buttonEqual = (Button) view.findViewById(R.id.buttonEqual);
		buttonEqual.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('=');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button buttonMinus = (Button) view.findViewById(R.id.buttonMinus);
		buttonMinus.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('-');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button buttonC = (Button) view.findViewById(R.id.buttonC);
		buttonC.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('C');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button buttonTimes = (Button) view.findViewById(R.id.buttonTimes);
		buttonTimes.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('*');
				sendEchoServer(myModel.getModel());
			}
		}); 
		
		final Button buttonDivide = (Button) view.findViewById(R.id.buttonDivide);
		buttonDivide.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				myModel.setModel('/');
				sendEchoServer(myModel.getModel());
			}
		}); 
	}
}