Week 04

Service, Intent, Activity Manager, ButterKnife, SDK Version

Úvod

Torch Application - The Final Part

  • Baterka síce funguje, ale aj tak má zatiaľ svoje nedostatky. Jedným z nich je, že ak sa napr. vypne obrazovka alebo aplikáciu minimalizujete, dôjde k uvoľneniu zdrojov, čo nemusí byť vždy žiadúce. A rovnako tak to nepríjemné blikanie, keď aplikáciu otočíte. Zatiaľ to vyzerá tak, že s tým, čo poznáme, sa už ďalej nepohneme. A preto si predstavíme ďalší stavebný komponent Android aplikácií. Dnes sa pozrieme na služby (slide).

Activity Lifecycle

  • S našou aplikáciou však máme stále jeden veľký problém - po zmene orientácie dôjde k resetnutiu stavu aktivity (spustí sa nanovo). To nie je veľmi dobré, nakoľko môžeme stratiť veľmi dôležité údaje (napr. výsledok športovej činnosti po 30min, stav rozohratej hry, …).

  • Problém by bolo možné vyriešiť zakázaním zmeny orientácie aplikácie. Toto je možné zabezpečiť pridaním nasledujúceho atribútu pre potrebnú aktivitu v súbore s manifestom aplikácie:

  • Aby sme lepšie porozumeli tomu, prečo je to vlastne tak, pozrime sa na životný cyklus aktivity (slide). Aktivita sa teda môže nachádzať v jednom z týchto stavov:

    • starting - V tomto momente dochádza k spusteniu aktivity a teda jej vytvoreniu ako objektu. Volajú sa postupne metódy onCreate(), onStart() a onResume()
    • running - Stav, v ktorom aplikácia vykonáva svoju činnosť v tzv. UI vlákne - je viditeľná a aktívna.
    • paused - Aplikácia stratila fokus, napr. je prekrytá inou nie plneobrazovkovou priesvitnou aktivitou. Aplikácia je teda čiastočne viditeľná a drží svoj stav. Pri prechode do tohto stavu volá metódu onPause().
    • stopped - Ak je aktivita kompletne prekrytá inou aktivitou, je v tomto stave. Stále síce obsahuje svoje členské premenné, ale nie je používateľovi viditeľná.
    • destroyed - Stav, kedy sa aktivita ukončuje (buď s povolením používateľa alebo automaticky). Ak sa znova zobrazí používateľovi, aktivita je vytvorená nanovo.
  • Basically, whenever Android destroys and recreates your Activity for orientation change, it calls onSaveInstanceState() before destroying and calls onCreate() after recreating. Whatever you save in the bundle in onSaveInstanceState, you can get back from the onCreate() parameter.

  • Vytvoríme teda metódu onSaveInstanceState() a uložíme v nej stav baterky:

    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putBoolean("state", this.isOn);
        }
        
  • A následne upravíme aj metódu onCreate(), v ktorej v prípade, že bol stav aktivity uložený, tak si ho odpamätáme:

    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        
            if(savedInstanceState != null){
                this.isOn = savedInstanceState.getBoolean("state");
                if(this.isOn == true){
                    ImageView image = (ImageView) findViewById(R.id.image);
                    Button button = (Button) findViewById(R.id.button);
        
                    image.setImageResource(R.drawable.bulb_on);
                    button.setText(R.string.turn_off);
                }
            }else{
                this.isOn = false;
            }
        }
        
  • Tento fragment kódu sa nám čiastočne prekrýva s metódou toggle(), takže môžeme urobiť menší refaktoring:

    • vytvoríme súkromnú metódu render(), ktorá len vyrenderuje aktivitu na základe aktuálneho stavu (na základe hodnoty členskej premennej isOn, a
    • aktualizujeme metódu toggle(), z ktorej presunieme všetok fragment kódu týkajúci sa renderovania do metódy render().
  • Metóda toggle() teda bude vyzerať nasledovne:

    
        public void toggle(View view){
            player.start();
            this.isOn = !this.isOn;
            render();
        }
        
  • Metóda render() bude po refaktoringu vyzerať nasledovne:

    
        private void render(){
            Button button = (Button) findViewById(R.id.button);
            ImageView image = (ImageView) findViewById(R.id.image);
            Camera.Parameters params = this.camera.getParameters();
        
            if(this.isOn == true) {
                image.setImageResource(R.drawable.bulb_on);
                button.setText(R.string.turn_off);
        
                params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                this.camera.setParameters(params);
                this.camera.startPreview();
            }else{
                image.setImageResource(R.drawable.bulb_off);
                button.setText(R.string.turn_on);
        
                params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                this.camera.setParameters(params);
                this.camera.stopPreview();
            }
        }
        
  • Následne metóda onCreate() bude vyzerať takto:

Services Introduction

  • Služba beží na pozadí bez priameho zásahu používateľa. Služba nemá žiadne používateľské rozhranie, nie je nijako zviazaná s UI. (alebo nemusí byť?)

  • Služby sú však spúšťané s vyššou prioritou ako neaktívne aktivity, takže Android ich v prípade nedostatočných systémových prostriedkov neukončí.

  • Tu však treba ešte povedať aj to, čo služba nie je. Takže:
    • (slide) Služba nie je samostatným procesom! Služba beží v rovnakom procese, ako aplikácia, ktorej je súčasťou.
    • (slide) Služba nie je vláknom! Nepracuje mimo hlavné vlákno aplikácie.
  • Android poskytuje aj niektoré systémové služby, ktoré je možné používať v rámci aplikácie, pokiaľ tá má potrebné prístupové práva. Prístup k týmto službám je možný prostredníctvom metódy getSystemService(). Trieda Context poskytuje niekoľko konštánt pre prístup k týmto službám, ako napríklad:

    • ALARM_SERVICE - umožňuje získať prístup ku službe AlarmService.
    • BATTERY_SERVICE - umožňuje získať prístup ku BatteryManager pre správu stavu batérie.

Service Types

  • V Androide máme k dispozícii dva typy služieb (slide):
    1. started - A service is “started” when an application component (such as an activity) starts it by calling startService(). Once started, a service can run in the background indefinitely, even if the component that started it is destroyed. Usually, a started service performs a single operation and does not return a result to the caller. For example, it might download or upload a file over the network. When the operation is done, the service should stop itself.
    2. bound - A service is “bound” when an application component binds to it by calling bindService(). A bound service offers a client-server interface that allows components to interact with the service, send requests, get results, and even do so across processes with interprocess communication (IPC). A bound service runs only as long as another application component is bound to it. Multiple components can bind to the service at once, but when all of them unbind, the service is destroyed.

Service Lifecycle

  • Samotná služba má však podobne ako aktivita svoj vlastný životný cyklus. Pozrime sa naň (slide).

  • Životný cyklus služby je výrazne jednoduchší, ako v prípade aktivity. Počas neho služba prechádza len troma metódami:
    1. onCreate() - význam tejto metódy je rovnaký ako v prípade aktivity - metóda sa spustí pri vytvorení služby,
    2. onStart() - metóda sa spustí po spustení služby (zavolaní metódy startService()), a
    3. onDestroy() - metóda sa spustí pri ukončovaní služby (po zavolaní metódy stopService()).
  • Okrem uvedených metód je možné so službou komunikovať prostredníctvom metódy onStartCommand(). Túto metódu zavolá systém vždy, keď klient spustí službu explicitne volaním metódy startService().

Service Example

TorchService

Spustenie služby pomocou Activity Manager-a

  • Vytvorenú službu môžeme otestovať zatiaľ pomocou nástroja am priamo na Android zariadení: shell adb shell am startservice -n sk.tuke.smart.torch/.TorchService
  • Následne ju môžeme vypnúť pomocou volania am s parametrom stopservice: shell adb shell am stopservice -n sk.tuke.smart.torch/.TorchService

Intent

Conclusion

  • Zhrňme však ešte postup, ktorý sme použili pri práci s hw súčasťami zariadenia. (slide)

  • Až teraz môžeme povedať, že je aplikácia hotová. Bolo by ešte možné vytvoriť niekoľko úprav (napr. zrušiť orientáciu na šírku, aby aplikácia zbytočne neblikala), ale tu sa už dá hovoriť o profite. (slide)

Refactoring with ButterKnife

  • Aj pre Android existuje množstvo knižníc, ktoré nejakým spôsobom rozširujú alebo uľahčujú vývoj aplikácií. Pokiaľ budete hľadať na internete, nájdete rozličné rebríčky typu (slide) “Must have libraries” alebo (slide) “X best Android libraries”] alebo (slide) “Top 5 libraries in 2015” a podobne. V rámci tohto kurzu sa s niektorými z nich tiež zoznámime. A dnes to bude rovno knižnica s názvom ButterKnife.

  • Butterknife is a popular View “injection” library for Android. This means that the library writes common boilerplate view code for you based on annotations to save you time and significantly reduce the lines of boilerplate code written.

