TP 4 : Android et les sockets
On veut interroger le serveur de date réalisé plus tôt.
1. Créez un projet approprié dans eclipse, puis une interface comportant un TextView dans lequel on écrira le résultat – la date envoyée par le serveur (ou un message d’erreur), et un bouton permettant de lancer une requête sur le serveur.
Attention, le système Android nécessite dispose d’un mécanisme de permission, censé prévenir l’utilisateur lorsque des opérations sensibles, telles que l’accès au réseau ou à la mémoire de masse, ou l’accès aux capteurs, sont réalisées. Notre application doit utiliser un service réseau, il faut donc lui en donner la permission dans le fichier AndroidManifest.xml (à trouvez dans le projet). Insérez la ligne suivante après celle qui définit les sdk utilisés.
<uses-permission android:name="android.permission.INTERNET"/>
2. Implémentez le traitement du bouton de requête (reprenez le code du client réalisé lors du TP 2).
Que se passe-t-il ?
3. Si cela n’a pas marché, c’est parce que le système Android impose que les appels réseau soient passés dans un thread séparé du thread principal. Modifiez votre code pour déplacer l’ouverture du socket, la lecture et sa fermeture dans un thread séparé. Que se passe-t-il maintenant ?
4. Vous devez trouver une exception de type « android.view.ViewRoot$CalledFromWrongThreadException » avec le message « Only the original thread that created a view hierarchy can touch its views.« . Cela signifie que le thread qui gère le socket ne peut pas écrire dans l’interface graphique, qui est gérée par un autre Thread. On va devoir utiliser un système d’envoi de message. Le principe est de créer un objet de type handle qui va faire l’intermédiaire entre les 2. Le thread qui gère les socket va envoyer un message par l’intermédiaire de ce handle, et celui ci va le renvoyer au thread qui gère l’interface graphique.
Pour cela on va déclare un objet handler, en surchargeant la méthode handleMessage de la classe Handler afin de lui faire réaliser le traitement voulu (ici afficheur.setText(text);).
mHandler = new Handler() { @Override public void handleMessage(Message msg) { String text = (String)msg.obj; afficheur.setText(text); } };
Ensuite lorsque dans le thread qui gère le socket on va utiliser :
Message msg = new Message(); msg.obj = answer; mHandler.sendMessage(msg);
On crée ici un message, on y copie la chaîne à transmettre, et on envoie le message au handler.
Voici le code complet de la classe :
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.graphics.Color; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class GetDateAppActivity extends Activity implements OnClickListener, Runnable { private static int DEFAULTPORT = 9090; private static String DEFAULTSERVER = "ouessant.univ-paris8.fr"; private int portNumber; private String serverName; private TextView afficheur; private Handler mHandler; private EditText serverField, portField; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.getdateapp_activity); portNumber=DEFAULTPORT; serverName=DEFAULTSERVER; Button t=(Button) this.findViewById(R.id.button1); t.setTextColor(Color.RED); t.setOnClickListener(this); afficheur=(TextView) this.findViewById(R.id.textView1); serverField=(EditText)this.findViewById(R.id.editText1); portField=(EditText)this.findViewById(R.id.editText2); mHandler = new Handler() { @Override public void handleMessage(Message msg) { String text = (String)msg.obj; afficheur.setText(text); } }; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } @Override public void onClick(View v) { if (v.getId()==R.id.button1) { this.serverName=this.serverField.getText().toString(); this.portNumber=Integer.parseInt(this.portField.getText().toString()); new Thread(this).start(); } } @Override public void run() { Socket s=null; try { s = new Socket(serverName, portNumber); } catch (IOException e) { //afficheur.setText("Cannot connect to "+server+" on port "+port); Message msg = new Message(); msg.obj = "Cannot connect to "+serverName+" on port "+portNumber; mHandler.sendMessage(msg); } if (s!=null) { BufferedReader input; try { input = new BufferedReader(new InputStreamReader(s.getInputStream())); String answer = input.readLine(); //afficheur.setText(answer); Message msg = new Message(); msg.obj = answer; mHandler.sendMessage(msg); input.close(); s.close(); } catch (IOException e) { e.printStackTrace(); } } } }