8. stretnutie: časť 3: Zdieľanie dát

Zdieľanie dát je skvelé! Fotku môžete vďaka zdieľaniu rovno poslať na Facebook. SMSku do kalendárika. Webovú stránku do Pocketu. Na rozdiel od iných platforiem nemusíte implementovať prepojenie appky so všetkými ostatnými, pretože sharing v Androide je univerzálny a flexibilný.

A na rozdiel od strašidelného API fotoaparátu je zdieľaní dát medzi aplikáciami pohodlná záležitosť.

Zdieľanie dát medzi aplikáciami

V Androide existuje jednoduchý mechanizmus výmeny dát medzi aplikáciami pomocou implicitných intentov.

Implicitný intent neobsahuje názov komponentu (aktivity, služby), ktorý sa má spustiť. Namiesto neho obsahuje len názov akcie, a cieľový komponent sa určí dynamicky.

Implicitný intent sa odošle do systému, ktorý preverí všetky komponenty a zistí, ktoré z nich ho dokážu spracovať. Overenie je efektívne vďaka intent filtrom, ktoré dokážu ihneď rozhodnúť, či intent prijať alebo nie.

Čo ak intent dokáže spracovať viacero komponentov? V takom prípade Android ponúkne dialógové okno, kde používateľ ručne určí cieľový komponent.

Vlastnosti intentu pre zdieľanie

