İçerik sağlayıcı oluşturma

İçerik sağlayıcı, merkezi bir veri deposuna erişimi yönetir. Bir sağlayıcıyı, manifest dosyasındaki öğelerle birlikte bir Android uygulamasında bir veya daha fazla sınıf olarak uygularsınız. Sınıflarınızdan birinde, sağlayıcınız ile diğer uygulamalar arasındaki arayüz olan ContentProvider alt sınıfını uyguluyorsunuz.

İçerik sağlayıcıların amacı diğer uygulamaların veri kullanımına sunmak olsa da uygulamanızda kullanıcıların sağlayıcınız tarafından yönetilen verileri sorgulamasına ve değiştirmesine olanak tanıyan etkinlikler bulunabilir.

Bu sayfada, içerik sağlayıcı oluşturmak için temel süreç ve kullanılacak API'lerin listesi bulunmaktadır.

Yapı oluşturmaya başlamadan önce

Sağlayıcı oluşturmaya başlamadan önce aşağıdakileri göz önünde bulundurun:

  • İçerik sağlayıcıya ihtiyacınız olup olmadığına karar verin. Aşağıdaki özelliklerden birini veya daha fazlasını sunmak istiyorsanız bir içerik sağlayıcı oluşturmanız gerekir:
    • Karmaşık verileri veya dosyaları diğer uygulamalara sunmak istiyorsanız.
    • Kullanıcıların uygulamanızdaki karmaşık verileri diğer uygulamalara kopyalamasına izin vermek istiyorsunuz.
    • Arama çerçevesini kullanarak özel arama önerileri sunmak istiyorsunuz.
    • Uygulama verilerinizi widget'lara göstermek istiyorsunuz.
    • AbstractThreadedSyncAdapter, CursorAdapter veya CursorLoader sınıflarını uygulamak istiyorsunuz.

    Kullanım tamamen kendi uygulamanızdaysa ve yukarıda listelenen özelliklerden hiçbirine ihtiyacınız yoksa veritabanlarını veya diğer kalıcı depolama türlerini kullanmak için bir sağlayıcıya ihtiyacınız yoktur. Bunun yerine, Veri ve dosya depolamaya genel bakış başlıklı makalede açıklanan depolama sistemlerinden birini kullanabilirsiniz.

  • Henüz yapmadıysanız sağlayıcılar ve nasıl çalıştıkları hakkında daha fazla bilgi edinmek için İçerik sağlayıcı ile ilgili temel bilgileri okuyun.

Ardından, sağlayıcınızı oluşturmak için aşağıdaki adımları uygulayın:

  1. Verileriniz için ham depolama alanını tasarlayın. İçerik sağlayıcı, verileri iki şekilde sunar:
    Dosya verileri
    Fotoğraf, ses veya video gibi normalde dosyalara giren veriler. Dosyaları uygulamanızın özel alanında depolayın. Sağlayıcınız, başka bir uygulamadan gelen dosya isteğine yanıt olarak dosya için bir herkese açık kullanıcı adı sunabilir.
    "Yapılandırılmış" veri
    Normalde bir veritabanına, diziye veya benzer bir yapıya giden veriler. Verileri, satır ve sütun tablolarıyla uyumlu bir formda depolayın. Satır, envanterdeki kişi veya öğe gibi bir varlığı temsil eder. Bir sütun, varlıkla ilgili kişi adı veya bir öğenin fiyatı gibi bazı verileri temsil eder. Bu tür verileri depolamak için yaygın olarak kullanılan bir yöntem SQLite veritabanındadır ancak herhangi bir kalıcı depolama türünü kullanabilirsiniz. Android sisteminde kullanılabilen depolama alanı türleri hakkında daha fazla bilgi edinmek için Veri depolamayı tasarlama bölümüne göz atın.
  2. ContentProvider sınıfının ve gerekli yöntemlerinin somut bir uygulamasını tanımlayın. Bu sınıf, verilerinizle Android sisteminin geri kalanı arasındaki arayüzdür. Bu sınıf hakkında daha fazla bilgi için ContentProvider sınıfını uygulama bölümüne bakın.
  3. Sağlayıcının yetki dizesini, içerik URI'lerini ve sütun adlarını tanımlayın. Sağlayıcının uygulamasının amaçları işlemesini istiyorsanız amaç işlemleri, ekstra veriler ve işaretler de tanımlayın. Ayrıca, verilerinize erişmek isteyen uygulamalar için ihtiyaç duyduğunuz izinleri tanımlayın. Tüm bu değerleri ayrı bir sözleşme sınıfında sabit olarak tanımlayabilirsiniz. Daha sonra, bu sınıfı diğer geliştiricilere sunabilirsiniz. İçerik URI'leri hakkında daha fazla bilgi için İçerik URI'leri tasarlama bölümüne bakın. Amaçlar hakkında daha fazla bilgi için Amaçlar ve veri erişimi bölümüne bakın.
  4. Örnek veri veya sağlayıcı ile bulut tabanlı veriler arasında verileri senkronize edebilen AbstractThreadedSyncAdapter uygulaması gibi isteğe bağlı diğer parçaları ekleyin.

