Rularea pe un thread separat
Stim deja ca nu putem executa operatii blocante pe thread-ul principal. Din acest motiv, orice operație ce presupune folosirea unor sockets trebuie realizată pe un thread dedicat.
Trebuie avut în vedere faptul că pe firul de execuție dedicat comunicației prin
rețea NU pot fi actualizate informațiile asociate controalelor grafice,
excepția generată în această situație fiind
CalledFromWrongThreadException.
Explicația este dată în mesajul care însoțește această excepție: numai firul de
execuție în care a fost instanțiat un control grafic (obiect de tip
android.view.View
) are dreptul de a realiza modificări asupra acestuia (Only
the original thread that created a view hierarchy can touch its views).
Ca si in alte laboratoare, vom folosi creea un thread separat si un handler pentru a actualiza interfata.
// In MainActivity
// Create handler as a class field
private Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// This runs on GUI thread. This is called
// When the thread used for communications is finished
String result = (String) msg.obj;
daytimeProtocolTextView.setText(result);
}
};
// Create and start the thread
Thread networkThread = new Thread(new Runnable() {
@Override
public void run() {
socket = new Socket();
...
result = "whatever the result is";
// Send result to GUI thread
Message message = mainHandler.obtainMessage();
message.obj = result;
mainHandler.sendMessage(message);
}
});
// This should be put on a button click listener
networkThread.start();
Exemplu
Se dorește interogarea serverului National Institute of Standards & Technology, care oferă un serviciu de interogare a datei și orei curente, cu o precizie ridicată, conform Daytime Protocol (RFC-867).
În acest sens, se va deschide un socket TCP, prin interogarea serverului
disponibil la adresa , pe portul 13, în cadrul
unui fir de execuție separat (clasa NISTCommunicationThread
). Întrucât
nu este necesară decât operația de primire a unor date, se va crea doar
un obiect de tip BufferedReader
, citindu-se două linii (una fiind
vidă - așadar ignorată, cealaltă conținând informațiile necesare, care
se doresc a fi afișate). Întrucât modificarea conținutului unui control
grafic nu poate fi realizată decât din contextul firului de execuție în
care a fost creat, acesta va fi obținut prin parametrul metodei post()
a obiectului respectiv, doar aici fiind permisă asocierea conținutului
solicitat. În momentul în care datele au fost preluate, socket-ul TCP
poate fi închis. Pe fiecare eveniment de tip apăsare a butonului se va
crea un fir de execuție dedicat în care se va instanția un obiect de tip
Socket
.
public class DayTimeProtocolActivity extends AppCompatActivity {
private Button getInformationButton;
private TextView daytimeProtocolTextView;
private Handler mainHandler;
private class NISTCommunicationThread extends Thread {
@Override
public void run() {
String dayTimeProtocol = null;
try {
Socket socket = new Socket(Constants.NIST_SERVER_HOST, Constants.NIST_SERVER_PORT);
BufferedReader bufferedReader = Utilities.getReader(socket);
bufferedReader.readLine();
dayTimeProtocol = bufferedReader.readLine();
Log.d(Constants.TAG, "The server returned: " + dayTimeProtocol);
} catch (UnknownHostException unknownHostException) {
Log.d(Constants.TAG, unknownHostException.getMessage());
if (Constants.DEBUG) {
unknownHostException.printStackTrace();
}
} catch (IOException ioException) {
Log.d(Constants.TAG, ioException.getMessage());
if (Constants.DEBUG) {
ioException.printStackTrace();
}
}
// Send result back to UI thread
Message message = mainHandler.obtainMessage();
message.obj = dayTimeProtocol;
mainHandler.sendMessage(message);
}
}
private class ButtonClickListener implements Button.OnClickListener {
@Override
public void onClick(View view) {
NISTCommunicationThread nistThread = new NISTCommunicationThread();
nistThread.start();
}
}
private ButtonClickListener buttonClickListener = new ButtonClickListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_day_time_protocol);
daytimeProtocolTextView = (TextView)findViewById(R.id.daytime_protocol_text_view);
getInformationButton = (Button)findViewById(R.id.get_information_button);
getInformationButton.setOnClickListener(buttonClickListener);
// Initialize handler on the main thread with direct message handling
mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
String result = (String) msg.obj;
daytimeProtocolTextView.setText(result);
}
};
}
@Override
protected void onDestroy() {
super.onDestroy();
mainHandler.removeCallbacksAndMessages(null);
}
}