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

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

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

Bu sayfada, içerik sağlayıcı oluşturma temel süreci ve kullanılacak API'lerin listesi yer alır.

Derlemeye başlamadan önce

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

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

    Kullanım tamamen kendi uygulamanızın içindeyse ve listelenen özelliklerin hiçbirine ihtiyacınız yoksa veritabanlarını veya diğer kalıcı depolama alanı türlerini kullanmak için sağlayıcıya ihtiyacınız yoktur. Bunun yerine, Veri ve dosya depolamaya genel bakış bölümünde açıklanan depolama sistemlerinden birini kullanabilirsiniz.

  • Henüz yapmadıysanız sağlayıcılar ve işleyiş şekilleri hakkında daha fazla bilgi edinmek için İçerik sağlayıcılarla ilgili temel bilgileri okuyun.

Sağlayıcınızı oluşturmak için aşağıdaki adımları uygulayın:

  1. Verileriniz için ham depolama tasarlayın. İçerik sağlayıcı, verileri iki şekilde sunar:
    Dosya verileri
    Normalde dosyalara yerleştirilen fotoğraf, ses veya video gibi veriler. Dosyaları uygulamanızın özel alanında depolayın. Başka bir uygulamadan dosya için yapılan isteğe yanıt olarak sağlayıcınız, dosya için bir herkese açık kullanıcı adı sunabilir.
    "Yapılandırılmış" veri
    Normalde bir veritabanına, diziye veya benzer yapıya giden veriler. Verileri, satır ve sütun tablolarıyla uyumlu bir biçimde depolayın. Satır, kişi veya envanterdeki bir öğe gibi bir varlığı temsil eder. Sütun, varlıkla ilgili bazı verileri (ör. kişinin adı veya öğenin fiyatı) temsil eder. Bu tür verileri SQLite veritabanında depolamak için yaygın olarak kullanılan yöntemlerden biridir. Bununla birlikte, dilediğiniz kalıcı depolama türünü de kullanabilirsiniz. Android sisteminde kullanılabilen depolama türleri hakkında daha fazla bilgi edinmek için Veri depolama tasarlama bölümüne bakın.
  2. ContentProvider sınıfının somut bir uygulamasını ve gerekli yöntemlerini tanımlayın. Bu sınıf, verileriniz ile 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ı uygulamasının amaçları işlemesini istiyorsanız intent işlemlerini, ekstra verileri ve işaretleri de tanımlayın. Ayrıca, verilerinize erişmek isteyen uygulamalar için gereken izinleri de tanımlayın. Tüm bu değerleri ayrı bir sözleşme sınıfında sabit değerler 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'ları 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. Sağlayıcı ile bulut tabanlı veriler arasında veri senkronizasyonu yapabilen örnek veriler veya AbstractThreadedSyncAdapter uygulaması gibi isteğe bağlı başka parçalar ekleyin.

Veri depolamayı tasarlayın

