2 komentarze

Wstęp do Intencji (Intents)

Lipiec 25, 2011 Intent Podstawowe komponenty Tutoriale

Intencje obok Aktywności są jednym z podstawowych komponentów z których zbudowane są Androidowe aplikacje. Mechanizm ten odpowiedzialny jest przede wszystkim za obsługę rozkazów wydawanych przez użytkownika. Za pomocą intencji możemy wprowadzić komunikację pomiędzy aplikacjami (lub mniejszymi komponentami, jak Usługi, Aktywności itp.). Jednak najważniejszym zadaniem tego komponentu jest uruchamianie odpowiednich aplikacji/Aktywności.

O możliwościach intencji

Jako, że uruchamianie Aktywności jest najczęstszym zastosowaniem Intencji, wpis ten będzie niemal w całości poświęcony tej czynności.

Typy uruchomień

Intencje uruchamiające podzielone zostały na dwa główne typy:

  • Jawne (explicit) – czyli takie, w których jasno precyzujemy obiekt, który chcemy stworzyć. W tym wypadku jednym z argumentów konkstruktora Intencji jest obiekt typu Class (np. MainActivity.class) wskazujący na klasę, której obiekt chcemy stworzyć. Najprostszy przykład to:
    Intent intent = new Intent(context, MainActivity.class);

    Intencja tak, jak łatwo się domyślić, uruchomi aktywność MainActivity.

  • Niejawne (implicit) – są to intencje, w których zawieramy informacje o tym co chcemy zrobić, bez podawania konkretnych klas, które mają to zrealizować. Najczęściej opisujemy to dwoma informacjami – co chcemy zrobić i na jakich danych chcemy tą czynność wykonać. W takim wypadku to system (za pomocą Filtrów Intencji, o których polecam poczytać, choćby na naszej stronie) decyduje jaka Aktywność ma być uruchomiona. Przykład:
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

    W tym wypadku informujemy nasz system o tym, że chcemy zobaczyć dane (Intent.ACTION_VIEW) zapisane pod adresem URI (w tym wypadku adres URL strony). Przykłady innych zastosowań możemy znaleźć w dokumentacji.

Przesyłanie danych

Oprócz uruchamiania komponentów Intencje pozwalają na dołączanie informacji, które mogą być przesłane do uruchamianego obiektu. Służy do tego zestaw metod putExtra(…), które pozwalają na dołączanie danych w postaci typów prostych (lub paczek Bundle). Przykładem takiej operacji niech będzie:

intent.putExtra("UserID", 123);

Aby pobrać powyższe dane w nowo uruchomionej Aktywności należy wykonać komplet poniższych operacji:

Intent intent = getIntent();
int userId = intent.getIntExtra("UserID", 0);

Pierwsza linijka pobiera Intencję, która uruchomiła Aktywność (należy pamiętać o sprawdzeniu czy przypadkiem zawracana wartość nie jest nullem).
Druga linijka to pobranie wartości za pomocą jednej z metod get…Extra(…). Pierwszym argumentem tych metod jest nazwa pod którą dana wartość została zapisana. Drugim natomiast – wartość domyślna, na wypadek gdyby Intencja nie zawierała żadnych danych zapisanych pod podanym ciągiem znaków.

Przykładowa aplikacja

Zbudujemy teraz aplikację, która wykorzystuje kilka podstawowych możliwości dostarczanych przez mechanizm Intencji.

Przygotowanie

Tworzymy aplikację składającą się z 3 Aktywności. Oto zrzuty oraz kod źródłowy poszczególnych layoutów przygotowanych dla każdego okna:

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView
		android:textAppearance="?android:attr/textAppearanceLarge"
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:text="@string/application_header"
		android:layout_gravity="center"></TextView>
	<Button
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:id="@+id/btnOpenActivity"
		android:text="@string/button_open_activity"></Button>
	<Button
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:id="@+id/btnOpenActivityForResponse"
		android:text="@string/button_open_activity_for_response"></Button>
	<Button
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:id="@+id/btnOpenUrl"
		android:text="@string/button_open_url"></Button>
	<Button
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:id="@+id/btnOpenGeo"
		android:text="@string/button_open_geo"></Button>
	<TextView
		android:text="---"
		android:id="@+id/tvResult"
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"></TextView>
</LinearLayout>

second.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/second_activity_information"></TextView>
</LinearLayout>

third.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<EditText
		android:layout_height="wrap_content"
		android:layout_width="fill_parent"
		android:id="@+id/etResponse"
		android:hint="@string/third_activity_hint">
		<requestFocus></requestFocus>
	</EditText>
	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/btnSendResponse"
		android:layout_gravity="right"
		android:text="@string/button_send_response"></Button>