Veri depolamayı tasarlayın

İçerik sağlayıcı, verilerin yapılandırılmış bir biçimde kaydedilmesi için kullanılan arayüzdür. Arayüzü oluşturmadan önce verilerin nasıl depolanacağına karar verin. Verileri istediğiniz biçimde depolayabilirsiniz. Ardından arayüzü, verileri gerektiği gibi okuyup yazacak şekilde tasarlayabilirsiniz.

Android'de kullanılabilen veri depolama teknolojilerinden bazıları şunlardır:

  • Yapılandırılmış verilerle çalışıyorsanız SQLite gibi bir ilişkisel veritabanı veya LevelDB gibi ilişkisel olmayan bir anahtar/değer veri deposu kullanmayı düşünün. Ses, görüntü veya video medyası gibi yapılandırılmamış verilerle çalışıyorsanız bu verileri dosya olarak depolamayı düşünebilirsiniz. Birkaç farklı depolama türünü birlikte kullanabilir ve gerekirse tek bir içerik sağlayıcı kullanarak kullanıma sunabilirsiniz.
  • Android sistemi, Android'in kendi sağlayıcıların tablo tabanlı verileri depolamak için kullandığı SQLite veritabanı API'sine erişim sağlayan Room kalıcılık kitaplığıyla etkileşim kurabilir. Bu kitaplığı kullanarak veritabanı oluşturmak için Odayı kullanarak verileri yerel veritabanına kaydetme bölümünde açıklandığı gibi bir RoomDatabase alt sınıfı örneklendirin.

    Deponuzu uygulamak için veritabanı kullanmanız gerekmez. Sağlayıcı, harici olarak ilişkisel veritabanına benzer şekilde bir tablo grubu olarak görünür ancak bu, sağlayıcının dahili uygulaması için bir gereklilik değildir.

  • Android'de dosya verilerini depolamak için çeşitli dosya odaklı API'ler bulunur. Dosya depolama hakkında daha fazla bilgi edinmek için Veri ve dosya depolamaya genel bakış bölümünü okuyun. Müzik veya video gibi medyayla ilgili veriler sunan bir sağlayıcı tasarlıyorsanız tablo verilerini ve dosyaları birleştiren bir sağlayıcı tasarlayabilirsiniz.
  • Nadir durumlarda, tek bir uygulama için birden fazla içerik sağlayıcı uygulamadan yararlanabilirsiniz. Örneğin, bir içerik sağlayıcı kullanarak bir widget'la bazı verileri paylaşmak ve diğer uygulamalarla paylaşmak için farklı bir veri kümesi göstermek isteyebilirsiniz.
  • Ağ tabanlı verilerle çalışmak için java.net ve android.net sınıflarını kullanın. Ayrıca, ağ tabanlı verileri veritabanı gibi bir yerel veri deposuyla senkronize edip tablo veya dosya biçiminde sunabilirsiniz.

Not: Deponuzda geriye dönük uyumlu olmayan bir değişiklik yaparsanız depoyu yeni bir sürüm numarasıyla işaretlemeniz gerekir. Ayrıca, yeni içerik sağlayıcının kullanıldığı uygulamanızın sürüm numarasını artırmanız gerekir. Bu değişikliğin yapılması, uyumlu olmayan bir içerik sağlayıcısına sahip bir uygulamayı yeniden yüklemeyi denediğinde sistemin eski sürüme geçirilmesinin ve sistemin kilitlenmesine neden olmasını önler.

Veri tasarımıyla ilgili dikkat edilmesi gereken noktalar

Aşağıda sağlayıcınızın veri yapısını tasarlamaya yönelik bazı ipuçları verilmiştir:

  • Tablo verilerinde her zaman, sağlayıcının her satır için benzersiz bir sayısal değer olarak tuttuğu bir "birincil anahtar" sütunu olmalıdır. Satırı diğer tablolardaki ilgili satırlara bağlamak için bu değeri kullanabilirsiniz ("yabancı anahtar" olarak kullanabilirsiniz). Bu sütun için herhangi bir adı kullanabilirsiniz, ancak sağlayıcı sorgusunun sonuçlarını ListView ile bağlamak, alınan sütunlardan birinin _ID adına sahip olmasını gerektirdiğinden BaseColumns._ID kullanmak en iyi seçimdir.
  • Bit eşlem görüntüleri veya diğer çok büyük dosya odaklı verileri sağlamak istiyorsanız verileri bir dosyada depolayın. Ardından, verileri doğrudan bir tabloda depolamak yerine dolaylı olarak sağlayın. Bunu yaparsanız sağlayıcınızın kullanıcılarına, verilere erişmek için ContentResolver dosya yöntemi kullanmaları gerektiğini söylemeniz gerekir.
  • Boyutları değişen veya yapısı değişen verileri depolamak için ikili büyük nesne (BLOB) veri türünü kullanın. Örneğin, protokol arabelleği veya JSON yapısı depolamak için BLOB sütunu kullanabilirsiniz.

    BLOB'u, şemadan bağımsız bir tablo uygulamak için de kullanabilirsiniz. Bu tablo türünde bir birincil anahtar sütunu, MIME türü sütunu ve bir veya daha fazla genel sütunu BLOB olarak tanımlarsınız. BLOB sütunlarındaki verilerin anlamı, MIME türü sütunundaki değerle gösterilir. Böylece farklı satır türlerini aynı tabloda depolayabilirsiniz. Kişi Sağlayıcı'nın "veri" tablosu ContactsContract.Data, şemadan bağımsız bir tablo örneğidir.