Zdieľanie dát teda spočíva vo vytvorení implicitného intentu a jeho odoslaniu do systému pomocou metódy startActivity(). Intent určený na zdieľanie dát medzi aplikáciami má nasledovné atribúty:

  • akcia (action): vždy nastavená na konštantu ACTION_SEND.
  • dáta (data): obsahuje Uri reprezentujúce adresu dát, s ktorými sa pracuje. Môže napríklad obsahovať Uri k dátam v content providerovi (ak zdieľame databázové dáta) alebo cestu k súboru v súborovom systéme (ak zdieľame súbor).
  • typ MIME: udáva typ dát, s ktorými sa pracuje. Textové dáta mávajú tradične typ text/plain, obrázky image/*.
  • kategória (category): dodatočná informácia o intente. Obvykle nie je potrebná.
  • dodatočné dáta extras: intent sa správa ako hashmapa a v extras môže niesť ľubovoľné dodatočné informácie.

Odosielanie fotiek

Obohaťme fotoaplikáciu o zdieľanie fotiek!

  • dodáme tlačidlo na lištu akcií
  • v obsluhe kliku na tlačidlo odošleme implicitný intent

Používateľské rozhranie: lišta akcií

Dodajme do lišty akcií tlačidlo:

<item android:id="@+id/shareAction"
    android:title="Share"
    android:showAsAction="ifRoom"
    />

Predpokladáme, že appka používa API pre Android 4 a novšie a nespolieha sa na knižnicu kompatibility. To je dôvod, prečo atribút showAsAction patrí do menného priestoru android a nie do app určeného pre atribúty z knižnice kompatibility.

Obslužný kód je klasika: nafúkneme lištu akcií z layoutu a kliknutie obslúžime megaswitchom. Kliknutie na zdieľanie bude presmerované na metódu sharePhoto():

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
        case R.id.shareAction:
            sharePhoto();
            return true;       
        default:
            return super.onOptionsItemSelected(item);                
    }
}

private void sharePhoto() {

}

Ukážkový kód pre odosielanie

Základný kód pre odoslanie je jednoduchý: vyplníme akciu, nastavíme MIME typ a odošleme intent:

Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setDataAndType(this.photoUri, "image/*");
shareIntent.putExtra(Intent.EXTRA_TEXT, "Fotr Photo");
startActivity(shareIntent);

Ak chcete nastaviť dáta i typ, použite metódu setDataAndType(). Metóda setData() totiž resetuje typ a naopak, setType() resetuje dáta.

Ak vytvoríme fotku a necháme ju nazdieľať, Android zistí, že ju dokáže spracovať viacero aktivít:

  • odoslanie cez Bluetooth
  • odoslanie do správy s prílohou
  • odoslanie do Picassy (ak máte nainštalované Google aplikácie).

Viacero komponentov pre spracovanie intentu

Ak existuje v systéme len jeden komponent, ktorý vie obslúžiť intent, priamo sa otvorí bez výzvy používateľa. Dialóg pre výber sa tiež nezobrazí, ak používateľ prednastavil aktivitu pre spracovanie konkrétneho typu intentu pomocou tlačidla Always.

A čo ak intent nevie spracovať žiaden komponent? Aplikácia spadne.

Ošetrenie intentov

Predtým než odošleme intent musíme skontrolovať, či existuje v systéme aspoň jedna aktivita, ktorá ho spracuje:

if (shareIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(shareIntent);
}

Vyhodnocovanie intentov

Pri vyhodnocovaní vhodných systémových komponentov, ktoré môžu spracovať intent, sa do úvahy berú nasledovné vlastnosti:

  • akcia uvedená v intente
  • dáta reprezentované Uri a MIME typom
  • kategória intentu.

Pozor, dáta uvedené v extras sa pri vyhodnocovaní neberú do úvahy.

Vyhodnocovanie intentov je podrobne, vrátane algoritmu, popísané v oficiálnom sprievodcovi na portáli Androidu..

Vynútenie výberu aktivity

Ak chceme vždy vyvolať dialógové okno, môžeme intent obaliť do výberového intentu:

Intent chooseSharingTarget = Intent.createChooser(shareIntent, title);
if (chooseSharingTarget.resolveActivity(getPackageManager()) != null) {
    startActivity(chooseSharingTarget);
}

Typické aplikácia prijímajúce intenty

Google Maps

Vďaka zdieľaniu môžete využiť zobrazovanie polohy na mape. Stačí vytvoriť intent s nasledovnými vlastnosťami:

  • akciu nastaviť na konštantu ACTION_VIEW
  • pripraviť Uri, ktoré v sebe ponesie dáta, napríklad Uri uri = Uri.parse("geo:37.7749,-122.4194")
  • nastaviť cieľový balíček setPackage("com.google.android.apps.maps"), ktorý obmedzí výber cieľových aktivít pre spracovanie intentu

Instagram

Odosielanie fotiek do Instagramu je tiež jednoduché. Nastavíte:

  • typ MIME na image/*
  • extras pre cestu k súboru pod kľúčom Intent.EXTRA_STREAM
  • extras pre popisok obrázka pod kľúčom Intent.EXTRA_TEXT

Prijímanie intentov

Na prijímanie intentov si vytvorme samostatný projekt Dropage, ktorý bude vedieť prijímať fotky. Aktivita dokáže prijímať implicitné intenty, ak:

  • deklarujeme v manifeste <intent-filter>
  • v aktivite získame intent a z neho dáta

Manifest

V elemente <activity> definujme <intent-filter>:

<intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="image/*" />
</intent-filter>

Potrebujeme definovať:

  • akciu: v tomto prípade android.intent.action.SEND
  • MIME typ: v tomto prípade využívame zástupný znak *, ktorý deklaruje podporu ľubovoľného formátu (napr. image/jpeg alebo image/png).
  • a kategóriu: pre implicitné intenty sme vždy povinní definovať kategóriu a to i v prípade, že odosielané intenty žiadnu kategóriu nedefinujú. Požiadavky sú dané dokumentáciou k intent filtrom.

Všimnime si, že naša aktivita už definuje jeden implicitný intentový filter a to pre jej spustenie z launchera, resp. z domovskej obrazovky.

Získanie intentu v aktivite

Získanie intentu môžeme dosiahnuť v metóde onCreate() aktivity:

Intent intent = getIntent();
if(Intent.ACTION_SEND.equals(intent.getAction())
        && intent.getType().startsWith("image/")) {

    Uri photoUri = intent.getData();
    showExifData(photoUri);
}

Pomocou metódy getIntent() získame intent, vďaka ktorému sa spustila aktivita a následne sa rozhodneme, o aký konkrétny intent ide. Aktivita totiž môže byť spustená nielen zdieľaním, ale i z hlavnej obrazovky či launchera, čo musíme zobrať do úvahy. V podmienke teda hľadáme správnu akciu a správny typ MIME a ak uspejeme, z dát vytiahneme Uri adresu, ktorú pošleme do pomocnej metódy.

Niektoré aplikácie neposielajú cestu k súboru v dátach ako Uri, ale v špeciálnom extra s kľúčom EXTRA_STREAM, kde pošlú adresu so schémou content://, ktorú treba vyhodnotiť. Takéto intenty nebudeme podporovať, pretože vyžadujú špinavú prácu prevodu URI na cestu k súboru. V praktických aplikáciách je možné pozrieť napríklad do githubového projektu iPaulPro/aFileChooser.

Načítavanie EXIF dát

V Androide existuje pomocná trieda ExifInterface, ktorá vie načítať EXIFové informácie z obrázka. Akurát, že obrázok musí byť reprezentovaný súborom. Cestu k súboru vieme vytiahnuť z Uri adresy pomocou metódy getPath(), ktorý následne prepošleme do ExifInterface.

private void showExifData(Uri photoUri) {
    try {
        if(photoUri == null) {
            return;
        }
        ExifInterface exifInterface = new ExifInterface(photoUri.getPath());
        int imageHeight = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, -1);
        int imageWidth = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, -1);

        imageWidthTextView.setText(Integer.toString(imageWidth));
        imageHeightTextView.setText(Integer.toString(imageHeight));

    } catch (IOException e) {
        Log.e(MainActivity.class.getName(), "Error while reading EXIF data", e);
    }
}

Ladenie intentov

Zistiť, čo všetko sa deje s intentom, môže byť náročné. Ak však nastavíte intentu príznak pre logovanie, budete ho vidieť v LogCate.

intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);

Logovanie prebieha na úrovni VERBOSE, a na to, aby ho bolo vidieť, je treba v LogCate vypnúť štandardný filter správ, ktorý je obmedzený na balíček aplikácie.

Kompletný projekt

Celý projekt pre prijímanie dát nájdete na GitHube v repozitári novotnyr/android-exifdropage-2015.

Jednoduché zdieľanie: tlačidlo Easy Sharing na lište akcií

Zdieľanie dát môžete uľahčiť ešte viac, ak použijete zabudované tlačidlo Easy Share na lište akcií. Do layoutu lišty akcií stačí dodať novú položku:

<item android:id="@+id/easyShareAction"
    android:actionProviderClass="android.widget.ShareActionProvider"
    android:title="Share"
    android:showAsAction="ifRoom"
    />

Rozdiel oproti tradičnej položke spočíva v atribúte actionProviderClass, ktorý udáva názov triedy zodpovedajúcej za správanie tlačidla.

Do kódu potom dodáme špeciálnu obsluhu pre získanie inštanciu ShareActionProvidera, ktorá bude zodpovedná za vyhľadávanie appiek podporujúcich zdieľanie.

private ShareActionProvider shareActionProvider;

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);

    MenuItem item = menu.findItem(R.id.easyShareAction);
    shareActionProvider = (ShareActionProvider) item.getActionProvider();
    return true;
}

Keďže naša aplikácia nepodporuje knižnicu kompatibility, importujeme triedu android.widget.ShareActionProvider.

Následne refaktorujeme kód: tvorbu intentu na zdieľanie vytiahneme do samostatnej metódy:

private Intent getShareIntent() {
    Intent shareIntent = new Intent(Intent.ACTION_SEND);
    shareIntent.setDataAndType(this.photoUri, "image/*");
    shareIntent.putExtra(Intent.EXTRA_TEXT, "Fotr Photo");
    shareIntent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
    return shareIntent;
}

Náš poskytovateľ zdieľania share action provider prepojíme s intentou v metóde, ktorú vytvoríme.

private void updateEasyShare() {
    if(this.shareActionProvider != null) {
        this.shareActionProvider.setShareIntent(getShareIntent());
    }
}

V poslednom kroku upravíme metódu onPhotoUriFound(), kde na konci aktualizujeme intent zdieľania a prepojíme ho s poskytovateľom akcie zdieľania:

@Override
protected void onPhotoUriFound() {
    ...

    updateEasyShare();
}

Tlačidlo Easy Share na lište akcií