</LinearLayout>

Na koniec jeszcze plik strings.xml, w którym przechowujemy napisy naszej aplikacji:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">HelloIntents</string>
    <string name="application_header">Intent examples</string>
    <string name="button_open_activity">Open new Activity</string>
    <string name="button_open_activity_for_response">Open Activity for response</string>
    <string name="button_open_url">Open URL address</string>
    <string name="button_open_geo">Open geo URI address</string>
    <string name="second_activity_information">Just click back button.</string>
    <string name="third_activity_hint">Type your response here...</string>
    <string name="button_send_response">Send back response</string>
</resources>

Przypominam też o tym aby każdy z tych layoutów ustawić w Aktywnościach, oraz o tym by nowe Aktywności opisać w pliku AndroidManifest.xml .

Kod źródłowy aplikacji

W naszej aplikacji zaprezentujemy 4 przykłady opisujące różne możliwości jakie dają nam intencje. Zaczniemy oczywiście od inicjalizacji elementów layoutu i ustawienia listenerów na naszych przyciskach w MainActivity:

public class MainActivity extends Activity {
	private Button btnOpenActivity;
	private Button btnOpenActivityForResponse;
	private Button btnOpenUrl;
	private Button btnOpenGeo;
	private TextView tvResult;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btnOpenActivity = (Button) findViewById(R.id.btnOpenActivity);
		btnOpenActivityForResponse = (Button) findViewById(R.id.btnOpenActivityForResponse);
		btnOpenGeo = (Button) findViewById(R.id.btnOpenGeo);
		btnOpenUrl = (Button) findViewById(R.id.btnOpenUrl);
		tvResult = (TextView) findViewById(R.id.tvResult);
		initButtonsOnClickListeners();
	}

	private void initButtonsOnClickListeners() {
		btnOpenActivity.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openSecondActivity();
			}
		});
		btnOpenActivityForResponse.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openActivityForResult();
			}
		});
		btnOpenGeo.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openGeoUriAddress();
			}
		});
		btnOpenUrl.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openUrlAddress();
			}
		});
	}
}

Jak widać, zaznaczyłem 4 metody, których implementacją zajmiemy się w poszczególnych przykładach.

Przykład 1 – jawne uruchamianie nowej Aktywności

Sposób uruchomienia nowej Aktywności został opisany w części teoretycznej tego artykułu. W naszej aplikacji wygląda to w ten sposób:

	...
	private void openSecondActivity() {
		Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
		startActivity(intent);
	}

Wszystko zgodnie z tym co opisaliśmy wcześniej. Argumentami konstruktora Intencji są: kontekst aplikacji oraz obiekt Class klasy Aktywności, którą chcemy uruchomić.

Przykład 2 – uruchamianie Aktywności w celu otrzymania od niej odpowiedzi

W drugim przykładzie urozmaicimy nieco kod zaprezentowany powyżej. System bowiem daje nam możliwość uruchomienia Aktywności po to, by zwróciła nam ona jakieś dane (np. otwieramy listę kontaktów po to by pobrać z niej konkretną osobę).
My otworzymy nową Aktywność po to by pobrać z niej dane wpisane do pola tekstowego. :)

Zaczynamy od implementacji metody wywoływanej przez drugi przycisk naszej aplikacji:

	...
	private void openActivityForResult() {
		Intent intent = new Intent(getApplicationContext(), ThirdActivity.class);
		startActivityForResult(intent, THIRD_ACTIVITY_REQUEST_CODE);
	}

Jak widać, różnicą w stosunku do poprzedniego przykładu jest metoda uruchamiająca Aktywność. Metoda startActivityForResult(…) przyjmuje bowiem jeszcze jeden argument. Jest nim identyfikator (liczba całkowita), na podstawie którego będzie można później stwierdzić skąd przyszła odpowiedź. U nas identyfikatorem jest stała klasy MainActivity:

public class MainActivity extends Activity {
	private static final int THIRD_ACTIVITY_REQUEST_CODE = 1;
	...

Teraz powinniśmy napisać mechanizm, który obsłuży zwracany wynik. Aby to zrobić należy przesłonić metodę Aktywności – onActivityResult(int requestCode, int resultCode, Intent data). Jak widać, przyjmuje ona 3 argumenty:

