0 komentarzy

Intent Filters, przypisywanie komponentów do konkretnych akcji lub danych

Sierpień 8, 2011 Intent Podstawowe komponenty Tutoriale

Filtry Intencji w Androidzie są bardzo ważnym elementem, który pozwala decydować systemowi jaki komponent (Aktywność, Usługa, Odbiorca treści) ma zostać wywołany dla Intencji posiadającej konkretną akcję lub dane. Daje nam to ogromne możliwości, bowiem w ten sposób możemy definiować zachowanie całego systemu operacyjnego.

Przykład
Mamy aplikację, w której klikamy na link jakiejś strony internetowej. I teraz, zamiast otwierać konkretną przeglądarkę z adresem przekazanym w argumencie, wysyłana jest intencja posiadająca dwa składniki:

  • dane (adres strony)
  • akcję do przeprowadzanie na danych (wyświetl zawartość znajdującą się pod powyższym adresem).

Dzięki Filtrom Intencji system teraz dobierze najbardziej pasującą aplikację, która obsłuży powyższą intencję. Daje nam to możliwość nie tylko wybrania ustawionej przez nas przeglądarki, ale np. otwarcia czytnika RSS dla adresu URL prowadzącego do tego typu danych.

Inny przykład
Potrzebujemy pobrać konkretną pozycję z listy kontaktów. Nie obchodzi nas jaka aplikacja to zrobi, ważne byśmy otrzymali oczekiwany wynik, w związku z czym wysyłamy intencję z:

  • akcją (pobierz kontakt)
  • danymi (adres Uri do listy kontaktów – w Androidzie content://contact/)

Dzięki temu system otworzy domyślną aplikację, w której wybierzemy konkretny kontakt. A my (programiści) nie musimy się martwić czy aplikacja, którą w innym wypadku musielibyśmy wskazać, jest zainstalowana na telefonie czy nie.

Tworzenie Filtrów Intencji

Aby zarejestrować nasz komponent do obsługi konkretnej intencji w pliku AndroidManifest.xml, wewnątrz elementu <activity> (lub service, receiver) musimy dodać element <intent-filter> . Oto przykładowy szkielet takiego kodu:

<activity android:label="@string/app_name" android:name=".MyActivity">
    <intent-filter>
        <action />
        <category />
        <data />
    </intent-filter>
</activity>

Co ważne – każdy z powyższych elementów (włącznie z <intent-filter>) może być wykorzystany wielokrotnie (dzięki temu możemy dokładnie sprecyzować jakie intencje będziemy obsługiwać).

Konfiguracja obsługiwanych intencji sprowadza się do edycji atrybutów elementów <action>, <category>, <data>.

<action />

Element <action> posiada jeden atrybut – android:name, w którym podajemy unikalny ciąg znaków identyfikujący akcję. Możemy tutaj wykorzystać gotowe wzory zapisane pod android.intent.action.* (wszystkie dostępne akcje można znaleźć w dokumentacji) lub tworzyć swoje (np za pomocą publicznych pól statycznych). Przy tworzeniu własnych nazw zalecane jest używanie ciągów znaków wzorowanych na nazwach paczek w Javie (np. com.example.appname.action.DOIT).

Przy użyciu wielu elementów <action /> w danym filtrze, wystarczy by pasowała tylko jedna aby system wziął nasz komponent pod uwagę.

<category />

<category> również posiada tylko jeden atrybut: android:name. Ciąg znaków, który podajemy tutaj możemy pobrać z android.intent.category.* (ten sam link do dokumentacji) . Niektóre ciekawsze kategorie to:

  • BROWSABLE - do obsługiwania danych „przeglądarkowych”. Do dopasowania akcji kliknięcia na link, adres e-mail itp. Dodatkowo – dane wychodzące z przeglądarki w telefonie do obsłużenia zawsze wymagają filtru z kategorią BROWSABLE.
  • DEFAULT - jeżeli komponent jest kandydatem do bycia domyślną aplikacją dla konkretnych danych. Jeżeli w systemie jest więcej domyślnych komponentów dla takich samych danych, po wysłaniu Intencji system prosi nas o wybranie aplikacji, która ją obsłuży.
  • LAUNCHER - za pomocą tej kategorii wskazujemy, która aktywność ma być tą główną w danej aplikacji.
  • HOME - tutaj Aktywność jest kandydatem do bycia główną aktywnością telefonu (czyli tym co jest uruchamiane po kliknięciu przycisku HOME).
  • ALTERNATIVE - wskazuje na to, że aktywność/usługa powinna być alternatywą do konkretnych danych. Nie jest tym samym co DEFALUT, ponieważ ALTERNATIVE dostarcza nam możliwości wskazania nowej akcji dla konkretnych danych (np. dotychczas można było jedynie wyświetlić listę kontaktów, a my dostarczamy akcji do ich usuwania).

System przy dopasowywaniu bierze pod uwagę wszystkie kategorie na raz. Dlatego by dopasować Intencję, musi ona pasować do wszystkich kategorii w obrębie filtru.

<data />

<data> jest elementem, która wskazuje jaki typ danych ma obsługiwać nasz komponent. W przeciwieństwie do powyższych elementów posiada kilka atrybutów pozwalających dokładnie określić typ danych:

  • android:scheme – schemat (protokół) adresu Uri (np. http, ftp, content)
  • android:host – czyli… host :) (np www.google.com)
  • android:port – port
  • android:path – ścieżka do pliku (np. /contacts/people)
  • android:mimetype – typ danych z jakimi ma być powiązany nasz element