İçerik sağlayıcı, yapılandırılmış bir biçimde kaydedilen verilerin arayüzüdür. Arayüzü oluşturmadan önce verilerin nasıl depolanacağına karar verin. Verileri istediğiniz herhangi bir biçimde depolayabilir ve 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 ilişkisel bir veritabanı veya LevelDB gibi ilişkisel olmayan bir anahtar/değer veri deposu olarak kullanabilirsiniz. Ses, görüntü veya video medyası gibi yapılandırılmamış verilerle çalışıyorsanız verileri dosya olarak depolamayı düşünebilirsiniz. Birkaç farklı depolama türünü karıştırıp eşleştirebilir ve gerekirse tek bir içerik sağlayıcı kullanarak bunları kullanıma sunabilirsiniz.
  • Android sistemi, Oda kalıcılık kitaplığı ile etkileşim kurabilir. Bu kitaplık, Android'in kendi sağlayıcılarının tablo tabanlı verileri depolamak için kullandığı SQLite veritabanı API'sine erişim sağlar. Bu kitaplığı kullanarak veritabanı oluşturmak için Odayı kullanarak verileri yerel bir veritabanına kaydetme bölümünde açıklandığı şekilde RoomDatabase alt sınıfını örneklendirin.

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

  • Android'in dosya verilerini depolamak için dosya tabanlı çeşitli API'leri vardır. Dosya depolama hakkında daha fazla bilgi 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 verileri ile dosyaları birleştiren bir sağlayıcınız olabilir.
  • Nadir durumlarda tek bir uygulama için birden fazla içerik sağlayıcı uygulamaktan yararlanabilirsiniz. Örneğin, bir içerik sağlayıcısı kullanarak bazı verileri bir widget'la paylaşmak ve diğer uygulamalarla paylaşmak üzere 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 edebilir ve ardından verileri tablo ya da dosya halinde 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ı da artırmanız gerekir. Bu değişikliğin yapılması, sistemdeki eski sürüme geçişlerin, uyumlu olmayan içerik sağlayıcısına sahip bir uygulamayı yeniden yüklemeye çalıştığında sistemin kilitlenmesine neden olmasını önler.

Veri tasarımında dikkat edilmesi gereken noktalar

Sağlayıcınızın veri yapısını tasarlamaya yönelik bazı ipuçları aşağıda 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. Bu değeri, satırı diğer tablolardaki ilgili satırlara bağlamak için kullanabilirsiniz ("yabancı anahtar" olarak). Bu sütun için herhangi bir ad kullanabilirsiniz ancak sağlayıcı sorgusunun sonuçlarının ListView ile bağlanması, alınan sütunlardan birinin _ID adını gerektirdiğinden en iyi seçenek BaseColumns._ID kullanmaktır.
  • Bit eşlem görüntüleri veya dosya odaklı başka çok büyük veri parçaları sağlamak istiyorsanız verileri bir dosyada depolayın ve 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 bir ContentResolver dosyası yöntemi kullanmaları gerektiğini bildirmeniz gerekir.
  • Boyutu değişiklik gösteren veya yapısı değişen verileri depolamak için ikili büyük nesne (BLOB) veri türünü kullanın. Örneğin, bir protokol arabelleği veya JSON yapısını depolamak için BLOB sütunu kullanabilirsiniz.

    Şemadan bağımsız bir tablo uygulamak için BLOB da kullanabilirsiniz. Bu tür tabloda, BLOB olarak birincil anahtar sütunu, MIME türü sütunu ve bir veya daha fazla genel sütun tanımlarsınız. BLOB sütunlarındaki verilerin anlamı, MIME türü sütunundaki değerle belirtilir. Bu sayede, farklı satır türlerini aynı tabloda depolayabilirsiniz. Kişi Sağlayıcı'nın "veri" tablosu ContactsContract.Data, şemadan bağımsız bir tabloya örnektir.

İçerik URI'lerini tasarlama

İçerik URI'si, sağlayıcıdaki verileri tanımlayan bir URI'dir. İçerik URI'leri, sağlayıcının tamamının sembolik adını (yetkili) ve bir tabloya veya dosyaya işaret eden bir adı (yol) içerir. İsteğe bağlı kimlik bölümü, bir tablodaki bağımsız bir satırı işaret eder. Her ContentProvider 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ı ile ilgili temel bilgiler konusuna bakın.

Bir otorite tasarlayın

Sağlayıcıların genellikle Android dahili adı olarak işlev gören tek bir yetkilisi vardır. Diğer sağlayıcılarla anlaşmazlık yaşamamak için sağlayıcı yetkilinizin temelinde internet alan adı sahipliğini (bunun tersi) kullanın. Bu öneri Android paket adları için de geçerli olduğundan sağlayıcı yetkinizi, 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 tabloları gösteren yollar ekleyerek yetkiliden içerik URI'leri oluşturur. Örneğin, table1 (tablo) ve table2 (tablo2) şeklinde 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 seviyesi için bir tablo olması gerekmez.