İçerik URI'lerini tasarlama

İçerik URI'si, bir sağlayıcıdaki verileri tanımlayan bir URI'dir. İçerik URI'leri arasında tüm sağlayıcının (yetkisi) sembolik adı ile bir tabloya veya dosyaya (yol) işaret eden bir ad bulunur. İsteğe bağlı kimlik bölümü, tablodaki bağımsız bir satırı işaret eder. ContentProvider işlevinin her veri erişimi yöntemi, bağımsız değişken olarak bir içerik URI'sine sahiptir. Bu sayede erişilecek tabloyu, satırı veya dosyayı belirleyebilirsiniz.

İçerik URI'leri hakkında bilgi için İçerik sağlayıcıyla ilgili temel bilgiler konusuna bakın.

Bir otorite tasarlayın

Bir sağlayıcının genellikle Android dahili adı olarak işlev gören tek bir yetkilisi vardır. Diğer sağlayıcılarla çakışma olmaması için sağlayıcı yetkinizin temeli olarak internet alan adı sahipliğini (ters yönde) kullanın. Bu öneri Android paket adları için de geçerli olduğundan, sağlayıcı yetkilinizi, sağlayıcıyı içeren paketin adının bir uzantısı olarak tanımlayabilirsiniz.

Örneğin, Android paketinizin adı com.example.<appname> ise sağlayıcınıza com.example.<appname>.provider yetkisi verin.

Yol yapısı tasarlama

Geliştiriciler genellikle bağımsız tablolara yönlendiren yolları ekleyerek yetkiliden içerik URI'leri oluşturur. Örneğin, table1 ve table2 olmak üzere iki tablonuz varsa bunları önceki örnekteki yetkiliyle birleştirerek com.example.<appname>.provider/table1 ve com.example.<appname>.provider/table2 içerik URI'lerini elde edebilirsiniz. Yollar tek bir segmentle sınırlı değildir ve yolun her düzeyi için bir tablo olması gerekmez.

İçerik URI'si kimliklerini işleme

Kurallarına göre, sağlayıcılar URI'nın sonundaki satır için kimlik değerine sahip bir içerik URI'sini kabul ederek tablodaki tek bir satıra erişim sunar. Ayrıca, kurallara göre sağlayıcılar, kimlik değerini tablonun _ID sütunuyla eşleştirir ve eşleşen satıra göre istenen erişimi gerçekleştirir.

Bu kural, sağlayıcıya erişen uygulamalar için ortak bir tasarım kalıbını basitleştirir. Uygulama, sağlayıcıya karşı bir sorgu yapar ve sonuç olarak elde edilen Cursor öğesini CursorAdapter kullanarak ListView içinde gösterir. CursorAdapter tanımı, Cursor içindeki sütunlardan birinin _ID olmasını gerektirir

Daha sonra kullanıcı, verileri incelemek veya değiştirmek için kullanıcı arayüzünde görüntülenen satırlardan birini seçer. Uygulama, ilgili satırı ListView destekleyen Cursor öğesinden alır, bu satır için _ID değerini alır, bunu içerik URI'sine ekler ve erişim isteğini sağlayıcıya gönderir. Daha sonra sağlayıcı, kullanıcının seçtiği satıra göre sorguyu veya değişikliği yapabilir.

İçerik URI'si kalıpları

Sağlayıcı API'si, gelen içerik URI'si için hangi işlemi gerçekleştireceğinizi seçmenize yardımcı olmak amacıyla içerik URI'si kalıplarını tam sayı değerleriyle eşleyen UriMatcher kolaylık sınıfını içerir. Belirli bir kalıpla eşleşen içerik URI'si veya URI'lar için istenen işlemi seçen switch ifadesinde tam sayı değerlerini kullanabilirsiniz.

İçerik URI'si kalıbı, joker karakterler kullanarak içerik URI'lerini eşleştirir:

  • *, herhangi bir uzunluktaki geçerli herhangi bir karakterden oluşan bir dizeyle eşleşir.
  • #, herhangi bir uzunluktaki sayısal karakterlerden oluşan bir dizeyle eşleşir.

İçerik URI işlemesini tasarlama ve kodlamaya örnek olarak, tablolara işaret eden aşağıdaki içerik URI'lerini tanıyan com.example.app.provider yetkisine sahip bir sağlayıcıyı düşünün:

  • content://com.example.app.provider/table1: table1 adlı bir tablo.
  • content://com.example.app.provider/table2/dataset1: dataset1 adlı bir tablo.
  • content://com.example.app.provider/table2/dataset2: dataset2 adlı bir tablo.
  • content://com.example.app.provider/table3: table3 adlı bir tablo.