Jak widać za pomocą powyższych atrybutów jesteśmy w stanie obsłużyć każdy adres URI.

Przykładowa aplikacja

Stworzymy teraz aplikację, w której zademonstrowane zostaną 3 przykłady Filtrów Intencji. Nie będziemy jednak zagłębiać się w nieco przydługawy kod źródłowy poszczególnych komponentów, a jedynie działanie i opis konkretnych filtrów.

Przykład 1

Z pierwszym przykładem wykorzystania filtrów intencji mamy do czynienia tak naprawdę przy każdym projekcie aplikacji Androidowej. W dodatku bez pisania dodatkowego kodu.
Filtrem tym jest oczywiście kod wskazujący na to, że dana Aktywność jest główną w konkretnej aplikacji:

<!-- MainActivity -->
<activity android:name=".MainActivity">
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />
		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>

Powyższy kod generowany jest oczywiście w pliku AndroidManifest.xml. W tym wypadku wskazuje on na to, że Aktywność MainActivity ma odpowiadać na akcję uruchomienia aplikacji (*.action.MAIN) oraz, że wywoływana jest wtedy, gdy system wybiera pierwszą ze szczytu Aktywności w danej aplikacji (*.category.LAUNCHER).

Przykład 2

Stworzymy teraz Aktywność, która będzie uruchamiana tylko w momencie gdy będziemy chcieli podejrzeć zawartość adresu URL, którego hostem będzie www.google.pl.

Oto kod źródłowy wszystkich wykorzystanych plików:

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">
	<EditText
		android:id="@+id/etLink"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:inputType="textUri"
		android:hint="Type site address with http://" />
	<Button
		android:id="@+id/btnServeSpecifyLink"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Serve address" />
	<Button
		android:id="@+id/btnPickBookmark"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Pick from bookmarks" />
</LinearLayout>

link_server.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:id="@+id/tvLinkServer"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" />
	<Button
		android:id="@+id/btnStartNextMatching"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Start next matching Activity" />
</LinearLayout>

MainActivity.java

package pl.froger.hello.intentfilters;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser.BookmarkColumns;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {
	public static final int BOOKMARK_PICK = 1234;

	private EditText etLink;
	private Button btnServeSpecifyLink;
	private Button btnPickBookmark;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		etLink = (EditText) findViewById(R.id.etLink);
		btnServeSpecifyLink = (Button) findViewById(R.id.btnServeSpecifyLink);
		btnPickBookmark = (Button) findViewById(R.id.btnPickBookmark);
		initButtonsOnClick();
	}

	private void initButtonsOnClick() {
		OnClickListener listener = new OnClickListener() {
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnServeSpecifyLink:
					serveLinkFromEditText();
					break;
				case R.id.btnPickBookmark:
					pickBookmark();
					break;
				default:
					break;
				}
			}
		};
		btnServeSpecifyLink.setOnClickListener(listener);
		btnPickBookmark.setOnClickListener(listener);
	}

	private void serveLinkFromEditText() {
		Uri uri = Uri.parse(etLink.getText().toString());
		Intent intent = new Intent(Intent.ACTION_VIEW, uri);
		startActivity(intent);
	}

	private void pickBookmark() {
		Intent intent = new Intent(Intent.ACTION_PICK, Uri.parse("content://browser"));
		startActivityForResult(intent, BOOKMARK_PICK);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		switch (requestCode) {
		case BOOKMARK_PICK:
			if (resultCode == Activity.RESULT_OK) {
				Uri bookmarkUri = data.getData();
				String[] bookmarks = new String[] { BookmarkColumns.URL };
				Cursor cursor = managedQuery(
						bookmarkUri, 
						bookmarks,
						null,
						null, 
						null);
				cursor.moveToFirst();
				String url = cursor.getString(cursor.getColumnIndexOrThrow(BookmarkColumns.URL));
				etLink.setText(url);
			}
			break;

		default:
			break;
		}
	}
}