  • int requestCode – jest to liczba całkowita, na podstawie której możemy zidentyfikować która Aktywność zwróciła nam wynik (ta sama, którą ustawiliśmy w metodzie startActivityForResult(…).
  • int resultCode – liczba całkowita na podstawie której możemy określić stan wykonanej operacji (np. za pomocą stałych Activity.RESULT_OK lub Activity.RESULT_CANCELED).
  • Intent data – dane zwrócone z Aktywności.

Nasza implementacja wygląda w ten sposób:

...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (THIRD_ACTIVITY_REQUEST_CODE == requestCode) {
		String response = data.getStringExtra(ThirdActivity.RESPONSE);
		insertResponse(response);
	}
}

Na koniec pozostało nam jeszcze zaimplementowanie mechanizmu zwracającego dane. Oto kod całej Aktywności ThirdActivity, która zgodnie z naszym założeniem powinna odesłać dane z pola tekstowego:

public class ThirdActivity extends Activity {
	public static final String RESPONSE = "Response";
	private EditText etResponse;
	private Button btnSendResponse;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.third);
		etResponse = (EditText) findViewById(R.id.etResponse);
		btnSendResponse = (Button) findViewById(R.id.btnSendResponse);
		btnSendResponse.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				prepareResponse();
				finish();
			}
		});
	}

	private void prepareResponse() {
		String response = etResponse.getText().toString();
		Intent resultIntent = new Intent();
		resultIntent.putExtra(RESPONSE, response);
		setResult(RESULT_OK, resultIntent);
	}
}

Jak widać odesłanie wyników jest stosunkowo prostą czynnością. Sprowadza się do:

  1. Utworzenia obiektu Intent, który chcemy odesłać (linijka 23),
  2. Wpisania do niego danych (linijka 24) za pomocą metod putExtra(…),
  3. Ustawienia odpowiedzi za pomocą metody setResult(int resultCode, Intent data)linijka 25. Argumenty ustawiane w tej metodzie pokrywają się z argumentami metody onActivityResult(…).

Jako ciekawostkę dodam, że metoda finish(), którą możemy znaleźć w 16 linijce powyższego kodu kończy działanie Aktywności.

To tyle w kwestii przesyłania i odbierania danych. Przejdźmy teraz do następnego przykładu.

Przykład 3 – niejawne uruchamianie Aktywności

W tym przykładzie uruchomimy Aktywność, którą wybierze za nas system. Tym razem bowiem zamiast wskazywać  konkretną klasę Aktywności, przekażemy jedynie akcję oraz dane, na których będziemy chcieli ją przeprowadzić. Jak to wygląda w praktyce? Tak:

...
private void openUrlAddress() {
	Uri url = Uri.parse("http://www.google.com");
	Intent intent = new Intent(Intent.ACTION_VIEW, url);
	startActivity(intent);
}

Jak widać, akcją którą chcemy wykonać jest Intent.ACTION_VIEW czyli… obejrzyj. :) Danymi, na których podziałamy rzeczoną akcją jest adres url. Nietrudno zatem się domyślić, że system uruchomi naszą domyślną przeglądarkę i wyświetli stronę przekazaną w adresie url.

Przykład 4 – niejawne uruchamianie Aktywności (nie tylko adresu url)

W zasadzie przykład ten jest powieleniem powyższego. Dzięki niemu jednak chciałem uzmysłowić ważną kwestię. Intencje bowiem operują na na adresach URI, a nie tylko podrzędnych do nich adresach URL (różnica URI a URL). Dzięki temu, oprócz adresów www możemy korzystać z takich zasobów jak:

  • Numery telefonów (tel:123456),
  • Dane zamieszczone w gdzieś w pamięci telefonu (książka telefoniczna – content://contacts/people/…)
  • i wiele innych (np. adres położenia geograficznego przedstawiony poniżej).

W naszym przykładzie spróbujemy wyświetlić dane zapisane pod adresem URI geo:50.07,19.97:

...
private void openGeoUriAddress() {
	Uri geoUri = Uri.parse("geo:50.07,19.97");
	Intent intent = new Intent(Intent.ACTION_VIEW, geoUri);
	if(isIntentAvailable(intent)) {
		startActivity(intent);
	} else {
		Toast.makeText(getApplicationContext(),
				"Your system hasn't necessary application for this intent",
				Toast.LENGTH_LONG)
				.show();
	}
}

Jak widać kod jest nieco dłuższy niż można było się tego spodziewać. Wynika to z faktu, że zabezpieczyliśmy go przed wysyłaniem Intencji, których system nie potrafi obsłużyć. Jak się bowiem okazuje, kiedy w systemie nie ma aplikacji mogącej obsłużyć nasze zapytanie, otrzymujemy wyjątek android.content.ActivityNotFoundException: No Activity found to handle Intent. Dlatego aby temu zapobiec, zaimplementowaliśmy metodę isIntentAvailable(Intent) (przy okazji odsyłam do odpowiedniego snippetu), której kod wygląda tak:

...
private boolean isIntentAvailable(Intent intent) {
	PackageManager packageManager = getApplicationContext().getPackageManager();
	List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
	return resolveInfo.size() > 0;
}

Oto co stanie się, gdy nasze urządzenie nie posiada aplikacji obsługującej adres URI geo: (w tym wypadku zrzut z emulatora AVD)

I tym sposobem skończyliśmy nasz wstęp do wykorzystania Intencji w naszych aplikacjach. :)

