8. stretnutie: časť 2: Fotoaparát

Trampoty s foťákom

Ako sme spomínali v úvode, radostné opojenie z foťáka rýchlo vystrieda strach a hnus z reálnej implementácie na zariadeniach. Aj v ideálnom svete mal poskytovať Android dve možnosti na využitie fotoaparátu:

  1. fotením ste poverili systémovu aktivitu, ktorá vo výsledku vrátila cestu k fotke
  2. alebo ste si fotenie zmanažovali sami, vrátane vykresľovania náhľadu, spracovania výsledku.

V prvej možnosti stačí poslať správny intent do systému, ktorým sa spustila interná fotoaplikácia a po zhotovení fotečky ste vo výsledku aktivity dostali adresu URI k výsledku. (Ideálny prípad opisuje dokumentácia).

Druhá možnosť ponechá všetko na vás, pre prípady, že reimplementujete Instagram: sami si musíte zistiť správny objektív (áno, zariadenia môžu mať predný a zadný foťák), zmanažovať si vykresľovanie náhľadu, ustanoviť poslucháčov pre odchytávanie výsledku, zapisovanie výsledku do súborov a uvoľnovanie prostriedkov (pretože k foťáku môže pristupovať len jediná appka v systéme).

A teraz k tvrdej realite: prvá možnosť je prechádzka mínovým poľom. Niektoré zariadenia bezdôvodne force-closujú, iné zariadenia nesprávne otáčajú náhľad fotky, ďalšie zase poskytujú náhľad nepoužiteľnej verzie, alebo ukladajú fotku na dve (alebo tri) miesta naraz, či pre istotu nevrátia žiaden výsledok. Riešeníe tejto situácie, ktorá pripomína Internet Explorer 6 a vývoj webových aplikácií v roku 20XX, poskytujú prinajmenšom dve knižnice:

Druhá knižnica je, v súlade s chaosom, odsúdená na kompletný prepis, ale doposiaľ (apríl 2015) sa tak nestalo.

Samotný Android pripravil alternatívne API pre prístup k fotoaparátu reprezentované balíčkom android.hardware.camera2, ale to je k dispozícii až od API Level 21 (Android 5) a nerieši problém so starými zariadeniami.

V tomto tutoriáli si teda ukážeme len krátku tour knižnicou AndroidCameraUtil, ktorá je v rámci možnosti použiteľná a dáva rozumné výsledky pre aplikáciu, ktorá nechce byť primárne fotoaplikáciou.

Aplikácia Fotr

Príprava projektu

Vytvorme v Studiu nový projekt Fotr.

Zavedenie knižnice do projektu

Knižnica AndroidCameraUtil zatiaľ nemá žiadne oficiálne vydania, ale zdrojáky z GitHubu sú voľne k dispozícii. Stiahnime si z repa ralfgehrehr/AndroidCameraUtil zdrojáky a rozbaľme ich do vhodného adresára, najlepšie do adresára pre projekty Studia.

Knižnica je pripravená na priamy import do nášho projektu ako modul.

Z hlavného menu zvoľme File | Import Module... a uveďme cestu k rozbalenému adresáru s knižnicou. Studio nájde v projekte dva moduly: library s jadrom knižnice a sample s demonštračným kódom. Ukážkový súbor nebudeme potrebovať, preto zrušíme začiarknutie pre jeho import. Pre poriadok premenujeme modul library na AndroidCameraUtil.

Import modulu

Po ukončení sprievodcu zistíme, že v projekte sa úspešne zjavila knižnica.

Aplikácia Fotr

Obdivovanie knižnice

Modul sample, ktorý sme pri importe odignorovali, obsahuje kompletnú demonštračnú aplikáciu s jednou aktivitou, ktorá dokáže odfotiť a spracovať výsledok. Preštudujte si zdrojový kód a inšpirujte sa ním.

Konfigurácia manifestu

Oprávnenia

Dôležitou vecou je konfigurácia manifestu. Predovšetkým potrebujeme získať viacero oprávnení

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />

Naša aplikácia žiada o oprávnenie čítať a zapisovať na SD kartu, používať foťák (CAMERA), vibrovať (VIBRATE) a využívať blesk (FLASHLIGHT).

Hardvérové a iné požiadavky aplikácie

Každá appka môže definovať hardvérové a iné požiadavky, bez ktorých nedokáže fungovať. Príkladom je GPS: ak vaša aplikácia bezpodmienečne využíva GPS, môžete to deklarovať v manifeste v elemente <uses-feature>. Na požiadavky potom prihliada obchod Google Play, ktorý odfiltruje z ponuky appky, ktoré vaše zariadenie nebude vedieť kvôli chýbajúcim požiadavkam spúšťať. Zoznam požiadaviek môžete nájsť v dokumentácii Androidu.