LinkServer.java

package pl.froger.hello.intentfilters;

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.TextView;

public class LinkServer extends Activity {
	private TextView tvLinkServer;
	private Button btnStartNextMatching;
	private Intent incomingIntent;

	String intentData;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getDataFromIntent();
		setContentView(R.layout.link_server);
		tvLinkServer = (TextView) findViewById(R.id.tvLinkServer);
		btnStartNextMatching = (Button) findViewById(R.id.btnStartNextMatching);
		btnStartNextMatching.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				startNextMatchingActivity(incomingIntent);
				finish();
			}
		});
		tvLinkServer.setText("Obsługujesz adres: " + intentData);
	}

	private void getDataFromIntent() {
		intentData = "";
		incomingIntent = getIntent();
		if (incomingIntent != null) {
			intentData = incomingIntent.getDataString();

		}
	}
}

W kodzie Aktywności LinkServer dodana została funkcjonalność wykorzystująca metodę startMatchingActivity(Intent intent). Pozwala ona na przesłanie Intencji do kolejnego komponentu, który według systemu do niej pasuje.

Filtr Intencji dla tego przykładu wygląda tak:

<!-- LinkServer Activity -->
<activity android:name=".LinkServer">
	<intent-filter>
		<action android:name="android.intent.action.VIEW" />
		<category android:name="android.intent.category.DEFAULT" />
		<category android:name="andorid.intent.category.BROWSABLE" />
		<data android:scheme="http" android:host="www.google.pl" />
	</intent-filter>
</activity>

Działanie przykładu
Wpisujemy adres URL z http:// na początku. Jeżeli jest to adres, który nie zawiera w hoście www.google.pl wtedy system odsyła naszą Intencję do domyślnej przeglądarki systemu.
Jeżeli jednak wpiszemy adres zawierający www.google.pl (np. http://www.google.pl/abc) system odeśle naszą Intencję do aktywności LinkServer. Kiedy natomiast w tej Aktywności klikniemy na przycisk wywołujący startNextMatchingActivity(Intent) – system wybierze następny komponent systemu pasujący do naszej Intencji, dzięki czemu wywołana zostanie domyślna przeglądarka.

Zrzuty ekranu

Dla adresu www.google.pl

Dla innego adresu

Przykład 3

W ostatnim przykładzie zasada działania jest taka, że filtr reaguje tylko na Intencję z akcją PICK, i adresem content://browser. Uruchomiona Aktywność powinna zwrócić wybraną przez nas zakładkę.

Oto kod źródłowy wszystkich wykorzystanych plików:

bookmarks.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content">
	<ListView
		android:id="@+id/lvBookmarksList"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" />
</LinearLayout>

MainActivity.java

package pl.froger.hello.intentfilters;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser.BookmarkColumns;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {
	public static final int BOOKMARK_PICK = 1234;

	private EditText etLink;
	private Button btnServeSpecifyLink;
	private Button btnPickBookmark;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		etLink = (EditText) findViewById(R.id.etLink);
		btnServeSpecifyLink = (Button) findViewById(R.id.btnServeSpecifyLink);
		btnPickBookmark = (Button) findViewById(R.id.btnPickBookmark);
		initButtonsOnClick();
	}

	private void initButtonsOnClick() {
		OnClickListener listener = new OnClickListener() {
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnServeSpecifyLink:
					serveLinkFromEditText();
					break;
				case R.id.btnPickBookmark:
					pickBookmark();
					break;
				default:
					break;
				}
			}
		};
		btnServeSpecifyLink.setOnClickListener(listener);
		btnPickBookmark.setOnClickListener(listener);
	}

	private void serveLinkFromEditText() {
		Uri uri = Uri.parse(etLink.getText().toString());
		Intent intent = new Intent(Intent.ACTION_VIEW, uri);
		startActivity(intent);
	}

	private void pickBookmark() {
		Intent intent = new Intent(Intent.ACTION_PICK, Uri.parse("content://browser"));
		startActivityForResult(intent, BOOKMARK_PICK);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		switch (requestCode) {
		case BOOKMARK_PICK:
			if (resultCode == Activity.RESULT_OK) {
				Uri bookmarkUri = data.getData();
				String[] bookmarks = new String[] { BookmarkColumns.URL };
				Cursor cursor = managedQuery(
						bookmarkUri, 
						bookmarks,
						null,
						null, 
						null);
				cursor.moveToFirst();
				String url = cursor.getString(cursor.getColumnIndexOrThrow(BookmarkColumns.URL));
				etLink.setText(url);
			}
			break;

		default:
			break;
		}
	}
}