İçerik URI'si kimliklerini işleyin

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

Bu kural, bir sağlayıcıya erişen uygulamalar için ortak bir tasarım kalıbını kolaylaştırır. Uygulama, sağlayıcıya karşı sorgu gerçekleştirir ve sonuç olarak, CursorAdapter kullanarak ListView sonucunu veren Cursor sonucunu gösterir. CursorAdapter tanımı, Cursor içindeki sütunlardan birinin _ID olmasını gerektirir

Daha sonra kullanıcı, verilere bakmak veya değiştirmek için kullanıcı arayüzünde görüntülenen satırlardan birini seçer. Uygulama, ListView öğesini destekleyen Cursor bölümünden ilgili satırı 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ı

provider API, gelen içerik URI'si için hangi işlemi gerçekleştireceğinizi seçmenize yardımcı olmak için içerik URI 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 bir switch ifadesinde tam sayı değerlerini kullanabilirsiniz.

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

  • *, uzunluğu ne olursa olsun 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'si işlemesini tasarlama ve kodlamaya örnek olarak, aşağıdaki tablolara işaret eden 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ı, bu içerik URI'lerini, kendilerine satır kimlikleri eklenmişse de tanır. Örneğin, table3 içinde 1 tarafından 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ı için bir içerik URI'si ile eşleşir ancak table1 veya table3 için içerik URI'leriyle eşleşmez.
content://com.example.app.provider/table3/#
table3 içindeki tek satırlar için bir içerik URI'sıyla 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> içerik URI kalıbı ve tek satırlar için content://<authority>/<path>/<id> kullanılarak tüm tablonun URI'lerini tek bir satırdaki URI'lardan farklı şekilde işler.

addURI() yöntemi, bir yetkiliyi ve yolu bir tam sayı değeriyle eşler. match() yöntemi, bir URI için tam sayı değerini döndürür. switch ifadesi, tablonun tamamını sorgulama veya tek bir kaydı 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 olan ContentUris, içerik URI'lerinin id kısmıyla çalışmak için pratik yöntemler sunar. Uri ve Uri.Builder sınıfları, mevcut Uri nesnelerini ayrıştırma ve yenilerini oluşturmak için kolay yöntemler içerir.

ContentProvider sınıfını uygulama

ContentProvider örneği, diğer uygulamalardan gelen istekleri işleyerek yapılandırılmış bir veri grubuna erişimi yönetir. Tüm erişim biçimleri sonunda ContentResolver yöntemini çağırır ve daha sonra, erişim için somut bir ContentProvider yöntemi çağrılır.

Gerekli yöntemler

ContentProvider soyut sınıfı, somut 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 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 verileri alın. Sorgulanacak tabloyu, döndürülecek satırlarla 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 öğrenmek için bağımsız değişkenleri kullanın. Yeni eklenen satır için bir 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ızdan 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ür. Bu yöntem, İçerik sağlayıcı MIME türlerini uygulama bölümünde daha ayrıntılı olarak açıklanmıştır.
onCreate()
Sağlayıcınızı başlatı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ğıdaki noktaları göz önünde bulundurmanız gerekir:

  • onCreate() dışındaki tüm yöntemler, aynı anda birden çok iş parçacığı tarafından çağrılabileceğinden iş parçacığı için güvenli olmaları gerekir. Birden çok iş parçacığı hakkında daha fazla bilgi edinmek için İşlemlere ve iş parçacıklarına genel bakış sayfasını inceleyin.
  • onCreate() bölgesinde 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 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 hiç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 depolama alanınız olarak SQLite veritabanı kullanıyorsanız SQLiteDatabase sınıfının query() yöntemlerinden biri tarafından döndürülen Cursor alanını döndürebilirsiniz.

Sorgu hiçbir satırla eşleşmezse getCount() yöntemi 0 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şursa döndürün.