S požiadavkami je to však prekérne. Zoberme si pôvodný tablet od Googlu: Nexus 7. Ten má len jediný, predný fotoaparát, čo nespĺňa požiadavku na typizovaný zadný foťák. V takomto prípade musíme byť ako rozprávková princezná, čo prišla s darom-nedarom a v manifeste musíme explicitne deklarovať využívanie fotoaparátu, ale nie jeho nutnosť.

Požiadavky teda deklarujme nasledovne v manifeste:

<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />
<uses-feature
    android:name="android.hardware.camera.front"
    android:required="false" />
<uses-feature
    android:name="android.hardware.camera.autofocus"
    android:required="false" />
<uses-feature
    android:name="android.hardware.camera.flash"
    android:required="false" />

Deklarácia používateľského rozhrania

Dodajme do layoutu hlavnej aktivity tlačidlo pre fotenie a widget pre zobrazenie miniatúry fotky:

<LinearLayout android:orientation="vertical" xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <Button
        android:text="Shoot!"
        android:onClick="onShootButtonClick"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <ImageView
        android:id="@+id/thumbnailImageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true" />

</LinearLayout>

Kód aktivity

Následne dodajme kód aktivity. Najdôležitejšie metódy sú dve:

  • onShootButtonClick() bola deklarovaná v layoute ako obslužná metóda pre kliknutie na tlačidlo. V nej zavoláme zdedenú metódu startCameraIntent(), ktorou spustíme aktivitu pre fotenie a získame výsledok.
  • onPhotoUriFound() sa zavolá vo chvíli, keď aktivita vytvorila fotku a skončila.

Vďaka základnej triede CameraIntentHelperActivity máme k dispozácii nasledovné inštančné premenné:

  • photoUri: adresa k súboru obsahujúcemu fotografiu
  • rotateXDegrees: kompenzácie rotácie súboru, ktorá opravuje chybu v zariadeniach zbytočne rotujúcich fotku

Kód zároveň opravuje chybu, kde niektoré zariadenia ukladajú fotku na dve, či tri miesta, na čo využíva pomocnú triedu BitmapHelper z knižnice, a to odstránením nadbytočných súborov.

public class MainActivity extends CameraIntentHelperActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


    public void onShootButtonClick(View v) {
        startCameraIntent();
    }

    @Override
    protected void onPhotoUriFound() {
        super.onPhotoUriFound();
        Bitmap photo = BitmapHelper.readBitmap(this, this.photoUri);
        if (photo != null) {
            photo = BitmapHelper.shrinkBitmap(photo, 300, this.rotateXDegrees);
            ImageView imageView = (ImageView) findViewById(R.id.thumbnailImageView);
            imageView.setImageBitmap(photo);
        }

        //Delete photo in second location (if applicable)
        if (this.preDefinedCameraUri != null && !this.preDefinedCameraUri.equals(this.photoUri)) {
            BitmapHelper.deleteImageWithUriIfExists(this.preDefinedCameraUri, this);
        }
        //Delete photo in third location (if applicable)
        if (this.photoUriIn3rdLocation != null) {
            BitmapHelper.deleteImageWithUriIfExists(this.photoUriIn3rdLocation, this);
        }
    }

}

Teraz už môžeme aplikáciu bez problémov spustiť.

Aktivita CameraIntentHelperActivity dedí od FragmentActivity a teda nepodporuje lištu akcií. Ak napriek tomu chcete využívať lištu akcií, musíte upraviť zdrojáky knižnice:

  • upraviť závislosť na knižnici kompatibility: v module AndroidCameraUtil nahradiť závislosť na novšiu verziu, napr. na compile 'com.android.support:appcompat-v7:22.1.0'
  • upraviť aktivitu a nechať ju dediť od ActionBarActivity

Úpravy sú nutné, ak budete chcieť napríklad zdieľať fotky priamo z aktivity.

Podpora emulátora

Ak spustíme aktivitu a začneme fotiť, získame demonštračné emulované dáta z GenyMotion. Tento emulátor však dokáže prepojiť webkameru z vášho hostiteľského počítača a dodávať z nej dáta priamo do appky.

Výsledná aplikácia

Výsledná aplikácia sa nachádza na GitHube, v repozitári novotnyr/android-fotr-demo-2015.