BookmarkPicker.java

package pl.froger.hello.intentfilters;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser.BookmarkColumns;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {
	public static final int BOOKMARK_PICK = 1234;

	private EditText etLink;
	private Button btnServeSpecifyLink;
	private Button btnPickBookmark;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		etLink = (EditText) findViewById(R.id.etLink);
		btnServeSpecifyLink = (Button) findViewById(R.id.btnServeSpecifyLink);
		btnPickBookmark = (Button) findViewById(R.id.btnPickBookmark);
		initButtonsOnClick();
	}

	private void initButtonsOnClick() {
		OnClickListener listener = new OnClickListener() {
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnServeSpecifyLink:
					serveLinkFromEditText();
					break;
				case R.id.btnPickBookmark:
					pickBookmark();
					break;
				default:
					break;
				}
			}
		};
		btnServeSpecifyLink.setOnClickListener(listener);
		btnPickBookmark.setOnClickListener(listener);
	}

	private void serveLinkFromEditText() {
		Uri uri = Uri.parse(etLink.getText().toString());
		Intent intent = new Intent(Intent.ACTION_VIEW, uri);
		startActivity(intent);
	}

	private void pickBookmark() {
		Intent intent = new Intent(Intent.ACTION_PICK, Uri.parse("content://browser"));
		startActivityForResult(intent, BOOKMARK_PICK);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		switch (requestCode) {
		case BOOKMARK_PICK:
			if (resultCode == Activity.RESULT_OK) {
				Uri bookmarkUri = data.getData();
				String[] bookmarks = new String[] { BookmarkColumns.URL };
				Cursor cursor = managedQuery(
						bookmarkUri, 
						bookmarks,
						null,
						null, 
						null);
				cursor.moveToFirst();
				String url = cursor.getString(cursor.getColumnIndexOrThrow(BookmarkColumns.URL));
				etLink.setText(url);
			}
			break;

		default:
			break;
		}
	}
}

Filtr intencji dla powyższego przykładu:

<!-- BookmarkPicker Activity -->
<activity android:name=".BookmarkPicker">
	<intent-filter>
		<action android:name="android.intent.action.PICK" />
		<category android:name="android.intent.category.DEFAULT" />
		<data android:path="browser" android:scheme="content" />
	</intent-filter>
</activity>

Zrzuty ekranu

Kompletny kod źródłowy

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

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="pl.froger.hello.intentfilters"
	android:versionCode="1"
	android:versionName="1.0">
	<uses-sdk android:minSdkVersion="8" />
	<application android:icon="@drawable/icon" android:label="@string/app_name">
		<!-- MainActivity -->
		<activity android:name=".MainActivity">
			<intent-filter>
				<action android:name="android.intent.action.MAIN" />
				<category android:name="android.intent.category.LAUNCHER" />
			</intent-filter>
		</activity>
		<!-- LinkServer Activity -->
		<activity android:name=".LinkServer">
			<intent-filter>
				<action android:name="android.intent.action.VIEW" />
				<category android:name="android.intent.category.DEFAULT" />
				<category android:name="andorid.intent.category.BROWSABLE" />
				<data android:scheme="http" android:host="www.google.pl" />
			</intent-filter>
		</activity>
		<!-- BookmarkPicker Activity -->
		<activity android:name=".BookmarkPicker">
			<intent-filter>
				<action android:name="android.intent.action.PICK" />
				<category android:name="android.intent.category.DEFAULT" />
				<data android:path="browser" android:scheme="content" />
			</intent-filter>
		</activity>
	</application>
	<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
	<uses-permission android:name="com.android.broswer.permission.WRITE_HISTORY_BOOKMARKS" />
</manifest>

Komentarze (0) Subskrybuj

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.