Android
|
Modified: |
Downloads
Calculator example using the EchoServer
EchoServer
The EchoServer below:
- sleeps for 1000ms
- waits for a connection from a client
- reads what the client sends
- echos back to the client
- 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 clientSystem.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.
- 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.
- 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.
- 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>
- 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);
|
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 {
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);
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.
- First define an instance variable line that can be accessed within any method, including the run(), of the EchoActivity class.
String line = ""; Assign line a value within the run() method:
line = in.readLine();
- Handler class allows sending and processing Message and Runnable objects associated with a thread's MessageQueue.
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();
- Define a Runnable object updateUI that is executed by posting a message to the Handler object.
final Runnable updateUI = new Runnable() {
public void run() {
message.setText( line );
}
};
- Handler post(Runnable) method is called from the Internet client thread to send the Runnable upDateUI to the UI thread (EchoActivity).
handler.post( updateUI );
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;
@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 );
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()); } }); } }