Android3_ME
Android Studio (1) – Programiranje
program 0 transaction.replace(R.id.content, selectedFragment); kjer se vsebina določi glede na izbrani fragment: private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { Fragment selectedFragment = null; switch (item.getItemId()) { case R.id.navigation_home: mTextMessage.setText(R.string.title_home); selectedFragment = ItemOneFragment.newInstance(); break; case R.id.navigation_dashboard: mTextMessage.setText(R.string.title_dashboard); selectedFragment = ItemTwoFragment.newInstance(); break; case R.id.navigation_notifications: mTextMessage.setText(R.string.title_notifications); selectedFragment = ItemThreeFragment.newInstance(); break; } FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.content, selectedFragment); transaction.commit(); return true; } };
program 1 public class ItemOneFragment extends Fragment
program 2 public static ItemOneFragment newInstance() { ItemOneFragment fragment = new ItemOneFragment(); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_item_one, container, false); return view; }
program 3 ******** MAIN ACTIVITY *********** <?xml version=“1.0“ encoding=“utf-8“?> <LinearLayout … </LinearLayout> ******** ITEM FRAGMENT *********** <RelativeLayout … </RelativeLayout>
program 4 TextView tv = new TextView(this); mContext = this.getContext(); Geocoder gcd = new Geocoder(mContext, Locale.getDefault()); locationManager = (LocationManager) mContext .getSystemService(LOCATION_SERVICE); writeToFile(formatTmp + „ GPS; „ + gpsAddress, mContext); preferences = PreferenceManager .getDefaultSharedPreferences(this.getApplicationContext());
program 5 import android.content.SharedPreferences; import android.preference.PreferenceManager; private SharedPreferences preferences; ************* BRANJE ************* preferences = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()); Integer cboxSMS_checked = preferences.getInt(„cboxSMS“, 0); if (cboxSMS_checked == 1){ //SEND SMS } else{ //WRITE TO FILE } ************* PISANJE ************* cboxSMS.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if ( isChecked ){ // STORE cboxSMS SharedPreferences.Editor editor = preferences.edit(); editor.putInt(„cboxSMS“, 1); // value to store nadaljevanje programa 5 nadaljevanje programa 5 editor.commit(); } else{ // STORE cboxSMS SharedPreferences.Editor editor = preferences.edit(); editor.putInt(„cboxSMS“, 0); // value to store editor.commit(); } } });
program 6 import android.widget.Toast; private void sendSMS(String phoneNumber, String message, Context context){ SmsManager sms = SmsManager.getDefault(); sms.sendTextMessage(phoneNumber, null, message, null, null); Toast.makeText(context, „SMS poslan na „ + phoneNumber, Toast.LENGTH_SHORT).show(); }
program 7 <uses-permission android_name=“android.permission.ACCESS_FINE_LOCATION“ />
program 8 import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; public class GPSTracker extends AppCompatActivity implements LocationListener { // flag for GPS status boolean isGPSEnabled = false; // flag for network status boolean isNetworkEnabled = false; // flag for GPS status boolean canGetLocation = false; Location location; double latitude; double longitude; // The minimum distance to change Updates in meters private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters // The minimum time between updates in milliseconds private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute // Declaring a Location Manager protected LocationManager locationManager; @Override protected void onCreate(Bundle savedInstanceState) { // … getLocation(); } }
program 9 public Location getLocation() { try { locationManager = (LocationManager) mContext .getSystemService(LOCATION_SERVICE); // getting GPS status isGPSEnabled = locationManager .isProviderEnabled(LocationManager.GPS_PROVIDER); // getting network status isNetworkEnabled = locationManager .isProviderEnabled (LocationManager.NETWORK_PROVIDER); if (!isGPSEnabled && !isNetworkEnabled) { nadaljevanje programa 9 nadaljevanje programa 9 // no network provider is enabled } else { this.canGetLocation = true; if (isNetworkEnabled) { locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this ); if (locationManager != null) { location = locationManager .getLastKnownLocation (LocationManager.NETWORK_PROVIDER); if (location != null) { latitude = location.getLatitude(); longitude = location.getLongitude(); } } } // if GPS Enabled get lat/long using GPS Services if (isGPSEnabled) { if (location == null) { locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this); if (locationManager != null) { location = locationManager.getLastKnownLocation (LocationManager.GPS_PROVIDER); if (location != null) { latitude = location.getLatitude(); longitude = location.getLongitude(); } } } } } } catch (Exception e) { e.printStackTrace(); } return location; }
program 10 import android.location.Geocoder; public Location getLocation() { // … String city = getCityName(latitude, longitude); // … } public String getCityName(double latitude, double longitude){ String cityName = null; String streetName = null; Geocoder gcd = new Geocoder(getBaseContext(), Locale.getDefault()); List<Address> addresses; try { addresses = gcd.getFromLocation( latitude, longitude, 1); if (addresses.size() > 0) System.out.println(addresses.get(0).getLocality()); cityName = addresses.get(0).getLocality(); streetName = addresses.get(0).getAddressLine(0); } catch (IOException e) { e.printStackTrace(); } String s = "Currrent Address: " + cityName + ", " + streetName; return s; }
[/vc_column_text][/vc_tta_section][/vc_tta_tabs]
Avtorica: Marjana Erdelji
E-pošta: marjana.erdelji@gmail.com
2017_256_26
Gibanje lahko sledimo na različne načine. Na kakšen način sledimo, je odvisno od vrste gibanja in razpoložljivih pripomočkov. Mi smo se tokrat osredotočili na zaznavanje in sledenje gibanja vozila v primeru kraje s pomočjo aplikacije na telefonu (telefon mora biti nameščen v vozilu), ki uporablja razpoložljive senzorje. Dokler vozilo stoji, želimo porabiti čim manj baterije telefona. Ko se premakne, pa želimo to vedeti takoj in pričakujemo tudi natančno informacijo o lokaciji, kjer se vozilo trenutno nahaja, neodvisno od porabljenih virov. Izdelali smo Android mobilno aplikacijo z imenom Sledenje gibanja (ang. Movement tracker). Projekt je pripravljen do faze prikaza delovanja (demo verzija).
V prvem članku na to temo smo si namestili programsko okolje Android Studio, v katerem smo programirali mobilno aplikacijo za pošiljanje SMS sporočil. V drugem članku smo nadaljevali s senzorji in programirali mobilno aplikacijo za detekcijo premika. Sedaj vam predstavljamo še tretjo mobilno aplikacijo, ki uporablja elemente prvih dveh in upošteva tudi GPS koordinate. Dvakrat smo vam že obljubili, da vam ne bo potrebno programirati. Tudi tokrat vam lahko to obljubimo. Kodo si lahko prenesete z dosegljive spletne povezave, še bolj bomo pa veseli, če ta demo projekt nadgradite v aplikacijo z dodano vrednostjo.
Opis projekta
Zastavili smo si rešiti problem, ki je zaznavanje in sledenje vozila v primeru kraje, s pomočjo aplikacije na telefonu. Preden zaidemo v programsko rešitev tega problema si poglejmo, katere funkcionalnosti smo želeli pokriti. V času mirovanja želimo na našem telefonu uporabiti programski senzor za določanje orientacije, ki uporablja strojni senzor pospeška in magnetni senzor. Na tak način se za določanje premika, ki v našem primeru primeru predstavlja sprožilec akcije oziroma alarm za krajo vozila, izognemo pogostemu preverjanju lokacije s pomočjo GPS-a in mobilnega interneta. V primeru alarma želimo vklopiti sledenje lokacije premika vozila, ki bi bilo dovolj točno. Javljanje alarma in lokacije je rešeno preko sms sporočil na zahtevo in s prikazom dogodkov v aplikaciji. Želimo tudi možnost nastavljanja nekaterih parametrov aplikacije. V produkcijski verziji aplikacije bi želeli kako drugače, za prikaz delovanja pa se zdi ustrezno.
Običajno združujemo funkcionalnosti na podlagi sorodnosti delovanja, kar neposredno vpliva tudi na izgled oziroma postavitev uporabniškega vmesnika (Slika 2). Vsebino smo razdelili na 3 strani – navigacija: Domov (ang. Home), Nastavitve (ang. Settings) in Dnevnik dogodkov (ang. Log). Domača stran je namenjena prikazu vrednosti senzorja pospeška, magnetnega senzorja in prikazu GPS koordinat ter naslova lokacije. Stran Nastavitve je namenjena za nastavitev številke za pošiljanje alarmov in možnost vklopa / izklopa obsega obveščanja. Na strani Dnevnik dogodkov pa prikažemo vse dogodke, ki so se zgodili.
Kaj je aplikacija in kaj je projekt? Ko omenjamo aplikacijo imamo v mislih mobilno aplikacijo, ki je nameščena na telefonu. Ko omenjamo projekt, je to projekt, izdelan v programskem okolju Android Studio, kar je pravzaprav programska koda aplikacije.
Postavitev strani smo uredili na sledeč način (Slika 2): prikazan je naslov navigacije, ta se spreminja, ko se sprehajamo po straneh. Naslednji element z imenom Fragment označuje vsebino, ki je vezana na stran, ki jo prikažemo. In navsezadnje: Če prikličete iz spomina prejšnji članek na to temo, premik zaznamo s pomočjo pospeškometra in magnetometra. Ob premiku se nam prikaže slika, ki označuje, da se je zgodil premik (DEVICE MOVED). To sliko želimo prikazati ne glede na to, na kateri strani se trenutno nahajamo.
Če sedaj to postavitev na uporabniškem vmesniku aplikacije “prevedemo” v strukturo projekta, bi le-ta izgledala kot je prikazano na Sliki 3. Na sliki vidimo katere razrede in vmesnike uporabljamo v projektu in katere funkcionalnosti so izvedene. Struktura je zahtevnejša zaradi uporabe navigacije in fragmentov. V primerjavi s prejšnjimi dvemi članki so dodane funkcionalnosti: GPS lokacija, shranjevanje parametrov aplikacije: shranjevanje številke, možnost o(ne)mogočanja pošiljanja SMS alarmov (premik, status baterije in GPS lokacije), pisanje dogodkov v datoteko in prikaz datoteke.
V izvedbi projekta si poglejmo, kako smo naredili nov projekt. Opisali smo tudi nekaj aplikacijskih komponent, ki smo jih uporabili: razred Fragment, razred Context, vmesnik SharedPreferences in gradnik Toast. Uporabo/delovanje smo prikazali skozi programsko kodo.
Izvedba projekta
Projekt smo tudi tokrat programirali v programskem okolju Android Studio. V Android Studiu smo odprli nov projekt in ga poimenovali Movement Tracker. Izbrali smo naslednjo konfiguracijo projekta: platforma za telefon in tablični računalnik z minimalnim SDK – API 16, Android 4.1 (Jelly Bean), ki je podprta na približno 99,2% napravah. Za razliko od prejšnjih dveh aplikacij, kjer smo začeli s prazno aktivnostjo, smo tokrat izbrali aktivnost z navigacijo (ang. Bottom Navigation Activity). Ime aktivnosti nismo preimenovali, pustili smo privzeto ime MainActivity. Tako smo ustvarili prazen projekt in si ogledali katere datoteke so se pripravile sedaj, ko smo izbrali navigacijo. Kaj hitro smo ugotovili, da moramo nekako poskrbeti za to, da bomo glede na navigacijo prikazovali pravilno vsebino. V tem koraku smo se spoznali s fragmenti, to je del celotne vsebine na uporabniškem vmesniku. Prvi fragment določa osrednji del vsebine Domov, drugi fragment osrednji del vsebine Nastavitve in tretji fragment osrednji del vsebine Dnevnika dogodkov. Če pogledamo na fragment z vidika datotek, tako kot naša glavna aktivnost MainActivity vsebuje datoteki MainActivity.java za programsko kodo in MainActivity.xml za postavitev elementov na strani uporabniškega vmesnika, tako tudi fragment vsebuje javansko kodo (npr. ItemOneFragment.java) in postavitev elementov na uporabniškem vmesniku (npr. ItemOneFragment.xml). Tu se bomo osredotočili predvsem na programsko kodo.
Fragment, razred
Fragment predstavlja prikaz dela uporabniškega vmesnika znotraj aktivnosti. Z uporabo fragmentov dosežemo dinamični prikaz strani v aplikaciji. Fragmente, ki bodo določili delno vsebino, moramo vezati na izbrano navigacijo. V našem primeru smo v MainActivity.java poiskali metodo OnNavigationItemSelectedListener(). Za vsako stran v navigaciji smo naredili nov primerek fragmenta z uporabo razreda Fragment. Za stran Domov smo določili fragment ItemOneFragment, za stran Nastavitve fragment ItemTwoFragment in za tretjo stran Dnevnik dogodkov fragment ItemThreeFragment. Ključna je vrstica, glej program 0.
V aktivnosti MainActivity.java v metodi onCreate določamo kateri fragment se naloži prvi, torej ko odpremo aplikacijo. V našem primeru je to prvi fragment ItemOneFragment, ki prikaže fragment osrednje vsebine Domov.
Osredotočimo se sedaj na posamezne fragmente. Za vsakega smo morali ustvariti po dve datoteki, eno formata .java za kodo in eno formata .xml za prikaz. Vsak posamezni fragment ima podobno definicijo, ki razširja razred Fragment, na primer, glej program 1.
OnCreateView je metoda, kjer določimo vsebino, ki se bo prikazala. V tej metodi na primer spreminjamo vsebino tekstovnih elementov v fragment idr. V MainActivity.java za razliko vsebino določamo v metodi OnCreate, glej program 2.
Omenimo lahko tudi, kako smo določili postavitev uporabniškega vmesnika. Glavna razlika je v tem, da glavna aktivnost vsebuje oznako LinearLayout, med tem ko fragmenti vsebujejo oznako RelativeLayout. Prikaz na strani spreminjamo torej tako, da v fragmentu določimo, kaj naj se prikaže, glej program 3.
Context, abstraktni razred
Context je abstraktni razred preko katerega dostopamo do globalnih informacij o okolju/stanju aplikacije. Omogoča dostop do aplikacijsko specifičnih virov in razredov, prav tako omogoča klice navzgor (ang. upcalls) za operacije kot so zagon aktivnosti, oddajanje in sprejemanje namer (intents) idr.
Kontekst v aplikaciji prikličemo z različnimi metodami, odvisno v katerem “kontekstu” ga uporabimo: getApplicationContext(), getContext(), getBaseContext(), this – kontekst trenutne aktivnosti.
Navajamo nekaj različnih primerov rabe konteksta aplikacije, glej program 4.
Konteksta se moramo zavedati, oziroma ga upoštevati. Brez konteksta aplikacija ni uporabna.
SharedPreferences, vmesnik
SharedPreferences je programski vmesnik, ki smo ga uporabili za shranjevanje uporabniško določenih parametrov (npr. omogočeno pošiljanje SMS alarmov). Parametri so na voljo tudi po ponovnem zagonu aplikacije. V kodi je prikazan primer rabe za branje in zapisovanje parametra cboxSMS. Ta parameter je vezan na to, ali je uporabnik omogočil ali onemogočil pošiljanje SMS alarmov, glej program 5.
Toast, gradnik
Toast je gradnik (ang. widget), ki smo ga uporabili pri pošiljanju SMS sporočil, da bi ohranili nadzor nad izvajanjem kode v ozadju. Ko se v ozadju pošlje SMS sporočilo, se nam na zaslonu prikaže okvirček z obvestilom o poslanem SMS-u. V kodi je prikazan primer rabe, glej program 6.
Določanje trenutne GPS lokacije
Za uporabo te funkcionalnosti boste žal morali vklopiti:
- (Wi-Fi,)
- GPS in
- Mobilne podatke.
- Če pobrskamo po androidovi dokumentaciji za razvijalce, lahko podatke o trenutni lokaciji dobimo z uporabo Google Play servisov, bolj natančno z uporabo ponudnika lokacije, ki se imenuje Fused Location Provider. Ta nam omogoča določanje zahtev na visokem nivoju, kot sta na primer visoka točnost ali nizka poraba moči. Prav tako omogoča optimizacijo porabe baterije naprave. Za uporabo Google Play servisov, moramo v projektu namestiti ta SDK pripomoček. Podatek o lokaciji dobimo s klicem metode getLastLocation().
Na sistemu Android 8.0 (API nivo 26) ali višje: če aplikacija teče v ozadju, ko zahtevamo trenutno lokacijo, jo naprava izračuna le nekajkrat vsako uro. Vprašanje je, če si mi to v primeru kraje lahko privoščimo. Vsekakor pa je treba aplikacijo, ki uporablja te storitve, prilagoditi tem omejitvam v ozadju. Mi smo za pridobivanje lokacije uporabili starejši API (servis LOCATION_SERVICES, uporaba objekta LocationManager), kjer smo implementirali razred LocationListener. Razlog je bolj preprosta koda.
V manifestu aplikacije, to je v datoteki AndroidManifest.xml moramo omogočiti aplikacijske pravice za dostop do podatkov o lokaciji. Na voljo imamo dva tipa pravic: ACCESS_COARSE_LOCATION in ACCESS_FINE_LOCATION. Tip pravice določa kako natančne podatke o lokaciji bomo dobili od API-ja. Prvi tip pravic nam bo vrnil lokacijo z natančnostjo primerljivo velikosti mestnega stanovanjskega bloka. Spodobilo bi se, da pred namestitvijo aplikacije na to uporabnika opozorimo in pred uporabo te storitve preverimo, ali so pravice omogočene (Nastavitve – Lokacijske storitve) in ponudimo dialogno okno za vklop storitev, ki jih aplikacija za svoje delovanje zateva , glej program 7.
Ko izvedemo našo aktivnost z vmesnikom LocationListener, moramo izvesti tudi njegove metode: onProviderEnabled, onLocationChanged, onStatusChanged in onProviderDisabled, česar v naših primerih kode ne bomo izpostavili. Pripravili smo si spremenljivke za označevanje statusa GPS in dosegljivost interneta, spremenljivki za GPS koordinati latitude in longitude ter določili minimalno razdaljo, ki povzroči osveževanje podatka o lokaciji in minimalen čas med osveževanjem podatka, glej program 8.
Metoda getLocation() je tista, ki pridobi infomacijo o lokaciji. Kličemo jo v metodi OnCreate in ob spremembah lokacije v metodi LocationChanged. Sprehodimo se na hitro čez metodo: Najprej preverimo, če sta GPS in internet omogočena. V tem primeru postavimo parametre za osveževanje. Če je bila uporaba objekta tipa LocationManager uspešna, preberemo GPS koordinati latitude in longitude , glej program 9.
Podatek o GPS koordinatah trenutne lokacije, na primer 46,0477579 (lat.) in 14,4829543 (long.) nam prav veliko ne pomeni, če ni prikazan vsaj na zemljevidu. Če zemljevida ne želimo uporabiti, imamo na voljo pridobivanje naslova iz GPS koordinat. Mi smo uporabili pridobivanje naslova s pomočjo razreda Geocoder. Raba tega razreda je precej enostavna, kar je razvidno iz kode , glej program 10.
S tem smo zaključili z opisovanjem naše aplikacije skozi kodo. Kakšen je sam izgled je najbolje preveriti na ekranskih slikah aplikacije (Sliki 4 in 5). Na videz enostavna aplikacija bi potrebovala še nekaj dodelave. A ker se podarjenemu konju ne gleda v zobe, smo vam z veseljem pripravili dva paketa aplikaciji: Movement Tracker in GPS Tracker. Aplikacija GPS Tracker je vsebovana v aplikaciji Movement Tracker, posebej smo jo priložili zgolj zaradi boljše preglednosti. Upamo, da si boste izdelali svojo aplikacijo, kljub neprijetnemu občutku, da je že vse napisano.