Sağlayıcı, eklenmiş bir satır kimliği varsa bu içerik URI'lerini de tanır. Örneğin, table3 içinde 1 ile tanımlanan satır için content://com.example.app.provider/table3/1.

Aşağıdaki içerik URI'si kalıpları mümkündür:

content://com.example.app.provider/*
Sağlayıcıdaki herhangi bir içerik URI'siyle eşleşir.
content://com.example.app.provider/table2/*
dataset1 ve dataset2 tablolarında bir içerik URI'siyle eşleşir ancak table1 veya table3 için içerik URI'leriyle eşleşmez.
content://com.example.app.provider/table3/#
table3 alanındaki tek satırlar için bir içerik URI'siyle eşleşir. Örneğin, 6 ile tanımlanan satır için content://com.example.app.provider/table3/6.

Aşağıdaki kod snippet'i, UriMatcher içindeki yöntemlerin nasıl çalıştığını gösterir. Bu kod, tablolar için content://<authority>/<path> ve tek satırlar için content://<authority>/<path>/<id> içerik URI kalıbını kullanarak tüm tablonun tamamı için URI'leri tek bir satırdaki URI'lerden farklı şekilde işler.

addURI() yöntemi, bir yetkiliyi ve yolu tam sayı değeriyle eşler. match() yöntemi, bir URI için tam sayı değerini döndürür. switch ifadesi, tüm tabloyu sorgulama ile tek bir kayıt için sorgulama arasında seçim yapar.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

Başka bir sınıf (ContentUris), içerik URI'lerinin id bölümüyle çalışmak için kolaylık yöntemleri sağlar. Uri ve Uri.Builder sınıfları, mevcut Uri nesneleri ayrıştırmak ve yenilerini oluşturmak için kolaylık yöntemleri içerir.

ContentProvider sınıfını uygulama

ContentProvider örneği, diğer uygulamalardan gelen istekleri işleyerek yapılandırılmış bir veri kümesine erişimi yönetir. Tüm erişim biçimleri sonunda ContentResolver yöntemini çağırır. Bu çağrı, erişim elde etmek için somut bir ContentProvider yöntemini çağırır.

Gerekli yöntemler

ContentProvider soyut sınıfı, soyut alt sınıfınızın bir parçası olarak uyguladığınız altı soyut yöntemi tanımlar. onCreate() dışındaki tüm bu yöntemler, içerik sağlayıcınıza erişmeye çalışan bir istemci uygulaması tarafından çağrılır.

query()
Sağlayıcınızdan veri alın. Sorgulanacak tabloyu, döndürülecek satırları, sütunları ve sonucun sıralama düzenini seçmek için bağımsız değişkenleri kullanın. Verileri Cursor nesnesi olarak döndürün.
insert()
Sağlayıcınıza yeni bir satır ekleyin. Hedef tabloyu seçmek ve kullanılacak sütun değerlerini almak için bağımsız değişkenleri kullanın. Yeni eklenen satır için içerik URI'si döndürün.
update()
Sağlayıcınızdaki mevcut satırları güncelleyin. Güncellenecek tabloyu ve satırları seçmek ve güncellenmiş sütun değerlerini almak için bağımsız değişkenleri kullanın. Güncellenen satır sayısını döndürür.
delete()
Sağlayıcınızdaki satırları silin. Tabloyu ve silinecek satırları seçmek için bağımsız değişkenleri kullanın. Silinen satır sayısını döndürür.
getType()
İçerik URI'sine karşılık gelen MIME türünü döndürün. Bu yöntem, İçerik sağlayıcı MIME türlerini uygulama bölümünde daha ayrıntılı olarak açıklanmaktadır.
onCreate()
Sağlayıcınızı ilk kullanıma hazırlayın. Android sistemi, sağlayıcınızı oluşturduktan hemen sonra bu yöntemi çağırır. Bir ContentResolver nesnesi erişmeye çalışana kadar sağlayıcınız oluşturulmaz.

Bu yöntemler, aynı şekilde adlandırılmış ContentResolver yöntemleriyle aynı imzaya sahiptir.

Bu yöntemleri uygularken aşağıdakileri göz önünde bulundurmanız gerekir:

  • onCreate() hariç tüm bu yöntemler aynı anda birden çok iş parçacığı tarafından çağrılabilir, bu nedenle iş parçacığı açısından güvenli olmalıdır. Birden fazla iş parçacığı hakkında daha fazla bilgi edinmek için İşlemlere ve ileti dizilerine genel bakış sayfasını inceleyin.
  • onCreate() öğesinde uzun işlemler yapmaktan kaçının. Başlatma görevlerini gerçekten ihtiyaç duyulana kadar erteleyin. onCreate() yöntemini uygulama ile ilgili bölümde bu konu daha ayrıntılı olarak açıklanmaktadır.
  • Bu yöntemleri uygulamanız gerekse de kodunuzun, beklenen veri türünü döndürmek dışında bir şey yapması gerekmez. Örneğin, insert() çağrısını yoksayıp 0 değerini döndürerek diğer uygulamaların bazı tablolara veri eklemesini önleyebilirsiniz.

query() yöntemini uygulama