Veri depolama alanınız olarak SQLite veritabanı kullanmıyorsanız somut Cursor alt sınıflarından birini kullanın. Örneğin, MatrixCursor sınıfı, her satırın Object örnekten oluşan bir dizi olduğu bir imleç uygular. Bu sınıfta, yeni satır eklemek için addRow() kısayolunu kullanın.

Android sistemi, Exception öğesini işlem sınırları arasında iletebilmelidir. 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 bu sütun için varsayılan bir değer sağlamak isteyebilirsiniz.

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

delete() yöntemini uygulama

delete() yönteminin, veri depolama alanınızdan satır silmesi gerekmez. Sağlayıcınızla bir senkronizasyon bağdaştırıcısı kullanıyorsanız satırı tamamen kaldırmak yerine silinmiş bir satırı "sil" işaretiyle işaretlemeyi düşünebilirsiniz. Senkronizasyon bağdaştırıcısı, silinen satırları kontrol edebilir ve sağlayıcıdan silmeden önce bunları sunucudan kaldırabilir.

Update() yöntemini uygulama

update() yöntemi, insert() tarafından kullanılan aynı ContentValues bağımsız değişkenini ve delete() ile ContentProvider.query() tarafından kullanılan aynı selection ve selectionArgs bağımsız değişkenlerini alır. Böylece, bu yöntemler arasında kodu yeniden kullanabilirsiniz.

onCreate() yöntemini uygulama

Android sistemi, sağlayıcıyı başlattığında onCreate() çağırır. Bu yöntemde yalnızca hızlı çalışan başlatma görevlerini gerçekleştirin ve sağlayıcı veriler için gerçekten bir istek alana kadar veritabanı oluşturma ile veri yüklemesini erteleyin. onCreate() ürününde uzun işlemler 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 yanıtını yavaşlatır.

Aşağıdaki iki snippet, ContentProvider.onCreate() ile Room.databaseBuilder() arasındaki etkileşimi göstermektedir. İlk snippet, veritabanı nesnesinin derlendiği ve veri erişimi nesnelerini işlediği durumlarda ContentProvider.onCreate() kullanımı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()
Herhangi bir 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öntemdir.

Tablolar için MIME türleri

getType() yöntemi, içerik URI'si 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'leriyle ilişkili veri türünü döndürün.

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

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

  • Bölümü 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> tedarikçilerini siz sağlarsınız. <name> değeri genel olarak benzersizdir ve <type> değeri, karşılık gelen URI kalıbında benzersizdir. Şirketinizin adı veya uygulamanızın Android paketi adının bir kısmı <name> için iyi bir seçimdir. <type> için iyi bir seçim, URI ile ilişkili tabloyu tanımlayan bir dizedir.

Örneğin, sağlayıcının yetkisi com.example.app.provider ise ve table1 adlı bir tabloyu gösterirse table1 içindeki birden fazla satır için MIME türü şu olur:

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

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

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, yalnızca istemcinin işlemek istediği MIME türlerini döndürebilmek için MIME türü filtre bağımsız değişkenine göre filtreleyin.

