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);
    }
}