ContentProvider.query() yöntemi, Cursor nesnesi döndürmeli veya başarısız olursa Exception hatası vermelidir. Veri depolamanız olarak bir SQLite veritabanı kullanıyorsanız SQLiteDatabase sınıfının query() yöntemlerinden biri tarafından döndürülen Cursor değerini döndürebilirsiniz.

Sorgu hiçbir satırla eşleşmezse getCount() yöntemi 0 değerini döndüren bir Cursor örneği döndürün. null değerini yalnızca sorgu işlemi sırasında dahili bir hata oluştuysa döndürün.

Veri depolamanız olarak SQLite veritabanı kullanmıyorsanız Cursor somut alt sınıflarından birini kullanın. Örneğin, MatrixCursor sınıfı, her satırın Object örnek dizisi olduğu bir imleç uygular. Bu sınıfta yeni bir satır eklemek için addRow() değerini kullanın.

Android sisteminin, Exception öğesini işlem sınırları arasında iletebilmesi gerekir. Android bunu, sorgu hatalarının işlenmesinde yararlı olan aşağıdaki istisnalar için yapabilir:

Insert() yöntemini uygulama

insert() yöntemi, ContentValues bağımsız değişkenindeki değerleri kullanarak uygun tabloya yeni bir satır ekler. Bir sütun adı ContentValues bağımsız değişkeninde yoksa sağlayıcı kodunuzda veya veritabanı şemanızda sütun için varsayılan bir değer sağlayabilirsiniz.

Bu yöntem, yeni satırın içerik URI'sini döndürür. Bunu oluşturmak için withAppendedId() kullanarak yeni satırın birincil anahtarını (genellikle _ID değerini) tablonun içerik URI'sine ekleyin.

delete() yöntemini uygulama

delete() yönteminin veri depolama alanınızdaki satırları silmesi gerekmez. Sağlayıcınızla senkronizasyon bağdaştırıcısı kullanıyorsanız silinmiş bir satırı tamamen kaldırmak yerine "sil" işaretiyle işaretlemenizi öneririz. Senkronizasyon bağdaştırıcısı, silinmiş satırları kontrol edip sağlayıcıdan silmeden önce bu satırları sunucudan kaldırabilir.

Update() yöntemini uygulama

update() yöntemi, insert() tarafından kullanılan aynı ContentValues bağımsız değişkeninin yanı sıra delete() ve ContentProvider.query() tarafından kullanılan aynı selection ve selectionArgs bağımsız değişkenlerini alır. Bu sayede kodu bu yöntemler arasında yeniden kullanabilirsiniz.

onCreate() yöntemini uygulama

Android sistemi, sağlayıcıyı başlattığında onCreate() adlı kuruluşu çağırır. Bu yöntemde yalnızca hızlı çalışan başlatma görevlerini gerçekleştirin ve sağlayıcı gerçekten veriler için istek alana kadar veritabanı oluşturma ve veri yüklemeyi erteleyin. onCreate() uygulamasında uzun görevler yaparsanız sağlayıcınızın başlatılmasını yavaşlatırsınız. Bu da sağlayıcının diğer uygulamalara verdiği yanıtı yavaşlatır.

Aşağıdaki iki snippet, ContentProvider.onCreate() ile Room.databaseBuilder() arasındaki etkileşimi göstermektedir. İlk snippet, veritabanı nesnesinin oluşturulduğu ve veri erişimi nesnelerinin oluşturulduğu yerde ContentProvider.onCreate() uygulamasını gösterir:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

ContentProvider MIME türlerini uygulama

ContentProvider sınıfının, MIME türlerini döndürmek için iki yöntemi vardır:

getType()
Her sağlayıcı için uyguladığınız gerekli yöntemlerden biri.
getStreamTypes()
Sağlayıcınız dosya sunuyorsa uygulamanız beklenen bir yöntem.

Tablolar için MIME türleri

getType() yöntemi, içerik URI bağımsız değişkeni tarafından döndürülen veri türünü açıklayan MIME biçiminde bir String döndürür. Uri bağımsız değişkeni, belirli bir URI yerine bir kalıp olabilir. Bu durumda, kalıpla eşleşen içerik URI'larıyla ilişkili verilerin türünü döndürün.

Metin, HTML veya JPEG gibi yaygın veri türleri için getType(), söz konusu verilere yönelik standart MIME türünü döndürür. Bu standart türlerin tam listesini IANA MIME Medya Türleri web sitesinde bulabilirsiniz.

getType(), bir tablo verisi satırına veya satırlarına işaret eden içerik URI'leri için Android'in tedarikçiye özel MIME biçiminde bir MIME türü döndürür:

  • Parçayı yazın: vnd
  • Alt tür bölümü:
    • URI kalıbı tek bir satır içinse: android.cursor.item/
    • URI kalıbı birden fazla satır içinse: android.cursor.dir/
  • Sağlayıcıya özel bölüm: vnd.<name>.<type>

    <name> ve <type> sizin tarafınızdan sağlanır. <name> değeri genel olarak, <type> değeri ise karşılık gelen URI kalıbına özeldir. Şirketinizin adı veya uygulamanızın Android paket adının bir kısmı <name> için iyi bir seçimdir. URI ile ilişkili tabloyu tanımlayan bir dize, <type> için iyi bir seçenektir.