Kompletny kod źródłowy

Cały projekt dostępny jest na naszym githubie – HelloIntents.

Cały kod źródłowy plików .xml wklejony został na początku opisu naszej przykładowej aplikacji.

MainActivity.java

package pl.froger.hellointents;

import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
	private static final int THIRD_ACTIVITY_REQUEST_CODE = 1;

	private Button btnOpenActivity;
	private Button btnOpenActivityForResponse;
	private Button btnOpenUrl;
	private Button btnOpenGeo;
	private TextView tvResult;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btnOpenActivity = (Button) findViewById(R.id.btnOpenActivity);
		btnOpenActivityForResponse = (Button) findViewById(R.id.btnOpenActivityForResponse);
		btnOpenGeo = (Button) findViewById(R.id.btnOpenGeo);
		btnOpenUrl = (Button) findViewById(R.id.btnOpenUrl);
		tvResult = (TextView) findViewById(R.id.tvResult);
		initButtonsOnClickListeners();
	}

	private void initButtonsOnClickListeners() {
		btnOpenActivity.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openSecondActivity();
			}
		});
		btnOpenActivityForResponse.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openActivityForResult();
			}
		});
		btnOpenGeo.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openGeoUriAddress();
			}
		});
		btnOpenUrl.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				openUrlAddress();
			}
		});
	}

	private void openSecondActivity() {
		Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
		startActivity(intent);
	}

	private void openActivityForResult() {
		Intent intent = new Intent(getApplicationContext(), ThirdActivity.class);
		startActivityForResult(intent, THIRD_ACTIVITY_REQUEST_CODE);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (THIRD_ACTIVITY_REQUEST_CODE == requestCode) {
			String response = data.getStringExtra(ThirdActivity.RESPONSE);
			insertResponse(response);
		}
	}

	private void insertResponse(String response) {
		tvResult.setText(response);
	}

	private void openUrlAddress() {
		Uri url = Uri.parse("http://www.google.com");
		Intent intent = new Intent(Intent.ACTION_VIEW, url);
		startActivity(intent);
	}

	private void openGeoUriAddress() {
		Uri geoUri = Uri.parse("geo:50.07,19.97");
		Intent intent = new Intent(Intent.ACTION_VIEW, geoUri);
		if(isIntentAvailable(intent)) {
			startActivity(intent);
		} else {
			Toast.makeText(getApplicationContext(),
					"Your system hasn't necessary application for this intent",
					Toast.LENGTH_LONG)
					.show();
		}
	}

	private boolean isIntentAvailable(Intent intent) {
		PackageManager packageManager = getApplicationContext().getPackageManager();
		List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
		return resolveInfo.size() > 0;
	}
}

SecondActivity.java

package pl.froger.hellointents;

import android.app.Activity;
import android.os.Bundle;

public class SecondActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second);
    }
}

ThirdActivity.java

package pl.froger.hellointents;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class ThirdActivity extends Activity {
	public static final String RESPONSE = "Response";
	private EditText etResponse;
	private Button btnSendResponse;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.third);
		etResponse = (EditText) findViewById(R.id.etResponse);
		btnSendResponse = (Button) findViewById(R.id.btnSendResponse);
		btnSendResponse.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				prepareResponse();
				finish();
			}
		});
	}

	private void prepareResponse() {
		String response = etResponse.getText().toString();
		Intent resultIntent = new Intent();
		resultIntent.putExtra(RESPONSE, response);
		setResult(RESULT_OK, resultIntent);
	}
}

Komentarze (2) Subskrybuj

 

  1. [...] powiadomień (Notifications) są jednym z podstawowych komponentów systemu (obok Aktywności, Intencji oraz Dostarczycieli treści). Jest to zbiór elementów, które „zaczepiają” [...]

  2. gjeszka pisze:

    Klasa MainActivity odwołuje do klasy bibliotecznej. Wszystko niby dobrze ale nie w klasie MainActivity nie rozpoznaje RESPONSE.

    Klasę biblioteczną mam dodaną w Manifest i zaimportowaną :/. Co może być jeszcze nie tak?

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.