ButterKnife Setup

Upozornenie

Preventvívne je dobré skontrolovať postup a verziu priamo zo stránky projektu

  • Je potrebné upraviť konfiguráciu Gradle pre aplikáciu (app/build.gradle) pridaním nasledovných riadkov do časti dependencies:

    compile 'com.jakewharton:butterknife:8.8.1'
        annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

ButterKnife Usage

  • There are three major features of ButterKnife:
    1. Improved View Lookups
    2. Improved Listener Attachments
    3. Improved Resource Lookups

Improved View Lookups

Improved Listener Attachments

  • Ak chceme ošetriť kliknutie na tlačidlo alebo vo všeobecnosti na pohľad, máme dve možnosti:
    1. buď v XML súbore definujúcom rozloženie aktivity pridáme príslušnému elementu atribút android:onClick, ktorého hodnotou bude názov metódy ošetrujúceho kliknutie, alebo
    2. vytvoríme anonymnú vnútornú triedu (inner-class) v metóde setOnClickListener() objektu príslušného pohľadu, napríklad takto:
  • Knižnica ButterKnife však ponúka riešenie pomocou anotácie metódy ošetrujúcej kliknutie @OnClick(), kde parametrom je identifikátor príslušného pohľadu. Je však rovnako možné príslušnej metóde pridať v rámci anotácie naraz niekoľko pohľadov, čo je aj náš prípad - metóda toggle() bude ošetrovať kliknutie na tlačidlo aj na obrázok. Treba však samozrejme odstrániť predchádzajúcu používanú metódu.

  • Anotovaná metóda toggle() bude vyzerať nasledovne:

Min/Target/Compile SDK Version

  • Význam jednotlivých položiek je nasledovný (slide):
    • min sdk version - Is the earliest release of the Android SDK that your application can run on. Usually this is because of a problem with the earlier APIs, lacking functionality, or some other behavioral issue.

    • target sdk version - The version your application was targeted to run on. Ideally this is because of some sort of optimal run conditions. If you were to “make your app for version 19” this is where that would be specified. It may run on earlier or later releases, but this is what you were aiming for. This is mostly to indicate how current your application is for use in the marketplace, etc.

    • compile sdk version - The version of android your IDE (or other means of compiling I suppose) uses to make your app when you publish a .apk file. This is useful for testing your application as it is a common need to compile your app as you develop it. As this will be the version to compile to an APK, it will naturally be the version of your release. Likewise it is advisable to have this match you target sdk version.

  • V našom prípade teda potrebujeme nastaviť hodnotu min sdk a target sdk na 19. V ideálnom prípade aj hodnotu verzie compile sdk. Tá však závisí od nainštalovanej verzie Android SDK build tools.

Additional Resources