Örneğin, bir sağlayıcının yetkilisi com.example.app.provider ise ve table1 adlı bir tabloyu ortaya çıkarıyorsa table1 içindeki birden fazla satırın MIME türü:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Tek bir table1 satırı için MIME türü:

vnd.android.cursor.item/vnd.com.example.provider.table1

Dosyalar için MIME türleri

Sağlayıcınız dosya sunuyorsa getStreamTypes() öğesini uygulayın. Yöntem, sağlayıcınızın belirli bir içerik URI'si için döndürebileceği dosyalar için bir String MIME türü dizisi döndürür. Sunduğunuz MIME türlerini, MIME türü filtre bağımsız değişkenine göre filtreleyin. Böylece yalnızca istemcinin işlemek istediği MIME türlerini döndürürsünüz.

Örneğin, fotoğraf resimlerini JPG, PNG ve GIF biçiminde dosya olarak sunan bir sağlayıcı düşünün. Bir uygulama, "image" (resim) olan bir öğe için image/* filtre dizesiyle ContentResolver.getStreamTypes() öğesini çağırırsa ContentProvider.getStreamTypes() yöntemi diziyi döndürür:

{ "image/jpeg", "image/png", "image/gif"}

Uygulama yalnızca JPG dosyalarıyla ilgileniyorsa *\/jpeg filtre dizesiyle ContentResolver.getStreamTypes() öğesini çağırabilir ve getStreamTypes() şunları döndürür:

{"image/jpeg"}

Sağlayıcınız filtre dizesinde istenen MIME türlerinden herhangi birini sunmuyorsa getStreamTypes(), null değerini döndürür.

Sözleşme sınıfı uygulama

Sözleşme sınıfı; URI'lar, sütun adları, MIME türleri ve sağlayıcıyla ilgili diğer meta veriler için sabit tanımlar içeren bir public final sınıfıdır. Bu sınıf, URI'ların, sütun adlarının vb. gerçek değerlerinde değişiklikler olsa bile sağlayıcıya doğru şekilde erişilebilmesini sağlayarak sağlayıcı ile diğer uygulamalar arasında bir sözleşme oluşturur.

Sözleşme sınıfı, genellikle sabit değerleri için hafızalı adlar içerdiğinden geliştiricilere de yardımcı olur. Böylece, geliştiricilerin sütun adları veya URI'lar için yanlış değerler kullanma ihtimali daha düşüktür. Bu bir sınıf olduğundan Javadoc dokümanlarını içerebilir. Android Studio gibi entegre geliştirme ortamları, sözleşme sınıfındaki sabit adları otomatik olarak tamamlayabilir ve sabit değerler için Javadoc'u görüntüleyebilir.

Geliştiriciler, sözleşme sınıfının sınıf dosyasına uygulamanızdan erişemezler ancak bu dosyayı, sağladığınız bir JAR dosyasından uygulamalarında statik olarak derleyebilirler.

ContactsContract sınıfı ve iç içe yerleştirilmiş sınıfları, sözleşme sınıflarına örnek olarak verilebilir.

İçerik sağlayıcı izinlerini uygulama

Android sisteminin tüm özellikleriyle ilgili izinler ve erişim, Güvenlik ipuçları bölümünde ayrıntılı olarak açıklanmaktadır. Veri ve dosya depolamaya genel bakış bölümünde, çeşitli depolama türleri için geçerli olan güvenlik ve izinler de açıklanmaktadır. Özetle önemli noktalar şunlardır:

  • Varsayılan olarak, cihazın dahili depolama alanında depolanan veri dosyaları, uygulamanıza ve sağlayıcınıza özeldir.
  • Oluşturduğunuz SQLiteDatabase veritabanları uygulamanıza ve sağlayıcınıza özeldir.
  • Harici depolama alanına kaydettiğiniz veri dosyaları varsayılan olarak herkese açıktır ve dünya genelinde okunabilir. Diğer uygulamalar harici depolamadaki dosyaları okumak ve yazmak için başka API çağrılarını kullanabileceğinden bu dosyalara erişimi kısıtlamak için içerik sağlayıcı kullanamazsınız.
  • Cihazınızın dahili depolama alanında dosya veya SQLite veritabanlarının açılması veya oluşturulması için yapılan yöntem, diğer tüm uygulamalara hem okuma hem de yazma erişimi sağlayabilir. Sağlayıcınızın deposu olarak dahili bir dosya veya veritabanı kullanır ve bu dosyaya "dünyada okunabilir" veya "dünyada yazılabilir" erişim izni verirseniz manifest dosyasında sağlayıcınız için belirlediğiniz izinler, verilerinizi korumaz. Dahili depolama alanındaki dosyalara ve veritabanlarına varsayılan erişim "gizli"dir. Sağlayıcınızın deposu için bu ayarı değiştirmeyin.

Verilerinize erişimi kontrol etmek için içerik sağlayıcı izinlerini kullanmak istiyorsanız verilerinizi dahili dosyalarda, SQLite veritabanlarında veya bulutta (ör. uzak bir sunucu) depolayın, dosyalar ile veritabanlarını uygulamanıza özel tutun.

İzinleri uygulama

Sağlayıcınızın varsayılan olarak ayarlanmış izinleri olmadığı için temel veriler gizli olsa bile tüm uygulamalar, varsayılan olarak sağlayıcınızdan okuma veya yazma işlemi yapabilir. Bunu değiştirmek için <provider> öğesinin özelliklerini veya alt öğelerini kullanarak manifest dosyanızda sağlayıcınız için izinleri ayarlayın. Sağlayıcının tamamı, belirli tablolar, belirli kayıtlar veya üçü için de geçerli olacak izinler ayarlayabilirsiniz.

Sağlayıcınızın izinlerini, manifest dosyanızdaki bir veya daha fazla <permission> öğesiyle tanımlarsınız. İzni sağlayıcınıza özel hale getirmek amacıyla android:name özelliği için Java tarzı kapsam oluşturma kullanın. Örneğin, okuma iznini com.example.app.provider.permission.READ_PROVIDER olarak adlandırın.

Aşağıdaki listede, sağlayıcının tamamı için geçerli olan izinlerden başlayarak daha ayrıntılı hale gelen izinlerin kapsamı açıklanmaktadır. Daha ayrıntılı izinler, daha geniş kapsamlı izinlere göre önceliklidir.

Okuma-yazma sağlayıcısı düzeyinde tek izin
Sağlayıcının tamamına hem okuma hem de yazma erişimini kontrol eden bir izin. Bu izin, <provider> öğesinin android:permission özelliğiyle belirtilir.
Okuma ve yazma sağlayıcı düzeyinde ayrı izinler
Sağlayıcının tamamı için okuma ve yazma izni. Bunları, <provider> öğesinin android:readPermission ve android:writePermission özellikleriyle belirtirsiniz. Bunlar, android:permission tarafından gerekli kılınan izne göre önceliklidir.
Yol düzeyinde izin
Sağlayıcınızdaki içerik URI'si için okuma, yazma veya okuma/yazma izni. Kontrol etmek istediğiniz her URI'yı <provider> öğesinin bir <path-permission> alt öğesiyle belirtirsiniz. Belirttiğiniz her içerik URI'si için bir okuma/yazma izni, bir okuma izni, bir yazma izni veya üçünü birden belirtebilirsiniz. Okuma ve yazma izinleri, okuma/yazma iznine göre önceliklidir. Ayrıca, yol düzeyindeki izin, sağlayıcı düzeyindeki izinlere göre önceliklidir.
Geçici izin
Bir uygulamaya normalde gereken izinlere sahip olmasa bile geçici erişim izni veren izin düzeyi. Geçici erişim özelliği, bir uygulamanın manifest dosyasında istemesi gereken izin sayısını azaltır. Geçici izinleri etkinleştirdiğinizde, yalnızca tüm verilerinize sürekli olarak erişen uygulamalar için sağlayıcınızın kalıcı izinlerine ihtiyacı vardır.

Örneğin, bir e-posta sağlayıcısı ve uygulaması uyguluyorsanız ve harici bir resim görüntüleyici uygulamasının sağlayıcınızdan gelen fotoğraf eklerini görüntülemesine izin vermek istiyorsanız ihtiyacınız olan izinleri göz önünde bulundurun. Resim görüntüleyiciye izin gerektirmeden gerekli erişimi vermek için fotoğrafların içerik URI'lerine yönelik geçici izinler ayarlayabilirsiniz.

E-posta uygulamanızı, kullanıcı bir fotoğraf görüntülemek istediğinde, resim görüntüleyiciye fotoğrafın içerik URI'sini ve izin işaretlerini içeren bir intent gönderecek şekilde tasarlayın. Ardından, görüntüleyici, sağlayıcınızın normal okuma iznine sahip olmasa bile fotoğrafı almak için e-posta sağlayıcınıza sorgu gönderebilir.

Geçici izinleri açmak için <provider> öğesinin android:grantUriPermissions özelliğini ayarlayın veya <provider> öğenize bir veya daha fazla <grant-uri-permission> alt öğesi ekleyin. Sağlayıcınızdan geçici bir izinle ilişkili içerik URI'si desteğini kaldırdığınızda Context.revokeUriPermission() çağrısı yapın.

Bu özelliğin değeri, sağlayıcınızın ne kadarının erişilebilir olduğunu belirler. Özellik "true" olarak ayarlanırsa sistem, sağlayıcınızın tamamına geçici izin vererek sağlayıcı veya yol düzeyindeki izinlerinizin gerektirdiği diğer izinleri geçersiz kılar.

Bu işaret "false" değerine ayarlanırsa <provider> öğenize <grant-uri-permission> alt öğeleri ekleyin. Her alt öğe, geçici erişim izni verilen içerik URI'sini veya URI'ları belirtir.

Bir uygulamaya geçici erişim yetkisi vermek için intent'in FLAG_GRANT_READ_URI_PERMISSION işaretini, FLAG_GRANT_WRITE_URI_PERMISSION işaretini veya her ikisini de içermesi gerekir. Bunlar, setFlags() yöntemiyle ayarlanır.

android:grantUriPermissions özelliği yoksa "false" olduğu varsayılır.

<provider> öğesi

Activity ve Service bileşenlerinde olduğu gibi, uygulamanın manifest dosyasında bir ContentProvider alt sınıfı <provider> öğesi kullanılarak tanımlanır. Android sistemi, öğeden aşağıdaki bilgileri alır:

Yetkili (android:authorities)
Sistem içindeki tüm sağlayıcıyı tanımlayan sembolik adlar. Bu özellik, İçerik URI'leri tasarlama bölümünde daha ayrıntılı olarak açıklanmaktadır.
Sağlayıcı sınıf adı (android:name)
ContentProvider uygulayan sınıf. Bu sınıf, ContentProvider sınıfını uygulama bölümünde daha ayrıntılı olarak açıklanmaktadır.
İzinler
Sağlayıcının verilerine erişmek için diğer uygulamaların sahip olması gereken izinleri belirten özellikler:

İzinler ve bunlarla ilgili özellikler, İçerik sağlayıcı izinlerini uygulama bölümünde daha ayrıntılı olarak açıklanmaktadır.

Başlangıç ve kontrol özellikleri
Bu özellikler, Android sisteminin sağlayıcıyı nasıl ve ne zaman başlatacağını, sağlayıcının işlem özelliklerini ve diğer çalışma zamanı ayarlarını belirler:
  • android:enabled: Sistemin sağlayıcıyı başlatmasına izin veren işaretleyici
  • android:exported: diğer uygulamaların bu sağlayıcıyı kullanmasına izin verdiğini işaretle
  • android:initOrder: aynı süreçteki diğer sağlayıcılara kıyasla bu sağlayıcının başlatılma sırası
  • android:multiProcess: Sistemin, arayan istemciyle aynı işlemde sağlayıcıyı başlatmasına izin veren bir işaret
  • android:process: Sağlayıcının çalıştırıldığı işlemin adıdır.
  • android:syncable: Sağlayıcıya ait verilerin sunucudaki verilerle senkronize edileceğini belirten işaret

Bu özellikler, <provider> öğesi kılavuzunda eksiksiz bir şekilde belgelenmiştir.

Bilgilendirici özellikler
Sağlayıcı için isteğe bağlı bir simge ve etiket:
  • android:icon: Sağlayıcının simgesini içeren çekilebilir bir kaynaktır. Bu simge, Ayarlar > Uygulamalar > Tümü'ndeki uygulama listesinde sağlayıcı etiketinin yanında görünür.
  • android:label: Sağlayıcıyı, sağlayıcıyı veya her ikisini de açıklayan bir bilgi etiketi. Bu etiket, Ayarlar > Uygulamalar > Tümü'ndeki uygulama listesinde görünür.

Bu özellikler, <provider> öğesi kılavuzunda eksiksiz bir şekilde belgelenmiştir.

Not: Android 11 veya sonraki sürümleri hedefliyorsanız diğer yapılandırma ihtiyaçları için paket görünürlüğü belgelerine göz atın.

Amaçlar ve veri erişimi

Uygulamalar, Intent ile dolaylı olarak içerik sağlayıcıya erişebilir. Uygulama, ContentResolver veya ContentProvider yöntemlerinin hiçbirini çağırmıyor. Bunun yerine, genellikle sağlayıcının kendi uygulamasının parçası olan bir etkinlik başlatan bir intent gönderir. Hedef etkinlik, kullanıcı arayüzünde verilerin alınması ve görüntülenmesinden sorumludur.

Amaçtaki işleme bağlı olarak, hedef etkinlik de kullanıcıdan sağlayıcının verilerinde değişiklik yapmasını isteyebilir. Amaç, hedef etkinliğin kullanıcı arayüzünde görüntülediği "ekstralar" verileri de içerebilir. Böylece kullanıcı, sağlayıcıdaki verileri değiştirmek için kullanmadan önce bu verileri değiştirme seçeneğine sahiptir.

Veri bütünlüğüne yardımcı olmak için intent erişimini kullanabilirsiniz. Sağlayıcınız, kesin olarak tanımlanmış iş mantığına göre verilerin eklenmesine, güncellenmesine ve silinmesine bağlı olabilir. Bu durumda, diğer uygulamaların verilerinizi doğrudan değiştirmesine izin vermek geçersiz verilere yol açabilir.

Geliştiricilerin amaç erişimini kullanmasını istiyorsanız bunu ayrıntılı bir şekilde belgelediğinizden emin olun. Uygulamanızın kullanıcı arayüzünü kullanarak intent erişiminin verileri kodlarla değiştirmeye çalışmaktan neden daha iyi olduğunu açıklayın.

Sağlayıcınızın verilerini değiştirmek isteyen gelen bir niyetin ele alınması, diğer amaçların işlenmesinden farklı değildir. Amaçlar hakkında daha fazla bilgi edinmek için Niyetler ve Niyet Filtreleri bölümünü okuyabilirsiniz.

Daha fazla bilgi için Takvim sağlayıcısına genel bakış konusuna bakın.