Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
colocvii:colocviu02 [2015/05/16 16:38]
Andrei Roșu-Cojocaru [Rezolvări]
colocvii:colocviu02 [2017/02/20 20:54] (current)
Line 2: Line 2:
  
 ===== Model de Subiect ===== ===== Model de Subiect =====
 +
 +<​HTML>​
 +<​center>​
 +</​HTML>​
 +
 +{{url>​http://​eim2016.andreirosucojocaru.ro/​_media/​colocvii/​colocviu02/​eim2016-tp02-varsim.pdf 90%,800px}}
 +
 +<​HTML>​
 +</​center>​
 +</​HTML>​
 +
 +{{ :​colocvii:​colocviu02:​eim2016-tp02-varsim.pdf }}
  
 ===== Observații Generale ===== ===== Observații Generale =====
Line 8: Line 20:
   * un cont Github personal, pe care să existe drepturi de citire și de scriere;   * un cont Github personal, pe care să existe drepturi de citire și de scriere;
   * SDK-ul de Android (cu o imagine pentru nivelul de API 16 - Jelly Bean 4.1);   * SDK-ul de Android (cu o imagine pentru nivelul de API 16 - Jelly Bean 4.1);
-  * mediul de dezvoltare integrat Eclipse, împreună cu plugin-ul Android Developer Tools;+  * mediul de dezvoltare integrat ​Android Studio sau Eclipse, împreună cu plugin-ul Android Developer Tools;
   * un dispozitiv mobil:   * un dispozitiv mobil:
     * fizic (bring your own device);     * fizic (bring your own device);
     * virtual: Genymotion, AVD.     * virtual: Genymotion, AVD.
  
 +<​hidden>​
 <note important>​În situația în care lucrați pe calculatoarele din laboratorul Ixia (EG106) este recomandat să folosiți: <note important>​În situația în care lucrați pe calculatoarele din laboratorul Ixia (EG106) este recomandat să folosiți:
   * fluxbox ca utilitar pentru gestiunea ferestrelor (se alege din interfața de autentificare); ​   * fluxbox ca utilitar pentru gestiunea ferestrelor (se alege din interfața de autentificare); ​
   * epiphany pentru navigare Internet.   * epiphany pentru navigare Internet.
 Este de preferat să nu se depășească memoria disponibilă,​ întrucât comportamentul acestor mașini este imprevizibil în momentul în care se folosește swap-ul (se blochează, fără posibilitatea de recuperare din eroare).</​note>​ Este de preferat să nu se depășească memoria disponibilă,​ întrucât comportamentul acestor mașini este imprevizibil în momentul în care se folosește swap-ul (se blochează, fără posibilitatea de recuperare din eroare).</​note>​
 +</​hidden>​
 +
 +În situația în care este necesară procesarea codului sursă (în format HTML) al paginii Internet furnizat ca răspuns la cererea transmisă către un serviciu web, se poate folosi [[http://​jsoup.org/​packages/​jsoup-1.9.1.jar|biblioteca Jsoup]].
 +
 +Integrarea sa în mediul integrat pentru dezvoltare **Android Studio 1.5.1** se face prin plasarea fișierului corespunzător în directorul //libs// al proiectului. Ulterior, din meniul contextual al aplicației Android (accesibil prin click dreapta) se accesează opțiunea //Open Module Settings// sau se apasă tasta F4.
 +
 +{{ :​colocvii:​colocviu02:​androidstudio_library01.png?​nolink }}
 +
 +În fereastra //Project Structure// se apasă tasta //+// pentru a se specifica un modul nou.
 +
 +{{ :​colocvii:​colocviu02:​androidstudio_library02.png?​nolink&​600 }}
 +
 +În fereastra //Create New Module// se indică tipul de modul //Import .JAR/.AAR Package//.
 +
 +{{ :​colocvii:​colocviu02:​androidstudio_library03.png?​nolink&​600 }}
 +
 +Se precizează:​
 +  * locația la care se găsește biblioteca ce va fi importată sub formă de modul;
 +  * denumirea subproiectului din care va face parte modulul;
 +
 +{{ :​colocvii:​colocviu02:​androidstudio_library04.png?​nolink&​600 }}
 +
 +După ce biblioteca a fost integrată în cadrul mediului integrat pentru dezvoltare Android Studio 1.5.1, aceasta va putea fi vizualizată în fereastra //Project Structure//​.
 +
 +{{ :​colocvii:​colocviu02:​androidstudio_library05.png?​nolink&​600 }}
  
-În situația în care este necesară procesarea codului sursă (în format HTML) al paginii Internet furnizat ca răspuns la cererea transmisă către un serviciu web, se poate folosi [[http://​jsoup.org/​packages/​jsoup-1.8.2.jar|biblioteca Jsoup]]. ​Integrarea sa în mediul integrat pentru dezvoltare Eclipse ​Luna SR1a (4.4.1) se face prin plasarea fișierului corespunzător în directorul //libs// al proiectului. Acesta devine vizibil în momentul în care se accesează operația //Refresh// din meniul contextual, disponibilă și prin tasta //F5//. Ulterior, pentru a fi inclus în classpath, este necesar ca din meniul contextual asociat fișierului ''​jsoup-1.8.2.jar''​ (disponibil pe butonul drept al mouse-ului) să se acceseze //Build Path// → //Add to Build Path//.+Integrarea sa în mediul integrat pentru dezvoltare ​**Eclipse ​Mars 1 (4.5.1)** se face prin plasarea fișierului corespunzător în directorul //libs// al proiectului. Acesta devine vizibil în momentul în care se accesează operația //Refresh// din meniul contextual, disponibilă și prin tasta //F5//. Ulterior, pentru a fi inclus în classpath, este necesar ca din meniul contextual asociat fișierului ''​jsoup-1.9.1.jar''​ (disponibil pe butonul drept al mouse-ului) să se acceseze //Build Path// → //Add to Build Path//.
  
 {{ :​colocvii:​colocviu02:​eclipse_library01.png?​nolink }} {{ :​colocvii:​colocviu02:​eclipse_library01.png?​nolink }}
  
-După ce biblioteca a fost integrată în cadrul mediului integrat pentru dezvoltare Eclipse ​Luna SR1a (4.4.1), aceasta va putea fi vizualizată în secțiunea //​Referenced Libraries// din proiectul corespunzător.+După ce biblioteca a fost integrată în cadrul mediului integrat pentru dezvoltare Eclipse ​Mars 1 (4.5.1), aceasta va putea fi vizualizată în secțiunea //​Referenced Libraries// din proiectul corespunzător.
  
 {{ :​colocvii:​colocviu02:​eclipse_library02.png?​nolink }} {{ :​colocvii:​colocviu02:​eclipse_library02.png?​nolink }}
Line 30: Line 68:
 ===== Rezolvări ===== ===== Rezolvări =====
  
-Proiectul ​Eclipse corespunzător aplicației Android ce conține rezolvările complete ale cerințelor colocviului ​este disponibil ​pe [[https://​github.com/​pdsd2015/​PracticalTest02|contul de Github al disciplinei]].+Proiectele Android Studio și Eclipse corespunzătoare aplicației Android ce conține rezolvările complete ale cerințelor colocviului ​sunt disponibile ​pe [[https://​github.com/​eim2016/​PracticalTest02|contul de Github al disciplinei]].
  
 **1.** Se accesează [[https://​www.github.com|Github]] și se realizează autentificarea în contul personal, prin intermediul butonului //Sign in//. **1.** Se accesează [[https://​www.github.com|Github]] și se realizează autentificarea în contul personal, prin intermediul butonului //Sign in//.
Line 38: Line 76:
 Se creează o zonă de lucru corespunzătoare unui proiect prin intermediului butonului //New Repository//​. Se creează o zonă de lucru corespunzătoare unui proiect prin intermediului butonului //New Repository//​.
  
-{{ :colocvii:colocviu01:​github_new_repository.png?​nolink }}+{{ :colocvii:colocviu02:​github_new_repository.png?​nolink }}
  
 Configurarea depozitului la distanță presupune specificarea:​ Configurarea depozitului la distanță presupune specificarea:​
Line 51: Line 89:
   * tipului de licență sub care este publicat codul sursă.   * tipului de licență sub care este publicat codul sursă.
  
-{{ :colocvii:colocviu01:​github_repository_configuration.png?​nolink }}+{{ :colocvii:colocviu02:​github_repository_configuration.png?​nolink }}
  
 **2.** Prin intermediul comenzii ''​git clone''​ se poate descărca întregul conținut în directorul curent (de pe discul local), inclusiv istoricul complet al versiunilor anterioare (care poate fi ulterior reconstituit după această copie, în cazul coruperii informațiilor stocate pe serverul la distanță). **2.** Prin intermediul comenzii ''​git clone''​ se poate descărca întregul conținut în directorul curent (de pe discul local), inclusiv istoricul complet al versiunilor anterioare (care poate fi ulterior reconstituit după această copie, în cazul coruperii informațiilor stocate pe serverul la distanță).
Line 58: Line 96:
  
 <​code>​ <​code>​
-student@pdsd2015:~$ git clone https://​www.github.com/​perfectstudent/​PracticalTest02+student@eim2016:~$ git clone https://​www.github.com/​perfectstudent/​PracticalTest02
 </​code>​ </​code>​
  
-**3.** Se urmăresc indicațiile disponibile în secțiunea [[:laboratoare:​laborator01#​crearea_unei_aplicatii_android_in_eclipse_luna_sr1a_441_-_obligatoriu|Crearea unei aplicații Android în Eclipse ​Luna SR1a (4.4.1)]].+**3.** Se urmăresc indicațiile disponibile în secțiunea [[laboratoare:​laborator01#​crearea_unei_aplicatii_android_in_eclipse_mars_1_451_-_obligatoriu|Crearea unei aplicații Android în Eclipse ​Mars 1 (4.5.1)]], respecti [[laboratoare:​laborator01#​crearea_unei_aplicatii_android_in_android_studio_151_-_obligatoriu|Crearea unei aplicații Android în Android Studio 1.5.1]].
  
 **4.** Pentru implementarea interfeței grafice, se vor defini controalele care asigură interacțiunea cu utilizatorul pentru fiecare dintre componentele aplicației Android: **4.** Pentru implementarea interfeței grafice, se vor defini controalele care asigură interacțiunea cu utilizatorul pentru fiecare dintre componentele aplicației Android:
Line 86: Line 124:
   android:​layout_height="​match_parent"​   android:​layout_height="​match_parent"​
   android:​orientation="​vertical"​   android:​orientation="​vertical"​
-  tools:​context="​ro.pub.cs.systems.pdsd.practicaltest02.graphicuserinterface.PracticalTest02MainActivity"​ >+  tools:​context="​ro.pub.cs.systems.eim.practicaltest02.graphicuserinterface.PracticalTest02MainActivity"​ >
  
   <​TextView   <​TextView
Line 247: Line 285:
 </​note>​ </​note>​
  
-**5.**+**5.** ​Implementarea serverului presupune:
  
-**6.**+**a)** un fir de execuție care gestionează solicitările de conexiune de la clienți: 
 +  * se va instanția un obiect de tip ''​ServerSocket''​ care va primi ca parametru portul indicat de utilizator: <code java> 
 +try { 
 +  serverSocket = new ServerSocket(port);​ 
 +} catch (IOException ioException) { 
 +  Log.e(Constants.TAG,​ "An exception has occurred: " + ioException.getMessage());​ 
 +  if (Constants.DEBUG) { 
 +    ioException.printStackTrace();​ 
 +  } 
 +} 
 +</​code>​ 
 +  * se va gestiona un obiect de tip ''​Hashmap''​ în care vor fi reținute informațiile meteorologice cu privire la diferite orașe care au fost interogate anterior, astfel încât acestea să fie furnizate de la nivel local, fără a mai fi necesară conexiunea la distanță (în acest sens trebuie definită și clasa model ''​WeatherForecastInformation''​ care să conțină datele meteorologice disponibile - temperatură,​ viteza vântului, stare generală, presiune, umiditate - implementând pentru fiecare dintre acestea metode de tip getter și setter): <code java> 
 +data = new HashMap<​String,​ WeatherForecastInformation>​();​ 
 +</​code>​ 
 +  *  atâta vreme cât firul de execuție nu este întrerupt (aplicația Android nu a fost distrusă), sunt acceptate conexiuni de la clienți (prin invocarea metodei ''​accept()''​ apelată pe obiectul de tip ''​ServerSocket'',​ aceasta întorcând un obiect de tip ''​Socket''​),​ comunicația dintre acestea fiind tratată pe un fir de execuție dedicat: <code java> 
 +@Override 
 +public void run() { 
 +  try { 
 +    while (!Thread.currentThread().isInterrupted()) { 
 +      Log.i(Constants.TAG,​ "​[SERVER] Waiting for a connection..."​);​ 
 +      Socket socket = serverSocket.accept();​ 
 +      Log.i(Constants.TAG,​ "​[SERVER] A connection request was received from " + socket.getInetAddress() + ":"​ + socket.getLocalPort());​ 
 +      CommunicationThread communicationThread = new CommunicationThread(this,​ socket); 
 +      communicationThread.start();​ 
 +    } 
 +  } catch (ClientProtocolException clientProtocolException) { 
 +    Log.e(Constants.TAG,​ "An exception has occurred: " + clientProtocolException.getMessage());​ 
 +    if (Constants.DEBUG) { 
 +      clientProtocolException.printStackTrace();​ 
 +    } 
 +  } catch (IOException ioException) { 
 +    Log.e(Constants.TAG,​ "An exception has occurred: " + ioException.getMessage());​ 
 +    if (Constants.DEBUG) { 
 +      ioException.printStackTrace();​ 
 +    } 
 +  } 
 +} 
 +</​code>​ 
 + 
 +De remarcat faptul că pentru un obiect de tip ''​Socket''​ se poate determina:​ 
 +  * adresa Internet, furnizată de metoda ''​getInetAddress()'';​ 
 +  * portul, furnizat de metoda ''​getLocalPort()''​. 
 + 
 +Pornirea firului de execuție corespunzător serverului va fi realizată pe metoda de callback a obiectului ascultător pentru evenimentul de tip apăsare a butonului aferent din interfața grafică: 
 + 
 +În prealabil, trebuie să se verifice completarea câmpului text care conține portul pe care vor fi acceptate conexiuni de la clienți. 
 + 
 +<code java> 
 +private class ConnectButtonClickListener implements Button.OnClickListener { 
 +  @Override 
 +  public void onClick(View view) { 
 +    String serverPort = serverPortEditText.getText().toString();​ 
 +    if (serverPort == null || serverPort.isEmpty()) { 
 +      Toast.makeText( 
 +        getApplicationContext(),​ 
 +        "​Server port should be filled!",​ 
 +        Toast.LENGTH_SHORT 
 +      ).show(); 
 +      return; 
 +    } 
 +    serverThread = new ServerThread(Integer.parseInt(serverPort));​ 
 +    if (serverThread.getServerSocket() != null) { 
 +      serverThread.start();​ 
 +    } else { 
 +      Log.e(Constants.TAG,​ "[MAIN ACTIVITY] Could not creat server thread!"​);​ 
 +    } 
 +  } 
 +} 
 +</​code>​ 
 + 
 +<note tip> 
 +Este important ca atunci când aplicația Android este distrusă să se oprească firul de execuție corespunzător serverului, eliberându-se resursele alocate: 
 + 
 +<code java> 
 +@Override 
 +protected void onDestroy() { 
 +  if (serverThread != null) { 
 +    serverThread.stopThread();​ 
 +  } 
 +  super.onDestroy();​ 
 +} 
 +</​code>​ 
 + 
 +<code java> 
 +public void stopThread() { 
 +  if (serverSocket != null) { 
 +    interrupt();​ 
 +    try { 
 +      if (serverSocket != null) { 
 +        serverSocket.close();​ 
 +      } 
 +    } catch (IOException ioException) { 
 +      Log.e(Constants.TAG,​ "An exception has occurred: " + ioException.getMessage());​ 
 +      if (Constants.DEBUG) { 
 +        ioException.printStackTrace();​ 
 +      } 
 +    } 
 +  } 
 +} 
 +</​code>​ 
 +</​note>​ 
 + 
 +**b)** un fir de execuție care gestionează comunicația dintre client și server: 
 +  * se obțin obiectele de tip ''​BufferedReader''​ și ''​PrintWriter''​ (prin apelul metodelor statice din clasa ''​Utilities'':​ ''​getReader()''​ și ''​getWriter()''​),​ prin care se vor realiza operațiile de citire și scriere pe canalul de comunicație;​ 
 +  * se obține o referință către obiectul gestionat de server ce reține informațiile meteorologice pentru orașele pentru care au fost realizate interogări anterior; 
 +  * se primesc de la client (prin citire de pe canalul de comunicație) parametrii necesari pentru furnizarea informațiilor meteorologice (oraș și tip de date: temperatură,​ viteza vântului, starea generală, presiune, umiditate, toate); 
 +  * în situația în care informațiile meteorologice se găsesc în obiectul gestionat de server, sunt preluate local; 
 +  * în situația în care informațiile meteorologice nu se găsesc în obiectul gestionat de server, sunt preluate prin interogarea serviciului Internet, la distanță:​ 
 +    * se realizează o cerere de tip ''​POST''​ la adresa Internet [[http://​www.wunderground.com/​cgi-bin/​findweather/​getForecast]];​ parametrul care trebuie precizat este ''​query''​ și are valoarea orașului pentru care se dorește să se obțină informațiile;​ acesta se atașează cererii de tip ''​POST'',​ folosind codificarea ''​UTF-8'';​ 
 +    * alternativ, se poate realiza o cerere de tip ''​GET''​ în care valoarea parametrului este inclusă în cadrul adresei Internet: [[https://​www.wunderground.com/​cgi-bin/​findweather/​getForecast?​query=...]]);​ 
 +    * se obține răspunsul sub forma unui șir de caractere, reprezentând codul sursă al paginii Internet;  
 +    * folosind biblioteca Jsoup, se inspectează documentul în format HTML furnizat pentru a determina locația la care sunt disponibile informațiile necesare <code html> 
 +<​script>​ 
 +  wui.asyncCityPage = true; 
 +  wui.bootstrapped.API = "";​ 
 +  wui.api_data = 
 +  { 
 +    "​response":​ { 
 +      "​version":​ "​2.0",​ 
 +      "​units":​ "​metric",​ 
 +      "​location":​ { 
 +        "​name":​ "​Bucuresti",​ 
 +        "​neighborhood":​null,​ 
 +        "​city":​ "​Bucuresti",​ 
 +        "​state":​ null, 
 +        "​state_name":"​Romania",​ 
 +        "​country":​ "​RO",​ 
 +        "​country_iso3166":​null,​ 
 +        "​country_name":"​Romania",​ 
 +        "​zip":"​00000",​ 
 +        "​magic":"​11",​ 
 +        "​wmo":"​15420",​ 
 +        "​latitude":​44.50000000,​ 
 +        "​longitude":​26.12999916,​ 
 +        "​elevation":​91.00000000,​ 
 +        "​l":​ "/​q/​zmw:​00000.11.15420"​ 
 +      }, 
 +      "​date":​ { 
 + ... 
 +      }, 
 +      "​current_observation":​ { 
 +        "​source":​ "​PWS",​ 
 +        "​station":​ { 
 +          ...  
 +        }, 
 +        "​estimated":​ null, 
 +        "​date":​ { 
 +          ... 
 +        }, 
 +        "​metar":​ "AAXX 16161 15420 22997 80502 10201 20109 30072 40180 55008 8807/ 333 55300 0//// 20454",​ 
 +        "​condition":"​Overcast",​ 
 +        "​temperature":​ 21.5, 
 +        "​humidity":​48,​ 
 +        "​wind_speed":​6.9, 
 +        ..., 
 +        "​pressure":​ 1018, 
 +        ..., 
 +      } 
 +    } 
 +  }; 
 +</​script>​ 
 +</​code>​ Se observă faptul că informațiile necesare se regăsesc în cadrul unei etichete de tip ''<​script>​ ... </​script>''​ care conține un obiect denumit ''​wui.api_dat''​ exprimat în format JSON. În acest sens, se obține lista tuturor etichetelor de tip ''​script''​ (se folosește metoda ''​getElementsByTag()''​),​ se preia conținutul acestora (prin intermediul metodei ''​data()''​ din cadrul clasei ''​Element''​) și se verifică dacă se regăsește șirul de caractere ''​wui.api_dat'';​ 
 +    ​* se inspectează documentul în format JSON pentru a obține informațiile necesare: se obțin, succesiv, obiectele atașate ca valori pentru cheile ''​response''​ → ''​current_observation''​ și ulterior datele meteorologice,​ regăsite ca valori sub cheile ''​temperature'',​ ''​wind_speed'',​ ''​condition'',​ ''​pressure'',​ ''​humidity'';​ 
 +    ​* se construiește un obiect de tipul ''​WeatherForecastInformation''​ folosind informațiile furnizate și se transmite către server pentru ca acesta să fie utilizat ulterior pentru cereri provenite de la alți clienți, vizând același oraș. 
 + 
 +<note tip> 
 +Pe server, metodele care gestionează obiectul ce conține informațiile meteorologice legate de orașse trebuie să fie **sincronizate**,​ astfel încât să nu apară inconsistențe în situația în care acestea sunt accesate concomitent de mai multe fire de execuție:​ 
 +<code java> 
 +public synchronized void setData(String city, WeatherForecastInformation weatherForecastInformation) { 
 +  this.data.put(city,​ weatherForecastInformation);​ 
 +} 
 + 
 +public synchronized HashMap<​String,​ WeatherForecastInformation>​ getData() { 
 +  return data; 
 +} 
 +</​code>​ 
 +</​note>​ 
 + 
 +  * se trimit la client (prin scriere pe canalul de comunicație) informațiile meteorologice solicitate;​ 
 +  * se eliberează resursele corespunzătoare canalului de comunicație. 
 + 
 +<code java> 
 +@Override 
 +public void run() { 
 +  if (socket != null) { 
 +    try { 
 +      BufferedReader bufferedReader = Utilities.getReader(socket);​ 
 +      PrintWriter ​   printWriter ​   = Utilities.getWriter(socket);​ 
 +      if (bufferedReader != null && printWriter != null) { 
 +        Log.i(Constants.TAG,​ "​[COMMUNICATION THREAD] Waiting for parameters from client (city / information type)!"​);​ 
 +        String city            = bufferedReader.readLine();​ 
 +        String informationType = bufferedReader.readLine();​ 
 +        HashMap<​String,​ WeatherForecastInformation>​ data = serverThread.getData();​ 
 +        WeatherForecastInformation weatherForecastInformation = null; 
 +        if (city != null && !city.isEmpty() && informationType != null && !informationType.isEmpty()) { 
 +          if (data.containsKey(city)) { 
 +            Log.i(Constants.TAG,​ "​[COMMUNICATION THREAD] Getting the information from the cache..."​);​ 
 +            weatherForecastInformation = data.get(city);​ 
 +          } else { 
 +            Log.i(Constants.TAG,​ "​[COMMUNICATION THREAD] Getting the information from the webservice..."​);​ 
 +            HttpClient httpClient = new DefaultHttpClient();​ 
 +            HttpPost httpPost = new HttpPost(Constants.WEB_SERVICE_ADDRESS);​ 
 +            List<​NameValuePair>​ params = new ArrayList<​NameValuePair>​();​ 
 +            params.add(new BasicNameValuePair(Constants.QUERY_ATTRIBUTE,​ city)); 
 +            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(params,​ HTTP.UTF_8);​ 
 +            httpPost.setEntity(urlEncodedFormEntity);​ 
 +            ResponseHandler<​String>​ responseHandler = new BasicResponseHandler();​ 
 +            String pageSourceCode = httpClient.execute(httpPost,​ responseHandler);​ 
 +            if (pageSourceCode != null) { 
 +              Document document = Jsoup.parse(pageSourceCode);​ 
 +              Element element = document.child(0);​ 
 +              Elements scripts = element.getElementsByTag(Constants.SCRIPT_TAG);​ 
 +              for (Element script: scripts) { 
 +                String scriptData = script.data();​ 
 +                if (scriptData.contains(Constants.SEARCH_KEY)) { 
 +                  int position = scriptData.indexOf(Constants.SEARCH_KEY) + Constants.SEARCH_KEY.length();​ 
 +                  scriptData = scriptData.substring(position);​ 
 +                  JSONObject content = new JSONObject(scriptData);​ 
 +   JSONObject currentObservation = content.getJSONObject(Constants.CURRENT_OBSERVATION);​ 
 +                  String temperature = currentObservation.getString(Constants.TEMPERATURE);​ 
 +                  String windSpeed = currentObservation.getString(Constants.WIND_SPEED);​ 
 +                  String condition = currentObservation.getString(Constants.CONDITION);​ 
 +                  String pressure = currentObservation.getString(Constants.PRESSURE);​ 
 +                  String humidity = currentObservation.getString(Constants.HUMIDITY);​ 
 +                  weatherForecastInformation = new WeatherForecastInformation( 
 +                    temperature,​ 
 +                    windSpeed,​ 
 +                    condition,​ 
 +                    pressure, 
 +                    humidity 
 +                  ); 
 +                  serverThread.setData(city,​ weatherForecastInformation);​ 
 +                  break; 
 +                } 
 +              } 
 +            } else { 
 +              Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] Error getting the information from the webservice!"​);​ 
 +            } 
 +          } 
 +          if (weatherForecastInformation != null) { 
 +            String result = null; 
 +            if (Constants.ALL.equals(informationType)) { 
 +              result = weatherForecastInformation.toString();​ 
 +            } else if (Constants.TEMPERATURE.equals(informationType)) { 
 +              result = weatherForecastInformation.getTemperature();​ 
 +            } else if (Constants.WIND_SPEED.equals(informationType)) { 
 +              result = weatherForecastInformation.getWindSpeed();​ 
 +            } else if (Constants.CONDITION.equals(informationType)) { 
 +              result = weatherForecastInformation.getCondition();​ 
 +            } else if (Constants.HUMIDITY.equals(informationType)) { 
 +              result = weatherForecastInformation.getHumidity();​ 
 +            } else if (Constants.PRESSURE.equals(informationType)) { 
 +              result = weatherForecastInformation.getPressure();​ 
 +            } else { 
 +              result = "Wrong information type (all / temperature / wind_speed / condition / humidity / pressure)!";​ 
 +            } 
 +            printWriter.println(result);​ 
 +            printWriter.flush();​ 
 +          } else { 
 +            Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] Weather Forecast information is null!"​);​ 
 +          } 
 +        } else { 
 +          Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] Error receiving parameters from client (city / information type)!"​);​ 
 +        } 
 +      } else { 
 +        Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] BufferedReader / PrintWriter are null!"​);​ 
 +      } 
 +      socket.close();​ 
 +    } catch (IOException ioException) { 
 +      Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] An exception has occurred: " + ioException.getMessage());​ 
 +      if (Constants.DEBUG) { 
 +        ioException.printStackTrace();​ 
 +      } 
 +    } catch (JSONException jsonException) { 
 +      Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] An exception has occurred: " + jsonException.getMessage());​ 
 +      if (Constants.DEBUG) { 
 +        jsonException.printStackTrace();​ 
 +      } 
 +    } 
 +  } else { 
 +    Log.e(Constants.TAG,​ "​[COMMUNICATION THREAD] Socket is null!"​);​ 
 +  } 
 +} 
 +</​code>​ 
 + 
 +<note important>​În cazul în care se folosește Android Studio și este instalat un SDK corespunzător nivelului de API 23 în care a fost eliminat suportul pentru biblioteca Apache HTTP Components, este necesar ca în fișierul build.gradle din directorul ''​app''​ al aplicației Android să se precizeze explicit folosirea acesteia (denumirea sa este ''​org.apache.http.legacy''​). 
 + 
 +<file gradle build.gradle>​ 
 +... 
 +android { 
 +  compileSdkVersion 23 
 +  buildToolsVersion "​23.0.2"​ 
 + 
 +  defaultConfig { 
 +    applicationId "​ro.pub.cs.systems.eim.practicaltest02"​ 
 +    minSdkVersion 16 
 +    targetSdkVersion 23 
 +    versionCode 1 
 +    versionName "​1.0"​ 
 +  } 
 +   
 +  buildTypes { 
 +    release { 
 +      minifyEnabled false 
 +      proguardFiles getDefaultProguardFile('​proguard-android.txt'​),​ '​proguard-rules.pro'​ 
 +    } 
 +  } 
 + 
 +  useLibrary '​org.apache.http.legacy'​ 
 +} 
 +... 
 +</​file>​ 
 +</​note>​ 
 + 
 +**6.** Implementarea clientului presupune un fir de execuție pe care sunt realizate următoarele operații:​ 
 +  * deschiderea unui canal de comunicație folosind parametrii de conexiune la server (adresa Internet, port); 
 +  * obținerea unor obiecte de tip ''​BufferedReader''​ și ''​PrintWriter''​ (prin apelul metodelor statice din clasa ''​Utilities'':​ ''​getReader()''​ și ''​getWriter()''​),​ prin care se vor realiza operațiile de citire și scriere pe canalul de comunicație;​ 
 +  * trimiterea la server (prin scriere pe canalul de comunicație) parametrii necesari pentru furnizarea informațiilor meteorologice (oraș și tip de date: temperatură,​ viteza vântului, starea generală, presiune, umiditate, toate); 
 +  * primirea de la server (prin citire de pa canalul de comunicație) a liniilor distincte conținând datele meteorologice (cât timp nu se primește EOF, reprezentat printr-o valoare ''​null''​ a șirului de caractere furnizat);​ 
 +  * vizualizarea datelor meterologice într-un obiect de tip ''​TextView'',​ din cadrul interfeței grafice; întucât modificarea se face din contextul unui fir de execuție care gestionează comunicația prin rețea, accesul la controlul grafic se face prin intermediul metodei ''​post()''​ care primește ca parametru un obiect anonim de tip ''​Runnable''​ al cărui conținut va fi rulat în contextul firului de execuție principal al aplicației Android; 
 +  * închiderea canalului de comunicație. 
 + 
 +<code java> 
 +@Override 
 +public void run() { 
 +  try { 
 +    socket = new Socket(address,​ port); 
 +    if (socket == null) { 
 +      Log.e(Constants.TAG,​ "​[CLIENT THREAD] Could not create socket!"​);​ 
 +      return; 
 +    } 
 +    BufferedReader bufferedReader = Utilities.getReader(socket);​ 
 +    PrintWriter ​   printWriter ​   = Utilities.getWriter(socket);​ 
 +    if (bufferedReader != null && printWriter != null) { 
 +      printWriter.println(city);​ 
 +      printWriter.flush();​ 
 +      printWriter.println(informationType);​ 
 +      printWriter.flush();​ 
 +      String weatherInformation;​ 
 +      while ((weatherInformation = bufferedReader.readLine()) != null) { 
 +        final String finalizedWeatherInformation = weatherInformation;​ 
 +        weatherForecastTextView.post(new Runnable() { 
 +          @Override 
 +          public void run() { 
 +            weatherForecastTextView.append(finalizedWeatherInformation + "​\n"​);​ 
 +          } 
 +        }); 
 +      } 
 +    } else { 
 +      Log.e(Constants.TAG,​ "​[CLIENT THREAD] BufferedReader / PrintWriter are null!"​);​ 
 +    } 
 +    socket.close();​ 
 +  } catch (IOException ioException) { 
 +    Log.e(Constants.TAG,​ "​[CLIENT THREAD] An exception has occurred: " + ioException.getMessage());​ 
 +    if (Constants.DEBUG) { 
 +      ioException.printStackTrace();​ 
 +    } 
 +  } 
 +} 
 +</​code>​ 
 + 
 +{{ :​colocvii:​colocviu02:​graphic_interface02.png?​nolink&​400 }} 
 + 
 +Pornirea firului de execuție corespunzător clientului va fi realizată pe metoda de callback a obiectului ascultător pentru evenimentul de tip apăsare a butonului aferent din interfața grafică. 
 + 
 +Verificările care trebuie realizate sunt: 
 +  * completarea câmpurilor text ce conțin parametrii de conexiune la server (adresă Internet, port); 
 +  * existența unui fir de execuție corespunzător serverului care să ruleze la momentul respectiv;​ 
 +  * completarea controalelor grafice ce conțin denumirea orașului și tipul de informație meteorologică ce se dorește a fi solicitată. 
 + 
 +<code java> 
 +private class GetWeatherForecastButtonClickListener implements Button.OnClickListener { 
 +  @Override 
 +  public void onClick(View view) { 
 +    String clientAddress = clientAddressEditText.getText().toString();​ 
 +    String clientPort ​   = clientPortEditText.getText().toString();​ 
 +    if (clientAddress == null || clientAddress.isEmpty() || 
 +      clientPort == null || clientPort.isEmpty()) { 
 +      Toast.makeText( 
 +        getApplicationContext(),​ 
 +        "​Client connection parameters should be filled!",​ 
 +        Toast.LENGTH_SHORT 
 +      ).show(); 
 +      return; 
 +    } 
 +    if (serverThread == null || !serverThread.isAlive()) { 
 +      Log.e(Constants.TAG,​ "[MAIN ACTIVITY] There is no server to connect to!"​);​ 
 +      return; 
 +    } 
 +    String city = cityEditText.getText().toString();​ 
 +    String informationType = informationTypeSpinner.getSelectedItem().toString();​ 
 +    if (city == null || city.isEmpty() || 
 +      informationType == null || informationType.isEmpty()) { 
 +      Toast.makeText( 
 +        getApplicationContext(),​ 
 +        "​Parameters from client (city / information type) should be filled!",​ 
 +        Toast.LENGTH_SHORT 
 +      ).show(); 
 +      return; 
 +    } 
 +    weatherForecastTextView.setText(Constants.EMPTY_STRING);​ 
 +    clientThread = new ClientThread( 
 +      clientAddress,​ 
 +      Integer.parseInt(clientPort),​ 
 +      city, 
 +      informationType,​ 
 +      weatherForecastTextView);​ 
 +    clientThread.start();​ 
 +  } 
 +} 
 +</​code>​ 
 + 
 +Conexiunea la serverul implementat în cadrul aplicației Android se poate realiza și prin intermediul unei console de pe mașina fizică, folosind utilitarele ''​nc''​ (Linux), respectiv ''​telnet''​ (Windows). 
 + 
 +Trebuie determinată adresa Internet la care poate fi accesat dispozitivul mobil: 
 +  * dispozitiv fizic: se folosește USB Tethering, iar adresa Internet este indicată de valoarea //Default Gateway// a rețelei ''​usb0''​ / ''​rndis0''​ - Linux, respectiv Ethernet pe Windows (se rulează comenzile ''​ifconfig''​ pe Linux, ''​ipconfig''​ pe Windows); 
 +  * dispozitiv virtual: 
 +    * Genymotion: adresele Internet sunt alocate de serverul DHCP din cadrul VirtualBox în intervalul ''​192.168.56.101''​ → ''​192.168.56.254'';​ 
 +    * AVD: se utilizează redirectarea porturilor. 
 + 
 +<columns 100 50> 
 + 
 +<​code>​ 
 +student@eim2016:​~$ nc 192.168.56.101 5000 
 +Bucuresti 
 +all 
 +temperature:​ 17.0 
 +wind_speed: 5.0 
 +condition: Clear 
 +pressure: 1015 
 +humidity: 68 
 +</​code>​ 
 + 
 +<​newcolumn>​ 
 + 
 +<​code>​ 
 +C:​\Users\Eim2016>​ telnet 192.168.56.101 5000 
 +Bucuresti 
 +all 
 +temperature:​ 17.0 
 +wind_speed: 5.0 
 +condition: Clear 
 +pressure: 1015 
 +humidity: 68 
 +Connection to host lost. 
 +</​code>​ 
 + 
 +</​columns>​
  
 **7.** Pentru încărcarea codului în contextul depozitului din cadrul contului Github personal: **7.** Pentru încărcarea codului în contextul depozitului din cadrul contului Github personal:
Line 259: Line 744:
  
 <​code>​ <​code>​
-student@pdsd2015:​~/​PracticalTest02$ git add * +student@eim2016:​~/​PracticalTest02$ git add * 
-student@pdsd2015:​~/​PracticalTest02$ git commit -m "​finished tasks for PracticalTest02"​ +student@eim2016:​~/​PracticalTest02$ git commit -m "​finished tasks for PracticalTest02"​ 
-student@pdsd2015:​~/​PracticalTest02$ git push origin master+student@eim2016:​~/​PracticalTest02$ git push origin master
 </​code>​ </​code>​
  
Line 267: Line 752:
  
 <​code>​ <​code>​
-student@pdsd2015:​~/​PracticalTest02$ git config --global user.name "​Perfect Student"​ +student@eim2016:​~/​PracticalTest02$ git config --global user.name "​Perfect Student"​ 
-student@pdsd2015:​~/​PracticalTest02$ git config --global user.email perfectstudent@cs.pub.ro+student@eim2016:​~/​PracticalTest02$ git config --global user.email perfectstudent@cs.pub.ro
 </​code>​ </​code>​
  
-**8.**+**8.** ​Înregistrarea unui serviciu în cadrul rețelei locale se face prin intermediul unor obiecte ''​NsdServiceInfo''​ (Android NSD) respectiv ''​ServiceInfo''​ (JmDNS). Acestea sunt transmise ca parametrii ai metodelor ''​registerService()''​ din clasele ''​NsdManager'',​ respectiv ''​JmDNS''​.
  
-**9.**+Aceste obiecte sunt caracterizate prin parametrii de conectare la serviciu: 
 +  ​* ''​NsdServiceInfo''​ - adresaInternet (metoda ''​setHost()''​) și port (metoda ''​setPort()''​);​ 
 +  ​* ''​ServiceInfo''​ - portul, configurat prin intermediul constructorului.
  
-**10.**+Se recomandă ca portul să fie furnizat de sistemul de operare, pentru a evita situațiile în care utilizatorul poate specifica un port care este ocupat. În acest sens, se instanțiază un obiect de tip ''​ServerSocket''​ care primește parametrul ''​0'',​ indicându-se astfel faptul că se dorește utilizarea unui port aleator, care poate fi folosit la momentul respectiv. Ulterior, parametrii de conectare pot fi obținuți folosind metodele specifice ale unui astfel de obiect: 
 +  ​* adresa Internet - ''​getInetAddress()'';​ 
 +  ​* port - ''​getLocalPort()''​.
  
 +<code java>
 +ServerSocket serverSocket = new ServerSocket(0);​
 +if (serverSocket != null) {
 +  InetAddress inetAddress = serverSocket.getInetAddress();​
 +  int port = serverSocket.getLocalPort();​
 +}
 +</​code>​
 +
 +**9.** Protocolul SIP pornește de la o presupunere optimistă conform căreia atât sursa cât și destinația se găsesc în cadrul aceluiași sistem autonom, astfel încât nu sunt necesare credențiale pentru autentificare. Din acest model, o cerere de tip ''​REGISTER''​ se transmite inițial de către user agent fără aceste informații. În condițiile în care răspunsul furnizat de registration server este //Status: 401 Unauthorized//,​ mesajul este retransmis împreună cu informațiile necesare identificării (SIP Authorization ID, Password). Se poate observa că dimensiunile celor două pachete sunt diferite (mesajul fără credențiale 1095 octeți, mesajul cu credențiale 1249 octeți). În cazul în care informațiile de autentificare sunt valide, răspunsul va fi //Status: 200 OK//.
 +
 +{{ :​laboratoare:​laborator09:​wireshark01.png?​nolink&​800 }}
 +
 +**10.** Harta Google este implementată în SDK-ul Android:
 +  * prin intermediul unei componente grafice de tipul [[https://​developer.android.com/​reference/​com/​google/​android/​gms/​maps/​MapView.html|MapView]];​
 +  * în cadrul unui fragment, de tipul [[http://​developer.android.com/​reference/​com/​google/​android/​gms/​maps/​MapFragment.html|MapFragment]].
 +
 +Referințele către aceste obiecte se obțin în mod obișnuit, prin intermediul metodelor ''​findViewById()'',​ respectiv ''​findFragmentById()''​.
 +
 +Pe baza controalelor grafice ''​MapView''​ sau ''​MapFragment'',​ se poate obține o instanță a unui obiect [[https://​developer.android.com/​reference/​com/​google/​android/​gms/​maps/​GoogleMap.html|GoogleMap]],​ prin intermediul metodelor ''​getMap()'',​ respectiv ''​getMapAsync()''​. Se recomandă să se folosească metoda asincronă care garantează faptul că obiectul furnizat este nenul. Metoda de callback ''​onMapReady()''​ a clasei ascultător ''​OnMapReadyCallback''​ nu va fi apelată în situația în care serviciul Google Play Services nu este disponibil pe dispozitivul mobil sau obiectul este distrus imediat după ce a fost creat.
 +
 +<code java>
 +if (googleMap == null) {
 +  ((MapFragment)getFragmentManager().findFragmentById(R.id.google_map)).getMapAsync(new OnMapReadyCallback() {
 +    @Override
 +    public void onMapReady(GoogleMap readyGoogleMap) {
 +      googleMap = readyGoogleMap;​
 +    }
 +  });
 +}
 +</​code>​
colocvii/colocviu02.1431783507.txt.gz · Last modified: 2017/02/20 20:42 (external edit)
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0