Örneğin, fotoğraf resimlerini JPG, PNG ve GIF biçimindeki dosya olarak sunan bir sağlayıcıyı düşünün. Bir uygulama, "resim" olan bir şey için image/* filtre dizesiyle ContentResolver.getStreamTypes() yöntemini ç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() yöntemini çağırabilir ve getStreamTypes() şu değeri 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 gerçek değerlerinde ve sütun adlarında değişiklik 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 anımsatıcı adlara sahip olduğundan geliştiricilere yardımcı olur. Bu sayede, geliştiricilerin sütun adları veya URI'ler için yanlış değerler kullanma olasılığı daha düşüktür. Sınıf olduğu için Javadoc belgelerini içerebilir. Android Studio gibi entegre geliştirme ortamları, sözleşme sınıfındaki sabit adları otomatik olarak tamamlayabilir ve sabitler için Javadoc'u görüntüleyebilir.

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

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

İçerik sağlayıcı izinlerini uygulama

Android sisteminin tüm özelliklerine ilişkin izinler ve erişim izinleri Güvenlik ipuçları bölümünde ayrıntılı olarak açıklanmıştır. Veri ve dosya depolamaya genel bakış bölümünde de çeşitli depolama türleri için geçerli olan güvenlik ve izinler 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.
  • Varsayılan olarak, harici depolamaya kaydettiğiniz veri dosyaları herkese açık ve herkes tarafından okunabilir olur. Diğer uygulamalar dosyaları okumak ve yazmak için başka API çağrılarını kullanabileceğinden, harici depolama alanındaki dosyalara erişimi kısıtlamak için içerik sağlayıcı kullanamazsınız.
  • Bu yöntem, cihazınızın dahili depolama alanındaki dosyaları veya SQLite veritabanlarını açmak veya oluşturmak için yapılan çağrıların diğer tüm uygulamalara hem okuma hem de yazma erişimi verebilir. Sağlayıcınızın deposu olarak dahili bir dosya veya veritabanı kullanıyorsanız ve ona "dünya tarafından okunabilir" ya da "dünyada yazılabilir" erişim verirseniz sağlayıcınız için manifest dosyasında ayarladığınız izinler verilerinizi korumaz. Dahili depolama alanındaki dosyalar ve veritabanları için varsayılan erişim "gizli"dir. Sağlayıcınızın deposu için bunu 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 uzak bir sunucu gibi bulutta depolayın, dosyalar ve veritabanlarını uygulamanız için gizli tutun.

İzinleri uygulama

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

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

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

Sağlayıcı düzeyinde tek bir okuma-yazma izni
<provider> öğesinin android:permission özelliğiyle belirtilen, sağlayıcının tamamına okuma ve yazma erişimini kontrol eden bir izin.
Sağlayıcı düzeyinde ayrı okuma ve yazma izinleri
Tüm sağlayıcı için bir okuma ve yazma izni. Bunları <provider> öğesinin android:readPermission ve android:writePermission özellikleriyle belirtirsiniz. android:permission özelliğinin gerektirdiği 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'yi, <provider> öğesinin <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 izinler, sağlayıcı düzeyindeki izinlere göre önceliklidir.
Geçici izin
Uygulama normalde gerekli olan izinlere sahip olmasa bile bir uygulamaya geçici erişim sağlayan izin düzeyidir. 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 sağlayıcınız için yalnızca kalıcı izinlere ihtiyacı olan uygulamalar, tüm verilerinize sürekli olarak erişen uygulamalardır.

Örneğin, bir e-posta sağlayıcı ile uygulama kuruyorsanız ve dışarıdan 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 uygulama, fotoğrafın içerik URI'sini ve izin bayraklarını içeren bir intent'i resim görüntüleyiciye gönderecek şekilde tasarlayın. Ardından resim görüntüleyici, sağlayıcınız için normal okuma iznine sahip olmasa bile fotoğrafı almak için e-posta sağlayıcınızı sorgulayabilir.

Geçici izinleri açmak için <provider> öğesinin android:grantUriPermissions özelliğini ayarlayın ya da <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'sine yönelik desteği kaldırdığınızda Context.revokeUriPermission() yöntemini çağırın.

Özelliğin değeri, sağlayıcınızın ne kadarının erişilebilir olacağını belirler. Özellik "true" olarak ayarlanırsa sistem, sağlayıcı düzeyindeki veya yol düzeyindeki izinlerin gerektirdiği diğer tüm izinleri geçersiz kılarak tüm sağlayıcınıza geçici izin verir.

Bu işaret "false" olarak 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ını belirtir.

Bir uygulamaya geçici erişim yetkisi verebilmek için niyet; FLAG_GRANT_READ_URI_PERMISSION işaretini, FLAG_GRANT_WRITE_URI_PERMISSION işaretini veya her ikisini birden içermelidir. 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, ContentProvider alt sınıfı da <provider> öğesi kullanılarak uygulamasının manifest dosyasında tanımlanır. Android sistemi, öğeden aşağıdaki bilgileri alır:

Yetki (android:authorities)
Sistem içinde tüm sağlayıcıyı tanımlayan sembolik adlar. Bu özellik, İçerik URI'lerini tasarlama bölümünde daha ayrıntılı olarak açıklanmıştır.
Sağlayıcı sınıfı adı (android:name)
ContentProvider yöntemini uygulayan sınıf. Bu sınıf, ContentProvider sınıfını uygulama bölümünde daha ayrıntılı olarak açıklanmıştı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 ilişkili özellikler, İçerik sağlayıcı izinlerini uygulama bölümünde daha ayrıntılı olarak açıklanmıştır.

Başlangıç ve kontrol özellikleri
Bu özellikler, Android sisteminin sağlayıcıyı nasıl ve ne zaman başlattığı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şaret
  • android:exported: Diğer uygulamaların bu sağlayıcıyı kullanmasına izin vermeyi işaretle
  • android:initOrder: bu sağlayıcının başlatıldığı sıra (aynı işlemdeki diğer sağlayıcılara göre)
  • android:multiProcess: Sistemin, sağlayıcıyı çağıran istemciyle aynı işlemde başlatmasını sağlayan işaret
  • android:process: sağlayıcının çalıştırıldığı sürecin adı
  • android:syncable: Sağlayıcının verilerinin bir sunucudaki verilerle senkronize edileceğini belirten işaret

Bu özellikler, <provider> öğesiyle ilgili kılavuzda eksiksiz bir şekilde açıklanmıştır.

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. Simge, Ayarlar > Uygulamalar > Tümü bölümündeki uygulama listesinde sağlayıcının etiketinin yanında görünür.
  • android:label: Sağlayıcıyı, verilerini veya her ikisini birden açıklayan bir bilgi etiketidir. Etiket, Ayarlar > Uygulamalar > Tümü bölümündeki uygulama listesinde görünür.

Bu özellikler, <provider> öğesiyle ilgili kılavuzda eksiksiz bir şekilde açıklanmıştır.

Amaçlar ve veri erişimi

Uygulamalar bir içerik sağlayıcıya Intent ile dolaylı olarak erişebilir. Uygulama, ContentResolver veya ContentProvider yöntemlerinin hiçbirini çağırmaz. Bunun yerine, genellikle sağlayıcının kendi uygulamasında yer alan bir etkinlik başlatan bir amaç gönderir. Hedef etkinliği, verileri alıp kullanıcı arayüzünde görüntülemekten sorumludur.

Amaçtaki işleme bağlı olarak, hedef etkinlik kullanıcıdan sağlayıcının verilerinde değişiklik yapmasını da isteyebilir. Bir amaç, hedef etkinliğin kullanıcı arayüzünde görüntülediği "ekstralar" verilerini de içerebilir. Böylece kullanıcı, sağlayıcıdaki verilerde değişiklik yapmak için kullanmadan önce bu verileri değiştirebilir.

Veri bütünlüğüne yardımcı olmak için intent erişimini kullanabilirsiniz. Sağlayıcınız verilerin kesin şekilde tanımlanmış iş mantığına göre eklenmesini, güncellenmesini ve silinmesini isteyebilir. Bu durumda, diğer uygulamaların verilerinizi doğrudan değiştirmesine izin vermek geçersiz verilere neden olabilir.

Geliştiricilerin intent erişimi kullanmasını istiyorsanız bunu kapsamlı bir şekilde belgelediğinizden emin olun. Uygulamanızın kullanıcı arayüzünü kullanarak intent erişiminin, verileri kodlarıyla 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 amaçların işlenmesi, diğer amaçların işlenmesinden farklı değildir. Amaçları kullanma hakkında daha fazla bilgi edinmek için Amaçlar ve Amaç Filtreleri bölümünü okuyabilirsiniz.

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