Content Provider — to komponent Androida, który zapewnia jednolity interfejs dostępu do danych między aplikacjami. Abstrahuje fizyczne przechowywanie (SQLite, pliki, źródła sieciowe) i zapewnia bezpieczną wymianę informacji przez ContentResolver. Według Android Developer Guide, 2026, Content Provider jest jednym z czterech podstawowych komponentów aplikacji Android obok Activity, Service i BroadcastReceiver. Jego zadaniem jest udostępnienie danych innym aplikacjom z kontrolą uprawnień odczytu i zapisu.
Najważniejsze
Content Provider — to komponent Androida, który zarządza dostępem do scentralizowanego magazynu danych i udostępnia je innym aplikacjom przez jednolity interfejs kontraktowy. Ukrywa szczegóły implementacji przechowywania: dane mogą być przechowywane w SQLite, systemie plików, chmurze lub być wynikiem żądania sieciowego.
Android zawiera wbudowane Content Provider dla danych systemowych — ContactsContract, MediaStore, CalendarContract, CallLog. Aplikacje innych firm również mogą tworzyć własnych dostawców do bezpiecznej wymiany danych. Każdy dostawca jest rejestrowany w AndroidManifest.xml z określeniem authority — unikalnego ciągu znaków, który stanowi pierwszą część URI.
Content Provider działa według modelu klient-serwer. Dostawca działa jako serwer, który implementuje sześć obowiązkowych metod: query, insert, update, delete, getType i onCreate. Klient (inna aplikacja) uzyskuje dostęp do dostawcy przez ContentResolver, który przekazuje wywołania do odpowiednich metod dostawcy przez mechanizm IPC Androida.
Każdy Content Provider jest identyfikowany przez URI schematu content://. Na przykład content://com.example.app.provider/items. Pierwsza część authority (com.example.app.provider) jest powiązana z klasą dostawcy w manifeście. Ścieżka /items wskazuje tabelę, a /items/5 — konkretny rekord o ID=5.
Gdy aplikacja wywołuje ContentResolver.query(URI), Android sprawdza uprawnienia wywołującego pakietu, znajduje dostawcę po authority i uruchamia jego proces, jeśli jeszcze nie jest uruchomiony. Dostawca wykonuje zapytanie i zwraca Cursor — obiekt zawierający wynik, który pozwala klientowi iterować po rekordach.
Klasa ContentProvider wymaga implementacji sześciu abstrakcyjnych metod. Każda metoda przyjmuje URI i zwraca wynik odpowiedni do typu operacji. System wywołuje te metody z dowolnego procesu, więc muszą być bezpieczne wątkowo i nie blokować wykonania na długi czas.
Metoda query przyjmuje URI, tablicę kolumn projection, ciąg selekcji selection z argumentami i kolejność sortowania. Zwraca Cursor z danymi. W implementacji należy rozparsować URI za pomocą UriMatcher i wykonać odpowiednie zapytanie SQL do bazy danych.
Te metody modyfikują dane w magazynie. insert otrzymuje ContentValues — pary klucz-wartość — i zwraca URI nowego rekordu. update i delete przyjmują selection do filtrowania rekordów i zwracają liczbę dotkniętych wierszy. Po zmianie danych dostawca musi powiadomić o tym przez ContentResolver.notifyChange.
| Metoda | Przeznaczenie | Zwracana wartość |
|---|---|---|
| query | Pobranie danych po URI | Cursor lub null |
| insert | Dodanie nowego rekordu | URI nowego rekordu |
| update | Aktualizacja istniejących rekordów | int (liczba wierszy) |
| delete | Usunięcie rekordów | int (liczba wierszy) |
| getType | Typ MIME dla URI | String |
| onCreate | Inicjalizacja dostawcy | boolean |
ContentResolver — to jednolity punkt dostępu do pracy ze wszystkimi Content Provider w systemie. Aplikacja kliencka nigdy nie wywołuje metod dostawcy bezpośrednio — tylko przez ContentResolver, który Android uzyskuje z kontekstu. Operacje CRUD w ContentResolver mają te same nazwy co u dostawcy, ale przyjmują URI zamiast bezpośrednich referencji.
Do parsowania przychodzących URI wewnątrz dostawcy używany jest UriMatcher. Pozwala on dopasować URI do kodu numerycznego — na przykład URI content://authority/items daje kod 1, a content://authority/items/# — kod 2. To eliminuje konieczność ręcznego parsowania ciągu URI w każdej metodzie.
// Używanie ContentResolver do dostępu do kontaktów
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(
uri,
arrayOf(ContactsContract.Contacts.DISPLAY_NAME),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME
))
Log.d("Kontakty", "Nazwa: $name")
}
}
Cursor po użyciu należy koniecznie zamknąć — w powyższym przykładzie robi to use (funkcja rozszerzająca Kotlin). Jeśli Cursor nie zostanie zamknięty, nastąpi wyciek pamięci, ponieważ utrzymuje on referencję do danych w puli Binder. Do scenariuszy UI używaj CursorLoader lub Room z LiveData/Flow.
Tworzenie własnego Content Provider zaczyna się od dziedziczenia po klasie ContentProvider. Dostawca współpracuje z bazą SQLite przez SQLiteOpenHelper i używa UriMatcher do określenia typu zapytania. Rozważmy minimalną implementację do zarządzania listą notatek.
Dostawca rejestrowany jest w AndroidManifest.xml wewnątrz tagu application. Atrybut authorities określa unikalny identyfikator, a exported decyduje, czy inne aplikacje mogą uzyskać dostęp do dostawcy. Bez exported=true dostawca jest dostępny tylko wewnątrz twojej aplikacji.
// Przykład Content Provider dla notatek
class NotesProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.app.notes"
const val NOTES_PATH = "notes"
const val NOTES_URI = "content://$AUTHORITY/$NOTES_PATH"
const val NOTES_ID = "content://$AUTHORITY/$NOTES_PATH/#"
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, NOTES_PATH, 1)
addURI(AUTHORITY, "$NOTES_PATH/#", 2)
}
}
override fun query(uri: Uri, projection: Array<String>?,
selection: String?, args: Array<String>?, sort: String?): Cursor? {
return when (uriMatcher.match(uri)) {
1 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, selection, args, null, null, sort)
2 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, "_id=?", arrayOf(uri.lastPathSegment), null, null, null)
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = dbHelper.writableDatabase.insert(TABLE_NOTES, null, values)
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
override fun delete(uri: Uri, selection: String?, args: Array<String>?): Int {
val count = dbHelper.writableDatabase.delete(TABLE_NOTES, selection, args)
context?.contentResolver?.notifyChange(uri, null)
return count
}
// getType, update, onCreate pominięte dla zwięzłości
}
Po utworzeniu klasy dostawcę należy zarejestrować w manifeście z atrybutami android:authorities i android:exported (true, jeśli dostawca jest publiczny). System tworzy instancję dostawcy przy pierwszym odwołaniu do niego — dzieje się to w wątku UI, więc onCreate powinien wykonywać się szybko.
Content Provider pozwala zarządzać dostępem do danych na dwóch poziomach: uprawnienia odczytu i uprawnienia zapisu. Są one określone w manifeście atrybutami android:readPermission i android:writePermission. Jeśli aplikacja kliencka nie ma odpowiedniego uprawnienia, system odrzuci wywołanie z SecurityException.
Android obsługuje tymczasowe uprawnienia przez flagi FLAG_GRANT_READ_URI_PERMISSION i FLAG_GRANT_WRITE_URI_PERMISSION. Jest to przydatne, gdy aplikacja przekazuje URI pliku innej aplikacji przez Intent — odbiorca uzyskuje dostęp tylko do konkretnego URI na ograniczony czas. System cofa tymczasowe uprawnienie po zakończeniu działania aplikacji odbierającej.
Dla systemowych dostawców Android wymaga określenia konkretnych uprawnień w manifeście aplikacji. Na przykład do dostępu do kontaktów potrzebne jest READ_CONTACTS, do kalendarza — READ_CALENDAR. Od Androida 6 uprawnienia te są wymagane podczas działania, a nie przy instalacji.
Często zadawane pytania
Content Provider — to komponent Androida, który zapewnia standardowy interfejs do wymiany danych między aplikacjami przez ContentResolver. Abstrahuje sposób przechowywania (SQLite, pliki, sieć) i zapewnia bezpieczny dostęp do danych z kontrolą uprawnień odczytu i zapisu.
Authority — to unikalny ciąg znaków identyfikujący dostawcę, który jest określony w AndroidManifest.xml. Stanowi pierwszą część URI content://authority/path i jest używany przez system do kierowania wywołań ContentResolver do odpowiedniego dostawcy. Authority musi być unikalna wśród wszystkich aplikacji na urządzeniu.
UriMatcher dopasowuje URI do kodów numerycznych. Dodajesz wzorce przez addURI, a następnie wywołujesz match, aby uzyskać kod dla przychodzącego URI. Pozwala to w metodach query, insert, update, delete określić, która tabela lub rekord jest żądany i wykonać odpowiednią operację na bazie danych.
Tak, Cursor należy koniecznie zamknąć po użyciu. Jeśli Cursor nie zostanie zamknięty, nastąpi wyciek pamięci, ponieważ utrzymuje on referencję Binder do danych dostawcy. W Kotlin używaj funkcji use do automatycznego zamykania, a w Javie — try-with-resources lub wywołania cursor.close() w finally.
Content Provider — to komponent do międzyaplikacyjnego dostępu do danych, podczas gdy SQLiteDatabase to wewnętrzne przechowywanie jednej aplikacji. Content Provider udostępnia interfejs URI i kontrolę uprawnień, a SQLiteDatabase działa bezpośrednio z bazą bez mechanizmów bezpieczeństwa na poziomie systemu operacyjnego.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również